diff --git a/.github/labeler.yml b/.github/labeler.yml index cf48df1..85a15b9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -56,6 +56,10 @@ A-cryptonight: - changed-files: - any-glob-to-any-file: cryptonight/** +A-constants: +- changed-files: + - any-glob-to-any-file: constants/** + A-storage: - changed-files: - any-glob-to-any-file: storage/** diff --git a/.github/workflows/architecture-book.yml b/.github/workflows/architecture-book.yml new file mode 100644 index 0000000..3e6decf --- /dev/null +++ b/.github/workflows/architecture-book.yml @@ -0,0 +1,40 @@ +# This action attempts to build the architecture book, if changed. + +name: Architecture mdBook + +on: + push: + branches: ['main'] + paths: ['books/architecture/**'] + pull_request: + paths: ['books/architecture/**'] + workflow_dispatch: + +env: + # Version of `mdbook` to install. + MDBOOK_VERSION: 0.4.36 + # Version of `mdbook-last-changed` to install. + # . + MDBOOK_LAST_CHANGED_VERSION: 0.1.4 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/mdbook + ~/.cargo/bin/mdbook-last-changed + key: architecture-book + + - name: Install mdBook + run: | + cargo install --locked --version ${MDBOOK_VERSION} mdbook || echo "mdbook already exists" + cargo install --locked --version ${MDBOOK_LAST_CHANGED_VERSION} mdbook-last-changed || echo "mdbook-last-changed already exists" + + - name: Build + run: mdbook build books/architecture \ No newline at end of file diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml deleted file mode 100644 index 9aa4451..0000000 --- a/.github/workflows/audit.yml +++ /dev/null @@ -1,33 +0,0 @@ -# This runs `cargo audit` on all dependencies (only if Cargo deps changed) - -name: Audit - -on: - push: - paths: - - '**/Cargo.toml' - - '**/Cargo.lock' - -env: - CARGO_TERM_COLOR: always - -jobs: - audit: - - runs-on: ubuntu-latest - - steps: - - name: Cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo - target - key: audit - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dependencies - run: cargo install cargo-audit --locked - - name: Audit - run: cargo audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c2271d..367e8e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,7 +133,12 @@ jobs: - name: Test run: | cargo test --all-features --workspace - cargo test --package cuprate-blockchain --no-default-features --features redb --features service + cargo test --package cuprate-blockchain --no-default-features --features redb + + - name: Hack Check + run: | + cargo install cargo-hack --locked + cargo hack --workspace check --feature-powerset --no-dev-deps # TODO: upload binaries with `actions/upload-artifact@v3` - name: Build diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 1301bb1..2ff2b6f 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -7,6 +7,7 @@ on: paths: - '**/Cargo.toml' - '**/Cargo.lock' + workflow_dispatch: env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 0000000..8ed932a --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,74 @@ +# This builds `cargo doc` and uploads it to the repo's GitHub Pages. + +name: Doc + +on: + push: + branches: [ "main" ] # Only deploy if `main` changes. + workflow_dispatch: + +env: + # Show colored output in CI. + CARGO_TERM_COLOR: always + # Generate an index page. + RUSTDOCFLAGS: '--cfg docsrs --show-type-layout --enable-index-page -Zunstable-options' + +jobs: + # Build documentation. + build: + # FIXME: how to build and merge Windows + macOS docs + # with Linux's? Similar to the OS toggle on docs.rs. + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + # Nightly required for some `cargo doc` settings. + toolchain: nightly + + - name: Cache + uses: actions/cache@v4 + with: + # Don't cache actual doc files, just build files. + # This is so that removed crates don't show up. + path: target/debug + key: doc + + # Packages other than `Boost` used by `Monero` are listed here. + # https://github.com/monero-project/monero/blob/c444a7e002036e834bfb4c68f04a121ce1af5825/.github/workflows/build.yml#L71 + + - name: Install dependencies (Linux) + run: sudo apt install -y libboost-dev + + - name: Documentation + run: cargo +nightly doc --workspace --all-features + + - name: Upload documentation + uses: actions/upload-pages-artifact@v3 + with: + path: target/doc/ + + # Deployment job. + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages + permissions: + contents: read + pages: write + id-token: write + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/monero-book.yml b/.github/workflows/monero-book.yml new file mode 100644 index 0000000..ab3660e --- /dev/null +++ b/.github/workflows/monero-book.yml @@ -0,0 +1,40 @@ +# This action attempts to build the Monero book, if changed. + +name: Monero mdBook + +on: + push: + branches: ['main'] + paths: ['books/protocol/**'] + pull_request: + paths: ['books/protocol/**'] + workflow_dispatch: + +env: + # Version of `mdbook` to install. + MDBOOK_VERSION: 0.4.36 + # Version of `mdbook-svgbob` to install. + # . + MDBOOK_SVGBOB_VERSION: 0.2.1 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/mdbook + ~/.cargo/bin/mdbook-svgbob + key: monero-book + + - name: Install mdBook + run: | + cargo install --locked --version ${MDBOOK_VERSION} mdbook || echo "mdbook already exists" + cargo install --locked --version ${MDBOOK_SVGBOB_VERSION} mdbook-svgbob || echo "mdbook-svgbob already exists" + + - name: Build + run: mdbook build books/protocol \ No newline at end of file diff --git a/.github/workflows/user-book.yml b/.github/workflows/user-book.yml new file mode 100644 index 0000000..cec2551 --- /dev/null +++ b/.github/workflows/user-book.yml @@ -0,0 +1,40 @@ +# This action attempts to build the user book, if changed. + +name: User mdBook + +on: + push: + branches: ['main'] + paths: ['books/user/**'] + pull_request: + paths: ['books/user/**'] + workflow_dispatch: + +env: + # Version of `mdbook` to install. + MDBOOK_VERSION: 0.4.36 + # Version of `mdbook-last-changed` to install. + # . + MDBOOK_LAST_CHANGED_VERSION: 0.1.4 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/mdbook + ~/.cargo/bin/mdbook-last-changed + key: user-book + + - name: Install mdBook + run: | + cargo install --locked --version ${MDBOOK_VERSION} mdbook || echo "mdbook already exists" + cargo install --locked --version ${MDBOOK_LAST_CHANGED_VERSION} mdbook-last-changed || echo "mdbook-last-changed already exists" + + - name: Build + run: mdbook build books/user \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c9c1f0..2d99060 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,12 +120,15 @@ Before pushing your code, please run the following at the root of the repository After that, ensure all other CI passes by running: -| Command | Does what | -|------------------------------------------------------------------------|-----------| -| `RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features` | Checks documentation is OK -| `cargo clippy --workspace --all-features --all-targets -- -D warnings` | Checks clippy lints are satisfied -| `cargo test --all-features --workspace` | Runs all tests -| `cargo build --all-features --all-targets --workspace` | Builds all code +| Command | Does what | +|------------------------------------------------------------------------|-------------------------------------------------------------------------| +| `RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features` | Checks documentation is OK | +| `cargo clippy --workspace --all-features --all-targets -- -D warnings` | Checks clippy lints are satisfied | +| `cargo test --all-features --workspace` | Runs all tests | +| `cargo build --all-features --all-targets --workspace` | Builds all code | +| `cargo hack --workspace check --feature-powerset --no-dev-deps` | Uses `cargo hack` to check our crates build with different features set | + +`cargo hack` can be installed with `cargo` from: https://github.com/taiki-e/cargo-hack. **Note: in order for some tests to work, you will need to place a [`monerod`](https://www.getmonero.org/downloads/) binary at the root of the repository.** @@ -216,9 +219,9 @@ The description of pull requests should generally follow the template laid out i If your pull request is long and/or has sections that need clarifying, consider leaving a review on your own PR with comments explaining the changes. ## 5. Documentation -Cuprate's crates (libraries) have inline documentation. +Cuprate's crates (libraries) have inline documentation, they are published from the `main` branch at https://doc.cuprate.org. -These can be built and viewed using the `cargo` tool. For example, to build and view a specific crate's documentation, run the following command at the repository's root: +Documentation can be built and viewed using the `cargo` tool. For example, to build and view a specific crate's documentation, run the following command at the repository's root: ```bash cargo doc --open --package $CRATE ``` diff --git a/Cargo.lock b/Cargo.lock index db6da25..cd9e397 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -30,10 +30,13 @@ dependencies = [ ] [[package]] -name = "allocator-api2" -version = "0.2.18" +name = "aho-corasick" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] [[package]] name = "android-tzdata" @@ -51,27 +54,50 @@ dependencies = [ ] [[package]] -name = "anstyle" -version = "1.0.7" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "async-lock" -version = "3.4.0" +name = "anstyle" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anyhow" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", + "serde", + "serde_json", ] [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -80,55 +106,106 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] -name = "autocfg" -version = "1.3.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", +] [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", -] - -[[package]] -name = "base58-monero" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978e81a45367d2409ecd33369a45dda2e9a3ca516153ec194de1fbda4b9fb79d" -dependencies = [ - "thiserror", - "tiny-keccak", + "windows-targets 0.52.6", ] [[package]] @@ -137,12 +214,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bincode" version = "1.3.3" @@ -204,6 +275,19 @@ dependencies = [ "digest", ] +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -233,7 +317,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn", "syn_derive", ] @@ -245,22 +329,22 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -271,15 +355,27 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +dependencies = [ + "serde", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.104" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -302,14 +398,41 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", ] [[package]] name = "clap" -version = "4.5.7" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -317,41 +440,59 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstyle", "clap_lex", + "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "const_format" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ - "crossbeam-utils", + "const_format_proc_macros", ] +[[package]] +name = "const_format_proc_macros" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -364,19 +505,64 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -446,6 +632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "subtle", + "zeroize", ] [[package]] @@ -463,17 +650,17 @@ name = "cuprate-address-book" version = "0.1.0" dependencies = [ "borsh", + "cuprate-constants", "cuprate-p2p-core", "cuprate-pruning", "cuprate-test-utils", - "cuprate-wire", "futures", - "indexmap 2.2.6", + "indexmap", "rand", "thiserror", "tokio", "tokio-util", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", ] @@ -487,57 +674,103 @@ dependencies = [ "tokio", ] +[[package]] +name = "cuprate-benchmark" +version = "0.0.0" +dependencies = [ + "cfg-if", + "cuprate-benchmark-example", + "cuprate-benchmark-lib", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "cuprate-benchmark-example" +version = "0.0.0" +dependencies = [ + "cuprate-benchmark-lib", +] + +[[package]] +name = "cuprate-benchmark-lib" +version = "0.0.0" + [[package]] name = "cuprate-blockchain" version = "0.0.0" dependencies = [ "bitflags 2.6.0", "bytemuck", - "crossbeam", + "bytes", + "cuprate-constants", "cuprate-database", + "cuprate-database-service", "cuprate-helper", "cuprate-pruning", "cuprate-test-utils", "cuprate-types", "curve25519-dalek", - "futures", "hex", "hex-literal", "monero-serai", - "paste", "pretty_assertions", + "proptest", + "rand", "rayon", + "serde", "tempfile", "thread_local", "tokio", - "tokio-util", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", ] [[package]] name = "cuprate-consensus" version = "0.1.0" dependencies = [ + "cfg-if", + "cuprate-consensus-context", "cuprate-consensus-rules", "cuprate-helper", "cuprate-test-utils", "cuprate-types", "curve25519-dalek", - "dalek-ff-group", "futures", "hex", "hex-literal", "monero-serai", - "multiexp", "proptest", "proptest-derive", + "rand", + "rayon", + "thiserror", + "thread_local", + "tokio", + "tokio-test", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", + "tracing", +] + +[[package]] +name = "cuprate-consensus-context" +version = "0.1.0" +dependencies = [ + "cuprate-consensus-rules", + "cuprate-helper", + "cuprate-types", + "futures", + "hex", + "monero-serai", "randomx-rs", "rayon", "thiserror", "thread_local", "tokio", "tokio-util", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", ] @@ -545,15 +778,16 @@ dependencies = [ name = "cuprate-consensus-rules" version = "0.1.0" dependencies = [ + "cfg-if", "crypto-bigint", + "cuprate-constants", "cuprate-cryptonight", "cuprate-helper", + "cuprate-types", "curve25519-dalek", - "dalek-ff-group", "hex", "hex-literal", "monero-serai", - "multiexp", "proptest", "proptest-derive", "rand", @@ -563,12 +797,41 @@ dependencies = [ "tracing", ] +[[package]] +name = "cuprate-constants" +version = "0.1.0" + +[[package]] +name = "cuprate-criterion-example" +version = "0.0.0" +dependencies = [ + "criterion", + "function_name", + "serde_json", +] + +[[package]] +name = "cuprate-criterion-json-rpc" +version = "0.0.0" +dependencies = [ + "criterion", + "cuprate-json-rpc", + "function_name", + "serde_json", +] + [[package]] name = "cuprate-cryptonight" version = "0.1.0" dependencies = [ - "cc", + "digest", + "groestl", "hex", + "jh", + "keccak", + "seq-macro", + "sha3", + "skein", "thiserror", ] @@ -583,7 +846,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", ] @@ -596,18 +859,33 @@ dependencies = [ "cfg-if", "heed", "page_size", + "paste", "redb", "serde", "tempfile", "thiserror", ] +[[package]] +name = "cuprate-database-service" +version = "0.1.0" +dependencies = [ + "crossbeam", + "cuprate-database", + "cuprate-helper", + "futures", + "rayon", + "serde", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", +] + [[package]] name = "cuprate-epee-encoding" version = "0.5.0" dependencies = [ "bytes", "cuprate-fixed-bytes", + "cuprate-helper", "hex", "paste", "ref-cast", @@ -621,17 +899,17 @@ dependencies = [ "clap", "cuprate-blockchain", "cuprate-consensus", + "cuprate-consensus-context", "cuprate-consensus-rules", + "cuprate-helper", "cuprate-types", "hex", "hex-literal", "monero-serai", - "rayon", "sha3", "thiserror", "tokio", - "tokio-test", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", ] [[package]] @@ -639,6 +917,8 @@ name = "cuprate-fixed-bytes" version = "0.1.0" dependencies = [ "bytes", + "serde", + "serde_json", "thiserror", ] @@ -648,11 +928,14 @@ version = "0.1.0" dependencies = [ "chrono", "crossbeam", + "cuprate-constants", + "curve25519-dalek", "dirs", "futures", "libc", "monero-serai", "rayon", + "serde", "tokio", "windows", ] @@ -673,6 +956,8 @@ version = "0.1.0" dependencies = [ "bitflags 2.6.0", "bytes", + "cfg-if", + "cuprate-helper", "futures", "proptest", "rand", @@ -686,19 +971,20 @@ dependencies = [ name = "cuprate-p2p" version = "0.1.0" dependencies = [ + "borsh", "bytes", "cuprate-address-book", "cuprate-async-buffer", + "cuprate-constants", "cuprate-fixed-bytes", "cuprate-helper", "cuprate-p2p-core", "cuprate-pruning", "cuprate-test-utils", + "cuprate-types", "cuprate-wire", - "dashmap", "futures", - "hex", - "indexmap 2.2.6", + "indexmap", "monero-serai", "pin-project", "proptest", @@ -710,29 +996,40 @@ dependencies = [ "tokio-stream", "tokio-test", "tokio-util", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", ] +[[package]] +name = "cuprate-p2p-bucket" +version = "0.1.0" +dependencies = [ + "arrayvec", + "rand", +] + [[package]] name = "cuprate-p2p-core" version = "0.1.0" dependencies = [ "async-trait", "borsh", + "cfg-if", "cuprate-helper", "cuprate-pruning", "cuprate-test-utils", + "cuprate-types", "cuprate-wire", "futures", "hex", + "hex-literal", "thiserror", "tokio", "tokio-stream", + "tokio-test", "tokio-util", - "tower", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", - "tracing-subscriber", ] [[package]] @@ -740,19 +1037,38 @@ name = "cuprate-pruning" version = "0.1.0" dependencies = [ "borsh", + "cuprate-constants", "thiserror", ] [[package]] name = "cuprate-rpc-interface" version = "0.0.0" +dependencies = [ + "anyhow", + "axum", + "cuprate-epee-encoding", + "cuprate-helper", + "cuprate-json-rpc", + "cuprate-rpc-types", + "cuprate-test-utils", + "futures", + "paste", + "serde", + "serde_json", + "tokio", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", + "ureq", +] [[package]] name = "cuprate-rpc-types" version = "0.0.0" dependencies = [ "cuprate-epee-encoding", - "monero-serai", + "cuprate-fixed-bytes", + "cuprate-test-utils", + "cuprate-types", "paste", "serde", "serde_json", @@ -764,7 +1080,6 @@ version = "0.1.0" dependencies = [ "async-trait", "borsh", - "bytes", "cuprate-helper", "cuprate-p2p-core", "cuprate-types", @@ -772,7 +1087,10 @@ dependencies = [ "futures", "hex", "hex-literal", + "monero-rpc", "monero-serai", + "monero-simple-request-rpc", + "paste", "pretty_assertions", "serde", "serde_json", @@ -784,13 +1102,45 @@ dependencies = [ [[package]] name = "cuprate-txpool" version = "0.0.0" +dependencies = [ + "bitflags 2.6.0", + "blake3", + "bytemuck", + "cuprate-database", + "cuprate-database-service", + "cuprate-helper", + "cuprate-test-utils", + "cuprate-types", + "hex", + "hex-literal", + "monero-serai", + "rayon", + "serde", + "tempfile", + "thiserror", + "tokio", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", +] [[package]] name = "cuprate-types" version = "0.0.0" dependencies = [ + "bytes", + "cuprate-epee-encoding", + "cuprate-fixed-bytes", + "cuprate-helper", "curve25519-dalek", + "hex", + "hex-literal", "monero-serai", + "pretty_assertions", + "proptest", + "proptest-derive", + "serde", + "serde_json", + "strum", + "thiserror", ] [[package]] @@ -801,11 +1151,93 @@ dependencies = [ "bytes", "cuprate-epee-encoding", "cuprate-fixed-bytes", + "cuprate-helper", "cuprate-levin", + "cuprate-types", "hex", "thiserror", ] +[[package]] +name = "cuprate-zmq-types" +version = "0.1.0" +dependencies = [ + "assert-json-diff", + "cuprate-types", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "cuprated" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.6.0", + "borsh", + "bytemuck", + "bytes", + "cfg-if", + "chrono", + "clap", + "const_format", + "crossbeam", + "crypto-bigint", + "cuprate-address-book", + "cuprate-async-buffer", + "cuprate-blockchain", + "cuprate-consensus", + "cuprate-consensus-context", + "cuprate-consensus-rules", + "cuprate-cryptonight", + "cuprate-dandelion-tower", + "cuprate-database", + "cuprate-database-service", + "cuprate-epee-encoding", + "cuprate-fast-sync", + "cuprate-fixed-bytes", + "cuprate-helper", + "cuprate-json-rpc", + "cuprate-levin", + "cuprate-p2p", + "cuprate-p2p-core", + "cuprate-pruning", + "cuprate-rpc-interface", + "cuprate-rpc-types", + "cuprate-test-utils", + "cuprate-txpool", + "cuprate-types", + "cuprate-wire", + "curve25519-dalek", + "dashmap", + "dirs", + "futures", + "hex", + "hex-literal", + "indexmap", + "monero-serai", + "paste", + "pin-project", + "rand", + "rand_distr", + "randomx-rs", + "rayon", + "serde", + "serde_bytes", + "serde_json", + "thiserror", + "thread_local", + "tokio", + "tokio-stream", + "tokio-util", + "toml", + "tower 0.5.1 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", + "tracing", + "tracing-subscriber", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -832,13 +1264,13 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "dalek-ff-group" version = "0.4.1" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ "crypto-bigint", "curve25519-dalek", @@ -853,11 +1285,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.3" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", + "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", @@ -926,9 +1359,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" @@ -946,32 +1379,11 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener", - "pin-project-lite", -] - [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "ff" @@ -990,10 +1402,20 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flexible-transcript" version = "0.3.2" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ "blake2", "digest", @@ -1018,6 +1440,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "function_name" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7" +dependencies = [ + "function_name-proc-macro", +] + +[[package]] +name = "function_name-proc-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333" + [[package]] name = "funty" version = "2.0.0" @@ -1026,9 +1463,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1040,9 +1477,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1050,44 +1487,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1124,9 +1561,18 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "groestl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343cfc165f92a988fd60292f7a0bfde4352a5a0beff9fbec29251ca4e9676e4d" +dependencies = [ + "digest", +] [[package]] name = "group" @@ -1140,10 +1586,33 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "h2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -1152,18 +1621,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", - "allocator-api2", ] [[package]] -name = "hdrhistogram" -version = "7.5.4" +name = "hashbrown" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" -dependencies = [ - "byteorder", - "num-traits", -] +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -1173,9 +1637,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "heed" -version = "0.20.3" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc30da4a93ff8cb98e535d595d6de42731d4719d707bc1c86f579158751a24e" +checksum = "7d4f449bab7320c56003d37732a917e18798e2f1709d80263face2b4f9436ddb" dependencies = [ "bitflags 2.6.0", "byteorder", @@ -1220,6 +1684,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -1227,15 +1694,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "http" version = "1.1.0" @@ -1249,9 +1707,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1272,22 +1730,30 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1297,9 +1763,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", @@ -1310,14 +1776,14 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls", - "tower-service", + "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -1328,16 +1794,15 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", - "tower-service", + "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1368,22 +1833,32 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown 0.15.0", ] [[package]] -name = "indexmap" -version = "2.2.6" +name = "is-terminal" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "equivalent", - "hashbrown 0.14.5", + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] @@ -1393,10 +1868,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "js-sys" -version = "0.3.69" +name = "jh" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "f65735f9e73adc203417d2e05352aef71d7e832ec090f65de26c96c9ec563aa5" +dependencies = [ + "digest", + "hex-literal", + "ppv-lite86", +] + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1412,21 +1898,21 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -1446,9 +1932,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lmdb-master-sys" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57640c190703d5ccf4a86aff4aeb749b2d287a8cb1723c76b51f39d77ab53b24" +checksum = "472c3760e2a8d0f61f322fb36788021bb36d573c502b50fa3e2bcaac3ec326c9" dependencies = [ "cc", "doxygen-rs", @@ -1467,9 +1953,24 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md-5" @@ -1483,9 +1984,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "merlin" @@ -1500,82 +2001,199 @@ dependencies = [ ] [[package]] -name = "miniz_oxide" -version = "0.7.3" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "monero-address" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "monero-io", + "monero-primitives", + "std-shims", + "thiserror", + "zeroize", +] + +[[package]] +name = "monero-borromean" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "monero-generators", + "monero-io", + "monero-primitives", + "std-shims", + "zeroize", +] + +[[package]] +name = "monero-bulletproofs" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "monero-generators", + "monero-io", + "monero-primitives", + "rand_core", + "std-shims", + "thiserror", + "zeroize", +] + +[[package]] +name = "monero-clsag" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "dalek-ff-group", + "flexible-transcript", + "group", + "monero-generators", + "monero-io", + "monero-primitives", + "rand_chacha", + "rand_core", + "std-shims", + "subtle", + "thiserror", + "zeroize", ] [[package]] name = "monero-generators" version = "0.4.0" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ "curve25519-dalek", "dalek-ff-group", "group", + "monero-io", "sha3", "std-shims", "subtle", ] +[[package]] +name = "monero-io" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "std-shims", +] + +[[package]] +name = "monero-mlsag" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "monero-generators", + "monero-io", + "monero-primitives", + "std-shims", + "thiserror", + "zeroize", +] + +[[package]] +name = "monero-primitives" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "curve25519-dalek", + "monero-generators", + "monero-io", + "sha3", + "std-shims", + "zeroize", +] + +[[package]] +name = "monero-rpc" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" +dependencies = [ + "async-trait", + "curve25519-dalek", + "hex", + "monero-address", + "monero-serai", + "serde", + "serde_json", + "std-shims", + "thiserror", + "zeroize", +] + [[package]] name = "monero-serai" version = "0.1.4-alpha" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ - "async-lock", - "async-trait", - "base58-monero", "curve25519-dalek", - "dalek-ff-group", - "digest_auth", - "flexible-transcript", - "group", - "hex", "hex-literal", + "monero-borromean", + "monero-bulletproofs", + "monero-clsag", "monero-generators", - "multiexp", - "pbkdf2", - "rand", - "rand_chacha", - "rand_core", - "rand_distr", - "serde", - "serde_json", - "sha3", - "simple-request", + "monero-io", + "monero-mlsag", + "monero-primitives", "std-shims", - "subtle", - "thiserror", - "tokio", "zeroize", ] [[package]] -name = "multiexp" -version = "0.4.0" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +name = "monero-simple-request-rpc" +version = "0.1.0" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ - "ff", - "group", - "rand_core", - "rustversion", - "std-shims", - "zeroize", + "async-trait", + "digest_auth", + "hex", + "monero-rpc", + "simple-request", + "tokio", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", ] [[package]] @@ -1588,30 +2206,26 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" -version = "0.36.0" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl-probe" @@ -1625,6 +2239,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "page_size" version = "0.6.0" @@ -1635,12 +2255,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" version = "0.12.3" @@ -1661,18 +2275,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", -] - -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", + "windows-targets 0.52.6", ] [[package]] @@ -1681,18 +2284,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -1729,7 +2320,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -1743,29 +2334,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1774,16 +2365,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "plotters" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + +[[package]] +name = "plotters-svg" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -1791,9 +2413,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] @@ -1823,18 +2445,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", @@ -1844,7 +2466,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", @@ -1852,13 +2474,13 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf16337405ca084e9c78985114633b6827711d22b9e6ef6c6c0d665eb3f0b6e" +checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1869,9 +2491,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1963,27 +2585,27 @@ dependencies = [ [[package]] name = "redb" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6dd20d3cdeb9c7d2366a0b16b93b35b75aec15309fbeb7ce477138c9f68c8c0" +checksum = "84b1de48a7cf7ba193e81e078d17ee2b786236eed1d3f7c60f8a09545efc4925" dependencies = [ "libc", ] [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -2007,14 +2629,52 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" @@ -2039,18 +2699,18 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -2061,10 +2721,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -2075,9 +2736,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -2088,25 +2749,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -2115,9 +2775,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rusty-fork" @@ -2138,12 +2798,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] -name = "schannel" -version = "0.1.23" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "windows-sys 0.52.0", + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", ] [[package]] @@ -2154,9 +2823,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -2167,9 +2836,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -2182,31 +2851,78 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] -name = "serde" -version = "1.0.203" +name = "seq-macro" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.203" +name = "serde_bytes" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +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", @@ -2233,6 +2949,21 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2245,14 +2976,14 @@ dependencies = [ [[package]] name = "simple-request" version = "0.1.0" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ "http-body-util", "hyper", "hyper-rustls", "hyper-util", "tokio", - "tower-service", + "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2261,6 +2992,16 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "skein" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f412279217fa74b69094bf6b5cde63dd0ece9b85d94fedda9bbfdfb2666125cf" +dependencies = [ + "digest", + "threefish", +] + [[package]] name = "slab" version = "0.4.9" @@ -2295,34 +3036,45 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "std-shims" version = "0.1.1" -source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1" +source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79" dependencies = [ "hashbrown 0.14.5", "spin", ] [[package]] -name = "subtle" -version = "2.5.0" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "strum_macros", ] [[package]] -name = "syn" -version = "2.0.66" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -2338,9 +3090,21 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "synchronoise" version = "1.0.1" @@ -2358,34 +3122,45 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2399,19 +3174,26 @@ dependencies = [ ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "threefish" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "a693d0c8cf16973fac5a93fbe47b8c6452e7097d4fcac49f3d7a18e39c76e62e" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "crunchy", + "serde", + "serde_json", ] [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2424,32 +3206,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2465,9 +3246,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -2490,16 +3271,13 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", - "futures-util", - "hashbrown 0.14.5", "pin-project-lite", "slab", "tokio", @@ -2507,54 +3285,92 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "0.6.6" +name = "toml" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.6", + "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower" -version = "0.4.13" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ "futures-core", "futures-util", - "hdrhistogram", - "indexmap 1.9.3", - "pin-project", "pin-project-lite", - "rand", - "slab", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "git+https://github.com/Cuprate/tower.git?rev=6c7faf0#6c7faf0e9dbc74aef5d3110313324bc7e1f997cf" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", "tokio", "tokio-util", - "tower-layer", - "tower-service", + "tower-layer 0.3.3 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", + "tower-service 0.3.3 (git+https://github.com/Cuprate/tower.git?rev=6c7faf0)", "tracing", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "git+https://github.com/Cuprate/tower.git?rev=6c7faf0#6c7faf0e9dbc74aef5d3110313324bc7e1f997cf" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "git+https://github.com/Cuprate/tower.git?rev=6c7faf0#6c7faf0e9dbc74aef5d3110313324bc7e1f997cf" [[package]] name = "tracing" @@ -2562,6 +3378,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", @@ -2575,7 +3392,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2585,6 +3402,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", ] [[package]] @@ -2593,7 +3422,16 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", "tracing-core", + "tracing-log", ] [[package]] @@ -2616,15 +3454,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -2635,12 +3473,36 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.2" @@ -2653,10 +3515,16 @@ dependencies = [ ] [[package]] -name = "version_check" -version = "0.9.4" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" @@ -2667,6 +3535,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2684,34 +3562,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2719,22 +3598,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "winapi" @@ -2752,6 +3650,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2760,12 +3667,12 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.57.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.5", + "windows-core 0.58.0", + "windows-targets 0.52.6", ] [[package]] @@ -2774,50 +3681,61 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.57.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ "windows-implement", "windows-interface", "windows-result", - "windows-targets 0.52.5", + "windows-strings", + "windows-targets 0.52.6", ] [[package]] name = "windows-implement" -version = "0.57.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "windows-interface" -version = "0.57.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -2835,7 +3753,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2855,18 +3782,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2877,9 +3804,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2889,9 +3816,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2901,15 +3828,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2919,9 +3846,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2931,9 +3858,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2943,9 +3870,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2955,15 +3882,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -2979,28 +3906,29 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -3020,5 +3948,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index b00a4b9..1813057 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,33 +1,61 @@ [workspace] resolver = "2" - members = [ + # Binaries + "binaries/cuprated", + + # Benchmarks + "benches/benchmark/bin", + "benches/benchmark/lib", + "benches/benchmark/example", + "benches/criterion/example", + "benches/criterion/cuprate-json-rpc", + + # Consensus "consensus", + "consensus/context", "consensus/fast-sync", "consensus/rules", - "cryptonight", - "helper", + + # Net "net/epee-encoding", "net/fixed-bytes", "net/levin", "net/wire", + + # P2P "p2p/p2p", "p2p/p2p-core", + "p2p/bucket", "p2p/dandelion-tower", "p2p/async-buffer", "p2p/address-book", + + # Storage "storage/blockchain", + "storage/service", "storage/txpool", "storage/database", - "pruning", - "test-utils", - "types", + + # RPC "rpc/json-rpc", "rpc/types", "rpc/interface", + + # ZMQ + "zmq/types", + + # Misc + "constants", + "cryptonight", + "helper", + "pruning", + "test-utils", + "types", ] [profile.release] +panic = "abort" lto = true # Build with LTO strip = "none" # Keep panic stack traces codegen-units = 1 # Optimize for binary speed over compile times @@ -46,50 +74,91 @@ opt-level = 1 opt-level = 3 [workspace.dependencies] -async-trait = { version = "0.1.74", default-features = false } -bitflags = { version = "2.4.2", default-features = false } -borsh = { version = "1.2.1", default-features = false } -bytemuck = { version = "1.14.3", default-features = false } -bytes = { version = "1.5.0", default-features = false } -cfg-if = { version = "1.0.0", default-features = false } -clap = { version = "4.4.7", default-features = false } -chrono = { version = "0.4.31", default-features = false } -crypto-bigint = { version = "0.5.5", default-features = false } -crossbeam = { version = "0.8.4", default-features = false } -curve25519-dalek = { version = "4.1.3", default-features = false } -dalek-ff-group = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false } -dashmap = { version = "5.5.3", default-features = false } -dirs = { version = "5.0.1", default-features = false } -futures = { version = "0.3.29", default-features = false } -hex = { version = "0.4.3", default-features = false } +# Cuprate members +cuprate-benchmark-lib = { path = "benches/benchmark/lib", default-features = false } +cuprate-benchmark-example = { path = "benches/benchmark/example", default-features = false } +cuprate-fast-sync = { path = "consensus/fast-sync", default-features = false } +cuprate-consensus-rules = { path = "consensus/rules", default-features = false } +cuprate-constants = { path = "constants", default-features = false } +cuprate-consensus = { path = "consensus", default-features = false } +cuprate-consensus-context = { path = "consensus/context", default-features = false } +cuprate-cryptonight = { path = "cryptonight", default-features = false } +cuprate-helper = { path = "helper", default-features = false } +cuprate-epee-encoding = { path = "net/epee-encoding", default-features = false } +cuprate-fixed-bytes = { path = "net/fixed-bytes", default-features = false } +cuprate-levin = { path = "net/levin", default-features = false } +cuprate-wire = { path = "net/wire", default-features = false } +cuprate-p2p = { path = "p2p/p2p", default-features = false } +cuprate-p2p-core = { path = "p2p/p2p-core", default-features = false } +cuprate-p2p-bucket = { path = "p2p/p2p-bucket", default-features = false } +cuprate-dandelion-tower = { path = "p2p/dandelion-tower", default-features = false } +cuprate-async-buffer = { path = "p2p/async-buffer", default-features = false } +cuprate-address-book = { path = "p2p/address-book", default-features = false } +cuprate-blockchain = { path = "storage/blockchain", default-features = false } +cuprate-database = { path = "storage/database", default-features = false } +cuprate-database-service = { path = "storage/service", default-features = false } +cuprate-txpool = { path = "storage/txpool", default-features = false } +cuprate-pruning = { path = "pruning", default-features = false } +cuprate-test-utils = { path = "test-utils", default-features = false } +cuprate-types = { path = "types", default-features = false } +cuprate-json-rpc = { path = "rpc/json-rpc", default-features = false } +cuprate-rpc-types = { path = "rpc/types", default-features = false } +cuprate-rpc-interface = { path = "rpc/interface", default-features = false } +cuprate-zmq-types = { path = "zmq/types", default-features = false } + +# External dependencies +anyhow = { version = "1", default-features = false } +arrayvec = { version = "0.7", default-features = false } +async-trait = { version = "0.1", default-features = false } +bitflags = { version = "2", default-features = false } +blake3 = { version = "1", default-features = false } +borsh = { version = "1", default-features = false } +bytemuck = { version = "1", default-features = false } +bytes = { version = "1", default-features = false } +cfg-if = { version = "1", default-features = false } +clap = { version = "4", default-features = false } +chrono = { version = "0.4", default-features = false } +crypto-bigint = { version = "0.5", default-features = false } +crossbeam = { version = "0.8", default-features = false } +const_format = { version = "0.2", default-features = false } +curve25519-dalek = { version = "4", default-features = false } +dashmap = { version = "6", default-features = false } +dirs = { version = "5", default-features = false } +futures = { version = "0.3", default-features = false } +hex = { version = "0.4", default-features = false } hex-literal = { version = "0.4", default-features = false } -indexmap = { version = "2.2.5", default-features = false } -monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false } -multiexp = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false } -paste = { version = "1.0.14", default-features = false } -pin-project = { version = "1.1.3", default-features = false } +indexmap = { version = "2", default-features = false } +monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce", default-features = false } +paste = { version = "1", default-features = false } +pin-project = { version = "1", default-features = false } randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "0028464", default-features = false } -rand = { version = "0.8.5", default-features = false } -rand_distr = { version = "0.4.3", default-features = false } -rayon = { version = "1.9.0", default-features = false } -serde_bytes = { version = "0.11.12", default-features = false } -serde_json = { version = "1.0.108", default-features = false } -serde = { version = "1.0.190", default-features = false } -thiserror = { version = "1.0.50", default-features = false } -thread_local = { version = "1.1.7", default-features = false } -tokio-util = { version = "0.7.10", default-features = false } -tokio-stream = { version = "0.1.14", default-features = false } -tokio = { version = "1.33.0", default-features = false } -tower = { version = "0.4.13", default-features = false } -tracing-subscriber = { version = "0.3.17", default-features = false } -tracing = { version = "0.1.40", default-features = false } +rand = { version = "0.8", default-features = false } +rand_distr = { version = "0.4", default-features = false } +rayon = { version = "1", default-features = false } +serde_bytes = { version = "0.11", default-features = false } +serde_json = { version = "1", default-features = false } +serde = { version = "1", default-features = false } +strum = { version = "0.26", default-features = false } +thiserror = { version = "1", default-features = false } +thread_local = { version = "1", default-features = false } +tokio-util = { version = "0.7", default-features = false } +tokio-stream = { version = "0.1", default-features = false } +tokio = { version = "1", default-features = false } +tower = { git = "https://github.com/Cuprate/tower.git", rev = "6c7faf0", default-features = false } # +toml = { version = "0.8", default-features = false } +tracing-subscriber = { version = "0.3", default-features = false } +tracing = { version = "0.1", default-features = false } ## workspace.dev-dependencies -tempfile = { version = "3" } -pretty_assertions = { version = "1.4.0" } -proptest = { version = "1" } -proptest-derive = { version = "0.4.0" } -tokio-test = { version = "0.4.4" } +criterion = { version = "0.5" } +function_name = { version = "0.3" } +monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" } +monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" } +tempfile = { version = "3" } +pretty_assertions = { version = "1" } +proptest = { version = "1" } +proptest-derive = { version = "0.5" } +tokio-test = { version = "0.4" } ## TODO: ## Potential dependencies. @@ -101,7 +170,222 @@ tokio-test = { version = "0.4.4" } # regex = { version = "1.10.2" } # Regular expressions | https://github.com/rust-lang/regex # ryu = { version = "1.0.15" } # Fast float to string formatting | https://github.com/dtolnay/ryu -# Maybe one day. -# disk = { version = "*" } # (De)serialization to/from disk with various file formats | https://github.com/hinto-janai/disk -# readable = { version = "*" } # Stack-based string formatting utilities | https://github.com/hinto-janai/readable -# json-rpc = { git = "https://github.com/hinto-janai/json-rpc" } # JSON-RPC 2.0 types +# Lints: cold, warm, hot: +[workspace.lints.clippy] +# Cold +borrow_as_ptr = "deny" +case_sensitive_file_extension_comparisons = "deny" +cast_lossless = "deny" +cast_ptr_alignment = "deny" +checked_conversions = "deny" +cloned_instead_of_copied = "deny" +const_is_empty = "deny" +doc_lazy_continuation = "deny" +doc_link_with_quotes = "deny" +duplicated_attributes = "deny" +empty_enum = "deny" +enum_glob_use = "deny" +expl_impl_clone_on_copy = "deny" +explicit_into_iter_loop = "deny" +filter_map_next = "deny" +flat_map_option = "deny" +from_iter_instead_of_collect = "deny" +if_not_else = "deny" +ignored_unit_patterns = "deny" +inconsistent_struct_constructor = "deny" +index_refutable_slice = "deny" +inefficient_to_string = "deny" +invalid_upcast_comparisons = "deny" +iter_filter_is_ok = "deny" +iter_filter_is_some = "deny" +implicit_clone = "deny" +legacy_numeric_constants = "deny" +manual_c_str_literals = "deny" +manual_pattern_char_comparison = "deny" +manual_instant_elapsed = "deny" +manual_inspect = "deny" +manual_is_variant_and = "deny" +manual_let_else = "deny" +manual_ok_or = "deny" +manual_string_new = "deny" +manual_unwrap_or_default = "deny" +map_unwrap_or = "deny" +match_bool = "deny" +match_same_arms = "deny" +match_wildcard_for_single_variants = "deny" +mismatching_type_param_order = "deny" +missing_transmute_annotations = "deny" +mut_mut = "deny" +needless_bitwise_bool = "deny" +needless_character_iteration = "deny" +needless_continue = "deny" +needless_for_each = "deny" +needless_maybe_sized = "deny" +needless_raw_string_hashes = "deny" +no_effect_underscore_binding = "deny" +no_mangle_with_rust_abi = "deny" +option_as_ref_cloned = "deny" +option_option = "deny" +ptr_as_ptr = "deny" +ptr_cast_constness = "deny" +pub_underscore_fields = "deny" +redundant_closure_for_method_calls = "deny" +ref_as_ptr = "deny" +ref_option_ref = "deny" +same_functions_in_if_condition = "deny" +semicolon_if_nothing_returned = "deny" +trivially_copy_pass_by_ref = "deny" +uninlined_format_args = "deny" +unnecessary_join = "deny" +unnested_or_patterns = "deny" +unused_async = "deny" +unused_self = "deny" +used_underscore_binding = "deny" +zero_sized_map_values = "deny" +as_ptr_cast_mut = "deny" +clear_with_drain = "deny" +collection_is_never_read = "deny" +debug_assert_with_mut_call = "deny" +derive_partial_eq_without_eq = "deny" +empty_line_after_doc_comments = "deny" +empty_line_after_outer_attr = "deny" +equatable_if_let = "deny" +iter_on_empty_collections = "deny" +iter_on_single_items = "deny" +iter_with_drain = "deny" +needless_collect = "deny" +needless_pass_by_ref_mut = "deny" +negative_feature_names = "deny" +non_send_fields_in_send_ty = "deny" +nonstandard_macro_braces = "deny" +path_buf_push_overwrite = "deny" +read_zero_byte_vec = "deny" +redundant_clone = "deny" +redundant_feature_names = "deny" +trailing_empty_array = "deny" +trait_duplication_in_bounds = "deny" +type_repetition_in_bounds = "deny" +uninhabited_references = "deny" +unnecessary_struct_initialization = "deny" +unused_peekable = "deny" +unused_rounding = "deny" +use_self = "deny" +useless_let_if_seq = "deny" +wildcard_dependencies = "deny" +unseparated_literal_suffix = "deny" +unnecessary_safety_doc = "deny" +unnecessary_safety_comment = "deny" +unnecessary_self_imports = "deny" +string_to_string = "deny" +rest_pat_in_fully_bound_structs = "deny" +redundant_type_annotations = "deny" +infinite_loop = "deny" +zero_repeat_side_effects = "deny" +non_zero_suggestions = "deny" +manual_is_power_of_two = "deny" +used_underscore_items = "deny" + +# Warm +cast_possible_truncation = "deny" +cast_possible_wrap = "deny" +cast_precision_loss = "deny" +cast_sign_loss = "deny" +copy_iterator = "deny" +doc_markdown = "deny" +explicit_deref_methods = "deny" +explicit_iter_loop = "deny" +float_cmp = "deny" +fn_params_excessive_bools = "deny" +into_iter_without_iter = "deny" +iter_without_into_iter = "deny" +iter_not_returning_iterator = "deny" +large_digit_groups = "deny" +large_types_passed_by_value = "deny" +manual_assert = "deny" +maybe_infinite_iter = "deny" +missing_fields_in_debug = "deny" +needless_pass_by_value = "deny" +range_minus_one = "deny" +range_plus_one = "deny" +redundant_else = "deny" +ref_binding_to_reference = "deny" +return_self_not_must_use = "deny" +single_match_else = "deny" +string_add_assign = "deny" +transmute_ptr_to_ptr = "deny" +unchecked_duration_subtraction = "deny" +unnecessary_box_returns = "deny" +unnecessary_wraps = "deny" +branches_sharing_code = "deny" +fallible_impl_from = "deny" +missing_const_for_fn = "deny" +significant_drop_in_scrutinee = "deny" +significant_drop_tightening = "deny" +try_err = "deny" +lossy_float_literal = "deny" +let_underscore_must_use = "deny" +iter_over_hash_type = "deny" +get_unwrap = "deny" +error_impl_error = "deny" +empty_structs_with_brackets = "deny" +empty_enum_variants_with_brackets = "deny" +empty_drop = "deny" +clone_on_ref_ptr = "deny" +upper_case_acronyms = "deny" +allow_attributes = "deny" + +# Hot +# inline_always = "deny" +# large_futures = "deny" +# large_stack_arrays = "deny" +# linkedlist = "deny" +# missing_errors_doc = "deny" +# missing_panics_doc = "deny" +# should_panic_without_expect = "deny" +# similar_names = "deny" +# too_many_lines = "deny" +# unreadable_literal = "deny" +# wildcard_imports = "deny" +# allow_attributes_without_reason = "deny" +# missing_assert_message = "deny" +# missing_docs_in_private_items = "deny" +undocumented_unsafe_blocks = "deny" +# multiple_unsafe_ops_per_block = "deny" +# single_char_lifetime_names = "deny" +# wildcard_enum_match_arm = "deny" + +[workspace.lints.rust] +# Cold +future_incompatible = { level = "deny", priority = -1 } +nonstandard_style = { level = "deny", priority = -1 } +absolute_paths_not_starting_with_crate = "deny" +explicit_outlives_requirements = "deny" +keyword_idents_2018 = "deny" +keyword_idents_2024 = "deny" +missing_abi = "deny" +non_ascii_idents = "deny" +non_local_definitions = "deny" +redundant_lifetimes = "deny" +single_use_lifetimes = "deny" +trivial_casts = "deny" +trivial_numeric_casts = "deny" +unsafe_op_in_unsafe_fn = "deny" +unused_crate_dependencies = "deny" +unused_import_braces = "deny" +unused_lifetimes = "deny" +unused_macro_rules = "deny" +ambiguous_glob_imports = "deny" +unused_unsafe = "deny" + +# Warm +let_underscore = { level = "deny", priority = -1 } +unreachable_pub = "deny" +unused_qualifications = "deny" +variant_size_differences = "deny" +non_camel_case_types = "deny" + +# Hot +# unused_results = "deny" +# non_exhaustive_omitted_patterns = "deny" +# missing_docs = "deny" +# missing_copy_implementations = "deny" diff --git a/README.md b/README.md index 100900d..a9050d5 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Cuprate maintains various documentation books: | [Monero's protocol book](https://monero-book.cuprate.org) | Documents the Monero protocol | | [Cuprate's user book](https://user.cuprate.org) | Practical user-guide for using `cuprated` | -For crate (library) documentation, see the `Documentation` section in [`CONTRIBUTING.md`](CONTRIBUTING.md). +For crate (library) documentation, see: https://doc.cuprate.org. This site holds documentation for Cuprate's crates and all dependencies. All Cuprate crates start with `cuprate_`, for example: [`cuprate_database`](https://doc.cuprate.org/cuprate_database). ## Contributing diff --git a/benches/README.md b/benches/README.md index 4640904..af6bb93 100644 --- a/benches/README.md +++ b/benches/README.md @@ -1 +1,5 @@ -# TODO +# Benches +This directory contains Cuprate's benchmarks and benchmarking utilities. + +See the [`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html) +to see how to create and run these benchmarks. \ No newline at end of file diff --git a/benches/benchmark/bin/Cargo.toml b/benches/benchmark/bin/Cargo.toml new file mode 100644 index 0000000..36d0b2c --- /dev/null +++ b/benches/benchmark/bin/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "cuprate-benchmark" +version = "0.0.0" +edition = "2021" +description = "Cuprate's benchmarking binary" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin" +keywords = ["cuprate", "benchmarking", "binary"] + +[features] +# All new benchmarks should be added here! +all = ["example"] + +# Non-benchmark features. +default = [] +json = [] +trace = [] +debug = [] +warn = [] +info = [] +error = [] + +# Benchmark features. +# New benchmarks should be added here! +example = [ + "dep:cuprate-benchmark-example" +] + +[dependencies] +cuprate-benchmark-lib = { workspace = true } +cuprate-benchmark-example = { workspace = true, optional = true } + +cfg-if = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["std"] } +tracing = { workspace = true, features = ["std", "attributes"] } +tracing-subscriber = { workspace = true, features = ["fmt", "std", "env-filter"] } + +[dev-dependencies] + +[lints] +workspace = true \ No newline at end of file diff --git a/benches/benchmark/bin/README.md b/benches/benchmark/bin/README.md new file mode 100644 index 0000000..ad0700f --- /dev/null +++ b/benches/benchmark/bin/README.md @@ -0,0 +1,27 @@ +## `cuprate-benchmark` +This crate links all benchmarks together into a single binary that can be run as: `cuprate-benchmark`. + +`cuprate-benchmark` will run all enabled benchmarks sequentially and print data at the end. + +## Benchmarks +Benchmarks are opt-in and enabled via features. + +| Feature | Enables which benchmark crate? | +|----------|--------------------------------| +| example | cuprate-benchmark-example | +| database | cuprate-benchmark-database | + +## Features +These are features that aren't for enabling benchmarks, but rather for other things. + +Since `cuprate-benchmark` is built right before it is ran, +these features almost act like command line arguments. + +| Features | Does what | +|----------|-----------| +| json | Prints JSON timings instead of a markdown table +| trace | Use the `trace` log-level +| debug | Use the `debug` log-level +| warn | Use the `warn` log-level +| info | Use the `info` log-level (default) +| error | Use the `error` log-level \ No newline at end of file diff --git a/benches/benchmark/bin/src/log.rs b/benches/benchmark/bin/src/log.rs new file mode 100644 index 0000000..455f130 --- /dev/null +++ b/benches/benchmark/bin/src/log.rs @@ -0,0 +1,29 @@ +use cfg_if::cfg_if; +use tracing::{info, instrument, Level}; +use tracing_subscriber::FmtSubscriber; + +/// Initializes the `tracing` logger. +#[instrument] +pub(crate) fn init_logger() { + const LOG_LEVEL: Level = { + cfg_if! { + if #[cfg(feature = "trace")] { + Level::TRACE + } else if #[cfg(feature = "debug")] { + Level::DEBUG + } else if #[cfg(feature = "warn")] { + Level::WARN + } else if #[cfg(feature = "info")] { + Level::INFO + } else if #[cfg(feature = "error")] { + Level::ERROR + } else { + Level::INFO + } + } + }; + + FmtSubscriber::builder().with_max_level(LOG_LEVEL).init(); + + info!("Log level: {LOG_LEVEL}"); +} diff --git a/benches/benchmark/bin/src/main.rs b/benches/benchmark/bin/src/main.rs new file mode 100644 index 0000000..02c480a --- /dev/null +++ b/benches/benchmark/bin/src/main.rs @@ -0,0 +1,49 @@ +#![doc = include_str!("../README.md")] +#![allow( + unused_crate_dependencies, + reason = "this crate imports many potentially unused dependencies" +)] + +mod log; +mod print; +mod run; +mod timings; + +use cfg_if::cfg_if; + +/// What `main()` does: +/// 1. Run all enabled benchmarks +/// 2. Record benchmark timings +/// 3. Print timing data +/// +/// To add a new benchmark to be ran here: +/// 1. Copy + paste a `cfg_if` block +/// 2. Change it to your benchmark's feature flag +/// 3. Change it to your benchmark's type +#[allow( + clippy::allow_attributes, + unused_variables, + unused_mut, + unreachable_code, + reason = "clippy does not account for all cfg()s" +)] +fn main() { + log::init_logger(); + + let mut timings = timings::Timings::new(); + + cfg_if! { + if #[cfg(not(any(feature = "example")))] { + println!("No feature specified. Use `--features $BENCHMARK_FEATURE` when building."); + return; + } + } + + cfg_if! { + if #[cfg(feature = "example")] { + run::run_benchmark::(&mut timings); + } + } + + print::print_timings(&timings); +} diff --git a/benches/benchmark/bin/src/print.rs b/benches/benchmark/bin/src/print.rs new file mode 100644 index 0000000..36a5f05 --- /dev/null +++ b/benches/benchmark/bin/src/print.rs @@ -0,0 +1,38 @@ +#![expect(dead_code, reason = "code hidden behind feature flags")] + +use cfg_if::cfg_if; + +use crate::timings::Timings; + +/// Print the final the final markdown table of benchmark timings. +pub(crate) fn print_timings(timings: &Timings) { + println!("\nFinished all benchmarks, printing results:"); + + cfg_if! { + if #[cfg(feature = "json")] { + print_timings_json(timings); + } else { + print_timings_markdown(timings); + } + } +} + +/// Default timing formatting. +pub(crate) fn print_timings_markdown(timings: &Timings) { + let mut s = String::new(); + s.push_str("| Benchmark | Time (seconds) |\n"); + s.push_str("|------------------------------------|----------------|"); + + #[expect(clippy::iter_over_hash_type)] + for (k, v) in timings { + s += &format!("\n| {k:<34} | {v:<14} |"); + } + + println!("\n{s}"); +} + +/// Enabled via `json` feature. +pub(crate) fn print_timings_json(timings: &Timings) { + let json = serde_json::to_string_pretty(timings).unwrap(); + println!("\n{json}"); +} diff --git a/benches/benchmark/bin/src/run.rs b/benches/benchmark/bin/src/run.rs new file mode 100644 index 0000000..05a220f --- /dev/null +++ b/benches/benchmark/bin/src/run.rs @@ -0,0 +1,36 @@ +use tracing::{info, instrument, trace}; + +use cuprate_benchmark_lib::Benchmark; + +use crate::timings::Timings; + +/// Run a [`Benchmark`] and record its timing. +#[instrument(skip_all)] +pub(crate) fn run_benchmark(timings: &mut Timings) { + // Get the benchmark name. + let name = B::name(); + trace!("Running benchmark: {name}"); + + // Setup the benchmark input. + let input = B::SETUP(); + + // Sleep before running the benchmark. + trace!("Pre-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION); + std::thread::sleep(B::PRE_SLEEP_DURATION); + + // Run/time the benchmark. + let now = std::time::Instant::now(); + B::MAIN(input); + let time = now.elapsed().as_secs_f32(); + + // Print the benchmark timings. + info!("{name:>34} ... {time}"); + assert!( + timings.insert(name, time).is_none(), + "There were 2 benchmarks with the same name - this collides the final output: {name}", + ); + + // Sleep for a cooldown period after the benchmark run. + trace!("Post-benchmark, sleeping for: {:?}", B::POST_SLEEP_DURATION); + std::thread::sleep(B::POST_SLEEP_DURATION); +} diff --git a/benches/benchmark/bin/src/timings.rs b/benches/benchmark/bin/src/timings.rs new file mode 100644 index 0000000..34a0795 --- /dev/null +++ b/benches/benchmark/bin/src/timings.rs @@ -0,0 +1,5 @@ +/// Benchmark timing data. +/// +/// - Key = benchmark name +/// - Value = benchmark time in seconds +pub(crate) type Timings = std::collections::HashMap<&'static str, f32>; diff --git a/benches/benchmark/example/Cargo.toml b/benches/benchmark/example/Cargo.toml new file mode 100644 index 0000000..5728bcd --- /dev/null +++ b/benches/benchmark/example/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cuprate-benchmark-example" +version = "0.0.0" +edition = "2021" +description = "Example showcasing Cuprate's benchmarking harness" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/example" +keywords = ["cuprate", "benchmarking", "example"] + +[dependencies] +cuprate-benchmark-lib = { path = "../lib" } + +[dev-dependencies] + +[lints] +workspace = true \ No newline at end of file diff --git a/benches/benchmark/example/README.md b/benches/benchmark/example/README.md new file mode 100644 index 0000000..be6b716 --- /dev/null +++ b/benches/benchmark/example/README.md @@ -0,0 +1,3 @@ +## `cuprate-benchmark-example` +This crate contains a short example benchmark that shows how to implement and use +`cuprate-benchmark-lib` so that it can be ran by `cuprate-benchmark`. \ No newline at end of file diff --git a/benches/benchmark/example/src/lib.rs b/benches/benchmark/example/src/lib.rs new file mode 100644 index 0000000..cc704a7 --- /dev/null +++ b/benches/benchmark/example/src/lib.rs @@ -0,0 +1,42 @@ +#![doc = include_str!("../README.md")] + +use std::hint::black_box; + +use cuprate_benchmark_lib::Benchmark; + +/// Marker struct that implements [`Benchmark`] +pub struct Example; + +/// The input to our benchmark function. +pub type ExampleBenchmarkInput = u64; + +/// The setup function that creates the input. +pub const fn example_benchmark_setup() -> ExampleBenchmarkInput { + 1 +} + +/// The main benchmarking function. +#[expect(clippy::unit_arg)] +pub fn example_benchmark_main(input: ExampleBenchmarkInput) { + // In this case, we're simply benchmarking the + // performance of simple arithmetic on the input data. + + fn math(input: ExampleBenchmarkInput, number: u64) { + let x = input; + let x = black_box(x * number); + let x = black_box(x / number); + let x = black_box(x + number); + let _ = black_box(x - number); + } + + for number in 1..100_000_000 { + black_box(math(input, number)); + } +} + +// This implementation will be run by `cuprate-benchmark`. +impl Benchmark for Example { + type Input = ExampleBenchmarkInput; + const SETUP: fn() -> Self::Input = example_benchmark_setup; + const MAIN: fn(Self::Input) = example_benchmark_main; +} diff --git a/benches/benchmark/lib/Cargo.toml b/benches/benchmark/lib/Cargo.toml new file mode 100644 index 0000000..b0771f0 --- /dev/null +++ b/benches/benchmark/lib/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cuprate-benchmark-lib" +version = "0.0.0" +edition = "2021" +description = "Cuprate's benchmarking library" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib" +keywords = ["cuprate", "benchmarking", "library"] + +[features] + +[dependencies] + +[dev-dependencies] + +[lints] +workspace = true \ No newline at end of file diff --git a/benches/benchmark/lib/README.md b/benches/benchmark/lib/README.md new file mode 100644 index 0000000..9ea79ae --- /dev/null +++ b/benches/benchmark/lib/README.md @@ -0,0 +1,15 @@ +## `cuprate-benchmark-lib` +This crate is the glue between +[`cuprate-benchmark`](https://github.com/Cuprate/cuprate/tree/benches/benches/benchmark/bin) +and all the benchmark crates. + +It defines the [`crate::Benchmark`] trait, which is the behavior of all benchmarks. + +See the [`cuprate-benchmark-example`](https://github.com/Cuprate/cuprate/tree/benches/benches/benchmark/example) +crate to see an example implementation of this trait. + +After implementing this trait, a few steps must +be done such that the `cuprate-benchmark` binary +can actually run your benchmark crate; see the +[`Benchmarking` section in the Architecture book](https://architecture.cuprate.org/benchmarking/intro.html) +to see how to do this. \ No newline at end of file diff --git a/benches/benchmark/lib/src/benchmark.rs b/benches/benchmark/lib/src/benchmark.rs new file mode 100644 index 0000000..4dca550 --- /dev/null +++ b/benches/benchmark/lib/src/benchmark.rs @@ -0,0 +1,45 @@ +//! Benchmarking trait. + +use std::time::Duration; + +/// A benchmarking function and its inputs. +pub trait Benchmark { + /// The benchmark's name. + /// + /// This is automatically implemented + /// as the name of the [`Self`] type. + // + // FIXME: use `const` instead of `fn` when stable + // + fn name() -> &'static str { + std::any::type_name::() + } + + /// Input to the main benchmarking function. + /// + /// This is passed to [`Self::MAIN`]. + type Input; + + /// Setup function to generate the input. + /// + /// This function is not timed. + const SETUP: fn() -> Self::Input; + + /// The main function to benchmark. + /// + /// The start of the timer begins right before + /// this function is called and ends after the + /// function returns. + const MAIN: fn(Self::Input); + + /// `cuprate-benchmark` will sleep for this [`Duration`] after + /// creating the [`Self::Input`], but before starting [`Self::MAIN`]. + /// + /// 1 second by default. + const PRE_SLEEP_DURATION: Duration = Duration::from_secs(1); + + /// `cuprate-benchmark` will sleep for this [`Duration`] after [`Self::MAIN`]. + /// + /// 1 second by default. + const POST_SLEEP_DURATION: Duration = Duration::from_secs(1); +} diff --git a/benches/benchmark/lib/src/lib.rs b/benches/benchmark/lib/src/lib.rs new file mode 100644 index 0000000..a6bec82 --- /dev/null +++ b/benches/benchmark/lib/src/lib.rs @@ -0,0 +1,5 @@ +#![doc = include_str!("../README.md")] + +mod benchmark; + +pub use benchmark::Benchmark; diff --git a/benches/criterion/cuprate-json-rpc/Cargo.toml b/benches/criterion/cuprate-json-rpc/Cargo.toml new file mode 100644 index 0000000..a0cae64 --- /dev/null +++ b/benches/criterion/cuprate-json-rpc/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "cuprate-criterion-json-rpc" +version = "0.0.0" +edition = "2021" +description = "Criterion benchmarking for cuprate-json-rpc" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-json-rpc" +keywords = ["cuprate", "json-rpc", "criterion", "benchmark"] + +[dependencies] +cuprate-json-rpc = { workspace = true } + +criterion = { workspace = true } +function_name = { workspace = true } +serde_json = { workspace = true, features = ["default"] } + +[[bench]] +name = "main" +harness = false + +[lints] +workspace = true \ No newline at end of file diff --git a/benches/criterion/cuprate-json-rpc/benches/main.rs b/benches/criterion/cuprate-json-rpc/benches/main.rs new file mode 100644 index 0000000..a724943 --- /dev/null +++ b/benches/criterion/cuprate-json-rpc/benches/main.rs @@ -0,0 +1,8 @@ +//! Benchmarks for `cuprate-json-rpc`. +#![allow(unused_crate_dependencies)] + +mod response; + +criterion::criterion_main! { + response::serde, +} diff --git a/benches/criterion/cuprate-json-rpc/benches/response.rs b/benches/criterion/cuprate-json-rpc/benches/response.rs new file mode 100644 index 0000000..890958e --- /dev/null +++ b/benches/criterion/cuprate-json-rpc/benches/response.rs @@ -0,0 +1,110 @@ +//! Benchmarks for [`Response`]. +#![allow(unused_attributes, unused_crate_dependencies)] + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use function_name::named; +use serde_json::{from_str, to_string_pretty}; + +use cuprate_json_rpc::{Id, Response}; + +// `serde` benchmarks on `Response`. +// +// These are benchmarked as `Response` has a custom serde implementation. +criterion_group! { + name = serde; + config = Criterion::default(); + targets = + response_from_str_u8, + response_from_str_u64, + response_from_str_string_5_len, + response_from_str_string_10_len, + response_from_str_string_100_len, + response_from_str_string_500_len, + response_to_string_pretty_u8, + response_to_string_pretty_u64, + response_to_string_pretty_string_5_len, + response_to_string_pretty_string_10_len, + response_to_string_pretty_string_100_len, + response_to_string_pretty_string_500_len, + response_from_str_bad_field_1, + response_from_str_bad_field_5, + response_from_str_bad_field_10, + response_from_str_bad_field_100, + response_from_str_missing_field, +} +criterion_main!(serde); + +/// Generate `from_str` deserialization benchmark functions for [`Response`]. +macro_rules! impl_from_str_benchmark { + ( + $( + $fn_name:ident => $request_type:ty => $request_string:literal, + )* + ) => { + $( + #[named] + fn $fn_name(c: &mut Criterion) { + let request_string = $request_string; + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let _r = from_str::>( + black_box(request_string) + ); + }); + }); + } + )* + }; +} + +impl_from_str_benchmark! { + response_from_str_u8 => u8 => r#"{"jsonrpc":"2.0","id":123,"result":0}"#, + response_from_str_u64 => u64 => r#"{"jsonrpc":"2.0","id":123,"result":0}"#, + response_from_str_string_5_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"hello"}"#, + response_from_str_string_10_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"hellohello"}"#, + response_from_str_string_100_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"}"#, + response_from_str_string_500_len => String => r#"{"jsonrpc":"2.0","id":123,"result":"helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"}"#, + + // The custom serde currently looks at all fields. + // These are for testing the performance if the serde + // has to parse through a bunch of unrelated fields. + response_from_str_bad_field_1 => u8 => r#"{"bad_field":0,"jsonrpc":"2.0","id":123,"result":0}"#, + response_from_str_bad_field_5 => u8 => r#"{"bad_field_1":0,"bad_field_2":0,"bad_field_3":0,"bad_field_4":0,"bad_field_5":0,"jsonrpc":"2.0","id":123,"result":0}"#, + response_from_str_bad_field_10 => u8 => r#"{"bad_field_1":0,"bad_field_2":0,"bad_field_3":0,"bad_field_4":0,"bad_field_5":0,"bad_field_6":0,"bad_field_7":0,"bad_field_8":0,"bad_field_9":0,"bad_field_10":0,"jsonrpc":"2.0","id":123,"result":0}"#, + response_from_str_bad_field_100 => u8 => r#"{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":0,"99":0,"100":0,"jsonrpc":"2.0","id":123,"result":0}"#, + + // These are missing the `jsonrpc` field. + response_from_str_missing_field => u8 => r#"{"id":123,"result":0}"#, +} + +/// Generate `to_string_pretty` serialization benchmark functions for [`Response`]. +macro_rules! impl_to_string_pretty_benchmark { + ( + $( + $fn_name:ident => $request_constructor:expr_2021, + )* + ) => { + $( + #[named] + fn $fn_name(c: &mut Criterion) { + let request = $request_constructor; + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let _s = to_string_pretty(black_box(&request)).unwrap(); + }); + }); + } + )* + }; +} + +impl_to_string_pretty_benchmark! { + response_to_string_pretty_u8 => Response::::ok(Id::Null, 0), + response_to_string_pretty_u64 => Response::::ok(Id::Null, 0), + response_to_string_pretty_string_5_len => Response::ok(Id::Null, String::from("hello")), + response_to_string_pretty_string_10_len => Response::ok(Id::Null, String::from("hellohello")), + response_to_string_pretty_string_100_len => Response::ok(Id::Null, String::from("helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld")), + response_to_string_pretty_string_500_len => Response::ok(Id::Null, String::from("helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld")), +} diff --git a/benches/criterion/cuprate-json-rpc/src/lib.rs b/benches/criterion/cuprate-json-rpc/src/lib.rs new file mode 100644 index 0000000..b29887a --- /dev/null +++ b/benches/criterion/cuprate-json-rpc/src/lib.rs @@ -0,0 +1,2 @@ +//! Benchmark lib for `cuprate-json-rpc`. +#![allow(unused_crate_dependencies, reason = "used in benchmarks")] diff --git a/benches/criterion/example/Cargo.toml b/benches/criterion/example/Cargo.toml new file mode 100644 index 0000000..43e6011 --- /dev/null +++ b/benches/criterion/example/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cuprate-criterion-example" +version = "0.0.0" +edition = "2021" +description = "Criterion benchmarking example for Cuprate" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/example" +keywords = ["cuprate", "criterion", "benchmark", "example"] + +[dependencies] +criterion = { workspace = true } +function_name = { workspace = true } +serde_json = { workspace = true, features = ["default"] } + +[[bench]] +name = "main" +harness = false + +[lints] +workspace = true \ No newline at end of file diff --git a/benches/criterion/example/README.md b/benches/criterion/example/README.md new file mode 100644 index 0000000..cf1983f --- /dev/null +++ b/benches/criterion/example/README.md @@ -0,0 +1,14 @@ +## `cuprate-criterion-example` +An example of using Criterion for benchmarking Cuprate crates. + +Consider copy+pasting this crate to use as a base when creating new Criterion benchmark crates. + +## `src/` +Benchmark crates have a `benches/` ran by `cargo bench`, but they are also crates themselves, +as in, they have a `src` folder that `benches/` can pull code from. + +The `src` directories in these benchmarking crates are usually filled with +helper functions, types, etc, that are used repeatedly in the benchmarks. + +## `benches/` +These are the actual benchmarks ran by `cargo bench`. diff --git a/benches/criterion/example/benches/example.rs b/benches/criterion/example/benches/example.rs new file mode 100644 index 0000000..7ea8e9a --- /dev/null +++ b/benches/criterion/example/benches/example.rs @@ -0,0 +1,48 @@ +//! Benchmarks. +#![allow(unused_attributes, unused_crate_dependencies)] + +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use function_name::named; + +use cuprate_criterion_example::SomeHardToCreateObject; + +// This is how you register criterion benchmarks. +criterion_group! { + name = benches; + config = Criterion::default(); + targets = benchmark_1, benchmark_range, +} +criterion_main!(benches); + +/// Benchmark a single input. +/// +/// +#[named] +fn benchmark_1(c: &mut Criterion) { + // It is recommended to use `function_name!()` as a benchmark + // identifier instead of manually re-typing the function name. + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(SomeHardToCreateObject::from(1)); + }); + }); +} + +/// Benchmark a range of inputs. +/// +/// +#[named] +fn benchmark_range(c: &mut Criterion) { + let mut group = c.benchmark_group(function_name!()); + + for i in 0..4 { + group.throughput(Throughput::Elements(i)); + group.bench_with_input(BenchmarkId::from_parameter(i), &i, |b, &i| { + b.iter(|| { + black_box(SomeHardToCreateObject::from(i)); + }); + }); + } + + group.finish(); +} diff --git a/benches/criterion/example/benches/main.rs b/benches/criterion/example/benches/main.rs new file mode 100644 index 0000000..d4f0bf8 --- /dev/null +++ b/benches/criterion/example/benches/main.rs @@ -0,0 +1,10 @@ +//! Benchmarks examples. +#![allow(unused_crate_dependencies)] + +// All modules within `benches/` are `mod`ed here. +mod example; + +// And all the Criterion benchmarks are registered like so: +criterion::criterion_main! { + example::benches, +} diff --git a/benches/criterion/example/src/lib.rs b/benches/criterion/example/src/lib.rs new file mode 100644 index 0000000..0f732a4 --- /dev/null +++ b/benches/criterion/example/src/lib.rs @@ -0,0 +1,13 @@ +#![doc = include_str!("../README.md")] // See the README for crate documentation. +#![allow(unused_crate_dependencies, reason = "used in benchmarks")] + +/// Shared type that all benchmarks can use. +#[expect(dead_code)] +pub struct SomeHardToCreateObject(u64); + +impl From for SomeHardToCreateObject { + /// Shared function that all benchmarks can use. + fn from(value: u64) -> Self { + Self(value) + } +} diff --git a/binaries/cuprated/Cargo.toml b/binaries/cuprated/Cargo.toml new file mode 100644 index 0000000..acf8827 --- /dev/null +++ b/binaries/cuprated/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "cuprated" +version = "0.0.1" +edition = "2021" +description = "The Cuprate Rust Monero node." +license = "AGPL-3.0-only" +authors = ["Boog900", "hinto-janai", "SyntheticBird45"] +repository = "https://github.com/Cuprate/cuprate/tree/main/binaries/cuprated" + +[dependencies] +# TODO: after v1.0.0, remove unneeded dependencies. +cuprate-consensus = { workspace = true } +cuprate-fast-sync = { workspace = true } +cuprate-consensus-context = { workspace = true } +cuprate-consensus-rules = { workspace = true } +cuprate-cryptonight = { workspace = true } +cuprate-helper = { workspace = true, features = ["serde"] } +cuprate-epee-encoding = { workspace = true } +cuprate-fixed-bytes = { workspace = true } +cuprate-levin = { workspace = true } +cuprate-wire = { workspace = true } +cuprate-p2p = { workspace = true } +cuprate-p2p-core = { workspace = true } +cuprate-dandelion-tower = { workspace = true, features = ["txpool"] } +cuprate-async-buffer = { workspace = true } +cuprate-address-book = { workspace = true } +cuprate-blockchain = { workspace = true } +cuprate-database-service = { workspace = true, features = ["serde"] } +cuprate-txpool = { workspace = true } +cuprate-database = { workspace = true, features = ["serde"] } +cuprate-pruning = { workspace = true } +cuprate-test-utils = { workspace = true } +cuprate-types = { workspace = true } +cuprate-json-rpc = { workspace = true } +cuprate-rpc-interface = { workspace = true } +cuprate-rpc-types = { workspace = true } + + +# TODO: after v1.0.0, remove unneeded dependencies. +anyhow = { workspace = true } +async-trait = { workspace = true } +bitflags = { workspace = true } +borsh = { workspace = true } +bytemuck = { workspace = true } +bytes = { workspace = true } +cfg-if = { workspace = true } +clap = { workspace = true, features = ["cargo", "help", "wrap_help"] } +chrono = { workspace = true } +crypto-bigint = { workspace = true } +crossbeam = { workspace = true } +curve25519-dalek = { workspace = true } +const_format = { workspace = true } +dashmap = { workspace = true } +dirs = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true } +indexmap = { workspace = true } +monero-serai = { workspace = true } +paste = { workspace = true } +pin-project = { workspace = true } +randomx-rs = { workspace = true } +rand = { workspace = true } +rand_distr = { workspace = true } +rayon = { workspace = true } +serde_bytes = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +thread_local = { workspace = true } +tokio-util = { workspace = true } +tokio-stream = { workspace = true } +tokio = { workspace = true } +toml = { workspace = true, features = ["parse", "display"]} +tower = { workspace = true } +tracing-subscriber = { workspace = true, features = ["std", "fmt", "default"] } +tracing = { workspace = true, features = ["default"] } + +[lints] +workspace = true diff --git a/binaries/cuprated/Cuprated.toml b/binaries/cuprated/Cuprated.toml new file mode 100644 index 0000000..d248ce1 --- /dev/null +++ b/binaries/cuprated/Cuprated.toml @@ -0,0 +1,67 @@ +# ____ _ +# / ___| _ _ __ _ __ __ _| |_ ___ +# | | | | | | '_ \| '__/ _` | __/ _ \ +# | |__| |_| | |_) | | | (_| | || __/ +# \____\__,_| .__/|_| \__,_|\__\___| +# |_| +# + +## The network to run on, valid values: "Mainnet", "Testnet", "Stagenet". +network = "Mainnet" + +## Tracing config. +[tracing] +## The minimum level for log events to be displayed. +level = "info" + +## Clear-net config. +[p2p.clear_net] +## The number of outbound connections we should make and maintain. +outbound_connections = 64 +## The number of extra connections we should make under load from the rest of Cuprate, i.e. when syncing. +extra_outbound_connections = 8 +## The maximum number of incoming we should allow. +max_inbound_connections = 128 +## The percent of outbound connections that should be to nodes we have not connected to before. +gray_peers_percent = 0.7 +## The port to accept connections on, if left `0` no connections will be accepted. +p2p_port = 0 +## The IP address to listen to connections on. +listen_on = "0.0.0.0" + +## The Clear-net addressbook config. +[p2p.clear_net.address_book_config] +## The size of the white peer list, which contains peers we have made a connection to before. +max_white_list_length = 1_000 +## The size of the gray peer list, which contains peers we have not made a connection to before. +max_gray_list_length = 5_000 +## The amount of time between address book saves. +peer_save_period = { secs = 90, nanos = 0 } + +## The block downloader config. +[p2p.block_downloader] +## The size of the buffer of sequential blocks waiting to be verified and added to the chain (bytes). +buffer_bytes = 50_000_000 +## The size of the queue of blocks which are waiting for a parent block to be downloaded (bytes). +in_progress_queue_bytes = 50_000_000 +## The target size of a batch of blocks (bytes), must not exceed 100MB. +target_batch_bytes= 5_000_000 +## The amount of time between checking the pool of connected peers for free peers to download blocks. +check_client_pool_interval = { secs = 30, nanos = 0 } + +## Storage config +[storage] +## The amount of reader threads to spawn. +reader_threads = "OnePerThread" + +## Txpool storage config. +[storage.txpool] +## The database sync mode for the txpool. +sync_mode = "Async" +## The maximum size of all the txs in the pool (bytes). +max_txpool_byte_size = 100_000_000 + +## Blockchain storage config. +[storage.blockchain] +## The database sync mode for the blockchain. +sync_mode = "Async" diff --git a/binaries/cuprated/README.md b/binaries/cuprated/README.md new file mode 100644 index 0000000..47f0408 --- /dev/null +++ b/binaries/cuprated/README.md @@ -0,0 +1,2 @@ +# `cuprated` +TODO diff --git a/binaries/cuprated/src/blockchain.rs b/binaries/cuprated/src/blockchain.rs new file mode 100644 index 0000000..c4b75e4 --- /dev/null +++ b/binaries/cuprated/src/blockchain.rs @@ -0,0 +1,101 @@ +//! Blockchain +//! +//! Contains the blockchain manager, syncer and an interface to mutate the blockchain. +use std::sync::Arc; + +use futures::FutureExt; +use tokio::sync::{mpsc, Notify}; +use tower::{BoxError, Service, ServiceExt}; + +use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; +use cuprate_consensus::{generate_genesis_block, BlockChainContextService, ContextConfig}; +use cuprate_cryptonight::cryptonight_hash_v0; +use cuprate_p2p::{block_downloader::BlockDownloaderConfig, NetworkInterface}; +use cuprate_p2p_core::{ClearNet, Network}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainWriteRequest}, + VerifiedBlockInformation, +}; + +use crate::constants::PANIC_CRITICAL_SERVICE_ERROR; + +mod chain_service; +pub mod interface; +mod manager; +mod syncer; +mod types; + +pub use types::{ + ConcreteBlockVerifierService, ConcreteTxVerifierService, ConsensusBlockchainReadHandle, +}; + +/// Checks if the genesis block is in the blockchain and adds it if not. +pub async fn check_add_genesis( + blockchain_read_handle: &mut BlockchainReadHandle, + blockchain_write_handle: &mut BlockchainWriteHandle, + network: Network, +) { + // Try to get the chain height, will fail if the genesis block is not in the DB. + if blockchain_read_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockchainReadRequest::ChainHeight) + .await + .is_ok() + { + return; + } + + let genesis = generate_genesis_block(network); + + assert_eq!(genesis.miner_transaction.prefix().outputs.len(), 1); + assert!(genesis.transactions.is_empty()); + + blockchain_write_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockchainWriteRequest::WriteBlock( + VerifiedBlockInformation { + block_blob: genesis.serialize(), + txs: vec![], + block_hash: genesis.hash(), + pow_hash: cryptonight_hash_v0(&genesis.serialize_pow_hash()), + height: 0, + generated_coins: genesis.miner_transaction.prefix().outputs[0] + .amount + .unwrap(), + weight: genesis.miner_transaction.weight(), + long_term_weight: genesis.miner_transaction.weight(), + cumulative_difficulty: 1, + block: genesis, + }, + )) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); +} + +/// Initializes the consensus services. +pub async fn init_consensus( + blockchain_read_handle: BlockchainReadHandle, + context_config: ContextConfig, +) -> Result< + ( + ConcreteBlockVerifierService, + ConcreteTxVerifierService, + BlockChainContextService, + ), + BoxError, +> { + let read_handle = ConsensusBlockchainReadHandle::new(blockchain_read_handle, BoxError::from); + + let ctx_service = + cuprate_consensus::initialize_blockchain_context(context_config, read_handle.clone()) + .await?; + + let (block_verifier_svc, tx_verifier_svc) = + cuprate_consensus::initialize_verifier(read_handle, ctx_service.clone()); + + Ok((block_verifier_svc, tx_verifier_svc, ctx_service)) +} diff --git a/binaries/cuprated/src/blockchain/chain_service.rs b/binaries/cuprated/src/blockchain/chain_service.rs new file mode 100644 index 0000000..af862d1 --- /dev/null +++ b/binaries/cuprated/src/blockchain/chain_service.rs @@ -0,0 +1,72 @@ +use std::task::{Context, Poll}; + +use futures::{future::BoxFuture, FutureExt, TryFutureExt}; +use tower::Service; + +use cuprate_blockchain::service::BlockchainReadHandle; +use cuprate_p2p::block_downloader::{ChainSvcRequest, ChainSvcResponse}; +use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse}; + +/// That service that allows retrieving the chain state to give to the P2P crates, so we can figure out +/// what blocks we need. +/// +/// This has a more minimal interface than [`BlockchainReadRequest`] to make using the p2p crates easier. +#[derive(Clone)] +pub struct ChainService(pub BlockchainReadHandle); + +impl Service for ChainService { + type Response = ChainSvcResponse; + type Error = tower::BoxError; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, req: ChainSvcRequest) -> Self::Future { + let map_res = |res: BlockchainResponse| match res { + BlockchainResponse::CompactChainHistory { + block_ids, + cumulative_difficulty, + } => ChainSvcResponse::CompactHistory { + block_ids, + cumulative_difficulty, + }, + BlockchainResponse::FindFirstUnknown(res) => ChainSvcResponse::FindFirstUnknown(res), + _ => unreachable!(), + }; + + match req { + ChainSvcRequest::CompactHistory => self + .0 + .call(BlockchainReadRequest::CompactChainHistory) + .map_ok(map_res) + .map_err(Into::into) + .boxed(), + ChainSvcRequest::FindFirstUnknown(req) => self + .0 + .call(BlockchainReadRequest::FindFirstUnknown(req)) + .map_ok(map_res) + .map_err(Into::into) + .boxed(), + ChainSvcRequest::CumulativeDifficulty => self + .0 + .call(BlockchainReadRequest::CompactChainHistory) + .map_ok(|res| { + // TODO create a custom request instead of hijacking this one. + // TODO: use the context cache. + let BlockchainResponse::CompactChainHistory { + cumulative_difficulty, + .. + } = res + else { + unreachable!() + }; + + ChainSvcResponse::CumulativeDifficulty(cumulative_difficulty) + }) + .map_err(Into::into) + .boxed(), + } + } +} diff --git a/binaries/cuprated/src/blockchain/interface.rs b/binaries/cuprated/src/blockchain/interface.rs new file mode 100644 index 0000000..2482784 --- /dev/null +++ b/binaries/cuprated/src/blockchain/interface.rs @@ -0,0 +1,188 @@ +//! The blockchain manager interface. +//! +//! This module contains all the functions to mutate the blockchain's state in any way, through the +//! blockchain manager. +use std::{ + collections::{HashMap, HashSet}, + sync::{LazyLock, Mutex, OnceLock}, +}; + +use monero_serai::{block::Block, transaction::Transaction}; +use tokio::sync::{mpsc, oneshot}; +use tower::{Service, ServiceExt}; + +use cuprate_blockchain::service::BlockchainReadHandle; +use cuprate_consensus::transactions::new_tx_verification_data; +use cuprate_txpool::service::{ + interface::{TxpoolReadRequest, TxpoolReadResponse}, + TxpoolReadHandle, +}; +use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse}; + +use crate::{ + blockchain::manager::{BlockchainManagerCommand, IncomingBlockOk}, + constants::PANIC_CRITICAL_SERVICE_ERROR, +}; + +/// The channel used to send [`BlockchainManagerCommand`]s to the blockchain manager. +/// +/// This channel is initialized in [`init_blockchain_manager`](super::manager::init_blockchain_manager), the functions +/// in this file document what happens if this is not initialized when they are called. +pub(super) static COMMAND_TX: OnceLock> = OnceLock::new(); + +/// An error that can be returned from [`handle_incoming_block`]. +#[derive(Debug, thiserror::Error)] +pub enum IncomingBlockError { + /// Some transactions in the block were unknown. + /// + /// The inner values are the block hash and the indexes of the missing txs in the block. + #[error("Unknown transactions in block.")] + UnknownTransactions([u8; 32], Vec), + /// We are missing the block's parent. + #[error("The block has an unknown parent.")] + Orphan, + /// The block was invalid. + #[error(transparent)] + InvalidBlock(anyhow::Error), +} + +/// Try to add a new block to the blockchain. +/// +/// On success returns [`IncomingBlockOk`]. +/// +/// # Errors +/// +/// This function will return an error if: +/// - the block was invalid +/// - we are missing transactions +/// - the block's parent is unknown +pub async fn handle_incoming_block( + block: Block, + mut given_txs: HashMap<[u8; 32], Transaction>, + blockchain_read_handle: &mut BlockchainReadHandle, + txpool_read_handle: &mut TxpoolReadHandle, +) -> Result { + /// A [`HashSet`] of block hashes that the blockchain manager is currently handling. + /// + /// This lock prevents sending the same block to the blockchain manager from multiple connections + /// before one of them actually gets added to the chain, allowing peers to do other things. + /// + /// This is used over something like a dashmap as we expect a lot of collisions in a short amount of + /// time for new blocks, so we would lose the benefit of sharded locks. A dashmap is made up of `RwLocks` + /// which are also more expensive than `Mutex`s. + static BLOCKS_BEING_HANDLED: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + + if given_txs.len() > block.transactions.len() { + return Err(IncomingBlockError::InvalidBlock(anyhow::anyhow!( + "Too many transactions given for block" + ))); + } + + if !block_exists(block.header.previous, blockchain_read_handle) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + { + return Err(IncomingBlockError::Orphan); + } + + let block_hash = block.hash(); + + if block_exists(block_hash, blockchain_read_handle) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + { + return Ok(IncomingBlockOk::AlreadyHave); + } + + let TxpoolReadResponse::TxsForBlock { mut txs, missing } = txpool_read_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(TxpoolReadRequest::TxsForBlock(block.transactions.clone())) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!() + }; + + if !missing.is_empty() { + let needed_hashes = missing.iter().map(|index| block.transactions[*index]); + + for needed_hash in needed_hashes { + let Some(tx) = given_txs.remove(&needed_hash) else { + // We return back the indexes of all txs missing from our pool, not taking into account the txs + // that were given with the block, as these txs will be dropped. It is not worth it to try to add + // these txs to the pool as this will only happen with a misbehaving peer or if the txpool reaches + // the size limit. + return Err(IncomingBlockError::UnknownTransactions(block_hash, missing)); + }; + + txs.insert( + needed_hash, + new_tx_verification_data(tx) + .map_err(|e| IncomingBlockError::InvalidBlock(e.into()))?, + ); + } + } + + let Some(incoming_block_tx) = COMMAND_TX.get() else { + // We could still be starting up the blockchain manager. + return Ok(IncomingBlockOk::NotReady); + }; + + // Add the blocks hash to the blocks being handled. + if !BLOCKS_BEING_HANDLED.lock().unwrap().insert(block_hash) { + // If another place is already adding this block then we can stop. + return Ok(IncomingBlockOk::AlreadyHave); + } + + // We must remove the block hash from `BLOCKS_BEING_HANDLED`. + let _guard = { + struct RemoveFromBlocksBeingHandled { + block_hash: [u8; 32], + } + impl Drop for RemoveFromBlocksBeingHandled { + fn drop(&mut self) { + BLOCKS_BEING_HANDLED + .lock() + .unwrap() + .remove(&self.block_hash); + } + } + RemoveFromBlocksBeingHandled { block_hash } + }; + + let (response_tx, response_rx) = oneshot::channel(); + + incoming_block_tx + .send(BlockchainManagerCommand::AddBlock { + block, + prepped_txs: txs, + response_tx, + }) + .await + .expect("TODO: don't actually panic here, an err means we are shutting down"); + + response_rx + .await + .expect("The blockchain manager will always respond") + .map_err(IncomingBlockError::InvalidBlock) +} + +/// Check if we have a block with the given hash. +async fn block_exists( + block_hash: [u8; 32], + blockchain_read_handle: &mut BlockchainReadHandle, +) -> Result { + let BlockchainResponse::FindBlock(chain) = blockchain_read_handle + .ready() + .await? + .call(BlockchainReadRequest::FindBlock(block_hash)) + .await? + else { + unreachable!(); + }; + + Ok(chain.is_some()) +} diff --git a/binaries/cuprated/src/blockchain/manager.rs b/binaries/cuprated/src/blockchain/manager.rs new file mode 100644 index 0000000..2166795 --- /dev/null +++ b/binaries/cuprated/src/blockchain/manager.rs @@ -0,0 +1,149 @@ +use std::{collections::HashMap, sync::Arc}; + +use futures::StreamExt; +use monero_serai::block::Block; +use tokio::sync::{mpsc, oneshot, Notify}; +use tower::{Service, ServiceExt}; +use tracing::error; + +use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; +use cuprate_consensus::{ + BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService, + BlockVerifierService, ExtendedConsensusError, TxVerifierService, VerifyBlockRequest, + VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse, +}; +use cuprate_consensus_context::RawBlockChainContext; +use cuprate_p2p::{ + block_downloader::{BlockBatch, BlockDownloaderConfig}, + BroadcastSvc, NetworkInterface, +}; +use cuprate_p2p_core::ClearNet; +use cuprate_txpool::service::TxpoolWriteHandle; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, TransactionVerificationData, +}; + +use crate::{ + blockchain::{ + chain_service::ChainService, + interface::COMMAND_TX, + syncer, + types::{ConcreteBlockVerifierService, ConsensusBlockchainReadHandle}, + }, + constants::PANIC_CRITICAL_SERVICE_ERROR, +}; + +mod commands; +mod handler; + +pub use commands::{BlockchainManagerCommand, IncomingBlockOk}; + +/// Initialize the blockchain manager. +/// +/// This function sets up the [`BlockchainManager`] and the [`syncer`] so that the functions in [`interface`](super::interface) +/// can be called. +pub async fn init_blockchain_manager( + clearnet_interface: NetworkInterface, + blockchain_write_handle: BlockchainWriteHandle, + blockchain_read_handle: BlockchainReadHandle, + txpool_write_handle: TxpoolWriteHandle, + mut blockchain_context_service: BlockChainContextService, + block_verifier_service: ConcreteBlockVerifierService, + block_downloader_config: BlockDownloaderConfig, +) { + // TODO: find good values for these size limits + let (batch_tx, batch_rx) = mpsc::channel(1); + let stop_current_block_downloader = Arc::new(Notify::new()); + let (command_tx, command_rx) = mpsc::channel(3); + + COMMAND_TX.set(command_tx).unwrap(); + + tokio::spawn(syncer::syncer( + blockchain_context_service.clone(), + ChainService(blockchain_read_handle.clone()), + clearnet_interface.clone(), + batch_tx, + Arc::clone(&stop_current_block_downloader), + block_downloader_config, + )); + + let BlockChainContextResponse::Context(blockchain_context) = blockchain_context_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockChainContextRequest::Context) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!() + }; + + let manager = BlockchainManager { + blockchain_write_handle, + blockchain_read_handle, + txpool_write_handle, + blockchain_context_service, + cached_blockchain_context: blockchain_context.unchecked_blockchain_context().clone(), + block_verifier_service, + stop_current_block_downloader, + broadcast_svc: clearnet_interface.broadcast_svc(), + }; + + tokio::spawn(manager.run(batch_rx, command_rx)); +} + +/// The blockchain manager. +/// +/// This handles all mutation of the blockchain, anything that changes the state of the blockchain must +/// go through this. +/// +/// Other parts of Cuprate can interface with this by using the functions in [`interface`](super::interface). +pub struct BlockchainManager { + /// The [`BlockchainWriteHandle`], this is the _only_ part of Cuprate where a [`BlockchainWriteHandle`] + /// is held. + blockchain_write_handle: BlockchainWriteHandle, + /// A [`BlockchainReadHandle`]. + blockchain_read_handle: BlockchainReadHandle, + /// A [`TxpoolWriteHandle`]. + txpool_write_handle: TxpoolWriteHandle, + // TODO: Improve the API of the cache service. + // TODO: rename the cache service -> `BlockchainContextService`. + /// The blockchain context cache, this caches the current state of the blockchain to quickly calculate/retrieve + /// values without needing to go to a [`BlockchainReadHandle`]. + blockchain_context_service: BlockChainContextService, + /// A cached context representing the current state. + cached_blockchain_context: RawBlockChainContext, + /// The block verifier service, to verify incoming blocks. + block_verifier_service: ConcreteBlockVerifierService, + /// A [`Notify`] to tell the [syncer](syncer::syncer) that we want to cancel this current download + /// attempt. + stop_current_block_downloader: Arc, + /// The broadcast service, to broadcast new blocks. + broadcast_svc: BroadcastSvc, +} + +impl BlockchainManager { + /// The [`BlockchainManager`] task. + pub async fn run( + mut self, + mut block_batch_rx: mpsc::Receiver, + mut command_rx: mpsc::Receiver, + ) { + loop { + tokio::select! { + Some(batch) = block_batch_rx.recv() => { + self.handle_incoming_block_batch( + batch, + ).await; + } + Some(incoming_command) = command_rx.recv() => { + self.handle_command(incoming_command).await; + } + else => { + todo!("TODO: exit the BC manager") + } + } + } + } +} diff --git a/binaries/cuprated/src/blockchain/manager/commands.rs b/binaries/cuprated/src/blockchain/manager/commands.rs new file mode 100644 index 0000000..643ed88 --- /dev/null +++ b/binaries/cuprated/src/blockchain/manager/commands.rs @@ -0,0 +1,32 @@ +//! This module contains the commands for the blockchain manager. +use std::collections::HashMap; + +use monero_serai::block::Block; +use tokio::sync::oneshot; + +use cuprate_types::TransactionVerificationData; + +/// The blockchain manager commands. +pub enum BlockchainManagerCommand { + /// Attempt to add a new block to the blockchain. + AddBlock { + /// The [`Block`] to add. + block: Block, + /// All the transactions defined in [`Block::transactions`]. + prepped_txs: HashMap<[u8; 32], TransactionVerificationData>, + /// The channel to send the response down. + response_tx: oneshot::Sender>, + }, +} + +/// The [`Ok`] response for an incoming block. +pub enum IncomingBlockOk { + /// The block was added to the main-chain. + AddedToMainChain, + /// The blockchain manager is not ready yet. + NotReady, + /// The block was added to an alt-chain. + AddedToAltChain, + /// We already have the block. + AlreadyHave, +} diff --git a/binaries/cuprated/src/blockchain/manager/handler.rs b/binaries/cuprated/src/blockchain/manager/handler.rs new file mode 100644 index 0000000..5d1cd2d --- /dev/null +++ b/binaries/cuprated/src/blockchain/manager/handler.rs @@ -0,0 +1,505 @@ +//! The blockchain manager handler functions. +use bytes::Bytes; +use futures::{TryFutureExt, TryStreamExt}; +use monero_serai::{ + block::Block, + transaction::{Input, Transaction}, +}; +use rayon::prelude::*; +use std::ops::ControlFlow; +use std::{collections::HashMap, sync::Arc}; +use tower::{Service, ServiceExt}; +use tracing::info; + +use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; +use cuprate_consensus::{ + block::PreparedBlock, transactions::new_tx_verification_data, BlockChainContextRequest, + BlockChainContextResponse, BlockVerifierService, ExtendedConsensusError, VerifyBlockRequest, + VerifyBlockResponse, VerifyTxRequest, VerifyTxResponse, +}; +use cuprate_consensus_context::NewBlockData; +use cuprate_helper::cast::usize_to_u64; +use cuprate_p2p::{block_downloader::BlockBatch, constants::LONG_BAN, BroadcastRequest}; +use cuprate_txpool::service::interface::TxpoolWriteRequest; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest}, + AltBlockInformation, HardFork, TransactionVerificationData, VerifiedBlockInformation, +}; + +use crate::{ + blockchain::manager::commands::{BlockchainManagerCommand, IncomingBlockOk}, + constants::PANIC_CRITICAL_SERVICE_ERROR, + signals::REORG_LOCK, +}; + +impl super::BlockchainManager { + /// Handle an incoming command from another part of Cuprate. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + pub async fn handle_command(&mut self, command: BlockchainManagerCommand) { + match command { + BlockchainManagerCommand::AddBlock { + block, + prepped_txs, + response_tx, + } => { + let res = self.handle_incoming_block(block, prepped_txs).await; + + drop(response_tx.send(res)); + } + } + } + + /// Broadcast a valid block to the network. + async fn broadcast_block(&mut self, block_bytes: Bytes, blockchain_height: usize) { + self.broadcast_svc + .ready() + .await + .expect("Broadcast service is Infallible.") + .call(BroadcastRequest::Block { + block_bytes, + current_blockchain_height: usize_to_u64(blockchain_height), + }) + .await + .expect("Broadcast service is Infallible."); + } + + /// Handle an incoming [`Block`]. + /// + /// This function will route to [`Self::handle_incoming_alt_block`] if the block does not follow + /// the top of the main chain. + /// + /// Otherwise, this function will validate and add the block to the main chain. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + pub async fn handle_incoming_block( + &mut self, + block: Block, + prepared_txs: HashMap<[u8; 32], TransactionVerificationData>, + ) -> Result { + if block.header.previous != self.cached_blockchain_context.top_hash { + self.handle_incoming_alt_block(block, prepared_txs).await?; + return Ok(IncomingBlockOk::AddedToAltChain); + } + + let VerifyBlockResponse::MainChain(verified_block) = self + .block_verifier_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(VerifyBlockRequest::MainChain { + block, + prepared_txs, + }) + .await? + else { + unreachable!(); + }; + + let block_blob = Bytes::copy_from_slice(&verified_block.block_blob); + self.add_valid_block_to_main_chain(verified_block).await; + + self.broadcast_block(block_blob, self.cached_blockchain_context.chain_height) + .await; + + Ok(IncomingBlockOk::AddedToMainChain) + } + + /// Handle an incoming [`BlockBatch`]. + /// + /// This function will route to [`Self::handle_incoming_block_batch_main_chain`] or [`Self::handle_incoming_block_batch_alt_chain`] + /// depending on if the first block in the batch follows from the top of our chain. + /// + /// # Panics + /// + /// This function will panic if the batch is empty or if any internal service returns an unexpected + /// error that we cannot recover from or if the incoming batch contains no blocks. + pub async fn handle_incoming_block_batch(&mut self, batch: BlockBatch) { + let (first_block, _) = batch + .blocks + .first() + .expect("Block batch should not be empty"); + + if first_block.header.previous == self.cached_blockchain_context.top_hash { + self.handle_incoming_block_batch_main_chain(batch).await; + } else { + self.handle_incoming_block_batch_alt_chain(batch).await; + } + } + + /// Handles an incoming [`BlockBatch`] that follows the main chain. + /// + /// This function will handle validating the blocks in the batch and adding them to the blockchain + /// database and context cache. + /// + /// This function will also handle banning the peer and canceling the block downloader if the + /// block is invalid. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from or if the incoming batch contains no blocks. + async fn handle_incoming_block_batch_main_chain(&mut self, batch: BlockBatch) { + info!( + "Handling batch to main chain height: {}", + batch.blocks.first().unwrap().0.number().unwrap() + ); + + let batch_prep_res = self + .block_verifier_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(VerifyBlockRequest::MainChainBatchPrepareBlocks { + blocks: batch.blocks, + }) + .await; + + let prepped_blocks = match batch_prep_res { + Ok(VerifyBlockResponse::MainChainBatchPrepped(prepped_blocks)) => prepped_blocks, + Err(_) => { + batch.peer_handle.ban_peer(LONG_BAN); + self.stop_current_block_downloader.notify_one(); + return; + } + _ => unreachable!(), + }; + + for (block, txs) in prepped_blocks { + let verify_res = self + .block_verifier_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(VerifyBlockRequest::MainChainPrepped { block, txs }) + .await; + + let verified_block = match verify_res { + Ok(VerifyBlockResponse::MainChain(verified_block)) => verified_block, + Err(_) => { + batch.peer_handle.ban_peer(LONG_BAN); + self.stop_current_block_downloader.notify_one(); + return; + } + _ => unreachable!(), + }; + + self.add_valid_block_to_main_chain(verified_block).await; + } + } + + /// Handles an incoming [`BlockBatch`] that does not follow the main-chain. + /// + /// This function will handle validating the alt-blocks to add them to our cache and reorging the + /// chain if the alt-chain has a higher cumulative difficulty. + /// + /// This function will also handle banning the peer and canceling the block downloader if the + /// alt block is invalid or if a reorg fails. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + async fn handle_incoming_block_batch_alt_chain(&mut self, mut batch: BlockBatch) { + // TODO: this needs testing (this whole section does but alt-blocks specifically). + + let mut blocks = batch.blocks.into_iter(); + + while let Some((block, txs)) = blocks.next() { + // async blocks work as try blocks. + let res = async { + let txs = txs + .into_par_iter() + .map(|tx| { + let tx = new_tx_verification_data(tx)?; + Ok((tx.tx_hash, tx)) + }) + .collect::>()?; + + let reorged = self.handle_incoming_alt_block(block, txs).await?; + + Ok::<_, anyhow::Error>(reorged) + } + .await; + + match res { + Err(e) => { + batch.peer_handle.ban_peer(LONG_BAN); + self.stop_current_block_downloader.notify_one(); + return; + } + Ok(AddAltBlock::Reorged) => { + // Collect the remaining blocks and add them to the main chain instead. + batch.blocks = blocks.collect(); + self.handle_incoming_block_batch_main_chain(batch).await; + return; + } + // continue adding alt blocks. + Ok(AddAltBlock::Cached) => (), + } + } + } + + /// Handles an incoming alt [`Block`]. + /// + /// This function will do some pre-validation of the alt block, then if the cumulative difficulty + /// of the alt chain is higher than the main chain it will attempt a reorg otherwise it will add + /// the alt block to the alt block cache. + /// + /// # Errors + /// + /// This will return an [`Err`] if: + /// - The alt block was invalid. + /// - An attempt to reorg the chain failed. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + async fn handle_incoming_alt_block( + &mut self, + block: Block, + prepared_txs: HashMap<[u8; 32], TransactionVerificationData>, + ) -> Result { + let VerifyBlockResponse::AltChain(alt_block_info) = self + .block_verifier_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(VerifyBlockRequest::AltChain { + block, + prepared_txs, + }) + .await? + else { + unreachable!(); + }; + + // TODO: check in consensus crate if alt block with this hash already exists. + + // If this alt chain + if alt_block_info.cumulative_difficulty + > self.cached_blockchain_context.cumulative_difficulty + { + self.try_do_reorg(alt_block_info).await?; + return Ok(AddAltBlock::Reorged); + } + + self.blockchain_write_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockchainWriteRequest::WriteAltBlock(alt_block_info)) + .await?; + + Ok(AddAltBlock::Cached) + } + + /// Attempt a re-org with the given top block of the alt-chain. + /// + /// This function will take a write lock on [`REORG_LOCK`] and then set up the blockchain database + /// and context cache to verify the alt-chain. It will then attempt to verify and add each block + /// in the alt-chain to the main-chain. Releasing the lock on [`REORG_LOCK`] when finished. + /// + /// # Errors + /// + /// This function will return an [`Err`] if the re-org was unsuccessful, if this happens the chain + /// will be returned back into its state it was at when then function was called. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + async fn try_do_reorg( + &mut self, + top_alt_block: AltBlockInformation, + ) -> Result<(), anyhow::Error> { + let _guard = REORG_LOCK.write().await; + + let BlockchainResponse::AltBlocksInChain(mut alt_blocks) = self + .blockchain_read_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockchainReadRequest::AltBlocksInChain( + top_alt_block.chain_id, + )) + .await? + else { + unreachable!(); + }; + + alt_blocks.push(top_alt_block); + + let split_height = alt_blocks[0].height; + let current_main_chain_height = self.cached_blockchain_context.chain_height; + + let BlockchainResponse::PopBlocks(old_main_chain_id) = self + .blockchain_write_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockchainWriteRequest::PopBlocks( + current_main_chain_height - split_height + 1, + )) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!(); + }; + + self.blockchain_context_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockChainContextRequest::PopBlocks { + numb_blocks: current_main_chain_height - split_height + 1, + }) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); + + let reorg_res = self.verify_add_alt_blocks_to_main_chain(alt_blocks).await; + + match reorg_res { + Ok(()) => Ok(()), + Err(e) => { + todo!("Reverse reorg") + } + } + } + + /// Verify and add a list of [`AltBlockInformation`]s to the main-chain. + /// + /// This function assumes the first [`AltBlockInformation`] is the next block in the blockchain + /// for the blockchain database and the context cache, or in other words that the blockchain database + /// and context cache have already had the top blocks popped to where the alt-chain meets the main-chain. + /// + /// # Errors + /// + /// This function will return an [`Err`] if the alt-blocks were invalid, in this case the re-org should + /// be aborted and the chain should be returned to its previous state. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + async fn verify_add_alt_blocks_to_main_chain( + &mut self, + alt_blocks: Vec, + ) -> Result<(), anyhow::Error> { + for mut alt_block in alt_blocks { + let prepped_txs = alt_block + .txs + .drain(..) + .map(|tx| Ok(Arc::new(tx.try_into()?))) + .collect::>()?; + + let prepped_block = PreparedBlock::new_alt_block(alt_block)?; + + let VerifyBlockResponse::MainChain(verified_block) = self + .block_verifier_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(VerifyBlockRequest::MainChainPrepped { + block: prepped_block, + txs: prepped_txs, + }) + .await? + else { + unreachable!(); + }; + + self.add_valid_block_to_main_chain(verified_block).await; + } + + Ok(()) + } + + /// Adds a [`VerifiedBlockInformation`] to the main-chain. + /// + /// This function will update the blockchain database and the context cache, it will also + /// update [`Self::cached_blockchain_context`]. + /// + /// # Panics + /// + /// This function will panic if any internal service returns an unexpected error that we cannot + /// recover from. + pub async fn add_valid_block_to_main_chain( + &mut self, + verified_block: VerifiedBlockInformation, + ) { + // FIXME: this is pretty inefficient, we should probably return the KI map created in the consensus crate. + let spent_key_images = verified_block + .txs + .iter() + .flat_map(|tx| { + tx.tx.prefix().inputs.iter().map(|input| match input { + Input::ToKey { key_image, .. } => key_image.compress().0, + Input::Gen(_) => unreachable!(), + }) + }) + .collect::>(); + + self.blockchain_context_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockChainContextRequest::Update(NewBlockData { + block_hash: verified_block.block_hash, + height: verified_block.height, + timestamp: verified_block.block.header.timestamp, + weight: verified_block.weight, + long_term_weight: verified_block.long_term_weight, + generated_coins: verified_block.generated_coins, + vote: HardFork::from_vote(verified_block.block.header.hardfork_signal), + cumulative_difficulty: verified_block.cumulative_difficulty, + })) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); + + self.blockchain_write_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockchainWriteRequest::WriteBlock(verified_block)) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); + + let BlockChainContextResponse::Context(blockchain_context) = self + .blockchain_context_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockChainContextRequest::Context) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!(); + }; + + self.cached_blockchain_context = blockchain_context.unchecked_blockchain_context().clone(); + + self.txpool_write_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(TxpoolWriteRequest::NewBlock { spent_key_images }) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); + } +} + +/// The result from successfully adding an alt-block. +enum AddAltBlock { + /// The alt-block was cached. + Cached, + /// The chain was reorged. + Reorged, +} diff --git a/binaries/cuprated/src/blockchain/syncer.rs b/binaries/cuprated/src/blockchain/syncer.rs new file mode 100644 index 0000000..69ad330 --- /dev/null +++ b/binaries/cuprated/src/blockchain/syncer.rs @@ -0,0 +1,147 @@ +// FIXME: This whole module is not great and should be rewritten when the PeerSet is made. +use std::{sync::Arc, time::Duration}; + +use futures::StreamExt; +use tokio::{ + sync::{mpsc, Notify}, + time::interval, +}; +use tower::{Service, ServiceExt}; +use tracing::instrument; + +use cuprate_consensus::{BlockChainContext, BlockChainContextRequest, BlockChainContextResponse}; +use cuprate_p2p::{ + block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse}, + NetworkInterface, PeerSetRequest, PeerSetResponse, +}; +use cuprate_p2p_core::ClearNet; + +const CHECK_SYNC_FREQUENCY: Duration = Duration::from_secs(30); + +/// An error returned from the [`syncer`]. +#[derive(Debug, thiserror::Error)] +pub enum SyncerError { + #[error("Incoming block channel closed.")] + IncomingBlockChannelClosed, + #[error("One of our services returned an error: {0}.")] + ServiceError(#[from] tower::BoxError), +} + +/// The syncer tasks that makes sure we are fully synchronised with our connected peers. +#[instrument(level = "debug", skip_all)] +pub async fn syncer( + mut context_svc: C, + our_chain: CN, + mut clearnet_interface: NetworkInterface, + incoming_block_batch_tx: mpsc::Sender, + stop_current_block_downloader: Arc, + block_downloader_config: BlockDownloaderConfig, +) -> Result<(), SyncerError> +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + >, + C::Future: Send + 'static, + CN: Service + + Clone + + Send + + 'static, + CN::Future: Send + 'static, +{ + tracing::info!("Starting blockchain syncer"); + + let mut check_sync_interval = interval(CHECK_SYNC_FREQUENCY); + + let BlockChainContextResponse::Context(mut blockchain_ctx) = context_svc + .ready() + .await? + .call(BlockChainContextRequest::Context) + .await? + else { + unreachable!(); + }; + + tracing::debug!("Waiting for new sync info in top sync channel"); + + loop { + check_sync_interval.tick().await; + + tracing::trace!("Checking connected peers to see if we are behind",); + + check_update_blockchain_context(&mut context_svc, &mut blockchain_ctx).await?; + let raw_blockchain_context = blockchain_ctx.unchecked_blockchain_context(); + + let PeerSetResponse::MostPoWSeen { + cumulative_difficulty, + .. + } = clearnet_interface + .peer_set() + .ready() + .await? + .call(PeerSetRequest::MostPoWSeen) + .await? + else { + unreachable!(); + }; + + if cumulative_difficulty <= raw_blockchain_context.cumulative_difficulty { + continue; + } + + tracing::debug!( + "We are behind peers claimed cumulative difficulty, starting block downloader" + ); + let mut block_batch_stream = + clearnet_interface.block_downloader(our_chain.clone(), block_downloader_config); + + loop { + tokio::select! { + () = stop_current_block_downloader.notified() => { + tracing::info!("Stopping block downloader"); + break; + } + batch = block_batch_stream.next() => { + let Some(batch) = batch else { + break; + }; + + tracing::debug!("Got batch, len: {}", batch.blocks.len()); + if incoming_block_batch_tx.send(batch).await.is_err() { + return Err(SyncerError::IncomingBlockChannelClosed); + } + } + } + } + } +} + +/// Checks if we should update the given [`BlockChainContext`] and updates it if needed. +async fn check_update_blockchain_context( + context_svc: C, + old_context: &mut BlockChainContext, +) -> Result<(), tower::BoxError> +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + >, + C::Future: Send + 'static, +{ + if old_context.blockchain_context().is_ok() { + return Ok(()); + } + + let BlockChainContextResponse::Context(ctx) = context_svc + .oneshot(BlockChainContextRequest::Context) + .await? + else { + unreachable!(); + }; + + *old_context = ctx; + + Ok(()) +} diff --git a/binaries/cuprated/src/blockchain/types.rs b/binaries/cuprated/src/blockchain/types.rs new file mode 100644 index 0000000..54e4662 --- /dev/null +++ b/binaries/cuprated/src/blockchain/types.rs @@ -0,0 +1,18 @@ +use tower::util::MapErr; + +use cuprate_blockchain::{cuprate_database::RuntimeError, service::BlockchainReadHandle}; +use cuprate_consensus::{BlockChainContextService, BlockVerifierService, TxVerifierService}; + +/// The [`BlockVerifierService`] with all generic types defined. +pub type ConcreteBlockVerifierService = BlockVerifierService< + BlockChainContextService, + ConcreteTxVerifierService, + ConsensusBlockchainReadHandle, +>; + +/// The [`TxVerifierService`] with all generic types defined. +pub type ConcreteTxVerifierService = TxVerifierService; + +/// The [`BlockchainReadHandle`] with the [`tower::Service::Error`] mapped to conform to what the consensus crate requires. +pub type ConsensusBlockchainReadHandle = + MapErr tower::BoxError>; diff --git a/binaries/cuprated/src/config.rs b/binaries/cuprated/src/config.rs new file mode 100644 index 0000000..c6267a6 --- /dev/null +++ b/binaries/cuprated/src/config.rs @@ -0,0 +1,159 @@ +//! cuprated config +use std::{ + fs::{read_to_string, File}, + io, + path::Path, + time::Duration, +}; + +use clap::Parser; +use serde::{Deserialize, Serialize}; + +use cuprate_consensus::ContextConfig; +use cuprate_helper::{ + fs::{CUPRATE_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME}, + network::Network, +}; +use cuprate_p2p::block_downloader::BlockDownloaderConfig; +use cuprate_p2p_core::{ClearNet, ClearNetServerCfg}; + +mod args; +mod fs; +mod p2p; +mod storage; +mod tracing_config; + +use crate::config::fs::FileSystemConfig; +use p2p::P2PConfig; +use storage::StorageConfig; +use tracing_config::TracingConfig; + +/// Reads the args & config file, returning a [`Config`]. +pub fn read_config_and_args() -> Config { + let args = args::Args::parse(); + args.do_quick_requests(); + + let config: Config = if let Some(config_file) = &args.config_file { + // If a config file was set in the args try to read it and exit if we can't. + match Config::read_from_path(config_file) { + Ok(config) => config, + Err(e) => { + eprintln!("Failed to read config from file: {e}"); + std::process::exit(1); + } + } + } else { + // First attempt to read the config file from the current directory. + std::env::current_dir() + .map(|path| path.join(DEFAULT_CONFIG_FILE_NAME)) + .map_err(Into::into) + .and_then(Config::read_from_path) + .inspect_err(|e| tracing::debug!("Failed to read config from current dir: {e}")) + // otherwise try the main config directory. + .or_else(|_| { + let file = CUPRATE_CONFIG_DIR.join(DEFAULT_CONFIG_FILE_NAME); + Config::read_from_path(file) + }) + .inspect_err(|e| { + tracing::debug!("Failed to read config from config dir: {e}"); + eprintln!("Failed to find/read config file, using default config."); + }) + .unwrap_or_default() + }; + + args.apply_args(config) +} + +/// The config for all of Cuprate. +#[derive(Default, Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct Config { + /// The network we should run on. + network: Network, + + /// [`tracing`] config. + tracing: TracingConfig, + + /// The P2P network config. + p2p: P2PConfig, + + /// The storage config. + storage: StorageConfig, + + fs: FileSystemConfig, +} + +impl Config { + /// Attempts to read a config file in [`toml`] format from the given [`Path`]. + /// + /// # Errors + /// + /// Will return an [`Err`] if the file cannot be read or if the file is not a valid [`toml`] config. + fn read_from_path(file: impl AsRef) -> Result { + let file_text = read_to_string(file.as_ref())?; + + Ok(toml::from_str(&file_text) + .inspect(|_| eprintln!("Using config at: {}", file.as_ref().to_string_lossy())) + .inspect_err(|e| { + eprintln!("{e}"); + eprintln!( + "Failed to parse config file at: {}", + file.as_ref().to_string_lossy() + ); + })?) + } + + /// Returns the current [`Network`] we are running on. + pub const fn network(&self) -> Network { + self.network + } + + /// The [`ClearNet`], [`cuprate_p2p::P2PConfig`]. + pub fn clearnet_p2p_config(&self) -> cuprate_p2p::P2PConfig { + cuprate_p2p::P2PConfig { + network: self.network, + seeds: p2p::clear_net_seed_nodes(self.network), + outbound_connections: self.p2p.clear_net.general.outbound_connections, + extra_outbound_connections: self.p2p.clear_net.general.extra_outbound_connections, + max_inbound_connections: self.p2p.clear_net.general.max_inbound_connections, + gray_peers_percent: self.p2p.clear_net.general.gray_peers_percent, + server_config: Some(ClearNetServerCfg { + ip: self.p2p.clear_net.listen_on, + }), + p2p_port: self.p2p.clear_net.general.p2p_port, + // TODO: set this if a public RPC server is set. + rpc_port: 0, + address_book_config: self + .p2p + .clear_net + .general + .address_book_config(&self.fs.cache_directory, self.network), + } + } + + /// The [`ContextConfig`]. + pub const fn context_config(&self) -> ContextConfig { + match self.network { + Network::Mainnet => ContextConfig::main_net(), + Network::Stagenet => ContextConfig::stage_net(), + Network::Testnet => ContextConfig::test_net(), + } + } + + /// The [`cuprate_blockchain`] config. + pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config { + let blockchain = &self.storage.blockchain; + + // We don't set reader threads as we manually make the reader threadpool. + cuprate_blockchain::config::ConfigBuilder::default() + .network(self.network) + .data_directory(self.fs.data_directory.clone()) + .sync_mode(blockchain.shared.sync_mode) + .build() + } + + /// The [`BlockDownloaderConfig`]. + pub fn block_downloader_config(&self) -> BlockDownloaderConfig { + self.p2p.block_downloader.clone().into() + } +} diff --git a/binaries/cuprated/src/config/args.rs b/binaries/cuprated/src/config/args.rs new file mode 100644 index 0000000..c4c2f9f --- /dev/null +++ b/binaries/cuprated/src/config/args.rs @@ -0,0 +1,55 @@ +use std::{io::Write, path::PathBuf, process::exit}; + +use clap::builder::TypedValueParser; + +use cuprate_helper::network::Network; + +use crate::{config::Config, constants::EXAMPLE_CONFIG}; + +/// Cuprate Args. +#[derive(clap::Parser, Debug)] +#[command(version, about)] +pub struct Args { + /// The network to run on. + #[arg( + long, + default_value_t = Network::Mainnet, + value_parser = clap::builder::PossibleValuesParser::new(["mainnet", "testnet", "stagenet"]) + .map(|s| s.parse::().unwrap()), + )] + pub network: Network, + /// The amount of outbound clear-net connections to maintain. + #[arg(long)] + pub outbound_connections: Option, + /// The PATH of the `cuprated` config file. + #[arg(long)] + pub config_file: Option, + /// Generate a config file and print it to stdout. + #[arg(long)] + pub generate_config: bool, +} + +impl Args { + /// Complete any quick requests asked for in [`Args`]. + /// + /// May cause the process to [`exit`]. + pub fn do_quick_requests(&self) { + if self.generate_config { + println!("{EXAMPLE_CONFIG}"); + exit(0); + } + } + + /// Apply the [`Args`] to the given [`Config`]. + /// + /// This may exit the program if a config value was set that requires an early exit. + pub const fn apply_args(&self, mut config: Config) -> Config { + config.network = self.network; + + if let Some(outbound_connections) = self.outbound_connections { + config.p2p.clear_net.general.outbound_connections = outbound_connections; + } + + config + } +} diff --git a/binaries/cuprated/src/config/fs.rs b/binaries/cuprated/src/config/fs.rs new file mode 100644 index 0000000..f8f6130 --- /dev/null +++ b/binaries/cuprated/src/config/fs.rs @@ -0,0 +1,21 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use cuprate_helper::fs::{CUPRATE_CACHE_DIR, CUPRATE_DATA_DIR}; + +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct FileSystemConfig { + pub data_directory: PathBuf, + pub cache_directory: PathBuf, +} + +impl Default for FileSystemConfig { + fn default() -> Self { + Self { + data_directory: CUPRATE_DATA_DIR.to_path_buf(), + cache_directory: CUPRATE_CACHE_DIR.to_path_buf(), + } + } +} diff --git a/binaries/cuprated/src/config/p2p.rs b/binaries/cuprated/src/config/p2p.rs new file mode 100644 index 0000000..51f8d0d --- /dev/null +++ b/binaries/cuprated/src/config/p2p.rs @@ -0,0 +1,178 @@ +use std::{ + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + path::Path, + time::Duration, +}; + +use serde::{Deserialize, Serialize}; + +use cuprate_helper::{fs::address_book_path, network::Network}; + +/// P2P config. +#[derive(Default, Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct P2PConfig { + /// Clear-net config. + pub clear_net: ClearNetConfig, + /// Block downloader config. + pub block_downloader: BlockDownloaderConfig, +} + +#[derive(Clone, Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct BlockDownloaderConfig { + /// The size in bytes of the buffer between the block downloader and the place which + /// is consuming the downloaded blocks. + pub buffer_bytes: usize, + /// The size of the in progress queue (in bytes) at which we stop requesting more blocks. + pub in_progress_queue_bytes: usize, + /// The [`Duration`] between checking the client pool for free peers. + pub check_client_pool_interval: Duration, + /// The target size of a single batch of blocks (in bytes). + pub target_batch_bytes: usize, +} + +impl From for cuprate_p2p::block_downloader::BlockDownloaderConfig { + fn from(value: BlockDownloaderConfig) -> Self { + Self { + buffer_bytes: value.buffer_bytes, + in_progress_queue_bytes: value.in_progress_queue_bytes, + check_client_pool_interval: value.check_client_pool_interval, + target_batch_bytes: value.target_batch_bytes, + initial_batch_len: 1, + } + } +} + +impl Default for BlockDownloaderConfig { + fn default() -> Self { + Self { + buffer_bytes: 50_000_000, + in_progress_queue_bytes: 50_000_000, + check_client_pool_interval: Duration::from_secs(30), + target_batch_bytes: 5_000_000, + } + } +} + +/// The config values for P2P clear-net. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct ClearNetConfig { + /// The server config. + pub listen_on: IpAddr, + #[serde(flatten)] + pub general: SharedNetConfig, +} + +impl Default for ClearNetConfig { + fn default() -> Self { + Self { + listen_on: IpAddr::V4(Ipv4Addr::UNSPECIFIED), + general: Default::default(), + } + } +} + +/// Network config values shared between all network zones. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct SharedNetConfig { + /// The number of outbound connections to make and try keep. + pub outbound_connections: usize, + /// The amount of extra connections we can make if we are under load from the rest of Cuprate. + pub extra_outbound_connections: usize, + /// The maximum amount of inbound connections + pub max_inbound_connections: usize, + /// The percent of connections that should be to peers we haven't connected to before. + pub gray_peers_percent: f64, + /// port to use to accept p2p connections. + pub p2p_port: u16, + /// The address book config. + address_book_config: AddressBookConfig, +} + +impl SharedNetConfig { + /// Returns the [`AddressBookConfig`]. + pub fn address_book_config( + &self, + cache_dir: &Path, + network: Network, + ) -> cuprate_address_book::AddressBookConfig { + cuprate_address_book::AddressBookConfig { + max_white_list_length: self.address_book_config.max_white_list_length, + max_gray_list_length: self.address_book_config.max_gray_list_length, + peer_store_directory: address_book_path(cache_dir, network), + peer_save_period: self.address_book_config.peer_save_period, + } + } +} + +impl Default for SharedNetConfig { + fn default() -> Self { + Self { + outbound_connections: 64, + extra_outbound_connections: 8, + max_inbound_connections: 128, + gray_peers_percent: 0.7, + p2p_port: 0, + address_book_config: AddressBookConfig::default(), + } + } +} + +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct AddressBookConfig { + max_white_list_length: usize, + max_gray_list_length: usize, + peer_save_period: Duration, +} + +impl Default for AddressBookConfig { + fn default() -> Self { + Self { + max_white_list_length: 1_000, + max_gray_list_length: 5_000, + peer_save_period: Duration::from_secs(30), + } + } +} + +/// Seed nodes for [`ClearNet`](cuprate_p2p_core::ClearNet). +pub fn clear_net_seed_nodes(network: Network) -> Vec { + let seeds = match network { + Network::Mainnet => [ + "176.9.0.187:18080", + "88.198.163.90:18080", + "66.85.74.134:18080", + "51.79.173.165:18080", + "192.99.8.110:18080", + "37.187.74.171:18080", + "77.172.183.193:18080", + ] + .as_slice(), + Network::Stagenet => [ + "176.9.0.187:38080", + "51.79.173.165:38080", + "192.99.8.110:38080", + "37.187.74.171:38080", + "77.172.183.193:38080", + ] + .as_slice(), + Network::Testnet => [ + "176.9.0.187:28080", + "51.79.173.165:28080", + "192.99.8.110:28080", + "37.187.74.171:28080", + "77.172.183.193:28080", + ] + .as_slice(), + }; + + seeds + .iter() + .map(|s| s.parse()) + .collect::>() + .unwrap() +} diff --git a/binaries/cuprated/src/config/storage.rs b/binaries/cuprated/src/config/storage.rs new file mode 100644 index 0000000..b3e3c1f --- /dev/null +++ b/binaries/cuprated/src/config/storage.rs @@ -0,0 +1,67 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use cuprate_database::config::SyncMode; +use cuprate_database_service::ReaderThreads; +use cuprate_helper::fs::CUPRATE_DATA_DIR; + +/// The storage config. +#[derive(Default, Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct StorageConfig { + /// The amount of reader threads to spawn between the tx-pool and blockchain. + pub reader_threads: ReaderThreads, + /// The tx-pool config. + pub txpool: TxpoolConfig, + /// The blockchain config. + pub blockchain: BlockchainConfig, +} + +/// The blockchain config. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct BlockchainConfig { + #[serde(flatten)] + pub shared: SharedStorageConfig, +} + +impl Default for BlockchainConfig { + fn default() -> Self { + Self { + shared: SharedStorageConfig { + sync_mode: SyncMode::Async, + }, + } + } +} + +/// The tx-pool config. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct TxpoolConfig { + #[serde(flatten)] + pub shared: SharedStorageConfig, + + /// The maximum size of the tx-pool. + pub max_txpool_byte_size: usize, +} + +impl Default for TxpoolConfig { + fn default() -> Self { + Self { + shared: SharedStorageConfig { + sync_mode: SyncMode::Async, + }, + max_txpool_byte_size: 100_000_000, + } + } +} + +/// Config values shared between the tx-pool and blockchain. +#[derive(Default, Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct SharedStorageConfig { + /// The [`SyncMode`] of the database. + pub sync_mode: SyncMode, +} diff --git a/binaries/cuprated/src/config/tracing_config.rs b/binaries/cuprated/src/config/tracing_config.rs new file mode 100644 index 0000000..859d516 --- /dev/null +++ b/binaries/cuprated/src/config/tracing_config.rs @@ -0,0 +1,42 @@ +use serde::{Deserialize, Serialize}; +use tracing::level_filters::LevelFilter; + +/// [`tracing`] config. +#[derive(Deserialize, Serialize)] +#[serde(deny_unknown_fields, default)] +pub struct TracingConfig { + /// The default minimum log level. + #[serde(with = "level_filter_serde")] + level: LevelFilter, +} + +impl Default for TracingConfig { + fn default() -> Self { + Self { + level: LevelFilter::INFO, + } + } +} + +mod level_filter_serde { + use std::str::FromStr; + + use serde::{Deserialize, Deserializer, Serializer}; + use tracing::level_filters::LevelFilter; + + #[expect(clippy::trivially_copy_pass_by_ref, reason = "serde")] + pub fn serialize(level_filter: &LevelFilter, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(&level_filter.to_string()) + } + + pub fn deserialize<'de, D>(d: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(d)?; + LevelFilter::from_str(&s).map_err(serde::de::Error::custom) + } +} diff --git a/binaries/cuprated/src/constants.rs b/binaries/cuprated/src/constants.rs new file mode 100644 index 0000000..057e8bd --- /dev/null +++ b/binaries/cuprated/src/constants.rs @@ -0,0 +1,46 @@ +//! General constants used throughout `cuprated`. + +use const_format::formatcp; + +/// `cuprated`'s semantic version (`MAJOR.MINOR.PATCH`) as string. +pub const VERSION: &str = clap::crate_version!(); + +/// [`VERSION`] + the build type. +/// +/// If a debug build, the suffix is `-debug`, else it is `-release`. +pub const VERSION_BUILD: &str = if cfg!(debug_assertions) { + formatcp!("{VERSION}-debug") +} else { + formatcp!("{VERSION}-release") +}; + +/// The panic message used when cuprated encounters a critical service error. +pub const PANIC_CRITICAL_SERVICE_ERROR: &str = + "A service critical to Cuprate's function returned an unexpected error."; + +pub const EXAMPLE_CONFIG: &str = include_str!("../Cuprated.toml"); + +#[cfg(test)] +mod test { + use super::*; + use crate::config::Config; + + #[test] + fn version() { + assert_eq!(VERSION, "0.0.1"); + } + + #[test] + fn version_build() { + if cfg!(debug_assertions) { + assert_eq!(VERSION_BUILD, "0.0.1-debug"); + } else { + assert_eq!(VERSION_BUILD, "0.0.1-release"); + } + } + + #[test] + fn generate_config_text_is_valid() { + let config: Config = toml::from_str(EXAMPLE_CONFIG).unwrap(); + } +} diff --git a/binaries/cuprated/src/main.rs b/binaries/cuprated/src/main.rs new file mode 100644 index 0000000..617c5b6 --- /dev/null +++ b/binaries/cuprated/src/main.rs @@ -0,0 +1,36 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![allow( + unused_imports, + unreachable_pub, + unreachable_code, + unused_crate_dependencies, + dead_code, + unused_variables, + clippy::needless_pass_by_value, + clippy::unused_async, + clippy::diverging_sub_expression, + unused_mut, + clippy::let_unit_value, + clippy::needless_pass_by_ref_mut, + reason = "TODO: remove after v1.0.0" +)] + +mod blockchain; +mod config; +mod constants; +mod p2p; +mod rpc; +mod signals; +mod statics; +mod txpool; + +fn main() { + // Initialize global static `LazyLock` data. + statics::init_lazylock_statics(); + + let _config = config::read_config_and_args(); + + // TODO: everything else. + todo!() +} diff --git a/binaries/cuprated/src/p2p.rs b/binaries/cuprated/src/p2p.rs new file mode 100644 index 0000000..aeb98b6 --- /dev/null +++ b/binaries/cuprated/src/p2p.rs @@ -0,0 +1,57 @@ +//! P2P +//! +//! Will handle initiating the P2P and contains a protocol request handler. +use futures::{FutureExt, TryFutureExt}; +use tokio::sync::oneshot; +use tower::ServiceExt; + +use cuprate_blockchain::service::BlockchainReadHandle; +use cuprate_consensus::BlockChainContextService; +use cuprate_p2p::{NetworkInterface, P2PConfig}; +use cuprate_p2p_core::ClearNet; +use cuprate_txpool::service::TxpoolReadHandle; + +use crate::txpool::IncomingTxHandler; + +mod core_sync_service; +mod network_address; +pub mod request_handler; + +pub use network_address::CrossNetworkInternalPeerId; + +/// Starts the P2P clearnet network, returning a [`NetworkInterface`] to interact with it. +/// +/// A [`oneshot::Sender`] is also returned to provide the [`IncomingTxHandler`], until this is provided network +/// handshakes can not be completed. +pub async fn start_clearnet_p2p( + blockchain_read_handle: BlockchainReadHandle, + blockchain_context_service: BlockChainContextService, + txpool_read_handle: TxpoolReadHandle, + config: P2PConfig, +) -> Result< + ( + NetworkInterface, + oneshot::Sender, + ), + tower::BoxError, +> { + let (incoming_tx_handler_tx, incoming_tx_handler_rx) = oneshot::channel(); + + let request_handler_maker = request_handler::P2pProtocolRequestHandlerMaker { + blockchain_read_handle, + blockchain_context_service: blockchain_context_service.clone(), + txpool_read_handle, + incoming_tx_handler: None, + incoming_tx_handler_fut: incoming_tx_handler_rx.shared(), + }; + + Ok(( + cuprate_p2p::initialize_network( + request_handler_maker.map_response(|s| s.map_err(Into::into)), + core_sync_service::CoreSyncService(blockchain_context_service), + config, + ) + .await?, + incoming_tx_handler_tx, + )) +} diff --git a/binaries/cuprated/src/p2p/core_sync_service.rs b/binaries/cuprated/src/p2p/core_sync_service.rs new file mode 100644 index 0000000..d3c3ca1 --- /dev/null +++ b/binaries/cuprated/src/p2p/core_sync_service.rs @@ -0,0 +1,49 @@ +use std::task::{Context, Poll}; + +use futures::{future::BoxFuture, FutureExt, TryFutureExt}; +use tower::Service; + +use cuprate_consensus::{ + BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService, +}; +use cuprate_helper::{cast::usize_to_u64, map::split_u128_into_low_high_bits}; +use cuprate_p2p_core::services::{CoreSyncDataRequest, CoreSyncDataResponse}; +use cuprate_wire::CoreSyncData; + +/// The core sync service. +#[derive(Clone)] +pub struct CoreSyncService(pub BlockChainContextService); + +impl Service for CoreSyncService { + type Response = CoreSyncDataResponse; + type Error = tower::BoxError; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + fn call(&mut self, _: CoreSyncDataRequest) -> Self::Future { + self.0 + .call(BlockChainContextRequest::Context) + .map_ok(|res| { + let BlockChainContextResponse::Context(context) = res else { + unreachable!() + }; + + let context = context.unchecked_blockchain_context(); + let (cumulative_difficulty, cumulative_difficulty_top64) = + split_u128_into_low_high_bits(context.cumulative_difficulty); + + CoreSyncDataResponse(CoreSyncData { + cumulative_difficulty, + cumulative_difficulty_top64, + current_height: usize_to_u64(context.chain_height), + pruning_seed: 0, + top_id: context.top_hash, + top_version: context.current_hf.as_u8(), + }) + }) + .boxed() + } +} diff --git a/binaries/cuprated/src/p2p/network_address.rs b/binaries/cuprated/src/p2p/network_address.rs new file mode 100644 index 0000000..7fa8e86 --- /dev/null +++ b/binaries/cuprated/src/p2p/network_address.rs @@ -0,0 +1,16 @@ +use std::net::SocketAddr; + +use cuprate_p2p_core::{client::InternalPeerID, ClearNet, NetworkZone}; + +/// An identifier for a P2P peer on any network. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CrossNetworkInternalPeerId { + /// A clear-net peer. + ClearNet(InternalPeerID<::Addr>), +} + +impl From::Addr>> for CrossNetworkInternalPeerId { + fn from(addr: InternalPeerID<::Addr>) -> Self { + Self::ClearNet(addr) + } +} diff --git a/binaries/cuprated/src/p2p/request_handler.rs b/binaries/cuprated/src/p2p/request_handler.rs new file mode 100644 index 0000000..7d72fa3 --- /dev/null +++ b/binaries/cuprated/src/p2p/request_handler.rs @@ -0,0 +1,422 @@ +use std::{ + collections::HashSet, + future::{ready, Ready}, + hash::Hash, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures::{ + future::{BoxFuture, Shared}, + FutureExt, +}; +use monero_serai::{block::Block, transaction::Transaction}; +use tokio::sync::{broadcast, oneshot, watch}; +use tokio_stream::wrappers::WatchStream; +use tower::{Service, ServiceExt}; + +use cuprate_blockchain::service::BlockchainReadHandle; +use cuprate_consensus::{ + transactions::new_tx_verification_data, BlockChainContextRequest, BlockChainContextResponse, + BlockChainContextService, +}; +use cuprate_dandelion_tower::TxState; +use cuprate_fixed_bytes::ByteArrayVec; +use cuprate_helper::cast::u64_to_usize; +use cuprate_helper::{ + asynch::rayon_spawn_async, + cast::usize_to_u64, + map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits}, +}; +use cuprate_p2p::constants::{ + MAX_BLOCKS_IDS_IN_CHAIN_ENTRY, MAX_BLOCK_BATCH_LEN, MAX_TRANSACTION_BLOB_SIZE, MEDIUM_BAN, +}; +use cuprate_p2p_core::{ + client::{InternalPeerID, PeerInformation}, + NetZoneAddress, NetworkZone, ProtocolRequest, ProtocolResponse, +}; +use cuprate_txpool::service::TxpoolReadHandle; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + BlockCompleteEntry, TransactionBlobs, TxsInBlock, +}; +use cuprate_wire::protocol::{ + ChainRequest, ChainResponse, FluffyMissingTransactionsRequest, GetObjectsRequest, + GetObjectsResponse, NewFluffyBlock, NewTransactions, +}; + +use crate::{ + blockchain::interface::{self as blockchain_interface, IncomingBlockError}, + constants::PANIC_CRITICAL_SERVICE_ERROR, + p2p::CrossNetworkInternalPeerId, + txpool::{IncomingTxError, IncomingTxHandler, IncomingTxs}, +}; + +/// The P2P protocol request handler [`MakeService`](tower::MakeService). +#[derive(Clone)] +pub struct P2pProtocolRequestHandlerMaker { + pub blockchain_read_handle: BlockchainReadHandle, + pub blockchain_context_service: BlockChainContextService, + pub txpool_read_handle: TxpoolReadHandle, + + /// The [`IncomingTxHandler`], wrapped in an [`Option`] as there is a cyclic reference between [`P2pProtocolRequestHandlerMaker`] + /// and the [`IncomingTxHandler`]. + pub incoming_tx_handler: Option, + + /// A [`Future`](std::future::Future) that produces the [`IncomingTxHandler`]. + pub incoming_tx_handler_fut: Shared>, +} + +impl Service> for P2pProtocolRequestHandlerMaker +where + InternalPeerID: Into, +{ + type Response = P2pProtocolRequestHandler; + type Error = tower::BoxError; + type Future = Ready>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + if self.incoming_tx_handler.is_none() { + return self + .incoming_tx_handler_fut + .poll_unpin(cx) + .map(|incoming_tx_handler| { + self.incoming_tx_handler = Some(incoming_tx_handler?); + Ok(()) + }); + } + + Poll::Ready(Ok(())) + } + + fn call(&mut self, peer_information: PeerInformation) -> Self::Future { + let Some(incoming_tx_handler) = self.incoming_tx_handler.clone() else { + panic!("poll_ready was not called or did not return `Poll::Ready`") + }; + + // TODO: check sync info? + + let blockchain_read_handle = self.blockchain_read_handle.clone(); + let txpool_read_handle = self.txpool_read_handle.clone(); + + ready(Ok(P2pProtocolRequestHandler { + peer_information, + blockchain_read_handle, + blockchain_context_service: self.blockchain_context_service.clone(), + txpool_read_handle, + incoming_tx_handler, + })) + } +} + +/// The P2P protocol request handler. +#[derive(Clone)] +pub struct P2pProtocolRequestHandler { + peer_information: PeerInformation, + blockchain_read_handle: BlockchainReadHandle, + blockchain_context_service: BlockChainContextService, + txpool_read_handle: TxpoolReadHandle, + incoming_tx_handler: IncomingTxHandler, +} + +impl Service for P2pProtocolRequestHandler +where + InternalPeerID: Into, +{ + type Response = ProtocolResponse; + type Error = anyhow::Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, request: ProtocolRequest) -> Self::Future { + match request { + ProtocolRequest::GetObjects(r) => { + get_objects(r, self.blockchain_read_handle.clone()).boxed() + } + ProtocolRequest::GetChain(r) => { + get_chain(r, self.blockchain_read_handle.clone()).boxed() + } + ProtocolRequest::FluffyMissingTxs(r) => { + fluffy_missing_txs(r, self.blockchain_read_handle.clone()).boxed() + } + ProtocolRequest::NewBlock(_) => ready(Err(anyhow::anyhow!( + "Peer sent a full block when we support fluffy blocks" + ))) + .boxed(), + ProtocolRequest::NewFluffyBlock(r) => new_fluffy_block( + self.peer_information.clone(), + r, + self.blockchain_read_handle.clone(), + self.txpool_read_handle.clone(), + ) + .boxed(), + ProtocolRequest::NewTransactions(r) => new_transactions( + self.peer_information.clone(), + r, + self.blockchain_context_service.clone(), + self.incoming_tx_handler.clone(), + ) + .boxed(), + ProtocolRequest::GetTxPoolCompliment(_) => ready(Ok(ProtocolResponse::NA)).boxed(), // TODO: should we support this? + } + } +} + +//---------------------------------------------------------------------------------------------------- Handler functions + +/// [`ProtocolRequest::GetObjects`] +async fn get_objects( + request: GetObjectsRequest, + mut blockchain_read_handle: BlockchainReadHandle, +) -> anyhow::Result { + if request.blocks.len() > MAX_BLOCK_BATCH_LEN { + anyhow::bail!("Peer requested more blocks than allowed.") + } + + let block_hashes: Vec<[u8; 32]> = (&request.blocks).into(); + // deallocate the backing `Bytes`. + drop(request); + + let BlockchainResponse::BlockCompleteEntries { + blocks, + missing_hashes, + blockchain_height, + } = blockchain_read_handle + .ready() + .await? + .call(BlockchainReadRequest::BlockCompleteEntries(block_hashes)) + .await? + else { + unreachable!(); + }; + + Ok(ProtocolResponse::GetObjects(GetObjectsResponse { + blocks, + missed_ids: ByteArrayVec::from(missing_hashes), + current_blockchain_height: usize_to_u64(blockchain_height), + })) +} + +/// [`ProtocolRequest::GetChain`] +async fn get_chain( + request: ChainRequest, + mut blockchain_read_handle: BlockchainReadHandle, +) -> anyhow::Result { + if request.block_ids.len() > MAX_BLOCKS_IDS_IN_CHAIN_ENTRY { + anyhow::bail!("Peer sent too many block hashes in chain request.") + } + + let block_hashes: Vec<[u8; 32]> = (&request.block_ids).into(); + let want_pruned_data = request.prune; + // deallocate the backing `Bytes`. + drop(request); + + let BlockchainResponse::NextChainEntry { + start_height, + chain_height, + block_ids, + block_weights, + cumulative_difficulty, + first_block_blob, + } = blockchain_read_handle + .ready() + .await? + .call(BlockchainReadRequest::NextChainEntry(block_hashes, 10_000)) + .await? + else { + unreachable!(); + }; + + let Some(start_height) = start_height else { + anyhow::bail!("The peers chain has a different genesis block than ours."); + }; + + let (cumulative_difficulty_low64, cumulative_difficulty_top64) = + split_u128_into_low_high_bits(cumulative_difficulty); + + Ok(ProtocolResponse::GetChain(ChainResponse { + start_height: usize_to_u64(std::num::NonZero::get(start_height)), + total_height: usize_to_u64(chain_height), + cumulative_difficulty_low64, + cumulative_difficulty_top64, + m_block_ids: ByteArrayVec::from(block_ids), + first_block: first_block_blob.map_or(Bytes::new(), Bytes::from), + // only needed when pruned + m_block_weights: if want_pruned_data { + block_weights.into_iter().map(usize_to_u64).collect() + } else { + vec![] + }, + })) +} + +/// [`ProtocolRequest::FluffyMissingTxs`] +async fn fluffy_missing_txs( + mut request: FluffyMissingTransactionsRequest, + mut blockchain_read_handle: BlockchainReadHandle, +) -> anyhow::Result { + let tx_indexes = std::mem::take(&mut request.missing_tx_indices); + let block_hash: [u8; 32] = *request.block_hash; + let current_blockchain_height = request.current_blockchain_height; + + // deallocate the backing `Bytes`. + drop(request); + + let BlockchainResponse::TxsInBlock(res) = blockchain_read_handle + .ready() + .await? + .call(BlockchainReadRequest::TxsInBlock { + block_hash, + tx_indexes, + }) + .await? + else { + unreachable!(); + }; + + let Some(TxsInBlock { block, txs }) = res else { + anyhow::bail!("The peer requested txs out of range."); + }; + + Ok(ProtocolResponse::NewFluffyBlock(NewFluffyBlock { + b: BlockCompleteEntry { + block: Bytes::from(block), + txs: TransactionBlobs::Normal(txs.into_iter().map(Bytes::from).collect()), + pruned: false, + // only needed for pruned blocks. + block_weight: 0, + }, + current_blockchain_height, + })) +} + +/// [`ProtocolRequest::NewFluffyBlock`] +async fn new_fluffy_block( + peer_information: PeerInformation, + request: NewFluffyBlock, + mut blockchain_read_handle: BlockchainReadHandle, + mut txpool_read_handle: TxpoolReadHandle, +) -> anyhow::Result { + // TODO: check context service here and ignore the block? + let current_blockchain_height = request.current_blockchain_height; + + peer_information + .core_sync_data + .lock() + .unwrap() + .current_height = current_blockchain_height; + + let (block, txs) = rayon_spawn_async(move || -> Result<_, anyhow::Error> { + let block = Block::read(&mut request.b.block.as_ref())?; + + let tx_blobs = request + .b + .txs + .take_normal() + .ok_or(anyhow::anyhow!("Peer sent pruned txs in fluffy block"))?; + + let txs = tx_blobs + .into_iter() + .map(|tx_blob| { + if tx_blob.len() > MAX_TRANSACTION_BLOB_SIZE { + anyhow::bail!("Peer sent a transaction over the size limit."); + } + + let tx = Transaction::read(&mut tx_blob.as_ref())?; + + Ok((tx.hash(), tx)) + }) + .collect::>()?; + + // The backing `Bytes` will be deallocated when this closure returns. + + Ok((block, txs)) + }) + .await?; + + let res = blockchain_interface::handle_incoming_block( + block, + txs, + &mut blockchain_read_handle, + &mut txpool_read_handle, + ) + .await; + + match res { + Ok(_) => Ok(ProtocolResponse::NA), + Err(IncomingBlockError::UnknownTransactions(block_hash, missing_tx_indices)) => Ok( + ProtocolResponse::FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest { + block_hash: block_hash.into(), + current_blockchain_height, + missing_tx_indices: missing_tx_indices.into_iter().map(usize_to_u64).collect(), + }), + ), + Err(IncomingBlockError::Orphan) => { + // Block's parent was unknown, could be syncing? + Ok(ProtocolResponse::NA) + } + Err(e) => Err(e.into()), + } +} + +/// [`ProtocolRequest::NewTransactions`] +async fn new_transactions( + peer_information: PeerInformation, + request: NewTransactions, + mut blockchain_context_service: BlockChainContextService, + mut incoming_tx_handler: IncomingTxHandler, +) -> anyhow::Result +where + A: NetZoneAddress, + InternalPeerID: Into, +{ + let BlockChainContextResponse::Context(context) = blockchain_context_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockChainContextRequest::Context) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!() + }; + + let context = context.unchecked_blockchain_context(); + + // If we are more than 2 blocks behind the peer then ignore the txs - we are probably still syncing. + if usize_to_u64(context.chain_height + 2) + < peer_information + .core_sync_data + .lock() + .unwrap() + .current_height + { + return Ok(ProtocolResponse::NA); + } + + let state = if request.dandelionpp_fluff { + TxState::Fluff + } else { + TxState::Stem { + from: peer_information.id.into(), + } + }; + + // Drop all the data except the stuff we still need. + let NewTransactions { txs, .. } = request; + + let res = incoming_tx_handler + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(IncomingTxs { txs, state }) + .await; + + match res { + Ok(()) => Ok(ProtocolResponse::NA), + Err(e) => Err(e.into()), + } +} diff --git a/binaries/cuprated/src/rpc.rs b/binaries/cuprated/src/rpc.rs new file mode 100644 index 0000000..255d90d --- /dev/null +++ b/binaries/cuprated/src/rpc.rs @@ -0,0 +1,12 @@ +//! RPC +//! +//! Will contain the code to initiate the RPC and a request handler. + +mod bin; +mod constants; +mod handler; +mod json; +mod other; +mod request; + +pub use handler::CupratedRpcHandler; diff --git a/binaries/cuprated/src/rpc/bin.rs b/binaries/cuprated/src/rpc/bin.rs new file mode 100644 index 0000000..b4e676d --- /dev/null +++ b/binaries/cuprated/src/rpc/bin.rs @@ -0,0 +1,85 @@ +use anyhow::Error; + +use cuprate_rpc_types::{ + bin::{ + BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksByHeightResponse, + GetBlocksRequest, GetBlocksResponse, GetHashesRequest, GetHashesResponse, + GetOutputIndexesRequest, GetOutputIndexesResponse, GetOutsRequest, GetOutsResponse, + GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse, + }, + json::{GetOutputDistributionRequest, GetOutputDistributionResponse}, +}; + +use crate::rpc::CupratedRpcHandler; + +/// Map a [`BinRequest`] to the function that will lead to a [`BinResponse`]. +pub(super) async fn map_request( + state: CupratedRpcHandler, + request: BinRequest, +) -> Result { + use BinRequest as Req; + use BinResponse as Resp; + + Ok(match request { + Req::GetBlocks(r) => Resp::GetBlocks(get_blocks(state, r).await?), + Req::GetBlocksByHeight(r) => Resp::GetBlocksByHeight(get_blocks_by_height(state, r).await?), + Req::GetHashes(r) => Resp::GetHashes(get_hashes(state, r).await?), + Req::GetOutputIndexes(r) => Resp::GetOutputIndexes(get_output_indexes(state, r).await?), + Req::GetOuts(r) => Resp::GetOuts(get_outs(state, r).await?), + Req::GetTransactionPoolHashes(r) => { + Resp::GetTransactionPoolHashes(get_transaction_pool_hashes(state, r).await?) + } + Req::GetOutputDistribution(r) => { + Resp::GetOutputDistribution(get_output_distribution(state, r).await?) + } + }) +} + +async fn get_blocks( + state: CupratedRpcHandler, + request: GetBlocksRequest, +) -> Result { + todo!() +} + +async fn get_blocks_by_height( + state: CupratedRpcHandler, + request: GetBlocksByHeightRequest, +) -> Result { + todo!() +} + +async fn get_hashes( + state: CupratedRpcHandler, + request: GetHashesRequest, +) -> Result { + todo!() +} + +async fn get_output_indexes( + state: CupratedRpcHandler, + request: GetOutputIndexesRequest, +) -> Result { + todo!() +} + +async fn get_outs( + state: CupratedRpcHandler, + request: GetOutsRequest, +) -> Result { + todo!() +} + +async fn get_transaction_pool_hashes( + state: CupratedRpcHandler, + request: GetTransactionPoolHashesRequest, +) -> Result { + todo!() +} + +async fn get_output_distribution( + state: CupratedRpcHandler, + request: GetOutputDistributionRequest, +) -> Result { + todo!() +} diff --git a/binaries/cuprated/src/rpc/constants.rs b/binaries/cuprated/src/rpc/constants.rs new file mode 100644 index 0000000..1236269 --- /dev/null +++ b/binaries/cuprated/src/rpc/constants.rs @@ -0,0 +1,5 @@ +//! Constants used within RPC. + +/// The string message used in RPC response fields for when +/// `cuprated` does not support a field that `monerod` has. +pub(super) const FIELD_NOT_SUPPORTED: &str = "`cuprated` does not support this field."; diff --git a/binaries/cuprated/src/rpc/handler.rs b/binaries/cuprated/src/rpc/handler.rs new file mode 100644 index 0000000..1f73403 --- /dev/null +++ b/binaries/cuprated/src/rpc/handler.rs @@ -0,0 +1,233 @@ +//! Dummy implementation of [`RpcHandler`]. + +use std::task::{Context, Poll}; + +use anyhow::Error; +use futures::future::BoxFuture; +use monero_serai::block::Block; +use tower::Service; + +use cuprate_blockchain::service::{BlockchainReadHandle, BlockchainWriteHandle}; +use cuprate_consensus::BlockChainContextService; +use cuprate_pruning::PruningSeed; +use cuprate_rpc_interface::RpcHandler; +use cuprate_rpc_types::{ + bin::{BinRequest, BinResponse}, + json::{JsonRpcRequest, JsonRpcResponse}, + other::{OtherRequest, OtherResponse}, +}; +use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle}; +use cuprate_types::{AddAuxPow, AuxPow, HardFork}; + +use crate::rpc::{bin, json, other}; + +/// TODO: use real type when public. +#[derive(Clone)] +#[expect(clippy::large_enum_variant)] +pub enum BlockchainManagerRequest { + /// Pop blocks off the top of the blockchain. + /// + /// Input is the amount of blocks to pop. + PopBlocks { amount: usize }, + + /// Start pruning the blockchain. + Prune, + + /// Is the blockchain pruned? + Pruned, + + /// Relay a block to the network. + RelayBlock(Block), + + /// Is the blockchain in the middle of syncing? + /// + /// This returning `false` does not necessarily + /// mean [`BlockchainManagerRequest::Synced`] will + /// return `true`, for example, if the network has been + /// cut off and we have no peers, this will return `false`, + /// however, [`BlockchainManagerRequest::Synced`] may return + /// `true` if the latest known chain tip is equal to our height. + Syncing, + + /// Is the blockchain fully synced? + Synced, + + /// Current target block time. + Target, + + /// The height of the next block in the chain. + TargetHeight, + + /// Generate new blocks. + /// + /// This request is only for regtest, see RPC's `generateblocks`. + GenerateBlocks { + /// Number of the blocks to be generated. + amount_of_blocks: u64, + /// The previous block's hash. + prev_block: [u8; 32], + /// The starting value for the nonce. + starting_nonce: u32, + /// The address that will receive the coinbase reward. + wallet_address: String, + }, + + // // TODO: the below requests actually belong to the block downloader/syncer: + // // + // /// Get [`Span`] data. + // /// + // /// This is data that describes an active downloading process, + // /// if we are fully synced, this will return an empty [`Vec`]. + // Spans, + + // + /// Get the next [`PruningSeed`] needed for a pruned sync. + NextNeededPruningSeed, +} + +/// TODO: use real type when public. +#[derive(Clone)] +pub enum BlockchainManagerResponse { + /// General OK response. + /// + /// Response to: + /// - [`BlockchainManagerRequest::Prune`] + /// - [`BlockchainManagerRequest::RelayBlock`] + Ok, + + /// Response to [`BlockchainManagerRequest::PopBlocks`] + PopBlocks { new_height: usize }, + + /// Response to [`BlockchainManagerRequest::Prune`] + Prune(PruningSeed), + + /// Response to [`BlockchainManagerRequest::Pruned`] + Pruned(bool), + + /// Response to [`BlockchainManagerRequest::Syncing`] + Syncing(bool), + + /// Response to [`BlockchainManagerRequest::Synced`] + Synced(bool), + + /// Response to [`BlockchainManagerRequest::Target`] + Target(std::time::Duration), + + /// Response to [`BlockchainManagerRequest::TargetHeight`] + TargetHeight { height: usize }, + + /// Response to [`BlockchainManagerRequest::GenerateBlocks`] + GenerateBlocks { + /// Hashes of the blocks generated. + blocks: Vec<[u8; 32]>, + /// The new top height. (TODO: is this correct?) + height: usize, + }, + + // /// Response to [`BlockchainManagerRequest::Spans`]. + // Spans(Vec>), + /// Response to [`BlockchainManagerRequest::NextNeededPruningSeed`]. + NextNeededPruningSeed(PruningSeed), +} + +/// TODO: use real type when public. +pub type BlockchainManagerHandle = cuprate_database_service::DatabaseReadService< + BlockchainManagerRequest, + BlockchainManagerResponse, +>; + +/// TODO +#[derive(Clone)] +pub struct CupratedRpcHandler { + /// Should this RPC server be [restricted](RpcHandler::restricted)? + /// + /// This is not `pub` on purpose, as it should not be mutated after [`Self::new`]. + restricted: bool, + + /// Read handle to the blockchain database. + pub blockchain_read: BlockchainReadHandle, + + /// Handle to the blockchain context service. + pub blockchain_context: BlockChainContextService, + + /// Handle to the blockchain manager. + pub blockchain_manager: BlockchainManagerHandle, + + /// Read handle to the transaction pool database. + pub txpool_read: TxpoolReadHandle, + + /// TODO: handle to txpool service. + pub txpool_manager: std::convert::Infallible, +} + +impl CupratedRpcHandler { + /// Create a new [`Self`]. + pub const fn new( + restricted: bool, + blockchain_read: BlockchainReadHandle, + blockchain_context: BlockChainContextService, + blockchain_manager: BlockchainManagerHandle, + txpool_read: TxpoolReadHandle, + txpool_manager: std::convert::Infallible, + ) -> Self { + Self { + restricted, + blockchain_read, + blockchain_context, + blockchain_manager, + txpool_read, + txpool_manager, + } + } +} + +impl RpcHandler for CupratedRpcHandler { + fn restricted(&self) -> bool { + self.restricted + } +} + +impl Service for CupratedRpcHandler { + type Response = JsonRpcResponse; + type Error = Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, request: JsonRpcRequest) -> Self::Future { + let state = self.clone(); + Box::pin(json::map_request(state, request)) + } +} + +impl Service for CupratedRpcHandler { + type Response = BinResponse; + type Error = Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, request: BinRequest) -> Self::Future { + let state = self.clone(); + Box::pin(bin::map_request(state, request)) + } +} + +impl Service for CupratedRpcHandler { + type Response = OtherResponse; + type Error = Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, request: OtherRequest) -> Self::Future { + let state = self.clone(); + Box::pin(other::map_request(state, request)) + } +} diff --git a/binaries/cuprated/src/rpc/json.rs b/binaries/cuprated/src/rpc/json.rs new file mode 100644 index 0000000..88b38b9 --- /dev/null +++ b/binaries/cuprated/src/rpc/json.rs @@ -0,0 +1,294 @@ +use std::sync::Arc; + +use anyhow::Error; +use tower::ServiceExt; + +use cuprate_rpc_types::json::{ + AddAuxPowRequest, AddAuxPowResponse, BannedRequest, BannedResponse, CalcPowRequest, + CalcPowResponse, FlushCacheRequest, FlushCacheResponse, FlushTransactionPoolRequest, + FlushTransactionPoolResponse, GenerateBlocksRequest, GenerateBlocksResponse, + GetAlternateChainsRequest, GetAlternateChainsResponse, GetBansRequest, GetBansResponse, + GetBlockCountRequest, GetBlockCountResponse, GetBlockHeaderByHashRequest, + GetBlockHeaderByHashResponse, GetBlockHeaderByHeightRequest, GetBlockHeaderByHeightResponse, + GetBlockHeadersRangeRequest, GetBlockHeadersRangeResponse, GetBlockRequest, GetBlockResponse, + GetCoinbaseTxSumRequest, GetCoinbaseTxSumResponse, GetConnectionsRequest, + GetConnectionsResponse, GetFeeEstimateRequest, GetFeeEstimateResponse, GetInfoRequest, + GetInfoResponse, GetLastBlockHeaderRequest, GetLastBlockHeaderResponse, GetMinerDataRequest, + GetMinerDataResponse, GetOutputHistogramRequest, GetOutputHistogramResponse, + GetTransactionPoolBacklogRequest, GetTransactionPoolBacklogResponse, GetTxIdsLooseRequest, + GetTxIdsLooseResponse, GetVersionRequest, GetVersionResponse, HardForkInfoRequest, + HardForkInfoResponse, JsonRpcRequest, JsonRpcResponse, OnGetBlockHashRequest, + OnGetBlockHashResponse, PruneBlockchainRequest, PruneBlockchainResponse, RelayTxRequest, + RelayTxResponse, SetBansRequest, SetBansResponse, SubmitBlockRequest, SubmitBlockResponse, + SyncInfoRequest, SyncInfoResponse, +}; + +use crate::rpc::CupratedRpcHandler; + +/// Map a [`JsonRpcRequest`] to the function that will lead to a [`JsonRpcResponse`]. +pub(super) async fn map_request( + state: CupratedRpcHandler, + request: JsonRpcRequest, +) -> Result { + use JsonRpcRequest as Req; + use JsonRpcResponse as Resp; + + Ok(match request { + Req::GetBlockCount(r) => Resp::GetBlockCount(get_block_count(state, r).await?), + Req::OnGetBlockHash(r) => Resp::OnGetBlockHash(on_get_block_hash(state, r).await?), + Req::SubmitBlock(r) => Resp::SubmitBlock(submit_block(state, r).await?), + Req::GenerateBlocks(r) => Resp::GenerateBlocks(generate_blocks(state, r).await?), + Req::GetLastBlockHeader(r) => { + Resp::GetLastBlockHeader(get_last_block_header(state, r).await?) + } + Req::GetBlockHeaderByHash(r) => { + Resp::GetBlockHeaderByHash(get_block_header_by_hash(state, r).await?) + } + Req::GetBlockHeaderByHeight(r) => { + Resp::GetBlockHeaderByHeight(get_block_header_by_height(state, r).await?) + } + Req::GetBlockHeadersRange(r) => { + Resp::GetBlockHeadersRange(get_block_headers_range(state, r).await?) + } + Req::GetBlock(r) => Resp::GetBlock(get_block(state, r).await?), + Req::GetConnections(r) => Resp::GetConnections(get_connections(state, r).await?), + Req::GetInfo(r) => Resp::GetInfo(get_info(state, r).await?), + Req::HardForkInfo(r) => Resp::HardForkInfo(hard_fork_info(state, r).await?), + Req::SetBans(r) => Resp::SetBans(set_bans(state, r).await?), + Req::GetBans(r) => Resp::GetBans(get_bans(state, r).await?), + Req::Banned(r) => Resp::Banned(banned(state, r).await?), + Req::FlushTransactionPool(r) => { + Resp::FlushTransactionPool(flush_transaction_pool(state, r).await?) + } + Req::GetOutputHistogram(r) => { + Resp::GetOutputHistogram(get_output_histogram(state, r).await?) + } + Req::GetCoinbaseTxSum(r) => Resp::GetCoinbaseTxSum(get_coinbase_tx_sum(state, r).await?), + Req::GetVersion(r) => Resp::GetVersion(get_version(state, r).await?), + Req::GetFeeEstimate(r) => Resp::GetFeeEstimate(get_fee_estimate(state, r).await?), + Req::GetAlternateChains(r) => { + Resp::GetAlternateChains(get_alternate_chains(state, r).await?) + } + Req::RelayTx(r) => Resp::RelayTx(relay_tx(state, r).await?), + Req::SyncInfo(r) => Resp::SyncInfo(sync_info(state, r).await?), + Req::GetTransactionPoolBacklog(r) => { + Resp::GetTransactionPoolBacklog(get_transaction_pool_backlog(state, r).await?) + } + Req::GetMinerData(r) => Resp::GetMinerData(get_miner_data(state, r).await?), + Req::PruneBlockchain(r) => Resp::PruneBlockchain(prune_blockchain(state, r).await?), + Req::CalcPow(r) => Resp::CalcPow(calc_pow(state, r).await?), + Req::FlushCache(r) => Resp::FlushCache(flush_cache(state, r).await?), + Req::AddAuxPow(r) => Resp::AddAuxPow(add_aux_pow(state, r).await?), + Req::GetTxIdsLoose(r) => Resp::GetTxIdsLoose(get_tx_ids_loose(state, r).await?), + }) +} + +async fn get_block_count( + state: CupratedRpcHandler, + request: GetBlockCountRequest, +) -> Result { + todo!() +} + +async fn on_get_block_hash( + state: CupratedRpcHandler, + request: OnGetBlockHashRequest, +) -> Result { + todo!() +} + +async fn submit_block( + state: CupratedRpcHandler, + request: SubmitBlockRequest, +) -> Result { + todo!() +} + +async fn generate_blocks( + state: CupratedRpcHandler, + request: GenerateBlocksRequest, +) -> Result { + todo!() +} + +async fn get_last_block_header( + state: CupratedRpcHandler, + request: GetLastBlockHeaderRequest, +) -> Result { + todo!() +} + +async fn get_block_header_by_hash( + state: CupratedRpcHandler, + request: GetBlockHeaderByHashRequest, +) -> Result { + todo!() +} + +async fn get_block_header_by_height( + state: CupratedRpcHandler, + request: GetBlockHeaderByHeightRequest, +) -> Result { + todo!() +} + +async fn get_block_headers_range( + state: CupratedRpcHandler, + request: GetBlockHeadersRangeRequest, +) -> Result { + todo!() +} + +async fn get_block( + state: CupratedRpcHandler, + request: GetBlockRequest, +) -> Result { + todo!() +} + +async fn get_connections( + state: CupratedRpcHandler, + request: GetConnectionsRequest, +) -> Result { + todo!() +} + +async fn get_info( + state: CupratedRpcHandler, + request: GetInfoRequest, +) -> Result { + todo!() +} + +async fn hard_fork_info( + state: CupratedRpcHandler, + request: HardForkInfoRequest, +) -> Result { + todo!() +} + +async fn set_bans( + state: CupratedRpcHandler, + request: SetBansRequest, +) -> Result { + todo!() +} + +async fn get_bans( + state: CupratedRpcHandler, + request: GetBansRequest, +) -> Result { + todo!() +} + +async fn banned( + state: CupratedRpcHandler, + request: BannedRequest, +) -> Result { + todo!() +} + +async fn flush_transaction_pool( + state: CupratedRpcHandler, + request: FlushTransactionPoolRequest, +) -> Result { + todo!() +} + +async fn get_output_histogram( + state: CupratedRpcHandler, + request: GetOutputHistogramRequest, +) -> Result { + todo!() +} + +async fn get_coinbase_tx_sum( + state: CupratedRpcHandler, + request: GetCoinbaseTxSumRequest, +) -> Result { + todo!() +} + +async fn get_version( + state: CupratedRpcHandler, + request: GetVersionRequest, +) -> Result { + todo!() +} + +async fn get_fee_estimate( + state: CupratedRpcHandler, + request: GetFeeEstimateRequest, +) -> Result { + todo!() +} + +async fn get_alternate_chains( + state: CupratedRpcHandler, + request: GetAlternateChainsRequest, +) -> Result { + todo!() +} + +async fn relay_tx( + state: CupratedRpcHandler, + request: RelayTxRequest, +) -> Result { + todo!() +} + +async fn sync_info( + state: CupratedRpcHandler, + request: SyncInfoRequest, +) -> Result { + todo!() +} + +async fn get_transaction_pool_backlog( + state: CupratedRpcHandler, + request: GetTransactionPoolBacklogRequest, +) -> Result { + todo!() +} + +async fn get_miner_data( + state: CupratedRpcHandler, + request: GetMinerDataRequest, +) -> Result { + todo!() +} + +async fn prune_blockchain( + state: CupratedRpcHandler, + request: PruneBlockchainRequest, +) -> Result { + todo!() +} + +async fn calc_pow( + state: CupratedRpcHandler, + request: CalcPowRequest, +) -> Result { + todo!() +} + +async fn flush_cache( + state: CupratedRpcHandler, + request: FlushCacheRequest, +) -> Result { + todo!() +} + +async fn add_aux_pow( + state: CupratedRpcHandler, + request: AddAuxPowRequest, +) -> Result { + todo!() +} + +async fn get_tx_ids_loose( + state: CupratedRpcHandler, + request: GetTxIdsLooseRequest, +) -> Result { + todo!() +} diff --git a/binaries/cuprated/src/rpc/other.rs b/binaries/cuprated/src/rpc/other.rs new file mode 100644 index 0000000..16b58c4 --- /dev/null +++ b/binaries/cuprated/src/rpc/other.rs @@ -0,0 +1,260 @@ +use anyhow::Error; + +use cuprate_rpc_types::other::{ + GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse, + GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest, + GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest, + GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse, + GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest, + GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse, + InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse, + MiningStatusRequest, MiningStatusResponse, OtherRequest, OtherResponse, OutPeersRequest, + OutPeersResponse, PopBlocksRequest, PopBlocksResponse, SaveBcRequest, SaveBcResponse, + SendRawTransactionRequest, SendRawTransactionResponse, SetBootstrapDaemonRequest, + SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest, + SetLogCategoriesResponse, SetLogHashRateRequest, SetLogHashRateResponse, SetLogLevelRequest, + SetLogLevelResponse, StartMiningRequest, StartMiningResponse, StopDaemonRequest, + StopDaemonResponse, StopMiningRequest, StopMiningResponse, UpdateRequest, UpdateResponse, +}; + +use crate::rpc::CupratedRpcHandler; + +/// Map a [`OtherRequest`] to the function that will lead to a [`OtherResponse`]. +pub(super) async fn map_request( + state: CupratedRpcHandler, + request: OtherRequest, +) -> Result { + use OtherRequest as Req; + use OtherResponse as Resp; + + Ok(match request { + Req::GetHeight(r) => Resp::GetHeight(get_height(state, r).await?), + Req::GetTransactions(r) => Resp::GetTransactions(get_transactions(state, r).await?), + Req::GetAltBlocksHashes(r) => { + Resp::GetAltBlocksHashes(get_alt_blocks_hashes(state, r).await?) + } + Req::IsKeyImageSpent(r) => Resp::IsKeyImageSpent(is_key_image_spent(state, r).await?), + Req::SendRawTransaction(r) => { + Resp::SendRawTransaction(send_raw_transaction(state, r).await?) + } + Req::StartMining(r) => Resp::StartMining(start_mining(state, r).await?), + Req::StopMining(r) => Resp::StopMining(stop_mining(state, r).await?), + Req::MiningStatus(r) => Resp::MiningStatus(mining_status(state, r).await?), + Req::SaveBc(r) => Resp::SaveBc(save_bc(state, r).await?), + Req::GetPeerList(r) => Resp::GetPeerList(get_peer_list(state, r).await?), + Req::SetLogHashRate(r) => Resp::SetLogHashRate(set_log_hash_rate(state, r).await?), + Req::SetLogLevel(r) => Resp::SetLogLevel(set_log_level(state, r).await?), + Req::SetLogCategories(r) => Resp::SetLogCategories(set_log_categories(state, r).await?), + Req::SetBootstrapDaemon(r) => { + Resp::SetBootstrapDaemon(set_bootstrap_daemon(state, r).await?) + } + Req::GetTransactionPool(r) => { + Resp::GetTransactionPool(get_transaction_pool(state, r).await?) + } + Req::GetTransactionPoolStats(r) => { + Resp::GetTransactionPoolStats(get_transaction_pool_stats(state, r).await?) + } + Req::StopDaemon(r) => Resp::StopDaemon(stop_daemon(state, r).await?), + Req::GetLimit(r) => Resp::GetLimit(get_limit(state, r).await?), + Req::SetLimit(r) => Resp::SetLimit(set_limit(state, r).await?), + Req::OutPeers(r) => Resp::OutPeers(out_peers(state, r).await?), + Req::InPeers(r) => Resp::InPeers(in_peers(state, r).await?), + Req::GetNetStats(r) => Resp::GetNetStats(get_net_stats(state, r).await?), + Req::GetOuts(r) => Resp::GetOuts(get_outs(state, r).await?), + Req::Update(r) => Resp::Update(update(state, r).await?), + Req::PopBlocks(r) => Resp::PopBlocks(pop_blocks(state, r).await?), + Req::GetTransactionPoolHashes(r) => { + Resp::GetTransactionPoolHashes(get_transaction_pool_hashes(state, r).await?) + } + Req::GetPublicNodes(r) => Resp::GetPublicNodes(get_public_nodes(state, r).await?), + }) +} + +async fn get_height( + state: CupratedRpcHandler, + request: GetHeightRequest, +) -> Result { + todo!() +} + +async fn get_transactions( + state: CupratedRpcHandler, + request: GetTransactionsRequest, +) -> Result { + todo!() +} + +async fn get_alt_blocks_hashes( + state: CupratedRpcHandler, + request: GetAltBlocksHashesRequest, +) -> Result { + todo!() +} + +async fn is_key_image_spent( + state: CupratedRpcHandler, + request: IsKeyImageSpentRequest, +) -> Result { + todo!() +} + +async fn send_raw_transaction( + state: CupratedRpcHandler, + request: SendRawTransactionRequest, +) -> Result { + todo!() +} + +async fn start_mining( + state: CupratedRpcHandler, + request: StartMiningRequest, +) -> Result { + todo!() +} + +async fn stop_mining( + state: CupratedRpcHandler, + request: StopMiningRequest, +) -> Result { + todo!() +} + +async fn mining_status( + state: CupratedRpcHandler, + request: MiningStatusRequest, +) -> Result { + todo!() +} + +async fn save_bc( + state: CupratedRpcHandler, + request: SaveBcRequest, +) -> Result { + todo!() +} + +async fn get_peer_list( + state: CupratedRpcHandler, + request: GetPeerListRequest, +) -> Result { + todo!() +} + +async fn set_log_hash_rate( + state: CupratedRpcHandler, + request: SetLogHashRateRequest, +) -> Result { + todo!() +} + +async fn set_log_level( + state: CupratedRpcHandler, + request: SetLogLevelRequest, +) -> Result { + todo!() +} + +async fn set_log_categories( + state: CupratedRpcHandler, + request: SetLogCategoriesRequest, +) -> Result { + todo!() +} + +async fn set_bootstrap_daemon( + state: CupratedRpcHandler, + request: SetBootstrapDaemonRequest, +) -> Result { + todo!() +} + +async fn get_transaction_pool( + state: CupratedRpcHandler, + request: GetTransactionPoolRequest, +) -> Result { + todo!() +} + +async fn get_transaction_pool_stats( + state: CupratedRpcHandler, + request: GetTransactionPoolStatsRequest, +) -> Result { + todo!() +} + +async fn stop_daemon( + state: CupratedRpcHandler, + request: StopDaemonRequest, +) -> Result { + todo!() +} + +async fn get_limit( + state: CupratedRpcHandler, + request: GetLimitRequest, +) -> Result { + todo!() +} + +async fn set_limit( + state: CupratedRpcHandler, + request: SetLimitRequest, +) -> Result { + todo!() +} + +async fn out_peers( + state: CupratedRpcHandler, + request: OutPeersRequest, +) -> Result { + todo!() +} + +async fn in_peers( + state: CupratedRpcHandler, + request: InPeersRequest, +) -> Result { + todo!() +} + +async fn get_net_stats( + state: CupratedRpcHandler, + request: GetNetStatsRequest, +) -> Result { + todo!() +} + +async fn get_outs( + state: CupratedRpcHandler, + request: GetOutsRequest, +) -> Result { + todo!() +} + +async fn update( + state: CupratedRpcHandler, + request: UpdateRequest, +) -> Result { + todo!() +} + +async fn pop_blocks( + state: CupratedRpcHandler, + request: PopBlocksRequest, +) -> Result { + todo!() +} + +async fn get_transaction_pool_hashes( + state: CupratedRpcHandler, + request: GetTransactionPoolHashesRequest, +) -> Result { + todo!() +} + +async fn get_public_nodes( + state: CupratedRpcHandler, + request: GetPublicNodesRequest, +) -> Result { + todo!() +} diff --git a/binaries/cuprated/src/rpc/request.rs b/binaries/cuprated/src/rpc/request.rs new file mode 100644 index 0000000..17e12b9 --- /dev/null +++ b/binaries/cuprated/src/rpc/request.rs @@ -0,0 +1,19 @@ +//! Convenience functions for requests/responses. +//! +//! This module implements many methods for +//! [`CupratedRpcHandler`](crate::rpc::CupratedRpcHandler) +//! that are simple wrappers around the request/response API provided +//! by the multiple [`tower::Service`]s. +//! +//! These exist to prevent noise like `unreachable!()` +//! from being everywhere in the actual handler functions. +//! +//! Each module implements methods for a specific API, e.g. +//! the [`blockchain`] modules contains methods for the +//! blockchain database [`tower::Service`] API. + +mod address_book; +mod blockchain; +mod blockchain_context; +mod blockchain_manager; +mod txpool; diff --git a/binaries/cuprated/src/rpc/request/address_book.rs b/binaries/cuprated/src/rpc/request/address_book.rs new file mode 100644 index 0000000..6760a6c --- /dev/null +++ b/binaries/cuprated/src/rpc/request/address_book.rs @@ -0,0 +1,168 @@ +//! Functions for TODO: doc enum message. + +use std::convert::Infallible; + +use anyhow::{anyhow, Error}; +use tower::ServiceExt; + +use cuprate_helper::cast::usize_to_u64; +use cuprate_p2p_core::{ + services::{AddressBookRequest, AddressBookResponse}, + types::{BanState, ConnectionId}, + AddressBook, NetworkZone, +}; +use cuprate_pruning::PruningSeed; +use cuprate_rpc_types::misc::{ConnectionInfo, Span}; + +use crate::rpc::constants::FIELD_NOT_SUPPORTED; + +// FIXME: use `anyhow::Error` over `tower::BoxError` in address book. + +/// [`AddressBookRequest::PeerlistSize`] +pub(crate) async fn peerlist_size( + address_book: &mut impl AddressBook, +) -> Result<(u64, u64), Error> { + let AddressBookResponse::PeerlistSize { white, grey } = address_book + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(AddressBookRequest::PeerlistSize) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok((usize_to_u64(white), usize_to_u64(grey))) +} + +/// [`AddressBookRequest::ConnectionInfo`] +pub(crate) async fn connection_info( + address_book: &mut impl AddressBook, +) -> Result, Error> { + let AddressBookResponse::ConnectionInfo(vec) = address_book + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(AddressBookRequest::ConnectionInfo) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + // FIXME: impl this map somewhere instead of inline. + let vec = vec + .into_iter() + .map(|info| { + let (ip, port) = match info.socket_addr { + Some(socket) => (socket.ip().to_string(), socket.port().to_string()), + None => (String::new(), String::new()), + }; + + ConnectionInfo { + address: info.address.to_string(), + address_type: info.address_type, + avg_download: info.avg_download, + avg_upload: info.avg_upload, + connection_id: String::from(ConnectionId::DEFAULT_STR), + current_download: info.current_download, + current_upload: info.current_upload, + height: info.height, + host: info.host, + incoming: info.incoming, + ip, + live_time: info.live_time, + localhost: info.localhost, + local_ip: info.local_ip, + peer_id: hex::encode(info.peer_id.to_ne_bytes()), + port, + pruning_seed: info.pruning_seed.compress(), + recv_count: info.recv_count, + recv_idle_time: info.recv_idle_time, + rpc_credits_per_hash: info.rpc_credits_per_hash, + rpc_port: info.rpc_port, + send_count: info.send_count, + send_idle_time: info.send_idle_time, + state: info.state, + support_flags: info.support_flags, + } + }) + .collect(); + + Ok(vec) +} + +/// [`AddressBookRequest::ConnectionCount`] +pub(crate) async fn connection_count( + address_book: &mut impl AddressBook, +) -> Result<(u64, u64), Error> { + let AddressBookResponse::ConnectionCount { incoming, outgoing } = address_book + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(AddressBookRequest::ConnectionCount) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok((usize_to_u64(incoming), usize_to_u64(outgoing))) +} + +/// [`AddressBookRequest::SetBan`] +pub(crate) async fn set_ban( + address_book: &mut impl AddressBook, + set_ban: cuprate_p2p_core::types::SetBan, +) -> Result<(), Error> { + let AddressBookResponse::Ok = address_book + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(AddressBookRequest::SetBan(set_ban)) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(()) +} + +/// [`AddressBookRequest::GetBan`] +pub(crate) async fn get_ban( + address_book: &mut impl AddressBook, + peer: Z::Addr, +) -> Result, Error> { + let AddressBookResponse::GetBan { unban_instant } = address_book + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(AddressBookRequest::GetBan(peer)) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(unban_instant) +} + +/// [`AddressBookRequest::GetBans`] +pub(crate) async fn get_bans( + address_book: &mut impl AddressBook, +) -> Result>, Error> { + let AddressBookResponse::GetBans(bans) = address_book + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(AddressBookRequest::GetBans) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(bans) +} diff --git a/binaries/cuprated/src/rpc/request/blockchain.rs b/binaries/cuprated/src/rpc/request/blockchain.rs new file mode 100644 index 0000000..97c7f48 --- /dev/null +++ b/binaries/cuprated/src/rpc/request/blockchain.rs @@ -0,0 +1,377 @@ +//! Functions for [`BlockchainReadRequest`]. + +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + ops::Range, +}; + +use anyhow::Error; +use monero_serai::block::Block; +use tower::{Service, ServiceExt}; + +use cuprate_blockchain::{service::BlockchainReadHandle, types::AltChainInfo}; +use cuprate_helper::cast::{u64_to_usize, usize_to_u64}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, ChainInfo, CoinbaseTxSum, ExtendedBlockHeader, HardFork, MinerData, + OutputHistogramEntry, OutputHistogramInput, OutputOnChain, +}; + +/// [`BlockchainReadRequest::Block`]. +pub(crate) async fn block( + blockchain_read: &mut BlockchainReadHandle, + height: u64, +) -> Result { + let BlockchainResponse::Block(block) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::Block { + height: u64_to_usize(height), + }) + .await? + else { + unreachable!(); + }; + + Ok(block) +} + +/// [`BlockchainReadRequest::BlockByHash`]. +pub(crate) async fn block_by_hash( + blockchain_read: &mut BlockchainReadHandle, + hash: [u8; 32], +) -> Result { + let BlockchainResponse::Block(block) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::BlockByHash(hash)) + .await? + else { + unreachable!(); + }; + + Ok(block) +} + +/// [`BlockchainReadRequest::BlockExtendedHeader`]. +pub(crate) async fn block_extended_header( + blockchain_read: &mut BlockchainReadHandle, + height: u64, +) -> Result { + let BlockchainResponse::BlockExtendedHeader(header) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::BlockExtendedHeader(u64_to_usize( + height, + ))) + .await? + else { + unreachable!(); + }; + + Ok(header) +} + +/// [`BlockchainReadRequest::BlockHash`]. +pub(crate) async fn block_hash( + blockchain_read: &mut BlockchainReadHandle, + height: u64, + chain: Chain, +) -> Result<[u8; 32], Error> { + let BlockchainResponse::BlockHash(hash) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::BlockHash( + u64_to_usize(height), + chain, + )) + .await? + else { + unreachable!(); + }; + + Ok(hash) +} + +/// [`BlockchainReadRequest::FindBlock`]. +pub(crate) async fn find_block( + blockchain_read: &mut BlockchainReadHandle, + block_hash: [u8; 32], +) -> Result, Error> { + let BlockchainResponse::FindBlock(option) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::FindBlock(block_hash)) + .await? + else { + unreachable!(); + }; + + Ok(option) +} + +/// [`BlockchainReadRequest::FilterUnknownHashes`]. +pub(crate) async fn filter_unknown_hashes( + blockchain_read: &mut BlockchainReadHandle, + block_hashes: HashSet<[u8; 32]>, +) -> Result, Error> { + let BlockchainResponse::FilterUnknownHashes(output) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::FilterUnknownHashes(block_hashes)) + .await? + else { + unreachable!(); + }; + + Ok(output) +} + +/// [`BlockchainReadRequest::BlockExtendedHeaderInRange`] +pub(crate) async fn block_extended_header_in_range( + blockchain_read: &mut BlockchainReadHandle, + range: Range, + chain: Chain, +) -> Result, Error> { + let BlockchainResponse::BlockExtendedHeaderInRange(output) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::BlockExtendedHeaderInRange( + range, chain, + )) + .await? + else { + unreachable!(); + }; + + Ok(output) +} + +/// [`BlockchainReadRequest::ChainHeight`]. +pub(crate) async fn chain_height( + blockchain_read: &mut BlockchainReadHandle, +) -> Result<(u64, [u8; 32]), Error> { + let BlockchainResponse::ChainHeight(height, hash) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::ChainHeight) + .await? + else { + unreachable!(); + }; + + Ok((usize_to_u64(height), hash)) +} + +/// [`BlockchainReadRequest::GeneratedCoins`]. +pub(crate) async fn generated_coins( + blockchain_read: &mut BlockchainReadHandle, + block_height: u64, +) -> Result { + let BlockchainResponse::GeneratedCoins(generated_coins) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::GeneratedCoins(u64_to_usize( + block_height, + ))) + .await? + else { + unreachable!(); + }; + + Ok(generated_coins) +} + +/// [`BlockchainReadRequest::Outputs`] +pub(crate) async fn outputs( + blockchain_read: &mut BlockchainReadHandle, + outputs: HashMap>, +) -> Result>, Error> { + let BlockchainResponse::Outputs(outputs) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::Outputs(outputs)) + .await? + else { + unreachable!(); + }; + + Ok(outputs) +} + +/// [`BlockchainReadRequest::NumberOutputsWithAmount`] +pub(crate) async fn number_outputs_with_amount( + blockchain_read: &mut BlockchainReadHandle, + output_amounts: Vec, +) -> Result, Error> { + let BlockchainResponse::NumberOutputsWithAmount(map) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::NumberOutputsWithAmount( + output_amounts, + )) + .await? + else { + unreachable!(); + }; + + Ok(map) +} + +/// [`BlockchainReadRequest::KeyImagesSpent`] +pub(crate) async fn key_images_spent( + blockchain_read: &mut BlockchainReadHandle, + key_images: HashSet<[u8; 32]>, +) -> Result { + let BlockchainResponse::KeyImagesSpent(is_spent) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::KeyImagesSpent(key_images)) + .await? + else { + unreachable!(); + }; + + Ok(is_spent) +} + +/// [`BlockchainReadRequest::CompactChainHistory`] +pub(crate) async fn compact_chain_history( + blockchain_read: &mut BlockchainReadHandle, +) -> Result<(Vec<[u8; 32]>, u128), Error> { + let BlockchainResponse::CompactChainHistory { + block_ids, + cumulative_difficulty, + } = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::CompactChainHistory) + .await? + else { + unreachable!(); + }; + + Ok((block_ids, cumulative_difficulty)) +} + +/// [`BlockchainReadRequest::FindFirstUnknown`] +pub(crate) async fn find_first_unknown( + blockchain_read: &mut BlockchainReadHandle, + hashes: Vec<[u8; 32]>, +) -> Result, Error> { + let BlockchainResponse::FindFirstUnknown(resp) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::FindFirstUnknown(hashes)) + .await? + else { + unreachable!(); + }; + + Ok(resp.map(|(index, height)| (index, usize_to_u64(height)))) +} + +/// [`BlockchainReadRequest::TotalTxCount`] +pub(crate) async fn total_tx_count( + blockchain_read: &mut BlockchainReadHandle, +) -> Result { + let BlockchainResponse::TotalTxCount(tx_count) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::TotalTxCount) + .await? + else { + unreachable!(); + }; + + Ok(usize_to_u64(tx_count)) +} + +/// [`BlockchainReadRequest::DatabaseSize`] +pub(crate) async fn database_size( + blockchain_read: &mut BlockchainReadHandle, +) -> Result<(u64, u64), Error> { + let BlockchainResponse::DatabaseSize { + database_size, + free_space, + } = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::DatabaseSize) + .await? + else { + unreachable!(); + }; + + Ok((database_size, free_space)) +} + +/// [`BlockchainReadRequest::OutputHistogram`] +pub(crate) async fn output_histogram( + blockchain_read: &mut BlockchainReadHandle, + input: OutputHistogramInput, +) -> Result, Error> { + let BlockchainResponse::OutputHistogram(histogram) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::OutputHistogram(input)) + .await? + else { + unreachable!(); + }; + + Ok(histogram) +} + +/// [`BlockchainReadRequest::CoinbaseTxSum`] +pub(crate) async fn coinbase_tx_sum( + blockchain_read: &mut BlockchainReadHandle, + height: u64, + count: u64, +) -> Result { + let BlockchainResponse::CoinbaseTxSum(sum) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::CoinbaseTxSum { + height: u64_to_usize(height), + count, + }) + .await? + else { + unreachable!(); + }; + + Ok(sum) +} + +/// [`BlockchainReadRequest::AltChains`] +pub(crate) async fn alt_chains( + blockchain_read: &mut BlockchainReadHandle, +) -> Result, Error> { + let BlockchainResponse::AltChains(vec) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::AltChains) + .await? + else { + unreachable!(); + }; + + Ok(vec) +} + +/// [`BlockchainReadRequest::AltChainCount`] +pub(crate) async fn alt_chain_count( + blockchain_read: &mut BlockchainReadHandle, +) -> Result { + let BlockchainResponse::AltChainCount(count) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::AltChainCount) + .await? + else { + unreachable!(); + }; + + Ok(usize_to_u64(count)) +} diff --git a/binaries/cuprated/src/rpc/request/blockchain_context.rs b/binaries/cuprated/src/rpc/request/blockchain_context.rs new file mode 100644 index 0000000..c6f0f22 --- /dev/null +++ b/binaries/cuprated/src/rpc/request/blockchain_context.rs @@ -0,0 +1,99 @@ +//! Functions for [`BlockChainContextRequest`] and [`BlockChainContextResponse`]. + +use std::convert::Infallible; + +use anyhow::{anyhow, Error}; +use monero_serai::block::Block; +use tower::{Service, ServiceExt}; + +use cuprate_consensus_context::{ + BlockChainContext, BlockChainContextRequest, BlockChainContextResponse, + BlockChainContextService, +}; +use cuprate_helper::cast::u64_to_usize; +use cuprate_types::{FeeEstimate, HardFork, HardForkInfo}; + +// FIXME: use `anyhow::Error` over `tower::BoxError` in blockchain context. + +/// [`BlockChainContextRequest::Context`]. +pub(crate) async fn context( + blockchain_context: &mut BlockChainContextService, +) -> Result { + let BlockChainContextResponse::Context(context) = blockchain_context + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(BlockChainContextRequest::Context) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(context) +} + +/// [`BlockChainContextRequest::HardForkInfo`]. +pub(crate) async fn hard_fork_info( + blockchain_context: &mut BlockChainContextService, + hard_fork: HardFork, +) -> Result { + let BlockChainContextResponse::HardForkInfo(hf_info) = blockchain_context + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(BlockChainContextRequest::HardForkInfo(hard_fork)) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(hf_info) +} + +/// [`BlockChainContextRequest::FeeEstimate`]. +pub(crate) async fn fee_estimate( + blockchain_context: &mut BlockChainContextService, + grace_blocks: u64, +) -> Result { + let BlockChainContextResponse::FeeEstimate(fee) = blockchain_context + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(BlockChainContextRequest::FeeEstimate { grace_blocks }) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(fee) +} + +/// [`BlockChainContextRequest::CalculatePow`] +pub(crate) async fn calculate_pow( + blockchain_context: &mut BlockChainContextService, + hardfork: HardFork, + height: u64, + block: Box, + seed_hash: [u8; 32], +) -> Result<[u8; 32], Error> { + let BlockChainContextResponse::CalculatePow(hash) = blockchain_context + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(BlockChainContextRequest::CalculatePow { + hardfork, + height: u64_to_usize(height), + block, + seed_hash, + }) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(hash) +} diff --git a/binaries/cuprated/src/rpc/request/blockchain_manager.rs b/binaries/cuprated/src/rpc/request/blockchain_manager.rs new file mode 100644 index 0000000..18b75de --- /dev/null +++ b/binaries/cuprated/src/rpc/request/blockchain_manager.rs @@ -0,0 +1,221 @@ +//! Functions for [`BlockchainManagerRequest`] & [`BlockchainManagerResponse`]. + +use anyhow::Error; +use monero_serai::block::Block; +use tower::{Service, ServiceExt}; + +use cuprate_helper::cast::{u64_to_usize, usize_to_u64}; +use cuprate_p2p_core::{types::ConnectionId, NetworkZone}; +use cuprate_pruning::PruningSeed; +use cuprate_rpc_types::misc::Span; +use cuprate_types::{AddAuxPow, AuxPow, HardFork}; + +use crate::rpc::{ + constants::FIELD_NOT_SUPPORTED, + handler::{BlockchainManagerHandle, BlockchainManagerRequest, BlockchainManagerResponse}, +}; + +/// [`BlockchainManagerRequest::PopBlocks`] +pub(crate) async fn pop_blocks( + blockchain_manager: &mut BlockchainManagerHandle, + amount: u64, +) -> Result { + let BlockchainManagerResponse::PopBlocks { new_height } = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::PopBlocks { + amount: u64_to_usize(amount), + }) + .await? + else { + unreachable!(); + }; + + Ok(usize_to_u64(new_height)) +} + +/// [`BlockchainManagerRequest::Prune`] +pub(crate) async fn prune( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::Prune(seed) = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::Prune) + .await? + else { + unreachable!(); + }; + + Ok(seed) +} + +/// [`BlockchainManagerRequest::Pruned`] +pub(crate) async fn pruned( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::Pruned(pruned) = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::Pruned) + .await? + else { + unreachable!(); + }; + + Ok(pruned) +} + +/// [`BlockchainManagerRequest::RelayBlock`] +pub(crate) async fn relay_block( + blockchain_manager: &mut BlockchainManagerHandle, + block: Block, +) -> Result<(), Error> { + let BlockchainManagerResponse::Ok = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::RelayBlock(block)) + .await? + else { + unreachable!(); + }; + + Ok(()) +} + +/// [`BlockchainManagerRequest::Syncing`] +pub(crate) async fn syncing( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::Syncing(syncing) = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::Syncing) + .await? + else { + unreachable!(); + }; + + Ok(syncing) +} + +/// [`BlockchainManagerRequest::Synced`] +pub(crate) async fn synced( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::Synced(syncing) = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::Synced) + .await? + else { + unreachable!(); + }; + + Ok(syncing) +} + +/// [`BlockchainManagerRequest::Target`] +pub(crate) async fn target( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::Target(target) = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::Target) + .await? + else { + unreachable!(); + }; + + Ok(target) +} + +/// [`BlockchainManagerRequest::TargetHeight`] +pub(crate) async fn target_height( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::TargetHeight { height } = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::TargetHeight) + .await? + else { + unreachable!(); + }; + + Ok(usize_to_u64(height)) +} + +/// [`BlockchainManagerRequest::GenerateBlocks`] +pub(crate) async fn generate_blocks( + blockchain_manager: &mut BlockchainManagerHandle, + amount_of_blocks: u64, + prev_block: [u8; 32], + starting_nonce: u32, + wallet_address: String, +) -> Result<(Vec<[u8; 32]>, u64), Error> { + let BlockchainManagerResponse::GenerateBlocks { blocks, height } = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::GenerateBlocks { + amount_of_blocks, + prev_block, + starting_nonce, + wallet_address, + }) + .await? + else { + unreachable!(); + }; + + Ok((blocks, usize_to_u64(height))) +} + +// [`BlockchainManagerRequest::Spans`] +pub(crate) async fn spans( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result, Error> { + // let BlockchainManagerResponse::Spans(vec) = blockchain_manager + // .ready() + // .await? + // .call(BlockchainManagerRequest::Spans) + // .await? + // else { + // unreachable!(); + // }; + + let vec: Vec> = todo!(); + + // FIXME: impl this map somewhere instead of inline. + let vec = vec + .into_iter() + .map(|span| Span { + connection_id: String::from(ConnectionId::DEFAULT_STR), + nblocks: span.nblocks, + rate: span.rate, + remote_address: span.remote_address.to_string(), + size: span.size, + speed: span.speed, + start_block_height: span.start_block_height, + }) + .collect(); + + Ok(vec) +} + +/// [`BlockchainManagerRequest::NextNeededPruningSeed`] +pub(crate) async fn next_needed_pruning_seed( + blockchain_manager: &mut BlockchainManagerHandle, +) -> Result { + let BlockchainManagerResponse::NextNeededPruningSeed(seed) = blockchain_manager + .ready() + .await? + .call(BlockchainManagerRequest::NextNeededPruningSeed) + .await? + else { + unreachable!(); + }; + + Ok(seed) +} diff --git a/binaries/cuprated/src/rpc/request/txpool.rs b/binaries/cuprated/src/rpc/request/txpool.rs new file mode 100644 index 0000000..eadbb23 --- /dev/null +++ b/binaries/cuprated/src/rpc/request/txpool.rs @@ -0,0 +1,72 @@ +//! Functions for [`TxpoolReadRequest`]. + +use std::convert::Infallible; + +use anyhow::{anyhow, Error}; +use tower::{Service, ServiceExt}; + +use cuprate_helper::cast::usize_to_u64; +use cuprate_txpool::{ + service::{ + interface::{TxpoolReadRequest, TxpoolReadResponse}, + TxpoolReadHandle, + }, + TxEntry, +}; + +// FIXME: use `anyhow::Error` over `tower::BoxError` in txpool. + +/// [`TxpoolReadRequest::Backlog`] +pub(crate) async fn backlog(txpool_read: &mut TxpoolReadHandle) -> Result, Error> { + let TxpoolReadResponse::Backlog(tx_entries) = txpool_read + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(TxpoolReadRequest::Backlog) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(tx_entries) +} + +/// [`TxpoolReadRequest::Size`] +pub(crate) async fn size( + txpool_read: &mut TxpoolReadHandle, + include_sensitive_txs: bool, +) -> Result { + let TxpoolReadResponse::Size(size) = txpool_read + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(TxpoolReadRequest::Size { + include_sensitive_txs, + }) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(usize_to_u64(size)) +} + +/// TODO +pub(crate) async fn flush( + txpool_manager: &mut Infallible, + tx_hashes: Vec<[u8; 32]>, +) -> Result<(), Error> { + todo!(); + Ok(()) +} + +/// TODO +pub(crate) async fn relay( + txpool_manager: &mut Infallible, + tx_hashes: Vec<[u8; 32]>, +) -> Result<(), Error> { + todo!(); + Ok(()) +} diff --git a/binaries/cuprated/src/signals.rs b/binaries/cuprated/src/signals.rs new file mode 100644 index 0000000..42148ca --- /dev/null +++ b/binaries/cuprated/src/signals.rs @@ -0,0 +1,12 @@ +//! Signals for Cuprate state used throughout the binary. + +use tokio::sync::RwLock; + +/// Reorg lock. +/// +/// A [`RwLock`] where a write lock is taken during a reorg and a read lock can be taken +/// for any operation which must complete without a reorg happening. +/// +/// Currently, the only operation that needs to take a read lock is adding txs to the tx-pool, +/// this can potentially be removed in the future, see: +pub static REORG_LOCK: RwLock<()> = RwLock::const_new(()); diff --git a/binaries/cuprated/src/statics.rs b/binaries/cuprated/src/statics.rs new file mode 100644 index 0000000..2d7338d --- /dev/null +++ b/binaries/cuprated/src/statics.rs @@ -0,0 +1,53 @@ +//! Global `static`s used throughout `cuprated`. + +use std::{ + sync::LazyLock, + time::{SystemTime, UNIX_EPOCH}, +}; + +/// Define all the `static`s that should be always be initialized early on. +/// +/// This wraps all `static`s inside a `LazyLock` and generates +/// a [`init_lazylock_statics`] function that must/should be +/// used by `main()` early on. +macro_rules! define_init_lazylock_statics { + ($( + $( #[$attr:meta] )* + $name:ident: $t:ty = $init_fn:expr_2021; + )*) => { + /// Initialize global static `LazyLock` data. + pub fn init_lazylock_statics() { + $( + LazyLock::force(&$name); + )* + } + + $( + $(#[$attr])* + pub static $name: LazyLock<$t> = LazyLock::new(|| $init_fn); + )* + }; +} + +define_init_lazylock_statics! { + /// The start time of `cuprated`. + START_INSTANT: SystemTime = SystemTime::now(); + + /// Start time of `cuprated` as a UNIX timestamp. + START_INSTANT_UNIX: u64 = START_INSTANT + .duration_since(UNIX_EPOCH) + .expect("Failed to set `cuprated` startup time.") + .as_secs(); +} + +#[cfg(test)] +mod test { + use super::*; + + /// Sanity check for startup UNIX time. + #[test] + fn start_instant_unix() { + // Fri Sep 27 01:07:13 AM UTC 2024 + assert!(*START_INSTANT_UNIX > 1727399233); + } +} diff --git a/binaries/cuprated/src/txpool.rs b/binaries/cuprated/src/txpool.rs new file mode 100644 index 0000000..2076956 --- /dev/null +++ b/binaries/cuprated/src/txpool.rs @@ -0,0 +1,15 @@ +//! Transaction Pool +//! +//! Handles initiating the tx-pool, providing the preprocessor required for the dandelion pool. +use cuprate_consensus::BlockChainContextService; +use cuprate_p2p::NetworkInterface; +use cuprate_p2p_core::ClearNet; +use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle}; + +use crate::blockchain::ConcreteTxVerifierService; + +mod dandelion; +mod incoming_tx; +mod txs_being_handled; + +pub use incoming_tx::{IncomingTxError, IncomingTxHandler, IncomingTxs}; diff --git a/binaries/cuprated/src/txpool/dandelion.rs b/binaries/cuprated/src/txpool/dandelion.rs new file mode 100644 index 0000000..00d9f5a --- /dev/null +++ b/binaries/cuprated/src/txpool/dandelion.rs @@ -0,0 +1,65 @@ +use std::time::Duration; + +use cuprate_dandelion_tower::{ + pool::DandelionPoolService, DandelionConfig, DandelionRouter, Graph, +}; +use cuprate_p2p::NetworkInterface; +use cuprate_p2p_core::ClearNet; +use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle}; + +use crate::{ + p2p::CrossNetworkInternalPeerId, + txpool::incoming_tx::{DandelionTx, TxId}, +}; + +mod diffuse_service; +mod stem_service; +mod tx_store; + +/// The configuration used for [`cuprate_dandelion_tower`]. +/// +/// TODO: should we expose this to users of cuprated? probably not. +const DANDELION_CONFIG: DandelionConfig = DandelionConfig { + time_between_hop: Duration::from_millis(175), + epoch_duration: Duration::from_secs(10 * 60), + fluff_probability: 0.12, + graph: Graph::FourRegular, +}; + +/// A [`DandelionRouter`] with all generic types defined. +type ConcreteDandelionRouter = DandelionRouter< + stem_service::OutboundPeerStream, + diffuse_service::DiffuseService, + CrossNetworkInternalPeerId, + stem_service::StemPeerService, + DandelionTx, +>; + +/// Starts the dandelion pool manager task and returns a handle to send txs to broadcast. +pub fn start_dandelion_pool_manager( + router: ConcreteDandelionRouter, + txpool_read_handle: TxpoolReadHandle, + txpool_write_handle: TxpoolWriteHandle, +) -> DandelionPoolService { + cuprate_dandelion_tower::pool::start_dandelion_pool_manager( + // TODO: make this constant configurable? + 32, + router, + tx_store::TxStoreService { + txpool_read_handle, + txpool_write_handle, + }, + DANDELION_CONFIG, + ) +} + +/// Creates a [`DandelionRouter`] from a [`NetworkInterface`]. +pub fn dandelion_router(clear_net: NetworkInterface) -> ConcreteDandelionRouter { + DandelionRouter::new( + diffuse_service::DiffuseService { + clear_net_broadcast_service: clear_net.broadcast_svc(), + }, + stem_service::OutboundPeerStream::new(clear_net), + DANDELION_CONFIG, + ) +} diff --git a/binaries/cuprated/src/txpool/dandelion/diffuse_service.rs b/binaries/cuprated/src/txpool/dandelion/diffuse_service.rs new file mode 100644 index 0000000..621503f --- /dev/null +++ b/binaries/cuprated/src/txpool/dandelion/diffuse_service.rs @@ -0,0 +1,44 @@ +use std::{ + future::{ready, Ready}, + task::{Context, Poll}, +}; + +use futures::FutureExt; +use tower::Service; + +use cuprate_dandelion_tower::traits::DiffuseRequest; +use cuprate_p2p::{BroadcastRequest, BroadcastSvc}; +use cuprate_p2p_core::ClearNet; + +use crate::txpool::dandelion::DandelionTx; + +/// The dandelion diffusion service. +pub struct DiffuseService { + pub clear_net_broadcast_service: BroadcastSvc, +} + +impl Service> for DiffuseService { + type Response = (); + type Error = tower::BoxError; + type Future = Ready>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.clear_net_broadcast_service + .poll_ready(cx) + .map_err(Into::into) + } + + fn call(&mut self, req: DiffuseRequest) -> Self::Future { + // TODO: the dandelion crate should pass along where we got the tx from. + let Ok(()) = self + .clear_net_broadcast_service + .call(BroadcastRequest::Transaction { + tx_bytes: req.0 .0, + direction: None, + received_from: None, + }) + .into_inner(); + + ready(Ok(())) + } +} diff --git a/binaries/cuprated/src/txpool/dandelion/stem_service.rs b/binaries/cuprated/src/txpool/dandelion/stem_service.rs new file mode 100644 index 0000000..2debfd4 --- /dev/null +++ b/binaries/cuprated/src/txpool/dandelion/stem_service.rs @@ -0,0 +1,105 @@ +use std::{ + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; + +use bytes::Bytes; +use futures::{future::BoxFuture, FutureExt, Stream}; +use tower::Service; + +use cuprate_dandelion_tower::{traits::StemRequest, OutboundPeer}; +use cuprate_p2p::{ClientDropGuard, NetworkInterface, PeerSetRequest, PeerSetResponse}; +use cuprate_p2p_core::{ + client::{Client, InternalPeerID}, + ClearNet, NetworkZone, PeerRequest, ProtocolRequest, +}; +use cuprate_wire::protocol::NewTransactions; + +use crate::{p2p::CrossNetworkInternalPeerId, txpool::dandelion::DandelionTx}; + +/// The dandelion outbound peer stream. +pub struct OutboundPeerStream { + clear_net: NetworkInterface, + state: OutboundPeerStreamState, +} + +impl OutboundPeerStream { + pub const fn new(clear_net: NetworkInterface) -> Self { + Self { + clear_net, + state: OutboundPeerStreamState::Standby, + } + } +} + +impl Stream for OutboundPeerStream { + type Item = Result< + OutboundPeer>, + tower::BoxError, + >; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + match &mut self.state { + OutboundPeerStreamState::Standby => { + let peer_set = self.clear_net.peer_set(); + let res = ready!(peer_set.poll_ready(cx)); + + self.state = OutboundPeerStreamState::AwaitingPeer( + peer_set.call(PeerSetRequest::StemPeer).boxed(), + ); + } + OutboundPeerStreamState::AwaitingPeer(fut) => { + let res = ready!(fut.poll_unpin(cx)); + + return Poll::Ready(Some(res.map(|res| { + let PeerSetResponse::StemPeer(stem_peer) = res else { + unreachable!() + }; + + match stem_peer { + Some(peer) => OutboundPeer::Peer( + CrossNetworkInternalPeerId::ClearNet(peer.info.id), + StemPeerService(peer), + ), + None => OutboundPeer::Exhausted, + } + }))); + } + } + } + } +} + +/// The state of the [`OutboundPeerStream`]. +enum OutboundPeerStreamState { + /// Standby state. + Standby, + /// Awaiting a response from the peer-set. + AwaitingPeer(BoxFuture<'static, Result, tower::BoxError>>), +} + +/// The stem service, used to send stem txs. +pub struct StemPeerService(ClientDropGuard); + +impl Service> for StemPeerService { + type Response = as Service>::Response; + type Error = tower::BoxError; + type Future = as Service>::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + fn call(&mut self, req: StemRequest) -> Self::Future { + self.0 + .call(PeerRequest::Protocol(ProtocolRequest::NewTransactions( + NewTransactions { + txs: vec![req.0 .0], + dandelionpp_fluff: false, + padding: Bytes::new(), + }, + ))) + } +} diff --git a/binaries/cuprated/src/txpool/dandelion/tx_store.rs b/binaries/cuprated/src/txpool/dandelion/tx_store.rs new file mode 100644 index 0000000..b890ffd --- /dev/null +++ b/binaries/cuprated/src/txpool/dandelion/tx_store.rs @@ -0,0 +1,74 @@ +use std::task::{Context, Poll}; + +use bytes::Bytes; +use futures::{future::BoxFuture, FutureExt}; +use tower::{Service, ServiceExt}; + +use cuprate_dandelion_tower::{ + traits::{TxStoreRequest, TxStoreResponse}, + State, +}; +use cuprate_database::RuntimeError; +use cuprate_txpool::service::{ + interface::{TxpoolReadRequest, TxpoolReadResponse, TxpoolWriteRequest}, + TxpoolReadHandle, TxpoolWriteHandle, +}; + +use super::{DandelionTx, TxId}; + +/// The dandelion tx-store service. +/// +/// This is just mapping the interface [`cuprate_dandelion_tower`] wants to what [`cuprate_txpool`] provides. +pub struct TxStoreService { + pub txpool_read_handle: TxpoolReadHandle, + pub txpool_write_handle: TxpoolWriteHandle, +} + +impl Service> for TxStoreService { + type Response = TxStoreResponse; + type Error = tower::BoxError; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: TxStoreRequest) -> Self::Future { + match req { + TxStoreRequest::Get(tx_id) => self + .txpool_read_handle + .clone() + .oneshot(TxpoolReadRequest::TxBlob(tx_id)) + .map(|res| match res { + Ok(TxpoolReadResponse::TxBlob { + tx_blob, + state_stem, + }) => { + let state = if state_stem { + State::Stem + } else { + State::Fluff + }; + + Ok(TxStoreResponse::Transaction(Some(( + DandelionTx(Bytes::from(tx_blob)), + state, + )))) + } + Err(RuntimeError::KeyNotFound) => Ok(TxStoreResponse::Transaction(None)), + Err(e) => Err(e.into()), + Ok(_) => unreachable!(), + }) + .boxed(), + TxStoreRequest::Promote(tx_id) => self + .txpool_write_handle + .clone() + .oneshot(TxpoolWriteRequest::Promote(tx_id)) + .map(|res| match res { + Ok(_) | Err(RuntimeError::KeyNotFound) => Ok(TxStoreResponse::Ok), + Err(e) => Err(e.into()), + }) + .boxed(), + } + } +} diff --git a/binaries/cuprated/src/txpool/incoming_tx.rs b/binaries/cuprated/src/txpool/incoming_tx.rs new file mode 100644 index 0000000..bf7684e --- /dev/null +++ b/binaries/cuprated/src/txpool/incoming_tx.rs @@ -0,0 +1,384 @@ +use std::{ + collections::HashSet, + sync::Arc, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures::{future::BoxFuture, FutureExt}; +use monero_serai::transaction::Transaction; +use tower::{Service, ServiceExt}; + +use cuprate_consensus::{ + transactions::new_tx_verification_data, BlockChainContextRequest, BlockChainContextResponse, + BlockChainContextService, ExtendedConsensusError, VerifyTxRequest, +}; +use cuprate_dandelion_tower::{ + pool::{DandelionPoolService, IncomingTxBuilder}, + State, TxState, +}; +use cuprate_helper::asynch::rayon_spawn_async; +use cuprate_p2p::NetworkInterface; +use cuprate_p2p_core::ClearNet; +use cuprate_txpool::{ + service::{ + interface::{ + TxpoolReadRequest, TxpoolReadResponse, TxpoolWriteRequest, TxpoolWriteResponse, + }, + TxpoolReadHandle, TxpoolWriteHandle, + }, + transaction_blob_hash, +}; +use cuprate_types::TransactionVerificationData; + +use crate::{ + blockchain::ConcreteTxVerifierService, + constants::PANIC_CRITICAL_SERVICE_ERROR, + p2p::CrossNetworkInternalPeerId, + signals::REORG_LOCK, + txpool::{ + dandelion, + txs_being_handled::{TxsBeingHandled, TxsBeingHandledLocally}, + }, +}; + +/// An error that can happen handling an incoming tx. +#[derive(Debug, thiserror::Error)] +pub enum IncomingTxError { + #[error("Error parsing tx: {0}")] + Parse(std::io::Error), + #[error(transparent)] + Consensus(ExtendedConsensusError), + #[error("Duplicate tx in message")] + DuplicateTransaction, +} + +/// Incoming transactions. +pub struct IncomingTxs { + /// The raw bytes of the transactions. + pub txs: Vec, + /// The routing state of the transactions. + pub state: TxState, +} + +/// The transaction type used for dandelion++. +#[derive(Clone)] +pub struct DandelionTx(pub Bytes); + +/// A transaction ID/hash. +pub(super) type TxId = [u8; 32]; + +/// The service than handles incoming transaction pool transactions. +/// +/// This service handles everything including verifying the tx, adding it to the pool and routing it to other nodes. +#[derive(Clone)] +pub struct IncomingTxHandler { + /// A store of txs currently being handled in incoming tx requests. + pub(super) txs_being_handled: TxsBeingHandled, + /// The blockchain context cache. + pub(super) blockchain_context_cache: BlockChainContextService, + /// The dandelion txpool manager. + pub(super) dandelion_pool_manager: + DandelionPoolService, + /// The transaction verifier service. + pub(super) tx_verifier_service: ConcreteTxVerifierService, + /// The txpool write handle. + pub(super) txpool_write_handle: TxpoolWriteHandle, + /// The txpool read handle. + pub(super) txpool_read_handle: TxpoolReadHandle, +} + +impl IncomingTxHandler { + /// Initialize the [`IncomingTxHandler`]. + #[expect(clippy::significant_drop_tightening)] + pub fn init( + clear_net: NetworkInterface, + txpool_write_handle: TxpoolWriteHandle, + txpool_read_handle: TxpoolReadHandle, + blockchain_context_cache: BlockChainContextService, + tx_verifier_service: ConcreteTxVerifierService, + ) -> Self { + let dandelion_router = dandelion::dandelion_router(clear_net); + + let dandelion_pool_manager = dandelion::start_dandelion_pool_manager( + dandelion_router, + txpool_read_handle.clone(), + txpool_write_handle.clone(), + ); + + Self { + txs_being_handled: TxsBeingHandled::new(), + blockchain_context_cache, + dandelion_pool_manager, + tx_verifier_service, + txpool_write_handle, + txpool_read_handle, + } + } +} + +impl Service for IncomingTxHandler { + type Response = (); + type Error = IncomingTxError; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: IncomingTxs) -> Self::Future { + handle_incoming_txs( + req, + self.txs_being_handled.clone(), + self.blockchain_context_cache.clone(), + self.tx_verifier_service.clone(), + self.txpool_write_handle.clone(), + self.txpool_read_handle.clone(), + self.dandelion_pool_manager.clone(), + ) + .boxed() + } +} + +/// Handles the incoming txs. +async fn handle_incoming_txs( + IncomingTxs { txs, state }: IncomingTxs, + txs_being_handled: TxsBeingHandled, + mut blockchain_context_cache: BlockChainContextService, + mut tx_verifier_service: ConcreteTxVerifierService, + mut txpool_write_handle: TxpoolWriteHandle, + mut txpool_read_handle: TxpoolReadHandle, + mut dandelion_pool_manager: DandelionPoolService, +) -> Result<(), IncomingTxError> { + let _reorg_guard = REORG_LOCK.read().await; + + let (txs, stem_pool_txs, txs_being_handled_guard) = + prepare_incoming_txs(txs, txs_being_handled, &mut txpool_read_handle).await?; + + let BlockChainContextResponse::Context(context) = blockchain_context_cache + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(BlockChainContextRequest::Context) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!() + }; + + let context = context.unchecked_blockchain_context(); + + tx_verifier_service + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(VerifyTxRequest::Prepped { + txs: txs.clone(), + current_chain_height: context.chain_height, + top_hash: context.top_hash, + time_for_time_lock: context.current_adjusted_timestamp_for_time_lock(), + hf: context.current_hf, + }) + .await + .map_err(IncomingTxError::Consensus)?; + + for tx in txs { + handle_valid_tx( + tx, + state.clone(), + &mut txpool_write_handle, + &mut dandelion_pool_manager, + ) + .await; + } + + // Re-relay any txs we got in the block that were already in our stem pool. + for stem_tx in stem_pool_txs { + rerelay_stem_tx( + &stem_tx, + state.clone(), + &mut txpool_read_handle, + &mut dandelion_pool_manager, + ) + .await; + } + + Ok(()) +} + +/// Prepares the incoming transactions for verification. +/// +/// This will filter out all transactions already in the pool or txs already being handled in another request. +/// +/// Returns in order: +/// - The [`TransactionVerificationData`] for all the txs we did not already have +/// - The Ids of the transactions in the incoming message that are in our stem-pool +/// - A [`TxsBeingHandledLocally`] guard that prevents verifying the same tx at the same time across 2 tasks. +async fn prepare_incoming_txs( + tx_blobs: Vec, + txs_being_handled: TxsBeingHandled, + txpool_read_handle: &mut TxpoolReadHandle, +) -> Result< + ( + Vec>, + Vec, + TxsBeingHandledLocally, + ), + IncomingTxError, +> { + let mut tx_blob_hashes = HashSet::new(); + let mut txs_being_handled_locally = txs_being_handled.local_tracker(); + + // Compute the blob hash for each tx and filter out the txs currently being handled by another incoming tx batch. + let txs = tx_blobs + .into_iter() + .filter_map(|tx_blob| { + let tx_blob_hash = transaction_blob_hash(&tx_blob); + + // If a duplicate is in here the incoming tx batch contained the same tx twice. + if !tx_blob_hashes.insert(tx_blob_hash) { + return Some(Err(IncomingTxError::DuplicateTransaction)); + } + + // If a duplicate is here it is being handled in another batch. + if !txs_being_handled_locally.try_add_tx(tx_blob_hash) { + return None; + } + + Some(Ok((tx_blob_hash, tx_blob))) + }) + .collect::, _>>()?; + + // Filter the txs already in the txpool out. + // This will leave the txs already in the pool in [`TxBeingHandledLocally`] but that shouldn't be an issue. + let TxpoolReadResponse::FilterKnownTxBlobHashes { + unknown_blob_hashes, + stem_pool_hashes, + } = txpool_read_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(TxpoolReadRequest::FilterKnownTxBlobHashes(tx_blob_hashes)) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + else { + unreachable!() + }; + + // Now prepare the txs for verification. + rayon_spawn_async(move || { + let txs = txs + .into_iter() + .filter_map(|(tx_blob_hash, tx_blob)| { + if unknown_blob_hashes.contains(&tx_blob_hash) { + Some(tx_blob) + } else { + None + } + }) + .map(|bytes| { + let tx = Transaction::read(&mut bytes.as_ref()).map_err(IncomingTxError::Parse)?; + + let tx = new_tx_verification_data(tx) + .map_err(|e| IncomingTxError::Consensus(e.into()))?; + + Ok(Arc::new(tx)) + }) + .collect::, IncomingTxError>>()?; + + Ok((txs, stem_pool_hashes, txs_being_handled_locally)) + }) + .await +} + +/// Handle a verified tx. +/// +/// This will add the tx to the txpool and route it to the network. +async fn handle_valid_tx( + tx: Arc, + state: TxState, + txpool_write_handle: &mut TxpoolWriteHandle, + dandelion_pool_manager: &mut DandelionPoolService< + DandelionTx, + TxId, + CrossNetworkInternalPeerId, + >, +) { + let incoming_tx = + IncomingTxBuilder::new(DandelionTx(Bytes::copy_from_slice(&tx.tx_blob)), tx.tx_hash); + + let TxpoolWriteResponse::AddTransaction(double_spend) = txpool_write_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(TxpoolWriteRequest::AddTransaction { + tx, + state_stem: state.is_stem_stage(), + }) + .await + .expect("TODO") + else { + unreachable!() + }; + + // TODO: track double spends to quickly ignore them from their blob hash. + if let Some(tx_hash) = double_spend { + return; + }; + + // TODO: There is a race condition possible if a tx and block come in at the same time: . + + let incoming_tx = incoming_tx + .with_routing_state(state) + .with_state_in_db(None) + .build() + .unwrap(); + + dandelion_pool_manager + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(incoming_tx) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); +} + +/// Re-relay a tx that was already in our stem pool. +async fn rerelay_stem_tx( + tx_hash: &TxId, + state: TxState, + txpool_read_handle: &mut TxpoolReadHandle, + dandelion_pool_manager: &mut DandelionPoolService< + DandelionTx, + TxId, + CrossNetworkInternalPeerId, + >, +) { + let Ok(TxpoolReadResponse::TxBlob { tx_blob, .. }) = txpool_read_handle + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(TxpoolReadRequest::TxBlob(*tx_hash)) + .await + else { + // The tx could have been dropped from the pool. + return; + }; + + let incoming_tx = + IncomingTxBuilder::new(DandelionTx(Bytes::copy_from_slice(&tx_blob)), *tx_hash); + + let incoming_tx = incoming_tx + .with_routing_state(state) + .with_state_in_db(Some(State::Stem)) + .build() + .unwrap(); + + dandelion_pool_manager + .ready() + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR) + .call(incoming_tx) + .await + .expect(PANIC_CRITICAL_SERVICE_ERROR); +} diff --git a/binaries/cuprated/src/txpool/txs_being_handled.rs b/binaries/cuprated/src/txpool/txs_being_handled.rs new file mode 100644 index 0000000..122b8ac --- /dev/null +++ b/binaries/cuprated/src/txpool/txs_being_handled.rs @@ -0,0 +1,53 @@ +use std::sync::Arc; + +use dashmap::DashSet; + +/// A set of txs currently being handled, shared between instances of the incoming tx handler. +#[derive(Clone)] +pub struct TxsBeingHandled(Arc>); + +impl TxsBeingHandled { + /// Create a new [`TxsBeingHandled`] + pub fn new() -> Self { + Self(Arc::new(DashSet::new())) + } + + /// Create a new [`TxsBeingHandledLocally`] that will keep track of txs being handled in a request. + pub fn local_tracker(&self) -> TxsBeingHandledLocally { + TxsBeingHandledLocally { + txs_being_handled: self.clone(), + txs: vec![], + } + } +} + +/// A tracker of txs being handled in a single request. This will add the txs to the global [`TxsBeingHandled`] +/// tracker as well. +/// +/// When this is dropped the txs will be removed from [`TxsBeingHandled`]. +pub struct TxsBeingHandledLocally { + txs_being_handled: TxsBeingHandled, + txs: Vec<[u8; 32]>, +} + +impl TxsBeingHandledLocally { + /// Try add a tx to the map from its [`transaction_blob_hash`](cuprate_txpool::transaction_blob_hash). + /// + /// Returns `true` if the tx was added and `false` if another task is already handling this tx. + pub fn try_add_tx(&mut self, tx_blob_hash: [u8; 32]) -> bool { + if !self.txs_being_handled.0.insert(tx_blob_hash) { + return false; + } + + self.txs.push(tx_blob_hash); + true + } +} + +impl Drop for TxsBeingHandledLocally { + fn drop(&mut self) { + for hash in &self.txs { + self.txs_being_handled.0.remove(hash); + } + } +} diff --git a/books/architecture/README.md b/books/architecture/README.md index e487843..88e86eb 100644 --- a/books/architecture/README.md +++ b/books/architecture/README.md @@ -1,4 +1,4 @@ -## Cuprate's architecture (implementation) book +## Cuprate's architecture book This book documents Cuprate's architecture and implementation. See: diff --git a/books/architecture/book.toml b/books/architecture/book.toml index 76724aa..996f7fe 100644 --- a/books/architecture/book.toml +++ b/books/architecture/book.toml @@ -1,19 +1,17 @@ [book] -authors = ["hinto-janai"] +authors = ["Cuprate Contributors"] language = "en" multilingual = false src = "src" title = "Cuprate Architecture" git-repository-url = "https://github.com/Cuprate/architecture-book" -# TODO: fix after importing real files. -# -# [preprocessor.last-changed] -# command = "mdbook-last-changed" -# renderer = ["html"] -# -# [output.html] -# default-theme = "ayu" -# preferred-dark-theme = "ayu" -# git-repository-url = "https://github.com/hinto-janai/cuprate-architecture" -# additional-css = ["last-changed.css"] +[preprocessor.last-changed] +command = "mdbook-last-changed" +renderer = ["html"] + +[output.html] +default-theme = "ayu" +preferred-dark-theme = "ayu" +git-repository-url = "https://github.com/Cuprate/architecture-book" +additional-css = ["last-changed.css"] diff --git a/books/architecture/last-changed.css b/books/architecture/last-changed.css new file mode 100644 index 0000000..a9abae5 --- /dev/null +++ b/books/architecture/last-changed.css @@ -0,0 +1,7 @@ +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid; + margin-top: 4%; + padding: 5px 0; +} \ No newline at end of file diff --git a/books/architecture/src/SUMMARY.md b/books/architecture/src/SUMMARY.md index 2b8615c..a99d099 100644 --- a/books/architecture/src/SUMMARY.md +++ b/books/architecture/src/SUMMARY.md @@ -1,3 +1,177 @@ # Summary -- [TODO](todo.md) +[Cuprate Architecture](cuprate-architecture.md) +[🟡 Foreword](foreword.md) + +--- + +- [🟠 Intro](intro/intro.md) + - [🟡 Who this book is for](intro/who-this-book-is-for.md) + - [🔴 Required knowledge](intro/required-knowledge.md) + - [🔴 How to use this book](intro/how-to-use-this-book.md) + +--- + +- [⚪️ Bird's eye view](birds-eye-view/intro.md) + - [⚪️ Map](birds-eye-view/map.md) + - [⚪️ Components](birds-eye-view/components.md) + +--- + +- [⚪️ Formats, protocols, types](formats-protocols-types/intro.md) + - [⚪️ monero_serai](formats-protocols-types/monero-serai.md) + - [⚪️ cuprate_types](formats-protocols-types/cuprate-types.md) + - [⚪️ cuprate_helper](formats-protocols-types/cuprate-helper.md) + - [⚪️ Epee](formats-protocols-types/epee.md) + - [⚪️ Levin](formats-protocols-types/levin.md) + +--- + +- [🟢 Storage](storage/intro.md) + - [🟢 Database abstraction](storage/db/intro.md) + - [🟢 Abstraction](storage/db/abstraction/intro.md) + - [🟢 Backend](storage/db/abstraction/backend.md) + - [🟢 ConcreteEnv](storage/db/abstraction/concrete_env.md) + - [🟢 Trait](storage/db/abstraction/trait.md) + - [🟢 Syncing](storage/db/syncing.md) + - [🟢 Resizing](storage/db/resizing.md) + - [🟢 (De)serialization](storage/db/serde.md) + - [🟢 Known issues and tradeoffs](storage/db/issues/intro.md) + - [🟢 Abstracting backends](storage/db/issues/traits.md) + - [🟢 Hot-swap](storage/db/issues/hot-swap.md) + - [🟢 Unaligned bytes](storage/db/issues/unaligned.md) + - [🟢 Endianness](storage/db/issues/endian.md) + - [🟢 Multimap](storage/db/issues/multimap.md) + - [🟢 Common behavior](storage/common/intro.md) + - [🟢 Types](storage/common/types.md) + - [🟢 `ops`](storage/common/ops.md) + - [🟢 `tower::Service`](storage/common/service/intro.md) + - [🟢 Initialization](storage/common/service/initialization.md) + - [🟢 Requests](storage/common/service/requests.md) + - [🟢 Responses](storage/common/service/responses.md) + - [🟢 Resizing](storage/common/service/resizing.md) + - [🟢 Thread model](storage/common/service/thread-model.md) + - [🟢 Shutdown](storage/common/service/shutdown.md) + - [🟢 Blockchain](storage/blockchain/intro.md) + - [🟢 Schema](storage/blockchain/schema/intro.md) + - [🟢 Tables](storage/blockchain/schema/tables.md) + - [🟢 Multimap tables](storage/blockchain/schema/multimap.md) + - [⚪️ Transaction pool](storage/txpool/intro.md) + - [⚪️ Pruning](storage/pruning/intro.md) + +--- + +- [🟢 RPC](rpc/intro.md) + - [🟡 JSON-RPC 2.0](rpc/json-rpc.md) + - [🟢 The types](rpc/types/intro.md) + - [🟢 Misc types](rpc/types/misc-types.md) + - [🟢 Base RPC types](rpc/types/base-types.md) + - [🟢 The type generator macro](rpc/types/macro.md) + - [🟢 Metadata](rpc/types/metadata.md) + - [🟡 (De)serialization](rpc/types/deserialization.md) + - [🟢 The interface](rpc/interface.md) + - [🔴 The handler](rpc/handler/intro.md) + - [🔴 The server](rpc/server/intro.md) + - [🟢 Differences with `monerod`](rpc/differences/intro.md) + - [🟢 JSON field ordering](rpc/differences/json-field-ordering.md) + - [🟢 JSON formatting](rpc/differences/json-formatting.md) + - [🟢 JSON strictness](rpc/differences/json-strictness.md) + - [🟡 JSON-RPC strictness](rpc/differences/json-rpc-strictness.md) + - [🟡 HTTP methods](rpc/differences/http-methods.md) + - [🟡 RPC payment](rpc/differences/rpc-payment.md) + - [🟢 Custom strings](rpc/differences/custom-strings.md) + - [🔴 Unsupported RPC calls](rpc/differences/unsupported-rpc-calls.md) + - [🔴 RPC calls with different behavior](rpc/differences/rpc-calls-with-different-behavior.md) + +--- + +- [⚪️ ZMQ](zmq/intro.md) + - [⚪️ TODO](zmq/todo.md) + +--- + +- [⚪️ Consensus](consensus/intro.md) + - [⚪️ Verifier](consensus/verifier.md) + - [⚪️ TODO](consensus/todo.md) + +--- + +- [⚪️ Networking](networking/intro.md) + - [⚪️ P2P](networking/p2p.md) + - [⚪️ Dandelion++](networking/dandelion.md) + - [⚪️ Proxy](networking/proxy.md) + - [⚪️ Tor](networking/tor.md) + - [⚪️ i2p](networking/i2p.md) + - [⚪️ IPv4/IPv6](networking/ipv4-ipv6.md) + +--- + +- [🔴 Instrumentation](instrumentation/intro.md) + - [⚪️ Logging](instrumentation/logging.md) + - [⚪️ Data collection](instrumentation/data-collection.md) + +--- + +- [⚪️ Binary](binary/intro.md) + - [⚪️ CLI](binary/cli.md) + - [⚪️ Config](binary/config.md) + - [⚪️ Logging](binary/logging.md) + +--- + +- [⚪️ Resources](resources/intro.md) + - [⚪️ File system](resources/fs/intro.md) + - [🟡 Index of PATHs](resources/fs/paths.md) + - [⚪️ Sockets](resources/sockets/index.md) + - [🔴 Index of ports](resources/sockets/ports.md) + - [⚪️ Memory](resources/memory.md) + - [🟡 Concurrency and parallelism](resources/cap/intro.md) + - [⚪️ Map](resources/cap/map.md) + - [⚪️ The RPC server](resources/cap/the-rpc-server.md) + - [⚪️ The database](resources/cap/the-database.md) + - [⚪️ The block downloader](resources/cap/the-block-downloader.md) + - [⚪️ The verifier](resources/cap/the-verifier.md) + - [⚪️ Thread exit](resources/cap/thread-exit.md) + - [🔴 Index of threads](resources/cap/threads.md) + +--- + +- [⚪️ External Monero libraries](external-monero-libraries/intro.md) + - [⚪️ Cryptonight](external-monero-libraries/cryptonight.md) + - [🔴 RandomX](external-monero-libraries/randomx.md) + - [🔴 monero_serai](external-monero-libraries/monero_serai.md) + +--- + +- [🟢 Benchmarking](benchmarking/intro.md) + - [🟢 Criterion](benchmarking/criterion/intro.md) + - [🟢 Creating](benchmarking/criterion/creating.md) + - [🟢 Running](benchmarking/criterion/running.md) + - [🟢 `cuprate-benchmark`](benchmarking/cuprate/intro.md) + - [🟢 Creating](benchmarking/cuprate/creating.md) + - [🟢 Running](benchmarking/cuprate/running.md) + +--- + +- [⚪️ Testing](testing/intro.md) + - [⚪️ Monero data](testing/monero-data.md) + - [⚪️ RPC client](testing/rpc-client.md) + - [⚪️ Spawning `monerod`](testing/spawning-monerod.md) +- [⚪️ Known issues and tradeoffs](known-issues-and-tradeoffs/intro.md) + - [⚪️ Networking](known-issues-and-tradeoffs/networking.md) + - [⚪️ RPC](known-issues-and-tradeoffs/rpc.md) + - [⚪️ Storage](known-issues-and-tradeoffs/storage.md) + +--- + +- [🟢 Monero oddities](oddities/intro.md) + - [🟡 Little-endian IPv4 addresses](oddities/le-ipv4.md) + +--- + +- [⚪️ Appendix](appendix/intro.md) + - [🟢 Crates](appendix/crates.md) + - [🔴 Contributing](appendix/contributing.md) + - [🔴 Build targets](appendix/build-targets.md) + - [🔴 Protocol book](appendix/protocol-book.md) + - [⚪️ User book](appendix/user-book.md) \ No newline at end of file diff --git a/books/architecture/src/appendix/build-targets.md b/books/architecture/src/appendix/build-targets.md new file mode 100644 index 0000000..495a3d6 --- /dev/null +++ b/books/architecture/src/appendix/build-targets.md @@ -0,0 +1,7 @@ +# Build targets +- x86 +- ARM64 +- Windows +- Linux +- macOS +- FreeBSD(?) diff --git a/books/architecture/src/appendix/contributing.md b/books/architecture/src/appendix/contributing.md new file mode 100644 index 0000000..675937a --- /dev/null +++ b/books/architecture/src/appendix/contributing.md @@ -0,0 +1,2 @@ +# Contributing + \ No newline at end of file diff --git a/books/architecture/src/appendix/crates.md b/books/architecture/src/appendix/crates.md new file mode 100644 index 0000000..5124180 --- /dev/null +++ b/books/architecture/src/appendix/crates.md @@ -0,0 +1,78 @@ +# Crates +This is an index of all of Cuprate's in-house crates it uses and maintains. + +They are categorized into groups. + +Crate documentation for each crate can be found by clicking the crate name or by visiting . Documentation can also be built manually by running this at the root of the `cuprate` repository: +```bash +cargo doc --package $CRATE +``` +For example, this will generate and open `cuprate-blockchain` documentation: +```bash +cargo doc --open --package cuprate-blockchain +``` + +## Consensus +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-consensus`](https://doc.cuprate.org/cuprate_consensus) | [`consensus/`](https://github.com/Cuprate/cuprate/tree/main/consensus) | TODO +| [`cuprate-consensus-context`](https://doc.cuprate.org/cuprate_consensus_context) | [`consensus/context/`](https://github.com/Cuprate/cuprate/tree/main/consensus/context) | TODO +| [`cuprate-consensus-rules`](https://doc.cuprate.org/cuprate_consensus_rules) | [`consensus/rules/`](https://github.com/Cuprate/cuprate/tree/main/consensus/rules) | TODO +| [`cuprate-fast-sync`](https://doc.cuprate.org/cuprate_fast_sync) | [`consensus/fast-sync/`](https://github.com/Cuprate/cuprate/tree/main/consensus/fast-sync) | Fast block synchronization + +## Networking +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-epee-encoding`](https://doc.cuprate.org/cuprate_epee_encoding) | [`net/epee-encoding/`](https://github.com/Cuprate/cuprate/tree/main/net/epee-encoding) | Epee (de)serialization +| [`cuprate-fixed-bytes`](https://doc.cuprate.org/cuprate_fixed_bytes) | [`net/fixed-bytes/`](https://github.com/Cuprate/cuprate/tree/main/net/fixed-bytes) | Fixed byte containers backed by `byte::Byte` +| [`cuprate-levin`](https://doc.cuprate.org/cuprate_levin) | [`net/levin/`](https://github.com/Cuprate/cuprate/tree/main/net/levin) | Levin bucket protocol implementation +| [`cuprate-wire`](https://doc.cuprate.org/cuprate_wire) | [`net/wire/`](https://github.com/Cuprate/cuprate/tree/main/net/wire) | TODO + +## P2P +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-address-book`](https://doc.cuprate.org/cuprate_address_book) | [`p2p/address-book/`](https://github.com/Cuprate/cuprate/tree/main/p2p/address-book) | TODO +| [`cuprate-async-buffer`](https://doc.cuprate.org/cuprate_async_buffer) | [`p2p/async-buffer/`](https://github.com/Cuprate/cuprate/tree/main/p2p/async-buffer) | A bounded SPSC, FIFO, asynchronous buffer that supports arbitrary weights for values +| [`cuprate-dandelion-tower`](https://doc.cuprate.org/cuprate_dandelion_tower) | [`p2p/dandelion-tower/`](https://github.com/Cuprate/cuprate/tree/main/p2p/dandelion-tower) | TODO +| [`cuprate-p2p`](https://doc.cuprate.org/cuprate_p2p) | [`p2p/p2p/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p) | TODO +| [`cuprate-p2p-bucket`](https://doc.cuprate.org/cuprate_p2p_bucket) | [`p2p/bucket/`](https://github.com/Cuprate/cuprate/tree/main/p2p/bucket) | A collection data structure discriminating its items into "buckets" of limited size. +| [`cuprate-p2p-core`](https://doc.cuprate.org/cuprate_p2p_core) | [`p2p/p2p-core/`](https://github.com/Cuprate/cuprate/tree/main/p2p/p2p-core) | TODO + +## Storage +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-blockchain`](https://doc.cuprate.org/cuprate_blockchain) | [`storage/blockchain/`](https://github.com/Cuprate/cuprate/tree/main/storage/blockchain) | Blockchain database built on-top of `cuprate-database` & `cuprate-database-service` +| [`cuprate-database`](https://doc.cuprate.org/cuprate_database) | [`storage/database/`](https://github.com/Cuprate/cuprate/tree/main/storage/database) | Pure database abstraction +| [`cuprate-database-service`](https://doc.cuprate.org/cuprate_database_service) | [`storage/database-service/`](https://github.com/Cuprate/cuprate/tree/main/storage/database-service) | `tower::Service` + thread-pool abstraction built on-top of `cuprate-database` +| [`cuprate-txpool`](https://doc.cuprate.org/cuprate_txpool) | [`storage/txpool/`](https://github.com/Cuprate/cuprate/tree/main/storage/txpool) | Transaction pool database built on-top of `cuprate-database` & `cuprate-database-service` + +## RPC +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-json-rpc`](https://doc.cuprate.org/cuprate_json_rpc) | [`rpc/json-rpc/`](https://github.com/Cuprate/cuprate/tree/main/rpc/json-rpc) | JSON-RPC 2.0 implementation +| [`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types) | [`rpc/types/`](https://github.com/Cuprate/cuprate/tree/main/rpc/types) | Monero RPC types and traits +| [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) | [`rpc/interface/`](https://github.com/Cuprate/cuprate/tree/main/rpc/interface) | RPC interface & routing +| [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler) | [`rpc/handler/`](https://github.com/Cuprate/cuprate/tree/main/rpc/handler) | RPC inner handlers + +## ZMQ +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-zmq-types`](https://doc.cuprate.org/cuprate_zmq_types) | [`zmq/types/`](https://github.com/Cuprate/cuprate/tree/main/zmq/types) | Message types for ZMQ Pub/Sub interface + +## 1-off crates +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-constants`](https://doc.cuprate.org/cuprate_constants) | [`constants/`](https://github.com/Cuprate/cuprate/tree/main/constants) | Shared `const/static` data across Cuprate +| [`cuprate-cryptonight`](https://doc.cuprate.org/cuprate_cryptonight) | [`cryptonight/`](https://github.com/Cuprate/cuprate/tree/main/cryptonight) | CryptoNight hash functions +| [`cuprate-pruning`](https://doc.cuprate.org/cuprate_pruning) | [`pruning/`](https://github.com/Cuprate/cuprate/tree/main/pruning) | Monero pruning logic/types +| [`cuprate-helper`](https://doc.cuprate.org/cuprate_helper) | [`helper/`](https://github.com/Cuprate/cuprate/tree/main/helper) | Kitchen-sink helper crate for Cuprate +| [`cuprate-test-utils`](https://doc.cuprate.org/cuprate_test_utils) | [`test-utils/`](https://github.com/Cuprate/cuprate/tree/main/test-utils) | Testing utilities for Cuprate +| [`cuprate-types`](https://doc.cuprate.org/cuprate_types) | [`types/`](https://github.com/Cuprate/cuprate/tree/main/types) | Shared types across Cuprate + +## Benchmarks +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-benchmark`](https://doc.cuprate.org/cuprate_benchmark) | [`benches/benchmark/bin/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin) | Cuprate benchmarking binary +| [`cuprate-benchmark-lib`](https://doc.cuprate.org/cuprate_benchmark_lib) | [`benches/benchmark/lib/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib) | Cuprate benchmarking library +| `cuprate-benchmark-*` | [`benches/benchmark/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/) | Benchmark for a Cuprate crate that uses `cuprate-benchmark` +| `cuprate-criterion-*` | [`benches/criterion/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) | Benchmark for a Cuprate crate that uses [Criterion](https://bheisler.github.io/criterion.rs/book) \ No newline at end of file diff --git a/books/architecture/src/appendix/intro.md b/books/architecture/src/appendix/intro.md new file mode 100644 index 0000000..fad5ae4 --- /dev/null +++ b/books/architecture/src/appendix/intro.md @@ -0,0 +1 @@ +# Appendix diff --git a/books/architecture/src/appendix/protocol-book.md b/books/architecture/src/appendix/protocol-book.md new file mode 100644 index 0000000..a855b73 --- /dev/null +++ b/books/architecture/src/appendix/protocol-book.md @@ -0,0 +1,2 @@ +# Protocol book + \ No newline at end of file diff --git a/books/architecture/src/appendix/user-book.md b/books/architecture/src/appendix/user-book.md new file mode 100644 index 0000000..0f12476 --- /dev/null +++ b/books/architecture/src/appendix/user-book.md @@ -0,0 +1 @@ +# ⚪️ User book diff --git a/books/architecture/src/benchmarking/criterion/creating.md b/books/architecture/src/benchmarking/criterion/creating.md new file mode 100644 index 0000000..0100904 --- /dev/null +++ b/books/architecture/src/benchmarking/criterion/creating.md @@ -0,0 +1,21 @@ +# Creating +Creating a new Criterion-based benchmarking crate for one of Cuprate's crates is relatively simple, +although, it requires knowledge of how to use Criterion first: + +1. Read the `Getting Started` section of +2. Copy [`benches/criterion/example`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/example) as base +3. Get started + +## Naming +New benchmark crates using Criterion should: +- Be in [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/) +- Be in the `cuprate-criterion-$CRATE_NAME` format + +For a real example, see: +[`cuprate-criterion-json-rpc`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-json-rpc). + +## Workspace +Finally, make sure to add the benchmark crate to the workspace +[`Cargo.toml`](https://github.com/Cuprate/cuprate/blob/main/Cargo.toml) file. + +Your benchmark is now ready to be ran. \ No newline at end of file diff --git a/books/architecture/src/benchmarking/criterion/intro.md b/books/architecture/src/benchmarking/criterion/intro.md new file mode 100644 index 0000000..b7a79b2 --- /dev/null +++ b/books/architecture/src/benchmarking/criterion/intro.md @@ -0,0 +1,4 @@ +# Criterion +Each sub-directory in [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) is a crate that uses [Criterion](https://bheisler.github.io/criterion.rs/book) for timing single functions and/or groups of functions. + +They are generally be small in scope. \ No newline at end of file diff --git a/books/architecture/src/benchmarking/criterion/running.md b/books/architecture/src/benchmarking/criterion/running.md new file mode 100644 index 0000000..14067f6 --- /dev/null +++ b/books/architecture/src/benchmarking/criterion/running.md @@ -0,0 +1,15 @@ +# Running +To run all Criterion benchmarks, run this from the repository root: +```bash +cargo bench +``` + +To run specific package(s), use: +```bash +cargo bench --package $CRITERION_BENCHMARK_CRATE_NAME +``` + +For example: +```bash +cargo bench --package cuprate-criterion-json-rpc +``` \ No newline at end of file diff --git a/books/architecture/src/benchmarking/cuprate/creating.md b/books/architecture/src/benchmarking/cuprate/creating.md new file mode 100644 index 0000000..76eab78 --- /dev/null +++ b/books/architecture/src/benchmarking/cuprate/creating.md @@ -0,0 +1,57 @@ +# Creating +New benchmarks are plugged into `cuprate-benchmark` by: +1. Implementing `cuprate_benchmark_lib::Benchmark` +1. Registering the benchmark in the `cuprate_benchmark` binary + +See [`benches/benchmark/example`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/example) +for an example. + +## Creating the benchmark crate +Before plugging into `cuprate-benchmark`, your actual benchmark crate must be created: + +1. Create a new crate inside `benches/benchmark` (consider copying `benches/benchmark/example` as a base) +1. Pull in `cuprate_benchmark_lib` as a dependency +1. Create a benchmark +1. Implement `cuprate_benchmark_lib::Benchmark` + +New benchmark crates using `cuprate-database` should: +- Be in [`benches/benchmark/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/) +- Be in the `cuprate-benchmark-$CRATE_NAME` format + +For a real example, see: +[`cuprate-benchmark-database`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/cuprate-database). + +## `cuprate_benchmark_lib::Benchmark` +This is the trait that standardizes all benchmarks ran under `cuprate-benchmark`. + +It must be implemented by your benchmarking crate. + +See `cuprate-benchmark-lib` crate documentation for a user-guide: . + +## Adding a feature to `cuprate-benchmark` +After your benchmark's behavior is defined, it must be registered +in the binary that is actually ran: `cuprate-benchmark`. + +If your benchmark is new, add a new crate feature to [`cuprate-benchmark`'s Cargo.toml file](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin/Cargo.toml) with an optional dependency to your benchmarking crate. + +Please remember to edit the feature table in the +[`README.md`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin/README.md) as well! + +## Adding to `cuprate-benchmark`'s `main()` +After adding your crate's feature, add a conditional line that run the benchmark +if the feature is enabled to the `main()` function: + +For example, if your crate's name is `egg`: +```rust +cfg_if! { + if #[cfg(feature = "egg")] { + run::run_benchmark::(&mut timings); + } +} +``` + +## Workspace +Finally, make sure to add the benchmark crate to the workspace +[`Cargo.toml`](https://github.com/Cuprate/cuprate/blob/main/Cargo.toml) file. + +Your benchmark is now ready to be ran. \ No newline at end of file diff --git a/books/architecture/src/benchmarking/cuprate/intro.md b/books/architecture/src/benchmarking/cuprate/intro.md new file mode 100644 index 0000000..25efb46 --- /dev/null +++ b/books/architecture/src/benchmarking/cuprate/intro.md @@ -0,0 +1,37 @@ +# cuprate-benchmark +Cuprate has 2 custom crates for general benchmarking: +- `cuprate-benchmark`; the actual binary crate ran +- `cuprate-benchmark-lib`; the library that other crates hook into + +The abstract purpose of `cuprate-benchmark` is very simple: +1. Set-up the benchmark +1. Start timer +1. Run benchmark +1. Output data + +`cuprate-benchmark` runs the benchmarks found in [`benches/benchmark/cuprate-*`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark). + +`cuprate-benchmark-lib` defines the `Benchmark` trait that all +benchmark crates implement to "plug-in" to the benchmarking harness. + +## Diagram +A diagram displaying the relation between `cuprate-benchmark` and related crates. + +``` + ┌─────────────────────┐ + │ cuprate_benchmark │ + │ (actual binary ran) │ + └──────────┬──────────┘ + ┌──────────────────┴───────────────────┐ + │ cuprate_benchmark_lib │ + │ ┌───────────────────────────────────┐│ + │ │ trait Benchmark ││ + │ └───────────────────────────────────┘│ + └──────────────────┬───────────────────┘ +┌───────────────────────────┐ │ ┌───────────────────────────┐ +│ cuprate_benchmark_example ├──┼───┤ cuprate_benchmark_* │ +└───────────────────────────┘ │ └───────────────────────────┘ +┌───────────────────────────┐ │ ┌───────────────────────────┐ +│ cuprate_benchmark_* ├──┴───┤ cuprate_benchmark_* │ +└───────────────────────────┘ └───────────────────────────┘ +``` \ No newline at end of file diff --git a/books/architecture/src/benchmarking/cuprate/running.md b/books/architecture/src/benchmarking/cuprate/running.md new file mode 100644 index 0000000..b776163 --- /dev/null +++ b/books/architecture/src/benchmarking/cuprate/running.md @@ -0,0 +1,16 @@ +# Running +`cuprate-benchmark` benchmarks are ran with this command: +```bash +cargo run --release --package cuprate-benchmark --features $BENCHMARK_CRATE_FEATURE +``` + +For example, to run the example benchmark: +```bash +cargo run --release --package cuprate-benchmark --features example +``` + +Use the `all` feature to run all benchmarks: +```bash +# Run all benchmarks +cargo run --release --package cuprate-benchmark --features all +``` diff --git a/books/architecture/src/benchmarking/intro.md b/books/architecture/src/benchmarking/intro.md new file mode 100644 index 0000000..e6ab6b1 --- /dev/null +++ b/books/architecture/src/benchmarking/intro.md @@ -0,0 +1,22 @@ +# Benchmarking +Cuprate has 2 types of benchmarks: +- [Criterion](https://bheisler.github.io/criterion.rs/book/user_guide/advanced_configuration.html) benchmarks +- `cuprate-benchmark` benchmarks + +Criterion is used for micro benchmarks; they time single functions, groups of functions, and generally are small in scope. + +`cuprate-benchmark` and [`cuprate-benchmark-lib`](https://doc.cuprate.org/cuprate_benchmark_lib) are custom in-house crates Cuprate uses for macro benchmarks; these test sub-systems, sections of a sub-system, or otherwise larger or more complicated code that isn't well-suited for micro benchmarks. + +## File layout and purpose +All benchmarking related files are in the [`benches/`](https://github.com/Cuprate/cuprate/tree/main/benches) folder. + +This directory is organized like such: + +| Directory | Purpose | +|-------------------------------|---------| +| [`benches/criterion/`](https://github.com/Cuprate/cuprate/tree/main/benches/criterion) | Criterion (micro) benchmarks +| `benches/criterion/cuprate-*` | Criterion benchmarks for the crate with the same name +| [`benches/benchmark/`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark) | Cuprate's custom benchmarking files +| [`benches/benchmark/bin`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/bin) | The `cuprate-benchmark` crate; the actual binary run that links all benchmarks +| [`benches/benchmark/lib`](https://github.com/Cuprate/cuprate/tree/main/benches/benchmark/lib) | The `cuprate-benchmark-lib` crate; the benchmarking framework all benchmarks plug into +| `benches/benchmark/cuprate-*` | `cuprate-benchmark` benchmarks for the crate with the same name diff --git a/books/architecture/src/binary/cli.md b/books/architecture/src/binary/cli.md new file mode 100644 index 0000000..1c515f4 --- /dev/null +++ b/books/architecture/src/binary/cli.md @@ -0,0 +1 @@ +# ⚪️ CLI diff --git a/books/architecture/src/binary/config.md b/books/architecture/src/binary/config.md new file mode 100644 index 0000000..c9582d0 --- /dev/null +++ b/books/architecture/src/binary/config.md @@ -0,0 +1 @@ +# ⚪️ Config diff --git a/books/architecture/src/binary/intro.md b/books/architecture/src/binary/intro.md new file mode 100644 index 0000000..dea12fa --- /dev/null +++ b/books/architecture/src/binary/intro.md @@ -0,0 +1 @@ +# ⚪️ Binary diff --git a/books/architecture/src/binary/logging.md b/books/architecture/src/binary/logging.md new file mode 100644 index 0000000..c7c88a3 --- /dev/null +++ b/books/architecture/src/binary/logging.md @@ -0,0 +1 @@ +# ⚪️ Logging diff --git a/books/architecture/src/birds-eye-view/components.md b/books/architecture/src/birds-eye-view/components.md new file mode 100644 index 0000000..19a17e2 --- /dev/null +++ b/books/architecture/src/birds-eye-view/components.md @@ -0,0 +1 @@ +# ⚪️ Components diff --git a/books/architecture/src/birds-eye-view/intro.md b/books/architecture/src/birds-eye-view/intro.md new file mode 100644 index 0000000..5ee2eb3 --- /dev/null +++ b/books/architecture/src/birds-eye-view/intro.md @@ -0,0 +1 @@ +# ⚪️ Bird's eye view diff --git a/books/architecture/src/birds-eye-view/map.md b/books/architecture/src/birds-eye-view/map.md new file mode 100644 index 0000000..1bde994 --- /dev/null +++ b/books/architecture/src/birds-eye-view/map.md @@ -0,0 +1 @@ +# ⚪️ Map diff --git a/books/architecture/src/consensus/intro.md b/books/architecture/src/consensus/intro.md new file mode 100644 index 0000000..32013b6 --- /dev/null +++ b/books/architecture/src/consensus/intro.md @@ -0,0 +1 @@ +# ⚪️ Consensus diff --git a/books/architecture/src/consensus/todo.md b/books/architecture/src/consensus/todo.md new file mode 100644 index 0000000..460d445 --- /dev/null +++ b/books/architecture/src/consensus/todo.md @@ -0,0 +1 @@ +# ⚪️ TODO diff --git a/books/architecture/src/consensus/verifier.md b/books/architecture/src/consensus/verifier.md new file mode 100644 index 0000000..128a3b0 --- /dev/null +++ b/books/architecture/src/consensus/verifier.md @@ -0,0 +1 @@ +# ⚪️ Verifier diff --git a/books/architecture/src/cuprate-architecture.md b/books/architecture/src/cuprate-architecture.md new file mode 100644 index 0000000..3c6c073 --- /dev/null +++ b/books/architecture/src/cuprate-architecture.md @@ -0,0 +1,22 @@ +# Cuprate Architecture +WIP + +[Cuprate](https://github.com/Cuprate/cuprate)'s architecture book. + +Sections are notated with colors indicating how complete they are: + +| Color | Meaning | +|-------|---------| +| ⚪️ | Empty +| 🔴 | Severely lacking information +| 🟠 | Lacking some information +| 🟡 | Almost ready +| 🟢 | OK + +--- + +Continue to the next chapter by clicking the right `>` button, or by selecting it on the left side. + +All chapters are viewable by clicking the top-left `☰` button. + +The entire book can searched by clicking the top-left 🔍 button. \ No newline at end of file diff --git a/books/architecture/src/external-monero-libraries/cryptonight.md b/books/architecture/src/external-monero-libraries/cryptonight.md new file mode 100644 index 0000000..80647b0 --- /dev/null +++ b/books/architecture/src/external-monero-libraries/cryptonight.md @@ -0,0 +1 @@ +# ⚪️ Cryptonight diff --git a/books/architecture/src/external-monero-libraries/intro.md b/books/architecture/src/external-monero-libraries/intro.md new file mode 100644 index 0000000..440a344 --- /dev/null +++ b/books/architecture/src/external-monero-libraries/intro.md @@ -0,0 +1 @@ +# ⚪️ External Monero libraries diff --git a/books/architecture/src/external-monero-libraries/monero_serai.md b/books/architecture/src/external-monero-libraries/monero_serai.md new file mode 100644 index 0000000..f1567b1 --- /dev/null +++ b/books/architecture/src/external-monero-libraries/monero_serai.md @@ -0,0 +1,2 @@ +# monero_serai + diff --git a/books/architecture/src/external-monero-libraries/randomx.md b/books/architecture/src/external-monero-libraries/randomx.md new file mode 100644 index 0000000..7705151 --- /dev/null +++ b/books/architecture/src/external-monero-libraries/randomx.md @@ -0,0 +1,2 @@ +# RandomX + \ No newline at end of file diff --git a/books/architecture/src/foreword.md b/books/architecture/src/foreword.md new file mode 100644 index 0000000..c85f18b --- /dev/null +++ b/books/architecture/src/foreword.md @@ -0,0 +1,36 @@ +# Foreword +Monero[^1] is a large software project, coming in at 329k lines of C++, C, headers, and make files.[^2] It is directly responsible for 2.6 billion dollars worth of value.[^3] It has had over 400 contributors, more if counting unnamed contributions.[^4] It has over 10,000 node operators and a large active userbase.[^5] + +The project wasn't always this big, but somewhere in the midst of contributors coming and going, various features being added, bugs being fixed, and celebrated cryptography being implemented - there was an aspect that was lost by the project that it could not easily gain again: **maintainability**. + +Within large and complicated software projects, there is an important transfer of knowledge that must occur for long-term survival. Much like an organism that must eventually pass the torch onto the next generation, projects must do the same for future contributors. + +However, newcomers often lack experience, past contributors might not be around, and current maintainers may be too busy. For whatever reason, this transfer of knowledge is not always smooth. + +There is a solution to this problem: **documentation**. + +The activity of writing the what, where, why, and how of the solutions to technical problems can be done in an author's lonesome. + +The activity of reading these ideas can be done by future readers at any time without permission. + +These readers may be new prospective contributors, it may be the current maintainers, it may be researchers, it may be users of various scale. Whoever it may be, documentation acts as the link between the past and present; a bottle of wisdom thrown into the river of time for future participants to open. + +This book is the manifestation of this will, for Cuprate[^6], an alternative Monero node. It documents Cuprate's implementation from head-to-toe such that in the case of a contributor's untimely disappearance, the project can continue. + +People come and go, documentation is forever. + +— hinto-janai + +--- + +[^1]: [`monero-project/monero`](https://github.com/monero-project/monero) + +[^2]: `git ls-files | grep "\.cpp$\|\.h$\|\.c$\|CMake" | xargs cat | wc -l` on [`cc73fe7`](https://github.com/monero-project/monero/tree/cc73fe71162d564ffda8e549b79a350bca53c454) + +[^3]: 2024-05-24: $143.55 USD * 18,151,608 XMR = $2,605,663,258 + +[^4]: `git log --all --pretty="%an" | sort -u | wc -l` on [`cc73fe7`](https://github.com/monero-project/monero/tree/cc73fe71162d564ffda8e549b79a350bca53c454) + +[^5]: + +[^6]: \ No newline at end of file diff --git a/books/architecture/src/formats-protocols-types/cuprate-helper.md b/books/architecture/src/formats-protocols-types/cuprate-helper.md new file mode 100644 index 0000000..6227829 --- /dev/null +++ b/books/architecture/src/formats-protocols-types/cuprate-helper.md @@ -0,0 +1 @@ +# ⚪️ cuprate_helper diff --git a/books/architecture/src/formats-protocols-types/cuprate-types.md b/books/architecture/src/formats-protocols-types/cuprate-types.md new file mode 100644 index 0000000..1069ce5 --- /dev/null +++ b/books/architecture/src/formats-protocols-types/cuprate-types.md @@ -0,0 +1 @@ +# ⚪️ cuprate_types diff --git a/books/architecture/src/formats-protocols-types/epee.md b/books/architecture/src/formats-protocols-types/epee.md new file mode 100644 index 0000000..4c0b17e --- /dev/null +++ b/books/architecture/src/formats-protocols-types/epee.md @@ -0,0 +1 @@ +# ⚪️ Epee diff --git a/books/architecture/src/formats-protocols-types/intro.md b/books/architecture/src/formats-protocols-types/intro.md new file mode 100644 index 0000000..77052fd --- /dev/null +++ b/books/architecture/src/formats-protocols-types/intro.md @@ -0,0 +1 @@ +# ⚪️ Formats, protocols, types diff --git a/books/architecture/src/formats-protocols-types/levin.md b/books/architecture/src/formats-protocols-types/levin.md new file mode 100644 index 0000000..72a88cc --- /dev/null +++ b/books/architecture/src/formats-protocols-types/levin.md @@ -0,0 +1 @@ +# ⚪️ Levin diff --git a/books/architecture/src/formats-protocols-types/monero-serai.md b/books/architecture/src/formats-protocols-types/monero-serai.md new file mode 100644 index 0000000..139af8e --- /dev/null +++ b/books/architecture/src/formats-protocols-types/monero-serai.md @@ -0,0 +1 @@ +# ⚪️ monero_serai diff --git a/books/architecture/src/instrumentation/data-collection.md b/books/architecture/src/instrumentation/data-collection.md new file mode 100644 index 0000000..7ea3d9f --- /dev/null +++ b/books/architecture/src/instrumentation/data-collection.md @@ -0,0 +1 @@ +# ⚪️ Data collection diff --git a/books/architecture/src/instrumentation/intro.md b/books/architecture/src/instrumentation/intro.md new file mode 100644 index 0000000..33640dd --- /dev/null +++ b/books/architecture/src/instrumentation/intro.md @@ -0,0 +1,2 @@ +# Instrumentation +Cuprate is built with [instrumentation](https://en.wikipedia.org/wiki/Instrumentation) in mind. \ No newline at end of file diff --git a/books/architecture/src/instrumentation/logging.md b/books/architecture/src/instrumentation/logging.md new file mode 100644 index 0000000..c7c88a3 --- /dev/null +++ b/books/architecture/src/instrumentation/logging.md @@ -0,0 +1 @@ +# ⚪️ Logging diff --git a/books/architecture/src/intro/how-to-use-this-book.md b/books/architecture/src/intro/how-to-use-this-book.md new file mode 100644 index 0000000..7664e04 --- /dev/null +++ b/books/architecture/src/intro/how-to-use-this-book.md @@ -0,0 +1,5 @@ +# How to use this book + +## Maintainers +## Contributors +## Researchers \ No newline at end of file diff --git a/books/architecture/src/intro/intro.md b/books/architecture/src/intro/intro.md new file mode 100644 index 0000000..db2603c --- /dev/null +++ b/books/architecture/src/intro/intro.md @@ -0,0 +1,15 @@ +# Intro +[Cuprate](https://github.com/Cuprate/cuprate) is an alternative [Monero](https://getmonero.org) node implementation. + +This book describes Cuprate's architecture, ranging from small things like database pruning to larger meta-components like the networking stack. + +A brief overview of some aspects covered within this book: +- Component designs +- Implementation details +- File location and purpose +- Design decisions and tradeoffs +- Things in relation to `monerod` +- Dependency usage + +## Source code +The source files for this book can be found on at: . \ No newline at end of file diff --git a/books/architecture/src/intro/required-knowledge.md b/books/architecture/src/intro/required-knowledge.md new file mode 100644 index 0000000..3262f05 --- /dev/null +++ b/books/architecture/src/intro/required-knowledge.md @@ -0,0 +1,28 @@ +# Required knowledge + +## General +- Rust +- Monero +- System design + +## Components +### Storage +- Embedded databases +- LMDB +- redb + +### RPC +- `axum` +- `tower` +- `async` +- JSON-RPC 2.0 +- Epee + +### Networking +- `tower` +- `tokio` +- `async` +- Levin + +### Instrumentation +- `tracing` diff --git a/books/architecture/src/intro/who-this-book-is-for.md b/books/architecture/src/intro/who-this-book-is-for.md new file mode 100644 index 0000000..4b5be2b --- /dev/null +++ b/books/architecture/src/intro/who-this-book-is-for.md @@ -0,0 +1,31 @@ +# Who this book is for + +## Maintainers +As mentioned in [`Foreword`](../foreword.md), the group of people that benefit from this book's value the most by far are the current and future Cuprate maintainers. + +Cuprate's system design is documented in this book such that if you were ever to build it again from scratch, you would have an excellent guide on how to do such, and also where improvements could be made. + +Practically, what that means for maintainers is that it acts as _the_ reference. During maintenance, it is quite valuable to have a book that contains condensed knowledge on the behavior of components, or how certain code works, or why it was built a certain way. + +## Contributors +Contributors also have access to the inner-workings of Cuprate via this book, which helps when making larger contributions. + +Design decisions and implementation details notated in this book helps answer questions such as: +- Why is it done this way? +- Why can it _not_ be done this way? +- Were other methods attempted? + +Cuprate's testing and benchmarking suites, unknown to new contributors, are also documented within this book. + +## Researchers +This book contains the why, where, and how of the _implementation_ of formal research. + +Although it is an informal specification, this book still acts as a more accessible overview of Cuprate compared to examining the codebase itself. + +## Operators & users +This book is not a practical guide for using Cuprate itself. + +For configuration, data collection (also important for researchers), and other practical usage, see [Cuprate's user book](https://user.cuprate.org). + +## Observers +Anyone curious enough is free to learn the inner-workings of Cuprate via this book, and maybe even contribute someday. \ No newline at end of file diff --git a/books/architecture/src/known-issues-and-tradeoffs/intro.md b/books/architecture/src/known-issues-and-tradeoffs/intro.md new file mode 100644 index 0000000..20ab7b5 --- /dev/null +++ b/books/architecture/src/known-issues-and-tradeoffs/intro.md @@ -0,0 +1 @@ +# ⚪️ Known issues and tradeoffs diff --git a/books/architecture/src/known-issues-and-tradeoffs/networking.md b/books/architecture/src/known-issues-and-tradeoffs/networking.md new file mode 100644 index 0000000..20487cb --- /dev/null +++ b/books/architecture/src/known-issues-and-tradeoffs/networking.md @@ -0,0 +1 @@ +# ⚪️ Networking diff --git a/books/architecture/src/known-issues-and-tradeoffs/rpc.md b/books/architecture/src/known-issues-and-tradeoffs/rpc.md new file mode 100644 index 0000000..3337f37 --- /dev/null +++ b/books/architecture/src/known-issues-and-tradeoffs/rpc.md @@ -0,0 +1 @@ +# ⚪️ RPC diff --git a/books/architecture/src/known-issues-and-tradeoffs/storage.md b/books/architecture/src/known-issues-and-tradeoffs/storage.md new file mode 100644 index 0000000..214cf15 --- /dev/null +++ b/books/architecture/src/known-issues-and-tradeoffs/storage.md @@ -0,0 +1 @@ +# ⚪️ Storage diff --git a/books/architecture/src/networking/dandelion.md b/books/architecture/src/networking/dandelion.md new file mode 100644 index 0000000..30916b7 --- /dev/null +++ b/books/architecture/src/networking/dandelion.md @@ -0,0 +1 @@ +# ⚪️ Dandelion++ diff --git a/books/architecture/src/networking/i2p.md b/books/architecture/src/networking/i2p.md new file mode 100644 index 0000000..986ab2a --- /dev/null +++ b/books/architecture/src/networking/i2p.md @@ -0,0 +1 @@ +# ⚪️ i2p diff --git a/books/architecture/src/networking/intro.md b/books/architecture/src/networking/intro.md new file mode 100644 index 0000000..20487cb --- /dev/null +++ b/books/architecture/src/networking/intro.md @@ -0,0 +1 @@ +# ⚪️ Networking diff --git a/books/architecture/src/networking/ipv4-ipv6.md b/books/architecture/src/networking/ipv4-ipv6.md new file mode 100644 index 0000000..07339b4 --- /dev/null +++ b/books/architecture/src/networking/ipv4-ipv6.md @@ -0,0 +1 @@ +# ⚪️ IPv4/IPv6 diff --git a/books/architecture/src/networking/p2p.md b/books/architecture/src/networking/p2p.md new file mode 100644 index 0000000..11b2015 --- /dev/null +++ b/books/architecture/src/networking/p2p.md @@ -0,0 +1 @@ +# ⚪️ P2P diff --git a/books/architecture/src/networking/proxy.md b/books/architecture/src/networking/proxy.md new file mode 100644 index 0000000..bd9e5f7 --- /dev/null +++ b/books/architecture/src/networking/proxy.md @@ -0,0 +1 @@ +# ⚪️ Proxy diff --git a/books/architecture/src/networking/tor.md b/books/architecture/src/networking/tor.md new file mode 100644 index 0000000..cc0a809 --- /dev/null +++ b/books/architecture/src/networking/tor.md @@ -0,0 +1 @@ +# ⚪️ Tor diff --git a/books/architecture/src/oddities/intro.md b/books/architecture/src/oddities/intro.md new file mode 100644 index 0000000..c0275b3 --- /dev/null +++ b/books/architecture/src/oddities/intro.md @@ -0,0 +1,37 @@ +# Monero oddities +This section is a list of any peculiar, interesting, +or non-standard behavior that Monero has that is not +planned on being changed or deprecated. + +This section exists to hold all the small yet noteworthy knowledge in one place, +instead of in any single contributor's mind. + +These are usually behaviors stemming from implementation rather than protocol/cryptography. + +## Formatting +This is the markdown formatting for each entry in this section. + +If applicable, consider using this formatting when adding to this section. + +```md +# + +## What +A detailed description of the behavior. + +## Expected +The norm or standard behavior that is usually expected. + +## Why +The reasoning behind why this behavior exists and/or +any links to more detailed discussion on the behavior. + +## Affects +A (potentially non-exhaustive) list of places that this behavior can/does affect. + +## Example +An example link or section of code where the behavior occurs. + +## Source +A link to original `monerod` code that defines the behavior. +``` \ No newline at end of file diff --git a/books/architecture/src/oddities/le-ipv4.md b/books/architecture/src/oddities/le-ipv4.md new file mode 100644 index 0000000..f64c1d7 --- /dev/null +++ b/books/architecture/src/oddities/le-ipv4.md @@ -0,0 +1,24 @@ +# Little-endian IPv4 addresses + +## What +Monero encodes IPv4 addresses in [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order. + +## Expected +In general, [networking-related protocols/code use _networking order_ (big-endian)](https://en.wikipedia.org/wiki/Endianness#Networking). + +## Why +TODO + +- +- + +## Affects +Any representation and (de)serialization of IPv4 addresses must keep little +endian in-mind, e.g. the P2P wire format or `int` encoded IPv4 addresses in RPC. + +For example, [the `ip` field in `set_bans`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#set_bans). + +For Cuprate, this means Rust's [`Ipv4Addr::from_bits/from`](https://doc.rust-lang.org/1.82.0/src/core/net/ip_addr.rs.html#1182) cannot be used in these cases as [it assumes big-endian encoding](https://doc.rust-lang.org/1.82.0/src/core/net/ip_addr.rs.html#540). + +## Source +- diff --git a/books/architecture/src/resources/cap/intro.md b/books/architecture/src/resources/cap/intro.md new file mode 100644 index 0000000..2cca180 --- /dev/null +++ b/books/architecture/src/resources/cap/intro.md @@ -0,0 +1,32 @@ +# Concurrency and parallelism +It is incumbent upon software like Cuprate to take advantage of today's highly parallel hardware as much as practically possible. + +With that said, programs must setup guardrails when operating in a concurrent and parallel manner, [for correctness and safety](https://en.wikipedia.org/wiki/Concurrency_(computer_science)). + +There are "synchronization primitives" that help with this, common ones being: +- [Locks](https://en.wikipedia.org/wiki/Lock_(computer_science)) +- [Channels](https://en.wikipedia.org/wiki/Channel_(programming)) +- [Atomics](https://en.wikipedia.org/wiki/Linearizability#Primitive_atomic_instructions) + +These tools are relatively easy to use in isolation, but trickier to do so when considering the entire system. It is not uncommon for _the_ bottleneck to be the [poor orchastration](https://en.wikipedia.org/wiki/Starvation_(computer_science)) of these primitives. + +## Analogy +A common analogy for a parallel system is an intersection. + +Like a parallel computer system, an intersection contains: +1. **Parallelism:** multiple individual units that want to move around (cars, pedestrians, etc) +1. **Synchronization primitives:** traffic lights, car lights, walk signals + +In theory, the amount of "work" the units can do is only limited by the speed of the units themselves, but in practice, the slow cascading reaction speeds between all units, the frequent hiccups that can occur, and the synchronization primitives themselves become bottlenecks far before the maximum speed of any unit is reached. + +A car that hogs the middle of the intersection on the wrong light is akin to a system thread holding onto a lock longer than it should be - it degrades total system output. + +Unlike humans however, computer systems at least have the potential to move at lightning speeds, but only if the above synchronization primitives are used correctly. + +## Goal +To aid the long-term maintenance of highly concurrent and parallel code, this section documents: +1. All system threads spawned and maintained +1. All major sections where synchronization primitives are used +1. The asynchronous behavior of some components + +and how these compose together efficiently in Cuprate. \ No newline at end of file diff --git a/books/architecture/src/resources/cap/map.md b/books/architecture/src/resources/cap/map.md new file mode 100644 index 0000000..1bde994 --- /dev/null +++ b/books/architecture/src/resources/cap/map.md @@ -0,0 +1 @@ +# ⚪️ Map diff --git a/books/architecture/src/resources/cap/the-block-downloader.md b/books/architecture/src/resources/cap/the-block-downloader.md new file mode 100644 index 0000000..c0dccba --- /dev/null +++ b/books/architecture/src/resources/cap/the-block-downloader.md @@ -0,0 +1 @@ +# ⚪️ The block downloader diff --git a/books/architecture/src/resources/cap/the-database.md b/books/architecture/src/resources/cap/the-database.md new file mode 100644 index 0000000..32e4b62 --- /dev/null +++ b/books/architecture/src/resources/cap/the-database.md @@ -0,0 +1 @@ +# ⚪️ The database diff --git a/books/architecture/src/resources/cap/the-rpc-server.md b/books/architecture/src/resources/cap/the-rpc-server.md new file mode 100644 index 0000000..cd654cf --- /dev/null +++ b/books/architecture/src/resources/cap/the-rpc-server.md @@ -0,0 +1 @@ +# ⚪️ The RPC server diff --git a/books/architecture/src/resources/cap/the-verifier.md b/books/architecture/src/resources/cap/the-verifier.md new file mode 100644 index 0000000..eeaedc6 --- /dev/null +++ b/books/architecture/src/resources/cap/the-verifier.md @@ -0,0 +1 @@ +# ⚪️ The verifier diff --git a/books/architecture/src/resources/cap/thread-exit.md b/books/architecture/src/resources/cap/thread-exit.md new file mode 100644 index 0000000..3925975 --- /dev/null +++ b/books/architecture/src/resources/cap/thread-exit.md @@ -0,0 +1 @@ +# ⚪️ Thread exit diff --git a/books/architecture/src/resources/cap/threads.md b/books/architecture/src/resources/cap/threads.md new file mode 100644 index 0000000..e40f2c7 --- /dev/null +++ b/books/architecture/src/resources/cap/threads.md @@ -0,0 +1,2 @@ +# Index of threads +This is an index of all of the system threads Cuprate actively uses. \ No newline at end of file diff --git a/books/architecture/src/resources/fs/intro.md b/books/architecture/src/resources/fs/intro.md new file mode 100644 index 0000000..b67ca07 --- /dev/null +++ b/books/architecture/src/resources/fs/intro.md @@ -0,0 +1 @@ +# ⚪️ File system diff --git a/books/architecture/src/resources/fs/paths.md b/books/architecture/src/resources/fs/paths.md new file mode 100644 index 0000000..0e5dc3d --- /dev/null +++ b/books/architecture/src/resources/fs/paths.md @@ -0,0 +1,87 @@ +# Index of PATHs +This is an index of all of the filesystem PATHs Cuprate actively uses. + +The [`cuprate_helper::fs`](https://doc.cuprate.org/cuprate_helper/fs/index.html) +module defines the general locations used throughout Cuprate. + +[`dirs`](https://docs.rs/dirs) is used internally, which follows +the PATH standards/conventions on each OS Cuprate supports, i.e.: +- the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and the [XDG user directory](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/) specifications on Linux +- the [Known Folder](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776911(v=vs.85).aspx) system on Windows +- the [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6) on macOS + +## Cache +Cuprate's cache directory. + +| OS | PATH | +|---------|-----------------------------------------| +| Windows | `C:\Users\Alice\AppData\Local\Cuprate\` | +| macOS | `/Users/Alice/Library/Caches/Cuprate/` | +| Linux | `/home/alice/.cache/cuprate/` | + +## Config +Cuprate's config directory. + +| OS | PATH | +|---------|-----------------------------------------------------| +| Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | +| macOS | `/Users/Alice/Library/Application Support/Cuprate/` | +| Linux | `/home/alice/.config/cuprate/` | + +## Data +Cuprate's data directory. + +| OS | PATH | +|---------|-----------------------------------------------------| +| Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | +| macOS | `/Users/Alice/Library/Application Support/Cuprate/` | +| Linux | `/home/alice/.local/share/cuprate/` | + +## Blockchain +Cuprate's blockchain directory. + +| OS | PATH | +|---------|----------------------------------------------------------------| +| Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\blockchain\` | +| macOS | `/Users/Alice/Library/Application Support/Cuprate/blockchain/` | +| Linux | `/home/alice/.local/share/cuprate/blockchain/` | + +## Transaction pool +Cuprate's transaction pool directory. + +| OS | PATH | +|---------|------------------------------------------------------------| +| Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\txpool\` | +| macOS | `/Users/Alice/Library/Application Support/Cuprate/txpool/` | +| Linux | `/home/alice/.local/share/cuprate/txpool/` | + +## Database +Cuprate's database location/filenames depend on: + +- Which database it is +- Which backend is being used + +--- + +`cuprate_blockchain` files are in the above mentioned `blockchain` folder. + +`cuprate_txpool` files are in the above mentioned `txpool` folder. + +--- + +If the `heed` backend is being used, these files will be created: + +| Filename | Purpose | +|------------|--------------------| +| `data.mdb` | Main data file | +| `lock.mdb` | Database lock file | + +For example: `/home/alice/.local/share/cuprate/blockchain/lock.mdb`. + +If the `redb` backend is being used, these files will be created: + +| Filename | Purpose | +|-------------|--------------------| +| `data.redb` | Main data file | + +For example: `/home/alice/.local/share/cuprate/txpool/data.redb`. \ No newline at end of file diff --git a/books/architecture/src/resources/intro.md b/books/architecture/src/resources/intro.md new file mode 100644 index 0000000..3c1229e --- /dev/null +++ b/books/architecture/src/resources/intro.md @@ -0,0 +1 @@ +# Resources diff --git a/books/architecture/src/resources/memory.md b/books/architecture/src/resources/memory.md new file mode 100644 index 0000000..e3624b5 --- /dev/null +++ b/books/architecture/src/resources/memory.md @@ -0,0 +1 @@ +# ⚪️ Memory diff --git a/books/architecture/src/resources/sockets/index.md b/books/architecture/src/resources/sockets/index.md new file mode 100644 index 0000000..1e65ffc --- /dev/null +++ b/books/architecture/src/resources/sockets/index.md @@ -0,0 +1 @@ +# Sockets diff --git a/books/architecture/src/resources/sockets/ports.md b/books/architecture/src/resources/sockets/ports.md new file mode 100644 index 0000000..38ebc1d --- /dev/null +++ b/books/architecture/src/resources/sockets/ports.md @@ -0,0 +1,2 @@ +# Index of ports +This is an index of all of the network sockets Cuprate actively uses. \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/custom-strings.md b/books/architecture/src/rpc/differences/custom-strings.md new file mode 100644 index 0000000..736c481 --- /dev/null +++ b/books/architecture/src/rpc/differences/custom-strings.md @@ -0,0 +1,7 @@ +# Custom strings +Many JSON response fields contain strings with custom messages. + +This may be error messages, status, etc. + +Although the field + string type will be followed, Cuprate will not always +have the exact same message, particularly when it comes to error messages. \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/http-methods.md b/books/architecture/src/rpc/differences/http-methods.md new file mode 100644 index 0000000..238e2e3 --- /dev/null +++ b/books/architecture/src/rpc/differences/http-methods.md @@ -0,0 +1,17 @@ +# HTTP methods +`monerod` endpoints supports multiple [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) +that do not necessarily make sense. + +For example: +```bash +curl \ + http://127.0.0.1:18081/get_limit \ + -H 'Content-Type: application/json' \ + --request DELETE +``` +This is sending an HTTP `DELETE` request, which should be a `GET`. + +`monerod` will respond to this the same as `GET`, `POST`, `PUT`, and `TRACE`. + +## Cuprate's behavior +> TODO: decide allowed HTTP methods for Cuprate . \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/intro.md b/books/architecture/src/rpc/differences/intro.md new file mode 100644 index 0000000..54027e2 --- /dev/null +++ b/books/architecture/src/rpc/differences/intro.md @@ -0,0 +1,9 @@ +# Differences with `monerod` +As noted in the [introduction](../intro.md), `monerod`'s RPC behavior cannot always be perfectly followed by Cuprate. + +The reasoning for the differences can vary from: +- Technical limitations +- Behavior being `monerod`-specific +- Purposeful decision to not support behavior + +This section lays out the details of the differences between `monerod`'s and Cuprate's RPC system. diff --git a/books/architecture/src/rpc/differences/json-field-ordering.md b/books/architecture/src/rpc/differences/json-field-ordering.md new file mode 100644 index 0000000..ad4c1f9 --- /dev/null +++ b/books/architecture/src/rpc/differences/json-field-ordering.md @@ -0,0 +1,51 @@ +# JSON field ordering +When serializing JSON, `monerod` has the behavior to order key fields within a scope alphabetically. + +For example: +```json +{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blockhashing_blob": "...", + "blocktemplate_blob": "...", + "difficulty": 283305047039, + "difficulty_top64": 0, + "expected_reward": 600000000000, + "height": 3195018, + "next_seed_hash": "", + "prev_hash": "9d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a", + "reserved_offset": 131, + "seed_hash": "e2aa0b7b55042cd48b02e395d78fa66a29815ccc1584e38db2d1f0e8485cd44f", + "seed_height": 3194880, + "status": "OK", + "untrusted": false, + "wide_difficulty": "0x41f64bf3ff" + } +} +``` +In the main `{}`, `id` comes before `jsonrpc`, which comes before `result`. + +The same alphabetical ordering is applied to the fields within `result`. + +Cuprate uses [`serde`](https://docs.rs/serde) for JSON serialization, +which serializes fields based on the _definition_ order, i.e. whatever +order the fields are defined in the code, is the order they will appear +in JSON. + +Some `struct` fields within Cuprate's RPC types happen to be alphabetical, but this is not a guarantee. + +As these are JSON maps, the ordering of fields should not matter, +although this is something to note as the output will technically differ. + +## Example incompatibility +An example of where this leads to incompatibility is if specific +line numbers are depended on to contain specific fields. + +For example, this will print the 10th line: +```bash +curl http://127.0.0.1:18081/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"get_block_template","params":{"wallet_address":"44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns","reserve_size":60}' -H 'Content-Type: application/json' | sed -n 10p +``` +It will be `"height": 3195018` in `monerod`'s case, but may not necessarily be for Cuprate. + +By all means, this should not be relied upon in the first place, although it is shown as an example. diff --git a/books/architecture/src/rpc/differences/json-formatting.md b/books/architecture/src/rpc/differences/json-formatting.md new file mode 100644 index 0000000..2d29d48 --- /dev/null +++ b/books/architecture/src/rpc/differences/json-formatting.md @@ -0,0 +1,118 @@ +# JSON formatting +In general, Cuprate's JSON formatting is very similar to `monerod`, but there are some differences. + +This is a list of those differences. + +## Pretty vs compact +> TODO: decide when handlers are created if we should allow custom formatting. + +Cuprate's RPC (really, [`serde_json`](https://docs.rs/serde_json)) can be configured to use either: +- [Pretty formatting](https://docs.rs/serde_json/latest/serde_json/ser/struct.PrettyFormatter.html) +- [Compact formatting](https://docs.rs/serde_json/latest/serde_json/ser/struct.CompactFormatter.html) + +`monerod` uses something _similar_ to pretty formatting. + +As an example, pretty formatting: +```json +{ + "number": 1, + "array": [ + 0, + 1 + ], + "string": "", + "array_of_objects": [ + { + "x": 1.0, + "y": -1.0 + }, + { + "x": 2.0, + "y": -2.0 + } + ] +} +``` +compact formatting: +```json +{"number":1,"array":[0,1],"string":"","array_of_objects":[{"x":1.0,"y":-1.0},{"x":2.0,"y":-2.0}]} +``` + +## Array of objects +`monerod` will format an array of objects like such: +```json +{ + "array_of_objects": [{ + "x": 0.0, + "y": 0.0, + },{ + "x": 0.0, + "y": 0.0, + },{ + "x": 0.0, + "y": 0.0 + }] +} +``` + +Cuprate will format the above like such: +```json +{ + "array_of_objects": [ + { + "x": 0.0, + "y": 0.0, + }, + { + "x": 0.0, + "y": 0.0, + }, + { + "x": 0.0, + "y": 0.0 + } + ] +} +``` + +## Array of maps containing named objects +An method that contains outputs like this is the `peers` field in the `sync_info` method: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"2.0","id":"0","method":"sync_info"}' \ + -H 'Content-Type: application/json' +``` + +`monerod` will format an array of maps that contains named objects like such: +```json +{ + "array": [{ + "named_object": { + "field": "" + } + },{ + "named_object": { + "field": "" + } + }] +} +``` + +Cuprate will format the above like such: +```json +{ + "array": [ + { + "named_object": { + "field": "" + } + }, + { + "named_object": { + "field": "" + } + } + ] +} +``` \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/json-rpc-strictness.md b/books/architecture/src/rpc/differences/json-rpc-strictness.md new file mode 100644 index 0000000..a30d79d --- /dev/null +++ b/books/architecture/src/rpc/differences/json-rpc-strictness.md @@ -0,0 +1,137 @@ +# JSON-RPC strictness +This is a list of behavior that `monerod`'s JSON-RPC implementation allows, that Cuprate's JSON-RPC implementation does not. + +In general, `monerod`'s JSON-RPC is quite lenient, going against the specification in many cases. +Cuprate's JSON-RPC implementation is slightly more strict. + +Cuprate also makes some decisions that are _different_ than `monerod`, but are not necessarily more or less strict. + +## Allowing an incorrect `jsonrpc` field +[The JSON-RPC 2.0 specification states that the `jsonrpc` field must be exactly `"2.0"`](https://www.jsonrpc.org/specification#request_object). + +`monerod` allows `jsonrpc` to: +- Be any string +- Be an empty array +- Be `null` +- Not exist at all + +Examples: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"???","method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` + +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":[],"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` + +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":null,"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` + +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` + +## Allowing `id` to be any type +JSON-RPC 2.0 responses must contain the same `id` as the original request. + +However, the [specification states](https://www.jsonrpc.org/specification#request_object): + +> An identifier established by the Client that MUST contain a String, Number, or NULL value if included + +`monerod` does not check this and allows `id` to be any JSON type, for example, a map: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"2.0","id":{"THIS":{"IS":"ALLOWED"}},"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` + +The response: +```json +{ + "id": { + "THIS": { + "IS": "ALLOWED" + } + }, + "jsonrpc": "2.0", + "result": { + "count": 3210225, + "status": "OK", + "untrusted": false + } +} +``` + +## Responding with `id:0` on error +The JSON-RPC [specification states](https://www.jsonrpc.org/specification#response_object): + +> If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null. + +Although, `monerod` will respond with `id:0` in these cases. + +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"2.0","id":asdf,"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` +Response: +```bash +{ + "error": { + "code": -32700, + "message": "Parse error" + }, + "id": 0, + "jsonrpc": "2.0" +} +``` + +## Responding to notifications +> TODO: decide on Cuprate behavior + +Requests that have no `id` field are "notifications". + +[The JSON-RPC 2.0 specification states that requests without +an `id` field must _not_ be responded to](https://www.jsonrpc.org/specification#notification). + +Example: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"2.0","method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` + +## Upper/mixed case fields +`monerod` will accept upper/mixed case fields on: +- `jsonrpc` +- `id` + +`method` however, is checked. + +The JSON-RPC 2.0 specification does not outright state what case to support, +although, Cuprate only supports lowercase as supporting upper/mixed case +is more code to add as `serde` by default is case-sensitive on `struct` fields. + +Example: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsONrPc":"2.0","iD":0,"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/json-strictness.md b/books/architecture/src/rpc/differences/json-strictness.md new file mode 100644 index 0000000..92b6fc2 --- /dev/null +++ b/books/architecture/src/rpc/differences/json-strictness.md @@ -0,0 +1,49 @@ +# JSON strictness +This is a list of behavior that `monerod`'s JSON parser allows, that Cuprate's JSON parser ([`serde_json`](https://docs.rs/serde_json)) does not. + +In general, `monerod`'s parser is quite lenient, allowing invalid JSON in many cases. +Cuprate's (really, `serde_json`) JSON parser is quite strict, essentially sticking to +the [JSON specification](https://datatracker.ietf.org/doc/html/rfc8259). + +Cuprate also makes some decisions that are _different_ than `monerod`, but are not necessarily more or less strict. + +## Missing closing bracket +`monerod` will accept JSON missing a final closing `}`. + +Example: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_block_count"' \ + -H 'Content-Type: application/json' +``` + +## Trailing ending comma +`monerod` will accept JSON containing a final trailing `,`. + +Example: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_block_count",}' \ + -H 'Content-Type: application/json' +``` + +## Allowing `-` in fields +`monerod` allows `-` as a valid value in certain fields, **not a string `"-"`, but the character `-`**. + +The fields where this is allowed seems to be any field `monerod` does not explicitly look for, examples include: +- `jsonrpc` +- `id` +- `params` (where parameters are not expected) +- Any ignored field + +The [JSON-RPC 2.0 specification does state that the response `id` should be `null` upon errors in detecting the request `id`](https://wwwjsonrpc.org/specification#response_object), although in this case, this is invalid JSON and should not make it this far. The response will contain the default `id: 0` in this case. + +Example: +```bash +curl \ + http://127.0.0.1:18081/json_rpc \ + -d '{"jsonrpc":-,"id":-,"params":-,"IGNORED_FIELD":-,"method":"get_block_count"}' \ + -H 'Content-Type: application/json' +``` \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/rpc-calls-with-different-behavior.md b/books/architecture/src/rpc/differences/rpc-calls-with-different-behavior.md new file mode 100644 index 0000000..6c1cb69 --- /dev/null +++ b/books/architecture/src/rpc/differences/rpc-calls-with-different-behavior.md @@ -0,0 +1,2 @@ +# RPC calls with different behavior +> TODO: compile RPC calls with different behavior after handlers are created. \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/rpc-payment.md b/books/architecture/src/rpc/differences/rpc-payment.md new file mode 100644 index 0000000..ae38c0d --- /dev/null +++ b/books/architecture/src/rpc/differences/rpc-payment.md @@ -0,0 +1,9 @@ +# RPC payment +The RPC payment system in `monerod` is a [pseudo-deprecated](https://github.com/monero-project/monero/issues/8722) +system that allows node operators to be compensated for RPC usage. + +Although this system is pseudo-deprecated, `monerod` still generates related fields in responses. [Cuprate follows this behavior](https://doc.cuprate.org/cuprate_rpc_types/base/struct.AccessResponseBase.html). + +However, the [associated endpoints](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L182-L187) and [actual functionality](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L260-L265) are not supported by Cuprate. The associated endpoints will return an error upon invocation. + +> TODO: decide on behavior and document . \ No newline at end of file diff --git a/books/architecture/src/rpc/differences/unsupported-rpc-calls.md b/books/architecture/src/rpc/differences/unsupported-rpc-calls.md new file mode 100644 index 0000000..ac0c2df --- /dev/null +++ b/books/architecture/src/rpc/differences/unsupported-rpc-calls.md @@ -0,0 +1,2 @@ +# Unsupported RPC calls +> TODO: compile unsupported RPC calls after handlers are created. \ No newline at end of file diff --git a/books/architecture/src/rpc/handler/intro.md b/books/architecture/src/rpc/handler/intro.md new file mode 100644 index 0000000..e664f5b --- /dev/null +++ b/books/architecture/src/rpc/handler/intro.md @@ -0,0 +1,2 @@ +# The handler +> TODO: fill after `cuprate-rpc-handler` is created. \ No newline at end of file diff --git a/books/architecture/src/rpc/interface.md b/books/architecture/src/rpc/interface.md new file mode 100644 index 0000000..3557ffb --- /dev/null +++ b/books/architecture/src/rpc/interface.md @@ -0,0 +1,36 @@ +# The interface +> This section is short as [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) contains detailed documentation. + +The RPC interface, which includes: + +- Endpoint routing (`/json_rpc`, `/get_blocks.bin`, etc) +- Route function signatures (`async fn json_rpc(...) -> Response`) +- Type (de)serialization +- Any miscellaneous handling (denying `restricted` RPC calls) + +is handled by the [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) crate. + +Essentially, this crate provides the API for the RPC. + +`cuprate-rpc-interface` is built on-top of [`axum`](https://docs.rs/axum) and [`tower`](https://docs.rs/tower), +which are the crates doing the bulk majority of the work. + +## Request -> Response +The functions that map requests to responses are not implemented by `cuprate-rpc-interface` itself, they must be provided by the user, i.e. it can be _customized_. + +In Rust terms, this crate provides you with: +```rust +async fn json_rpc( + state: State, + request: Request, +) -> Response { + /* your handler here */ +} +``` +and you provide the function body. + +The main handler crate is [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler). +This crate implements the standard RPC behavior, i.e. it mostly mirrors `monerod`. + +Although, it's worth noting that other implementations are possible, such as an RPC handler that caches blocks, +or an RPC handler that only accepts certain endpoints, or any combination. \ No newline at end of file diff --git a/books/architecture/src/rpc/intro.md b/books/architecture/src/rpc/intro.md new file mode 100644 index 0000000..acfc604 --- /dev/null +++ b/books/architecture/src/rpc/intro.md @@ -0,0 +1,30 @@ +# RPC +`monerod`'s daemon RPC has three kinds of RPC calls: +1. [JSON-RPC 2.0](https://www.jsonrpc.org/specification) methods, called at the `/json_rpc` endpoint +1. JSON (but not JSON-RPC 2.0) methods called at their own endpoints, e.g. [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) +1. Binary ([epee](../../formats-protocols-types/epee.html)) RPC methods called at their own endpoints ending in `.bin`, e.g. [`/get_blocks.bin`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin) + +Cuprate's RPC aims to mirror `monerod`'s as much as it practically can. + +This includes, but is not limited to: +- Using the same endpoints +- Receiving the same request data +- Sending the same response data +- Responding with the same HTTP status codes +- Following internal behavior (e.g. [`/pop_blocks`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#pop_blocks)) + +Not all `monerod` behavior can always be followed, however. + +Some are not followed on purpose, some cannot be followed due to technical limitations, and some cannot be due to the behavior being `monerod` specific such as the [`/set_log_categories`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#set_log_categories) endpoint which uses `monerod`'s logging categories. + +Both subtle and large differences between Cuprate's RPC and `monerod`'s RPC are documented in the [Differences with `monerod`](differences/intro.md) section. + +## Main RPC components +The main components that make up Cuprate's RPC are noted below, alongside the equivalent `monerod` code and other notes. + +| Cuprate crate | `monerod` (rough) equivalent | Purpose | Notes | +|---------------|------------------------------|---------|-------| +| [`cuprate-json-rpc`](https://doc.cuprate.org/cuprate_json_rpc) | [`jsonrpc_structs.h`](https://github.com/monero-project/monero/blob/caa62bc9ea1c5f2ffe3ffa440ad230e1de509bfd/contrib/epee/include/net/jsonrpc_structs.h), [`http_server_handlers_map2.h`](https://github.com/monero-project/monero/blob/caa62bc9ea1c5f2ffe3ffa440ad230e1de509bfd/contrib/epee/include/net/http_server_handlers_map2.h) | JSON-RPC 2.0 implementation | `monerod`'s JSON-RPC 2.0 handling is spread across a few files. The first defines some data structures, the second contains macros that (essentially) implement JSON-RPC 2.0. +| [`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types) | [`core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/caa62bc9ea1c5f2ffe3ffa440ad230e1de509bfd/src/rpc/core_rpc_server_commands_defs.h) | RPC request/response type definitions and (de)serialization | | +| [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) | [`core_rpc_server.h`](https://github.com/monero-project/monero/blob/caa62bc9ea1c5f2ffe3ffa440ad230e1de509bfd/src/rpc/core_rpc_server.h) | RPC interface, routing, endpoints | | +| [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler) | [`core_rpc_server.cpp`](https://github.com/monero-project/monero/blob/caa62bc9ea1c5f2ffe3ffa440ad230e1de509bfd/src/rpc/core_rpc_server.cpp) | RPC request/response handling | These are the "inner handler" functions that turn requests into responses | \ No newline at end of file diff --git a/books/architecture/src/rpc/json-rpc.md b/books/architecture/src/rpc/json-rpc.md new file mode 100644 index 0000000..ac52cd1 --- /dev/null +++ b/books/architecture/src/rpc/json-rpc.md @@ -0,0 +1,10 @@ +# JSON-RPC 2.0 +Cuprate has a standalone crate that implements the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification, [`cuprate-json-rpc`](https://doc.cuprate.org/cuprate_json_rpc). The RPC methods at the `/json_rpc` endpoint use this crate's types, functions, and (de)serialization. + +There is nothing too special about Cuprate's implementation. +Any small notes and differences are noted in the crate documentation. + +As such, there is not much to document here, instead, consider reading the very +brief JSON-RPC 2.0 specification, and the `cuprate-json-rpc` crate documentation. + +> TODO: document `method/params` vs flattened `base` when figured out. diff --git a/books/architecture/src/rpc/server/intro.md b/books/architecture/src/rpc/server/intro.md new file mode 100644 index 0000000..0178ce2 --- /dev/null +++ b/books/architecture/src/rpc/server/intro.md @@ -0,0 +1,2 @@ +# 🔴 The server +> TODO: fill after `cuprate-rpc-server` or binary impl is created. \ No newline at end of file diff --git a/books/architecture/src/rpc/types/base-types.md b/books/architecture/src/rpc/types/base-types.md new file mode 100644 index 0000000..feda38d --- /dev/null +++ b/books/architecture/src/rpc/types/base-types.md @@ -0,0 +1,39 @@ +# Base RPC types +There exists a few "base" types that many types are built on-top of in `monerod`. +These are also implemented in [`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types/base/index.html). + +For example, many requests include these 2 fields: +```json +{ + "status": "OK", + "untrusted": false, +} +``` +This is [`rpc_response_base`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L101-L112) in `monerod`, and [`ResponseBase`](https://doc.cuprate.org/cuprate_rpc_types/base/struct.ResponseBase.html) in Cuprate. + +These types are [flattened](https://serde.rs/field-attrs.html#flatten) into other types, i.e. the fields +from these base types are injected into the given type. For example, [`get_block_count`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_count)'s response type is defined [like such in Cuprate](https://doc.cuprate.org/cuprate_rpc_types/json/struct.GetBlockCountResponse.html): +```rust +struct GetBlockCountResponse { + // The fields of this `base` type are directly + // injected into `GetBlockCountResponse` during + // (de)serialization. + // + // I.e. it is as if this `base` field were actually these 2 fields: + // status: Status, + // untrusted: bool, + base: ResponseBase, + count: u64, +} +``` +The JSON output of this type would look something like: +```json +{ + "status": "OK", + "untrusted": "false", + "count": 993163 +} +``` + +## RPC payment +`monerod` also contains RPC base types for the [RPC payment](https://doc.cuprate.org/cuprate_rpc_types/base/struct.AccessResponseBase.html) system. Although the RPC payment system [is](https://github.com/monero-project/monero/issues/8722) [pseudo](https://github.com/monero-project/monero/pull/8724) [deprecated](https://github.com/monero-project/monero/pull/8843), `monerod` still generates these fields in responses, and thus, [so does Cuprate](https://doc.cuprate.org/cuprate_rpc_types/base/struct.AccessResponseBase.html). \ No newline at end of file diff --git a/books/architecture/src/rpc/types/deserialization.md b/books/architecture/src/rpc/types/deserialization.md new file mode 100644 index 0000000..736ef24 --- /dev/null +++ b/books/architecture/src/rpc/types/deserialization.md @@ -0,0 +1,38 @@ +# (De)serialization +A crucial responsibility of [`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types) +is to provide the _correct_ (de)serialization of types. + +The input/output of Cuprate's RPC should match `monerod` (as much as practically possible). + +A simple example of this is that [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) +should respond with the exact same data for both `monerod` and Cuprate: +```json +{ + "hash": "7e23a28cfa6df925d5b63940baf60b83c0cbb65da95f49b19e7cf0ce7dd709ce", + "height": 2287217, + "status": "OK", + "untrusted": false +} +``` +Behavior would be considered incompatible if any of the following were true: +- Fields are missing +- Extra fields exist +- Field types are incorrect (`string` instead of `number`, etc) + +## JSON +(De)serialization for JSON is implemented using [`serde`](https://docs.rs/serde) and [`serde_json`](https://docs.rs/serde_json). + +[`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) (the main crate responsible +for the actual output) uses `serde_json` for JSON formatting. It is _mostly_ the same formatting as `monerod`, [although there are slight differences](../differences/json-formatting.md). + +Technically, the formatting of the JSON output is not handled by `cuprate-rpc-types`, users are free to choose whatever formatting they desire. + +## Epee +(De)serialization for the [epee binary format](../../formats-protocols-types/epee.md) is +handled by Cuprate's in-house [cuprate-epee-encoding](https://doc.cuprate.org/cuprate_epee_encoding) library. + +## Bitcasted `struct`s +> TODO: + +## Compressed data +> TODO: \ No newline at end of file diff --git a/books/architecture/src/rpc/types/intro.md b/books/architecture/src/rpc/types/intro.md new file mode 100644 index 0000000..87b2f6e --- /dev/null +++ b/books/architecture/src/rpc/types/intro.md @@ -0,0 +1,31 @@ +# The types +Cuprate has a crate that defines all the types related to RPC: [`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types). + +The main purpose of this crate is to port the types used in `monerod`'s RPC and to re-implement +(de)serialization for those types, whether that be JSON, `epee`, or a custom mix. + +The bulk majority of these types are [request & response types](macro.md), i.e. the inputs +Cuprate's RPC is expecting from users, and the output it will respond with. + +## Example +To showcase an example of the kinds of types defined in this crate, here is a request type: +```rust +#[serde(transparent)] +#[repr(transparent)] +struct OnGetBlockHashRequest { + block_height: [u64; 1], +} +``` +This is the input (`params`) expected in the [`on_get_block_hash`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#on_get_block_hash) method. + +As seen above, the type itself encodes some properties, such as being (de)serialized [transparently](https://serde.rs/container-attrs.html#transparent), and the input being an array with 1 length, rather than a single `u64`. [This is to match the behavior of `monerod`](https://github.com/monero-project/monero/blob/caa62bc9ea1c5f2ffe3ffa440ad230e1de509bfd/src/rpc/core_rpc_server.cpp#L1826). + +An example JSON form of this type would be: +```json +{ + "jsonrpc": "2.0", + "id": "0", + "method": "on_get_block_hash", + "params": [912345] // <- This can (de)serialize as a `OnGetBlockHashRequest` +} +``` \ No newline at end of file diff --git a/books/architecture/src/rpc/types/macro.md b/books/architecture/src/rpc/types/macro.md new file mode 100644 index 0000000..49fb8bc --- /dev/null +++ b/books/architecture/src/rpc/types/macro.md @@ -0,0 +1,16 @@ +# The type generator macro +Request and response types make up the majority of [`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types). + +- Request types are the inputs expected _from_ users +- Response types are what will be outputted _to_ users + +Regardless of being meant for JSON-RPC, binary, or a standalone JSON endpoint, +all request/response types are defined using the ["type generator macro"](https://github.com/Cuprate/cuprate/blob/bd375eae40acfad7c8d0205bb10afd0b78e424d2/rpc/types/src/macros.rs#L46). This macro is important because it defines _all_ request/response types. + +This macro: +- Defines a matching pair of request & response types +- Implements many `derive` traits, e.g. `Clone` on those types +- Implements both `serde` and `epee` on those types +- Automates documentation, tests, etc. + +See [here](https://github.com/Cuprate/cuprate/blob/bd375eae40acfad7c8d0205bb10afd0b78e424d2/rpc/types/src/macros.rs#L46) for example usage of this macro. \ No newline at end of file diff --git a/books/architecture/src/rpc/types/metadata.md b/books/architecture/src/rpc/types/metadata.md new file mode 100644 index 0000000..a9c8c73 --- /dev/null +++ b/books/architecture/src/rpc/types/metadata.md @@ -0,0 +1,13 @@ +# Metadata +[`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types) also provides +some `trait`s to access some metadata surrounding RPC data types. + +For example, [`trait RpcCall`](https://doc.cuprate.org/cuprate_rpc_types/trait.RpcCall.html) +allows accessing whether an RPC request is [`restricted`](https://doc.cuprate.org/cuprate_rpc_types/trait.RpcCall.html#associatedconstant.IS_RESTRICTED) or not. + +`monerod` has a boolean permission system. RPC calls can be restricted or not. +If an RPC call is restricted, it will only be allowed on un-restricted RPC servers (`18081`). +If an RPC call is _not_ restricted, it will be allowed on all RPC server types (`18081` & `18089`). + +This metadata is used in crates that build upon `cuprate-rpc-types`, e.g. +to know if an RPC call should be allowed through or not. \ No newline at end of file diff --git a/books/architecture/src/rpc/types/misc-types.md b/books/architecture/src/rpc/types/misc-types.md new file mode 100644 index 0000000..07bd516 --- /dev/null +++ b/books/architecture/src/rpc/types/misc-types.md @@ -0,0 +1,11 @@ +# Misc types +Other than the main request/response types, this crate is also responsible +for any [miscellaneous types](https://doc.cuprate.org/cuprate_rpc_types/misc) used within `monerod`'s RPC. + +For example, the `status` field within many RPC responses is defined within +[`cuprate-rpc-types`](https://doc.cuprate.org/cuprate_rpc_types/misc/enum.Status.html). + +Types that aren't requests/responses but exist _within_ request/response +types are also defined in this crate, such as the +[`Distribution`](https://doc.cuprate.org/cuprate_rpc_types/misc/enum.Distribution.html) +structure returned from the [`get_output_distribution`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_output_distribution) method. \ No newline at end of file diff --git a/books/architecture/src/storage/blockchain/intro.md b/books/architecture/src/storage/blockchain/intro.md new file mode 100644 index 0000000..9d35fca --- /dev/null +++ b/books/architecture/src/storage/blockchain/intro.md @@ -0,0 +1,3 @@ +# Blockchain +This section contains storage information specific to [`cuprate_blockchain`](https://doc.cuprate.org/cuprate_blockchain), +the database built on-top of [`cuprate_database`](https://doc.cuprate.org/cuprate_database) that stores the blockchain. diff --git a/books/architecture/src/storage/blockchain/schema/intro.md b/books/architecture/src/storage/blockchain/schema/intro.md new file mode 100644 index 0000000..3bd825f --- /dev/null +++ b/books/architecture/src/storage/blockchain/schema/intro.md @@ -0,0 +1,2 @@ +# Schema +This section contains the schema of `cuprate_blockchain`'s database tables. \ No newline at end of file diff --git a/books/architecture/src/storage/blockchain/schema/multimap.md b/books/architecture/src/storage/blockchain/schema/multimap.md new file mode 100644 index 0000000..2a4c6eb --- /dev/null +++ b/books/architecture/src/storage/blockchain/schema/multimap.md @@ -0,0 +1,45 @@ +# Multimap tables +## Outputs +When referencing outputs, Monero will [use the amount and the amount index](https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/blockchain_db/lmdb/db_lmdb.cpp#L3447-L3449). This means 2 keys are needed to reach an output. + +With LMDB you can set the `DUP_SORT` flag on a table and then set the key/value to: +```rust +Key = KEY_PART_1 +``` +```rust +Value = { + KEY_PART_2, + VALUE // The actual value we are storing. +} +``` + +Then you can set a custom value sorting function that only takes `KEY_PART_2` into account; this is how `monerod` does it. + +This requires that the underlying database supports: +- multimap tables +- custom sort functions on values +- setting a cursor on a specific key/value + +## How `cuprate_blockchain` does it +Another way to implement this is as follows: +```rust +Key = { KEY_PART_1, KEY_PART_2 } +``` +```rust +Value = VALUE +``` + +Then the key type is simply used to look up the value; this is how `cuprate_blockchain` does it +as [`cuprate_database` does not have a multimap abstraction (yet)](../../db/issues/multimap.md). + +For example, the key/value pair for outputs is: +```rust +PreRctOutputId => Output +``` +where `PreRctOutputId` looks like this: +```rust +struct PreRctOutputId { + amount: u64, + amount_index: u64, +} +``` \ No newline at end of file diff --git a/books/architecture/src/storage/blockchain/schema/tables.md b/books/architecture/src/storage/blockchain/schema/tables.md new file mode 100644 index 0000000..15e0c63 --- /dev/null +++ b/books/architecture/src/storage/blockchain/schema/tables.md @@ -0,0 +1,39 @@ +# Tables + +> See also: & . + +The `CamelCase` names of the table headers documented here (e.g. `TxIds`) are the actual type name of the table within `cuprate_blockchain`. + +Note that words written within `code blocks` mean that it is a real type defined and usable within `cuprate_blockchain`. Other standard types like u64 and type aliases (TxId) are written normally. + +Within `cuprate_blockchain::tables`, the below table is essentially defined as-is with [a macro](https://github.com/Cuprate/cuprate/blob/31ce89412aa174fc33754f22c9a6d9ef5ddeda28/database/src/tables.rs#L369-L470). + +Many of the data types stored are the same data types, although are different semantically, as such, a map of aliases used and their real data types is also provided below. + +| Alias | Real Type | +|----------------------------------------------------|-----------| +| BlockHeight, Amount, AmountIndex, TxId, UnlockTime | u64 +| BlockHash, KeyImage, TxHash, PrunableHash | [u8; 32] + +--- + +| Table | Key | Value | Description | +|--------------------|----------------------|-------------------------|-------------| +| `BlockHeaderBlobs` | BlockHeight | `StorableVec` | Maps a block's height to a serialized byte form of its header +| `BlockTxsHashes` | BlockHeight | `StorableVec<[u8; 32]>` | Maps a block's height to the block's transaction hashes +| `BlockHeights` | BlockHash | BlockHeight | Maps a block's hash to its height +| `BlockInfos` | BlockHeight | `BlockInfo` | Contains metadata of all blocks +| `KeyImages` | KeyImage | () | This table is a set with no value, it stores transaction key images +| `NumOutputs` | Amount | u64 | Maps an output's amount to the number of outputs with that amount +| `Outputs` | `PreRctOutputId` | `Output` | This table contains legacy CryptoNote outputs which have clear amounts. This table will not contain an output with 0 amount. +| `PrunedTxBlobs` | TxId | `StorableVec` | Contains pruned transaction blobs (even if the database is not pruned) +| `PrunableTxBlobs` | TxId | `StorableVec` | Contains the prunable part of a transaction +| `PrunableHashes` | TxId | PrunableHash | Contains the hash of the prunable part of a transaction +| `RctOutputs` | AmountIndex | `RctOutput` | Contains RingCT outputs mapped from their global RCT index +| `TxBlobs` | TxId | `StorableVec` | Serialized transaction blobs (bytes) +| `TxIds` | TxHash | TxId | Maps a transaction's hash to its index/ID +| `TxHeights` | TxId | BlockHeight | Maps a transaction's ID to the height of the block it comes from +| `TxOutputs` | TxId | `StorableVec` | Gives the amount indices of a transaction's outputs +| `TxUnlockTime` | TxId | UnlockTime | Stores the unlock time of a transaction (only if it has a non-zero lock time) + + \ No newline at end of file diff --git a/books/architecture/src/storage/common/intro.md b/books/architecture/src/storage/common/intro.md new file mode 100644 index 0000000..a772d87 --- /dev/null +++ b/books/architecture/src/storage/common/intro.md @@ -0,0 +1,9 @@ +# Common behavior +The crates that build on-top of the database abstraction ([`cuprate_database`](https://doc.cuprate.org/cuprate_database)) +share some common behavior including but not limited to: + +- Defining their specific database tables and types +- Having an `ops` module +- Exposing a `tower::Service` API (backed by a threadpool) for public usage + +This section provides more details on these behaviors. \ No newline at end of file diff --git a/books/architecture/src/storage/common/ops.md b/books/architecture/src/storage/common/ops.md new file mode 100644 index 0000000..3a4e617 --- /dev/null +++ b/books/architecture/src/storage/common/ops.md @@ -0,0 +1,21 @@ +# `ops` +Both [`cuprate_blockchain`](https://doc.cuprate.org/cuprate_blockchain) +and [`cuprate_txpool`](https://doc.cuprate.org/cuprate_txpool) expose an +`ops` module containing abstracted abstracted Monero-related database operations. + +For example, [`cuprate_blockchain::ops::block::add_block`](https://doc.cuprate.org/cuprate_blockchain/ops/block/fn.add_block.html). + +These functions build on-top of the database traits and allow for more abstracted database operations. + +For example, instead of these signatures: +```rust +fn get(_: &Key) -> Value; +fn put(_: &Key, &Value); +``` +the `ops` module provides much higher-level signatures like such: +```rust +fn add_block(block: &Block) -> Result<_, _>; +``` + +Although these functions are exposed, they are not the main API, that would be next section: +the [`tower::Service`](./service/intro.md) (which uses these functions). \ No newline at end of file diff --git a/books/architecture/src/storage/common/service/initialization.md b/books/architecture/src/storage/common/service/initialization.md new file mode 100644 index 0000000..8350971 --- /dev/null +++ b/books/architecture/src/storage/common/service/initialization.md @@ -0,0 +1,9 @@ +# Initialization +A database service is started simply by calling: [`init()`](https://doc.cuprate.org/cuprate_blockchain/service/fn.init.html). + +This function initializes the database, spawns threads, and returns a: +- Read handle to the database +- Write handle to the database +- The database itself + +These handles implement the `tower::Service` trait, which allows sending requests and receiving responses `async`hronously. \ No newline at end of file diff --git a/books/architecture/src/storage/common/service/intro.md b/books/architecture/src/storage/common/service/intro.md new file mode 100644 index 0000000..bba7486 --- /dev/null +++ b/books/architecture/src/storage/common/service/intro.md @@ -0,0 +1,65 @@ +# tower::Service +Both [`cuprate_blockchain`](https://doc.cuprate.org/cuprate_blockchain) +and [`cuprate_txpool`](https://doc.cuprate.org/cuprate_txpool) provide +`async` [`tower::Service`](https://docs.rs/tower)s that define database requests/responses. + +The main API that other Cuprate crates use. + +There are 2 `tower::Service`s: +1. A read service which is backed by a [`rayon::ThreadPool`](https://docs.rs/rayon) +1. A write service which spawns a single thread to handle write requests + +As this behavior is the same across all users of [`cuprate_database`](https://doc.cuprate.org/cuprate_database), +it is extracted into its own crate: [`cuprate_database_service`](https://doc.cuprate.org/cuprate_database_service). + +## Diagram +As a recap, here is how this looks to a user of a higher-level database crate, +`cuprate_blockchain` in this example. Starting from the lowest layer: + +1. `cuprate_database` is used to abstract the database +1. `cuprate_blockchain` builds on-top of that with tables, types, operations +1. `cuprate_blockchain` exposes a `tower::Service` using `cuprate_database_service` +1. The user now interfaces with `cuprate_blockchain` with that `tower::Service` in a request/response fashion + +``` + ┌──────────────────┐ + │ cuprate_database │ + └────────┬─────────┘ +┌─────────────────────────────────┴─────────────────────────────────┐ +│ cuprate_blockchain │ +│ │ +│ ┌──────────────────────┐ ┌─────────────────────────────────────┐ │ +│ │ Tables, types │ │ ops │ │ +│ │ ┌───────────┐┌─────┐ │ │ ┌─────────────┐ ┌──────────┐┌─────┐ │ │ +│ │ │ BlockInfo ││ ... │ ├──┤ │ add_block() │ │ add_tx() ││ ... │ │ │ +│ │ └───────────┘└─────┘ │ │ └─────────────┘ └──────────┘└─────┘ │ │ +│ └──────────────────────┘ └─────┬───────────────────────────────┘ │ +│ │ │ +│ ┌─────────┴───────────────────────────────┐ │ +│ │ tower::Service │ │ +│ │ ┌──────────────────────────────┐┌─────┐ │ │ +│ │ │ Blockchain{Read,Write}Handle ││ ... │ │ │ +│ │ └──────────────────────────────┘└─────┘ │ │ +│ └─────────┬───────────────────────────────┘ │ +│ │ │ +└─────────────────────────────────┼─────────────────────────────────┘ + │ + ┌─────┴─────┐ + ┌────────────────────┴────┐ ┌────┴──────────────────────────────────┐ + │ Database requests │ │ Database responses │ + │ ┌─────────────────────┐ │ │ ┌───────────────────────────────────┐ │ + │ │ FindBlock([u8; 32]) │ │ │ │ FindBlock(Option<(Chain, usize)>) │ │ + │ └─────────────────────┘ │ │ └───────────────────────────────────┘ │ + │ ┌─────────────────────┐ │ │ ┌───────────────────────────────────┐ │ + │ │ ChainHeight │ │ │ │ ChainHeight(usize, [u8; 32]) │ │ + │ └─────────────────────┘ │ │ └───────────────────────────────────┘ │ + │ ┌─────────────────────┐ │ │ ┌───────────────────────────────────┐ │ + │ │ ... │ │ │ │ ... │ │ + │ └─────────────────────┘ │ │ └───────────────────────────────────┘ │ + └─────────────────────────┘ └───────────────────────────────────────┘ + ▲ │ + │ ▼ + ┌─────────────────────────┐ + │ cuprate_blockchain user │ + └─────────────────────────┘ +``` \ No newline at end of file diff --git a/books/architecture/src/storage/common/service/requests.md b/books/architecture/src/storage/common/service/requests.md new file mode 100644 index 0000000..9157359 --- /dev/null +++ b/books/architecture/src/storage/common/service/requests.md @@ -0,0 +1,8 @@ +# Requests +Along with the 2 handles, there are 2 types of requests: +- Read requests, e.g. [`BlockchainReadRequest`](https://doc.cuprate.org/cuprate_types/blockchain/enum.BlockchainReadRequest.html) +- Write requests, e.g. [`BlockchainWriteRequest`](https://doc.cuprate.org/cuprate_types/blockchain/enum.BlockchainWriteRequest.html) + +Quite obviously: +- Read requests are for retrieving various data from the database +- Write requests are for writing data to the database \ No newline at end of file diff --git a/books/architecture/src/storage/common/service/resizing.md b/books/architecture/src/storage/common/service/resizing.md new file mode 100644 index 0000000..13cd3b4 --- /dev/null +++ b/books/architecture/src/storage/common/service/resizing.md @@ -0,0 +1,15 @@ +# Resizing +As noted in the [`cuprate_database` resizing section](../../db/resizing.md), +builders on-top of `cuprate_database` are responsible for resizing the database. + +In `cuprate_{blockchain,txpool}`'s case, that means the `tower::Service` must know +how to resize. This logic is shared between both crates, defined in `cuprate_database_service`: +. + +By default, this uses a _similar_ algorithm as `monerod`'s: + +- [If there's not enough space to fit a write request's data](https://github.com/Cuprate/cuprate/blob/0941f68efcd7dfe66124ad0c1934277f47da9090/storage/service/src/service/write.rs#L130), start a resize +- Each resize adds around [`1,073,745,920`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/resize.rs#L104-L160) bytes to the current map size +- A resize will be [attempted `3` times](https://github.com/Cuprate/cuprate/blob/0941f68efcd7dfe66124ad0c1934277f47da9090/storage/service/src/service/write.rs#L110) before failing + +There are other [resizing algorithms](https://doc.cuprate.org/cuprate_database/resize/enum.ResizeAlgorithm.html) that define how the database's memory map grows, although currently the behavior of `monerod` is closely followed (for no particular reason). \ No newline at end of file diff --git a/books/architecture/src/storage/common/service/responses.md b/books/architecture/src/storage/common/service/responses.md new file mode 100644 index 0000000..c03b42f --- /dev/null +++ b/books/architecture/src/storage/common/service/responses.md @@ -0,0 +1,18 @@ +# Responses +After sending a request using the read/write handle, the value returned is _not_ the response, yet an `async`hronous channel that will eventually return the response: +```rust,ignore +// Send a request. +// tower::Service::call() +// V +let response_channel: Channel = read_handle.call(BlockchainReadRequest::ChainHeight)?; + +// Await the response. +let response: BlockchainReadRequest = response_channel.await?; +``` + +After `await`ing the returned channel, a `Response` will eventually be returned when +the `Service` threadpool has fetched the value from the database and sent it off. + +Both read/write requests variants match in name with `Response` variants, i.e. +- `BlockchainReadRequest::ChainHeight` leads to `BlockchainResponse::ChainHeight` +- `BlockchainWriteRequest::WriteBlock` leads to `BlockchainResponse::WriteBlockOk` diff --git a/books/architecture/src/storage/common/service/shutdown.md b/books/architecture/src/storage/common/service/shutdown.md new file mode 100644 index 0000000..4f9890e --- /dev/null +++ b/books/architecture/src/storage/common/service/shutdown.md @@ -0,0 +1,4 @@ +# Shutdown +Once the read/write handles to the `tower::Service` are `Drop`ed, the backing thread(pool) will gracefully exit, automatically. + +Note the writer thread and reader threadpool aren't connected whatsoever; dropping the write handle will make the writer thread exit, however, the reader handle is free to be held onto and can be continued to be read from - and vice-versa for the write handle. diff --git a/books/architecture/src/storage/common/service/thread-model.md b/books/architecture/src/storage/common/service/thread-model.md new file mode 100644 index 0000000..b69d62c --- /dev/null +++ b/books/architecture/src/storage/common/service/thread-model.md @@ -0,0 +1,23 @@ +# Thread model +The base database abstractions themselves are not concerned with parallelism, they are mostly functions to be called from a single-thread. + +However, the `cuprate_database_service` API, _does_ have a thread model backing it. + +When a `Service`'s init() function is called, threads will be spawned and +maintained until the user drops (disconnects) the returned handles. + +The current behavior for thread count is: +- [1 writer thread](https://github.com/Cuprate/cuprate/blob/0941f68efcd7dfe66124ad0c1934277f47da9090/storage/service/src/service/write.rs#L48-L52) +- [As many reader threads as there are system threads](https://github.com/Cuprate/cuprate/blob/0941f68efcd7dfe66124ad0c1934277f47da9090/storage/service/src/reader_threads.rs#L44-L49) + +For example, on a system with 32-threads, `cuprate_database_service` will spawn: +- 1 writer thread +- 32 reader threads + +whose sole responsibility is to listen for database requests, access the database (potentially in parallel), and return a response. + +Note that the `1 system thread = 1 reader thread` model is only the default setting, the reader thread count can be configured by the user to be any number between `1 .. amount_of_system_threads`. + +The reader threads are managed by [`rayon`](https://docs.rs/rayon). + +For an example of where multiple reader threads are used: given a request that asks if any key-image within a set already exists, `cuprate_blockchain` will [split that work between the threads with `rayon`](https://github.com/Cuprate/cuprate/blob/0941f68efcd7dfe66124ad0c1934277f47da9090/storage/blockchain/src/service/read.rs#L400). \ No newline at end of file diff --git a/books/architecture/src/storage/common/types.md b/books/architecture/src/storage/common/types.md new file mode 100644 index 0000000..b6f2c6f --- /dev/null +++ b/books/architecture/src/storage/common/types.md @@ -0,0 +1,21 @@ +# Types +## POD types +Since [all types in the database are POD types](../db/serde.md), we must often +provide mappings between outside types and the types actually stored in the database. + +A common case is mapping infallible types to and from [`bitflags`](https://docs.rs/bitflag) and/or their raw integer representation. +For example, the [`OutputFlag`](https://doc.cuprate.org/cuprate_blockchain/types/struct.OutputFlags.html) type or `bool` types. + +As types like `enum`s, `bool`s and `char`s cannot be casted from an integer infallibly, +`bytemuck::Pod` cannot be implemented on it safely. Thus, we store some infallible version +of it inside the database with a custom type and map them when fetching the data. + +## Lean types +Another reason why database crates define their own types is +to cut any unneeded data from the type. + +Many of the types used in normal operation (e.g. [`cuprate_types::VerifiedBlockInformation`](https://doc.cuprate.org/cuprate_types/struct.VerifiedBlockInformation.html)) contain lots of extra pre-processed data for convenience. + +This would be a waste to store in the database, so in this example, the much leaner +"raw" [`BlockInfo`](https://doc.cuprate.org/cuprate_blockchain/types/struct.BlockInfo.html) +type is stored. diff --git a/books/architecture/src/storage/db/abstraction/backend.md b/books/architecture/src/storage/db/abstraction/backend.md new file mode 100644 index 0000000..02e796a --- /dev/null +++ b/books/architecture/src/storage/db/abstraction/backend.md @@ -0,0 +1,50 @@ +# Backend +First, we need an actual database implementation. + +`cuprate-database`'s `trait`s allow abstracting over the actual database, such that any backend in particular could be used. + +This page is an enumeration of all the backends Cuprate has, has tried, and may try in the future. + +## `heed` +The default database used is [`heed`](https://github.com/meilisearch/heed) (LMDB). The upstream versions from [`crates.io`](https://crates.io/crates/heed) are used. `LMDB` should not need to be installed as `heed` has a build script that pulls it in automatically. + +`heed`'s filenames inside Cuprate's data folder are: + +| Filename | Purpose | +|------------|---------| +| `data.mdb` | Main data file +| `lock.mdb` | Database lock file + +`heed`-specific notes: +- [There is a maximum reader limit](https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L1372). Other potential processes (e.g. `xmrblocks`) that are also reading the `data.mdb` file need to be accounted for +- [LMDB does not work on remote filesystem](https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L129) + +## `redb` +The 2nd database backend is the 100% Rust [`redb`](https://github.com/cberner/redb). + +The upstream versions from [`crates.io`](https://crates.io/crates/redb) are used. + +`redb`'s filenames inside Cuprate's data folder are: + +| Filename | Purpose | +|-------------|---------| +| `data.redb` | Main data file + + + +## `redb-memory` +This backend is 100% the same as `redb`, although, it uses [`redb::backend::InMemoryBackend`](https://docs.rs/redb/2.1.2/redb/backends/struct.InMemoryBackend.html) which is a database that completely resides in memory instead of a file. + +All other details about this should be the same as the normal `redb` backend. + +## `sanakirja` +[`sanakirja`](https://docs.rs/sanakirja) was a candidate as a backend, however there were problems with maximum value sizes. + +The default maximum value size is [1012 bytes](https://docs.rs/sanakirja/1.4.1/sanakirja/trait.Storable.html) which was too small for our requirements. Using [`sanakirja::Slice`](https://docs.rs/sanakirja/1.4.1/sanakirja/union.Slice.html) and [sanakirja::UnsizedStorage](https://docs.rs/sanakirja/1.4.1/sanakirja/trait.UnsizedStorable.html) was attempted, but there were bugs found when inserting a value in-between `512..=4096` bytes. + +As such, it is not implemented. + +## `MDBX` +[`MDBX`](https://erthink.github.io/libmdbx) was a candidate as a backend, however MDBX deprecated the custom key/value comparison functions, this makes it a bit trickier to implement multimap tables. It is also quite similar to the main backend LMDB (of which it was originally a fork of). + +As such, it is not implemented (yet). diff --git a/books/architecture/src/storage/db/abstraction/concrete_env.md b/books/architecture/src/storage/db/abstraction/concrete_env.md new file mode 100644 index 0000000..059358e --- /dev/null +++ b/books/architecture/src/storage/db/abstraction/concrete_env.md @@ -0,0 +1,15 @@ +# `ConcreteEnv` +After a backend is selected, the main database environment struct is "abstracted" by putting it in the non-generic, concrete [`struct ConcreteEnv`](https://doc.cuprate.org/cuprate_database/struct.ConcreteEnv.html). + +This is the main object used when handling the database directly. + +This struct contains all the data necessary to operate the database. +The actual database backend `ConcreteEnv` will use internally [depends on which backend feature is used](https://github.com/Cuprate/cuprate/blob/0941f68efcd7dfe66124ad0c1934277f47da9090/storage/database/src/backend/mod.rs#L3-L13). + +`ConcreteEnv` itself is not too important, what is important is that: +1. It allows callers to not directly reference any particular backend environment +1. It implements [`trait Env`](https://doc.cuprate.org/cuprate_database/trait.Env.html) which opens the door to all the other database traits + +The equivalent "database environment" objects in the backends themselves are: +- [`heed::Env`](https://docs.rs/heed/0.20.0/heed/struct.Env.html) +- [`redb::Database`](https://docs.rs/redb/2.1.0/redb/struct.Database.html) \ No newline at end of file diff --git a/books/architecture/src/storage/db/abstraction/intro.md b/books/architecture/src/storage/db/abstraction/intro.md new file mode 100644 index 0000000..34a4320 --- /dev/null +++ b/books/architecture/src/storage/db/abstraction/intro.md @@ -0,0 +1,33 @@ +# Abstraction +This next section details how `cuprate_database` abstracts multiple database backends into 1 API. + +## Diagram +A simple diagram describing the responsibilities/relationship of `cuprate_database`. + +```text +┌───────────────────────────────────────────────────────────────────────┐ +│ cuprate_database │ +│ │ +│ ┌───────────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ Database traits │ │ Backends │ │ +│ │ ┌─────┐┌──────┐┌────────┐ │ │ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ Env ││ TxRw ││ ... │ ├─────┤ │ heed (LMDB) │ │ redb │ │ │ +│ │ └─────┘└──────┘└────────┘ │ │ └─────────────┘ └─────────────┘ │ │ +│ └──────────┬─────────────┬──┘ └──┬──────────────────────────────┘ │ +│ │ └─────┬─────┘ │ +│ │ ┌─────────┴──────────────┐ │ +│ │ │ Database types │ │ +│ │ │ ┌─────────────┐┌─────┐ │ │ +│ │ │ │ ConcreteEnv ││ ... │ │ │ +│ │ │ └─────────────┘└─────┘ │ │ +│ │ └─────────┬──────────────┘ │ +│ │ │ │ +└────────────┼───────────────────┼──────────────────────────────────────┘ + │ │ + └───────────────────┤ + │ + ▼ + ┌───────────────────────┐ + │ cuprate_database user │ + └───────────────────────┘ +``` \ No newline at end of file diff --git a/books/architecture/src/storage/db/abstraction/trait.md b/books/architecture/src/storage/db/abstraction/trait.md new file mode 100644 index 0000000..e7b25d2 --- /dev/null +++ b/books/architecture/src/storage/db/abstraction/trait.md @@ -0,0 +1,49 @@ +# Trait +`cuprate_database` provides a set of `trait`s that abstract over the various database backends. + +This allows the function signatures and behavior to stay the same but allows for swapping out databases in an easier fashion. + +All common behavior of the backend's are encapsulated here and used instead of using the backend directly. + +Examples: +- [`trait Env`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/env.rs) +- [`trait {TxRo, TxRw}`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/transaction.rs) +- [`trait {DatabaseRo, DatabaseRw}`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/database.rs) + +For example, instead of calling `heed` or `redb`'s `get()` function directly, `DatabaseRo::get()` is called. + +## Usage +With a `ConcreteEnv` and a particular backend selected, +we can now start using it alongside these traits to start +doing database operations in a generic manner. + +An example: + +```rust +use cuprate_database::{ + ConcreteEnv, + config::ConfigBuilder, + Env, EnvInner, + DatabaseRo, DatabaseRw, TxRo, TxRw, +}; + +// Initialize the database environment. +let env = ConcreteEnv::open(config)?; + +// Open up a transaction + tables for writing. +let env_inner = env.env_inner(); +let tx_rw = env_inner.tx_rw()?; +env_inner.create_db::(&tx_rw)?; + +// Write data to the table. +{ + let mut table = env_inner.open_db_rw::
(&tx_rw)?; + table.put(&0, &1)?; +} + +// Commit the transaction. +TxRw::commit(tx_rw)?; +``` + +As seen above, there is no direct call to `heed` or `redb`. +Their functionality is abstracted behind `ConcreteEnv` and the `trait`s. \ No newline at end of file diff --git a/books/architecture/src/storage/db/intro.md b/books/architecture/src/storage/db/intro.md new file mode 100644 index 0000000..5973fbe --- /dev/null +++ b/books/architecture/src/storage/db/intro.md @@ -0,0 +1,23 @@ +# Database abstraction +[`cuprate_database`](https://doc.cuprate.org/cuprate_database) is Cuprate’s database abstraction. + +This crate abstracts various database backends with `trait`s. + +All backends have the following attributes: + +- [Embedded](https://en.wikipedia.org/wiki/Embedded_database) +- [Multiversion concurrency control](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) +- [ACID](https://en.wikipedia.org/wiki/ACID) +- Are `(key, value)` oriented and have the expected API (`get()`, `insert()`, `delete()`) +- Are table oriented (`"table_name" -> (key, value)`) +- Allows concurrent readers + +The currently implemented backends are: +- [`heed`](https://github.com/meilisearch/heed) (LMDB) +- [`redb`](https://github.com/cberner/redb) + +Said precicely, `cuprate_database` is the embedded database other Cuprate +crates interact with instead of using any particular backend implementation. +This allows the backend to be swapped and/or future backends to be implemented. + +This section will go over `cuprate_database` details. \ No newline at end of file diff --git a/books/architecture/src/storage/db/issues/endian.md b/books/architecture/src/storage/db/issues/endian.md new file mode 100644 index 0000000..577e586 --- /dev/null +++ b/books/architecture/src/storage/db/issues/endian.md @@ -0,0 +1,6 @@ +# Endianness +`cuprate_database`'s (de)serialization and storage of bytes are native-endian, as in, byte storage order will depend on the machine it is running on. + +As Cuprate's build-targets are all little-endian ([big-endian by default machines barely exist](https://en.wikipedia.org/wiki/Endianness#Hardware)), this doesn't matter much and the byte ordering can be seen as a constant. + +Practically, this means `cuprated`'s database files can be transferred across computers, as can `monerod`'s. \ No newline at end of file diff --git a/books/architecture/src/storage/db/issues/hot-swap.md b/books/architecture/src/storage/db/issues/hot-swap.md new file mode 100644 index 0000000..aebfe20 --- /dev/null +++ b/books/architecture/src/storage/db/issues/hot-swap.md @@ -0,0 +1,17 @@ +# Hot-swappable backends +> See also: . + +Using a different backend is really as simple as re-building `cuprate_database` with a different feature flag: +```bash +# Use LMDB. +cargo build --package cuprate-database --features heed + +# Use redb. +cargo build --package cuprate-database --features redb +``` + +This is "good enough" for now, however ideally, this hot-swapping of backends would be able to be done at _runtime_. + +As it is now, `cuprate_database` cannot compile both backends and swap based on user input at runtime; it must be compiled with a certain backend, which will produce a binary with only that backend. + +This also means things like [CI testing multiple backends is awkward](https://github.com/Cuprate/cuprate/blob/main/.github/workflows/ci.yml#L132-L136), as we must re-compile with different feature flags instead. \ No newline at end of file diff --git a/books/architecture/src/storage/db/issues/intro.md b/books/architecture/src/storage/db/issues/intro.md new file mode 100644 index 0000000..eee4981 --- /dev/null +++ b/books/architecture/src/storage/db/issues/intro.md @@ -0,0 +1,7 @@ +# Known issues and tradeoffs +`cuprate_database` takes many tradeoffs, whether due to: +- Prioritizing certain values over others +- Not having a better solution +- Being "good enough" + +This section is a list of the larger ones, along with issues that don't have answers yet. \ No newline at end of file diff --git a/books/architecture/src/storage/db/issues/multimap.md b/books/architecture/src/storage/db/issues/multimap.md new file mode 100644 index 0000000..7e43ce1 --- /dev/null +++ b/books/architecture/src/storage/db/issues/multimap.md @@ -0,0 +1,22 @@ +# Multimap +`cuprate_database` does not currently have an abstraction for [multimap tables](https://en.wikipedia.org/wiki/Multimap). + +All tables are single maps of keys to values. + +This matters as this means some of `cuprate_blockchain`'s tables differ from `monerod`'s tables - the primary key is stored _for all_ entries, compared to `monerod` only needing to store it once: + +```rust +// `monerod` only stores `amount: 1` once, +// `cuprated` stores it each time it appears. +struct PreRctOutputId { amount: 1, amount_index: 0 } +struct PreRctOutputId { amount: 1, amount_index: 1 } +``` + +This means `cuprated`'s database will be slightly larger than `monerod`'s. + +The current method `cuprate_blockchain` uses will be "good enough" as the multimap +keys needed for now are fixed, e.g. pre-RCT outputs are no longer being produced. + +This may need to change in the future when multimap is all but required, e.g. for FCMP++. + +Until then, multimap tables are not implemented as they are tricky to implement across all backends. \ No newline at end of file diff --git a/books/architecture/src/storage/db/issues/traits.md b/books/architecture/src/storage/db/issues/traits.md new file mode 100644 index 0000000..9cf66e4 --- /dev/null +++ b/books/architecture/src/storage/db/issues/traits.md @@ -0,0 +1,15 @@ +# Traits abstracting backends +Although all database backends used are very similar, they have some crucial differences in small implementation details that must be worked around when conforming them to `cuprate_database`'s traits. + +Put simply: using `cuprate_database`'s traits is less efficient and more awkward than using the backend directly. + +For example: +- [Data types must be wrapped in compatibility layers when they otherwise wouldn't be](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/backend/heed/env.rs#L101-L116) +- [There are types that only apply to a specific backend, but are visible to all](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/error.rs#L86-L89) +- [There are extra layers of abstraction to smoothen the differences between all backends](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/env.rs#L62-L68) +- [Existing functionality of backends must be taken away, as it isn't supported in the others](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/database.rs#L27-L34) + +This is a _tradeoff_ that `cuprate_database` takes, as: +- The backend itself is usually not the source of bottlenecks in the greater system, as such, small inefficiencies are OK +- None of the lost functionality is crucial for operation +- The ability to use, test, and swap between multiple database backends is [worth it](https://github.com/Cuprate/cuprate/pull/35#issuecomment-1952804393) diff --git a/books/architecture/src/storage/db/issues/unaligned.md b/books/architecture/src/storage/db/issues/unaligned.md new file mode 100644 index 0000000..3c45c19 --- /dev/null +++ b/books/architecture/src/storage/db/issues/unaligned.md @@ -0,0 +1,24 @@ +# Copying unaligned bytes +As mentioned in [`(De)serialization`](../serde.md), bytes are _copied_ when they are turned into a type `T` due to unaligned bytes being returned from database backends. + +Using a regular reference cast results in an improperly aligned type `T`; [such a type even existing causes undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html). In our case, `bytemuck` saves us by panicking before this occurs. + +Thus, when using `cuprate_database`'s database traits, an _owned_ `T` is returned. + +This is doubly unfortunately for `&[u8]` as this does not even need deserialization. + +For example, `StorableVec` could have been this: +```rust +enum StorableBytes<'a, T: Storable> { + Owned(T), + Ref(&'a T), +} +``` +but this would require supporting types that must be copied regardless with the occasional `&[u8]` that can be returned without casting. This was hard to do so in a generic way, thus all `[u8]`'s are copied and returned as owned `StorableVec`s. + +This is a _tradeoff_ `cuprate_database` takes as: +- `bytemuck::pod_read_unaligned` is cheap enough +- The main API, `service`, needs to return owned value anyway +- Having no references removes a lot of lifetime complexity + +The alternative is somehow fixing the alignment issues in the backends mentioned previously. \ No newline at end of file diff --git a/books/architecture/src/storage/db/resizing.md b/books/architecture/src/storage/db/resizing.md new file mode 100644 index 0000000..ebf989e --- /dev/null +++ b/books/architecture/src/storage/db/resizing.md @@ -0,0 +1,8 @@ +# Resizing +`cuprate_database` itself does not handle memory map resizes automatically +(for database backends that need resizing, i.e. heed/LMDB). + +When a user directly using `cuprate_database`, it is up to them on how to resize. The database will return [`RuntimeError::ResizeNeeded`](https://doc.cuprate.org/cuprate_database/enum.RuntimeError.html#variant.ResizeNeeded) when it needs resizing. + +However, `cuprate_database` exposes some [resizing algorithms](https://doc.cuprate.org/cuprate_database/resize/index.html) +that define how the database's memory map grows. \ No newline at end of file diff --git a/books/architecture/src/storage/db/serde.md b/books/architecture/src/storage/db/serde.md new file mode 100644 index 0000000..de17f30 --- /dev/null +++ b/books/architecture/src/storage/db/serde.md @@ -0,0 +1,44 @@ +# (De)serialization +All types stored inside the database are either bytes already or are perfectly bitcast-able. + +As such, they do not incur heavy (de)serialization costs when storing/fetching them from the database. The main (de)serialization used is [`bytemuck`](https://docs.rs/bytemuck)'s traits and casting functions. + +## Size and layout +The size & layout of types is stable across compiler versions, as they are set and determined with [`#[repr(C)]`](https://doc.rust-lang.org/nomicon/other-reprs.html#reprc) and `bytemuck`'s derive macros such as [`bytemuck::Pod`](https://docs.rs/bytemuck/latest/bytemuck/derive.Pod.html). + +Note that the data stored in the tables are still type-safe; we still refer to the key and values within our tables by the type. + +## How +The main deserialization `trait` for database storage is [`Storable`](https://doc.cuprate.org/cuprate_database/trait.Storable.html). + +- Before storage, the type is [simply cast into bytes](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L125) +- When fetching, the bytes are [simply cast into the type](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L130) + +When a type is casted into bytes, [the reference is casted](https://docs.rs/bytemuck/latest/bytemuck/fn.bytes_of.html), i.e. this is zero-cost serialization. + +However, it is worth noting that when bytes are casted into the type, [it is copied](https://docs.rs/bytemuck/latest/bytemuck/fn.pod_read_unaligned.html). This is due to byte alignment guarantee issues with both backends, see: +- +- + +Without this, `bytemuck` will panic with [`TargetAlignmentGreaterAndInputNotAligned`](https://docs.rs/bytemuck/latest/bytemuck/enum.PodCastError.html#variant.TargetAlignmentGreaterAndInputNotAligned) when casting. + +Copying the bytes fixes this problem, although it is more costly than necessary. However, in the main use-case for `cuprate_database` (`tower::Service` API) the bytes would need to be owned regardless as the `Request/Response` API uses owned data types (`T`, `Vec`, `HashMap`, etc). + +Practically speaking, this means lower-level database functions that normally look like such: +```rust +fn get(key: &Key) -> &Value; +``` +end up looking like this in `cuprate_database`: +```rust +fn get(key: &Key) -> Value; +``` + +Since each backend has its own (de)serialization methods, our types are wrapped in compatibility types that map our `Storable` functions into whatever is required for the backend, e.g: +- [`StorableHeed`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/backend/heed/storable.rs#L11-L45) +- [`StorableRedb`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/backend/redb/storable.rs#L11-L30) + +Compatibility structs also exist for any `Storable` containers: +- [`StorableVec`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L135-L191) +- [`StorableBytes`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L208-L241) + +Again, it's unfortunate that these must be owned, although in the `tower::Service` use-case, they would have to be owned anyway. \ No newline at end of file diff --git a/books/architecture/src/storage/db/syncing.md b/books/architecture/src/storage/db/syncing.md new file mode 100644 index 0000000..3f3444e --- /dev/null +++ b/books/architecture/src/storage/db/syncing.md @@ -0,0 +1,17 @@ +# Syncing +`cuprate_database`'s database has 5 disk syncing modes. + +1. `FastThenSafe` +1. `Safe` +1. `Async` +1. `Threshold` +1. `Fast` + +The default mode is `Safe`. + +This means that upon each transaction commit, all the data that was written will be fully synced to disk. +This is the slowest, but safest mode of operation. + +Note that upon any database `Drop`, the current implementation will sync to disk regardless of any configuration. + +For more information on the other modes, read the documentation [here](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/config/sync_mode.rs#L63-L144). diff --git a/books/architecture/src/storage/intro.md b/books/architecture/src/storage/intro.md new file mode 100644 index 0000000..a28a017 --- /dev/null +++ b/books/architecture/src/storage/intro.md @@ -0,0 +1,34 @@ +# Storage +This section covers all things related to the on-disk storage of data within Cuprate. + +## Overview +The quick overview is that Cuprate has a [database abstraction crate](./database-abstraction.md) +that handles "low-level" database details such as key and value (de)serialization, tables, transactions, etc. + +This database abstraction crate is then used by all crates that need on-disk storage, i.e. the +- [Blockchain database](./blockchain/intro.md) +- [Transaction pool database](./txpool/intro.md) + +## Service +The interface provided by all crates building on-top of the +database abstraction is a [`tower::Service`](https://docs.rs/tower), i.e. +database requests/responses are sent/received asynchronously. + +As the interface details are similar across crates (threadpool, read operations, write operations), +the interface itself is abstracted in the [`cuprate_database_service`](./common/service/intro.md) crate, +which is then used by the crates. + +## Diagram +This is roughly how database crates are set up. + +```text + ┌─────────────────┐ +┌──────────────────────────────────┐ │ │ +│ Some crate that needs a database │ ┌────────────────┐ │ │ +│ │ │ Public │ │ │ +│ ┌──────────────────────────────┐ │─►│ tower::Service │◄─►│ Rest of Cuprate │ +│ │ Database abstraction │ │ │ API │ │ │ +│ └──────────────────────────────┘ │ └────────────────┘ │ │ +└──────────────────────────────────┘ │ │ + └─────────────────┘ +``` diff --git a/books/architecture/src/storage/pruning/intro.md b/books/architecture/src/storage/pruning/intro.md new file mode 100644 index 0000000..cfeee69 --- /dev/null +++ b/books/architecture/src/storage/pruning/intro.md @@ -0,0 +1 @@ +# ⚪️ Pruning diff --git a/books/architecture/src/storage/txpool/intro.md b/books/architecture/src/storage/txpool/intro.md new file mode 100644 index 0000000..4eb139b --- /dev/null +++ b/books/architecture/src/storage/txpool/intro.md @@ -0,0 +1 @@ +# ⚪️ Transaction pool diff --git a/books/architecture/src/testing/intro.md b/books/architecture/src/testing/intro.md new file mode 100644 index 0000000..397ae90 --- /dev/null +++ b/books/architecture/src/testing/intro.md @@ -0,0 +1 @@ +# ⚪️ Testing diff --git a/books/architecture/src/testing/monero-data.md b/books/architecture/src/testing/monero-data.md new file mode 100644 index 0000000..915af28 --- /dev/null +++ b/books/architecture/src/testing/monero-data.md @@ -0,0 +1 @@ +# ⚪️ Monero data diff --git a/books/architecture/src/testing/rpc-client.md b/books/architecture/src/testing/rpc-client.md new file mode 100644 index 0000000..5a373c2 --- /dev/null +++ b/books/architecture/src/testing/rpc-client.md @@ -0,0 +1 @@ +# ⚪️ RPC client diff --git a/books/architecture/src/testing/spawning-monerod.md b/books/architecture/src/testing/spawning-monerod.md new file mode 100644 index 0000000..1552266 --- /dev/null +++ b/books/architecture/src/testing/spawning-monerod.md @@ -0,0 +1 @@ +# ⚪️ Spawning monerod diff --git a/books/architecture/src/zmq/intro.md b/books/architecture/src/zmq/intro.md new file mode 100644 index 0000000..0b668b3 --- /dev/null +++ b/books/architecture/src/zmq/intro.md @@ -0,0 +1 @@ +# ⚪️ ZMQ diff --git a/books/architecture/src/todo.md b/books/architecture/src/zmq/todo.md similarity index 100% rename from books/architecture/src/todo.md rename to books/architecture/src/zmq/todo.md diff --git a/books/protocol/src/SUMMARY.md b/books/protocol/src/SUMMARY.md index 1a4b1f0..682e0e7 100644 --- a/books/protocol/src/SUMMARY.md +++ b/books/protocol/src/SUMMARY.md @@ -23,5 +23,14 @@ - [Bulletproofs+](./consensus_rules/transactions/ring_ct/bulletproofs+.md) - [P2P Network](./p2p_network.md) - [Levin Protocol](./p2p_network/levin.md) - - [P2P Messages](./p2p_network/messages.md) + - [Admin Messages](./p2p_network/levin/admin.md) + - [Protocol Messages](./p2p_network/levin/protocol.md) + - [Common Types](./p2p_network/common_types.md) + - [Message Flows](./p2p_network/message_flows.md) + - [Handshake](./p2p_network/message_flows/handshake.md) + - [Timed Sync](./p2p_network/message_flows/timed_sync.md) + - [New Block](./p2p_network/message_flows/new_block.md) + - [New Transactions](./p2p_network/message_flows/new_transactions.md) + - [Chain Sync](./p2p_network/message_flows/chain_sync.md) + - [Get Blocks](./p2p_network/message_flows/get_blocks.md) - [Pruning](./pruning.md) diff --git a/books/protocol/src/consensus_rules.md b/books/protocol/src/consensus_rules.md index b06e4a8..e56e148 100644 --- a/books/protocol/src/consensus_rules.md +++ b/books/protocol/src/consensus_rules.md @@ -24,11 +24,12 @@ an Ed25519 point which is not the negative identity and with y coordinate fully Prime Order Point: a point in the prime subgroup. + PoW Hash: -the hash calculated by using the active proof of work function. +the hash calculated from the block hashing blob by using the active proof of work function. Block Hash: -the keccak hash of the block. +the keccak hash of the block hashing blob, this is a slightly different hashing blob than the one used to calculate the `PoW Hash`. Transaction Blob: the raw bytes of a serialized transaction. diff --git a/books/protocol/src/p2p_network.md b/books/protocol/src/p2p_network.md index e0d9a79..89bd1be 100644 --- a/books/protocol/src/p2p_network.md +++ b/books/protocol/src/p2p_network.md @@ -1,3 +1,3 @@ # P2P Network -This chapter contains descriptions of Monero's peer to peer network, including messages, flows, expected responses, etc. +This chapter contains descriptions of Monero's peer to peer network, including messages, flows, etc. diff --git a/books/protocol/src/p2p_network/common_types.md b/books/protocol/src/p2p_network/common_types.md new file mode 100644 index 0000000..0bf29cb --- /dev/null +++ b/books/protocol/src/p2p_network/common_types.md @@ -0,0 +1,116 @@ +# Common P2P Types + +This chapter contains definitions of types used in multiple P2P messages. + +### Support Flags + +Support flags specify any protocol extensions the peer supports, currently only the first bit is used: + +`FLUFFY_BLOCKS = 1` - for if the peer supports receiving fluffy blocks. + +### Basic Node Data [^b-n-d] { #basic-node-data } + +| Fields | Type | Description | +|------------------------|---------------------------------------|-------------------------------------------------------------------------------------------| +| `network_id` | A UUID (epee string) | A fixed constant value for a specific network (mainnet,testnet,stagenet) | +| `my_port` | u32 | The peer's inbound port, if the peer does not want inbound connections this should be `0` | +| `rpc_port` | u16 | The peer's RPC port, if the peer does not want inbound connections this should be `0` | +| `rpc_credits_per_hash` | u32 | States how much it costs to use this node in credits per hashes, `0` being free | +| `peer_id` | u64 | A fixed ID for the node, set to 1 for anonymity networks | +| `support_flags` | [support flags](#support-flags) (u32) | Specifies any protocol extensions the peer supports | + +### Core Sync Data [^c-s-d] { #core-sync-data } + +| Fields | Type | Description | +|-------------------------------|------------------------|---------------------------------------------------------------| +| `current_height` | u64 | The current chain height | +| `cumulative_difficulty` | u64 | The low 64 bits of the cumulative difficulty | +| `cumulative_difficulty_top64` | u64 | The high 64 bits of the cumulative difficulty | +| `top_id` | [u8; 32] (epee string) | The hash of the top block | +| `top_version` | u8 | The hardfork version of the top block | +| `pruning_seed` | u32 | THe pruning seed of the node, `0` if the node does no pruning | + +### Network Address [^network-addr] { #network-address } + +Network addresses are serialized differently than other types, the fields needed depend on the `type` field: + +| Fields | Type | Description | +| ------ | --------------------------------------- | ---------------- | +| `type` | u8 | The address type | +| `addr` | An object whose fields depend on `type` | The address | + +#### IPv4 + +`type = 1` + +| Fields | Type | Description | +| -------- | ---- | ---------------- | +| `m_ip` | u32 | The IPv4 address | +| `m_port` | u16 | The port | + + +#### IPv6 + +`type = 2` + +| Fields | Type | Description | +| -------- | ---------------------- | ---------------- | +| `addr` | [u8; 16] (epee string) | The IPv6 address | +| `m_port` | u16 | The port | + +#### Tor + +TODO: + +#### I2p + +TODO: + +### Peer List Entry Base [^pl-entry-base] { #peer-list-entry-base } + +| Fields | Type | Description | +|------------------------|-------------------------------------|-------------------------------------------------------------------------------------------------------| +| `adr` | [Network Address](#network-address) | The address of the peer | +| `id` | u64 | The random, self assigned, ID of this node | +| `last_seen` | i64 | A field marking when this peer was last seen, although this is zeroed before sending over the network | +| `pruning_seed` | u32 | This peer's pruning seed, `0` if the peer does no pruning | +| `rpc_port` | u16 | This node's RPC port, `0` if this peer has no public RPC port. | +| `rpc_credits_per_hash` | u32 | States how much it costs to use this node in credits per hashes, `0` being free | + +### Tx Blob Entry [^tb-entry] { #tx-blob-entry } + +| Fields | Type | Description | +| --------------- | ---------------------- | --------------------------------------- | +| `blob` | bytes (epee string) | The pruned tx blob | +| `prunable_hash` | [u8; 32] (epee string) | The hash of the prunable part of the tx | + +### Block Complete Entry [^bc-entry] { #block-complete-entry } + +| Fields | Type | Description | +|----------------|---------------------|-----------------------------------------------------------| +| `pruned` | bool | True if the block is pruned, false otherwise | +| `block` | bytes (epee string) | The block blob | +| `block_weight` | u64 | The block's weight | +| `txs` | depends on `pruned` | The transaction blobs, the exact type depends on `pruned` | + +If `pruned` is true: + +`txs` is a vector of [Tx Blob Entry](#tx-blob-entry) + +If `pruned` is false: + +`txs` is a vector of bytes. + +--- + +[^b-n-d]: + +[^c-s-d]: + +[^network-addr]: + +[^pl-entry-base]: + +[^tb-entry]: + +[^bc-entry]: diff --git a/books/protocol/src/p2p_network/epee.md b/books/protocol/src/p2p_network/epee.md deleted file mode 100644 index 2f8161d..0000000 --- a/books/protocol/src/p2p_network/epee.md +++ /dev/null @@ -1,3 +0,0 @@ -# Epee Binary Format - -The epee binary format is described here: TODO diff --git a/books/protocol/src/p2p_network/levin.md b/books/protocol/src/p2p_network/levin.md index de74660..ec92f7d 100644 --- a/books/protocol/src/p2p_network/levin.md +++ b/books/protocol/src/p2p_network/levin.md @@ -10,16 +10,16 @@ of buckets that will be combined into a single message. ### Bucket Format | Field | Type | Size (bytes) | -| ------ | ----------------------------- | ------------ | +|--------|-------------------------------|--------------| | Header | [BucketHeader](#bucketheader) | 33 | | Body | bytes | dynamic | ### BucketHeader -Format: +Format[^header-format]: | Field | Type | Size (bytes) | -| ---------------- | ------ | ------------ | +|------------------|--------|--------------| | Signature | LE u64 | 8 | | Size | LE u64 | 8 | | Expect Response | bool | 1 | @@ -32,7 +32,7 @@ Format: The signature field is fixed for every bucket and is used to tell apart peers running different protocols. -Its value should be `0x0101010101012101` +Its value should be `0x0101010101012101` [^signature] #### Size @@ -53,7 +53,7 @@ responses should be `1`. #### Flags -This is a bit-flag field that determines what type of bucket this is: +This is a bit-flag field that determines what type of bucket this is[^flags]: | Type | Bits set | | -------------- | ----------- | @@ -66,3 +66,17 @@ This is a bit-flag field that determines what type of bucket this is: #### Protocol Version This is a fixed value of 1. + +## Bucket Body + +All bucket bodies are serialized in the epee binary format which is described here: https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/docs/PORTABLE_STORAGE.md + +Exact message types are described in the next chapters. + +--- + +[^header-format]: + +[^signature]: + +[^flags]: diff --git a/books/protocol/src/p2p_network/levin/admin.md b/books/protocol/src/p2p_network/levin/admin.md new file mode 100644 index 0000000..a718646 --- /dev/null +++ b/books/protocol/src/p2p_network/levin/admin.md @@ -0,0 +1,102 @@ +# Admin Messages + +This chapter describes admin messages, and documents the current admin messages. Admin messages are a subset of messages that handle connection +creation, making sure connections are still alive, and sharing peer lists. + +## Levin + +All admin messages are in the request/response levin format. This means requests will set the [expect response bit](./levin.md#expect-response) and +responses will set the return code to [`1`](./levin.md#return-code). + +## Messages + +### Handshake + +ID: `1001`[^handshake-id] + +#### Request [^handshake-req] { #handshake-request } + +| Fields | Type | Description | +|----------------|-------------------------------------------------------|--------------------------------------| +| `node_data` | [basic node data](../common_types.md#basic-node-data) | Static information about our node | +| `payload_data` | [core sync data](../common_types.md#core-sync-data) | Information on the node's sync state | + +#### Response [^handshake-res] { #handshake-response } + +| Fields | Type | Description | +|----------------------|--------------------------------------------------------------------------|-----------------------------------------| +| `node_data` | [basic node data](../common_types.md#basic-node-data) | Static information about our node | +| `payload_data` | [core sync data](../common_types.md#core-sync-data) | Information on the node's sync state | +| `local_peerlist_new` | A Vec of [peer list entry base](../common_types.md#peer-list-entry-base) | A list of peers in the node's peer list | + +### Timed Sync + +ID: `1002`[^timed-sync-id] + +#### Request [^timed-sync-req] { #timed-sync-request } + +| Fields | Type | Description | +| -------------- | --------------------------------------------------- | ------------------------------------ | +| `payload_data` | [core sync data](../common_types.md#core-sync-data) | Information on the node's sync state | + +#### Response [^timed-sync-res] { #timed-sync-response } + +| Fields | Type | Description | +|----------------------|--------------------------------------------------------------------------|-----------------------------------------| +| `payload_data` | [core sync data](../common_types.md#core-sync-data) | Information on the node's sync state | +| `local_peerlist_new` | A Vec of [peer list entry base](../common_types.md#peer-list-entry-base) | A list of peers in the node's peer list | + +### Ping + +ID: `1003`[^ping-id] + +#### Request [^ping-req] { #ping-request } + +No data is serialized for a ping request. + +#### Response [^ping-res] { #ping-response } + +| Fields | Type | Description | +| --------- | ------ | --------------------------------- | +| `status` | string | Will be `OK` for successful pings | +| `peer_id` | u64 | The self assigned id of the peer | + +### Request Support Flags + +ID: `1007`[^support-flags] + +#### Request [^sf-req] { #support-flags-request } + +No data is serialized for a support flags request. + +#### Response [^sf-res] { #support-flags-response } + +| Fields | Type | Description | +| --------------- | ---- | ------------------------------------------------------------ | +| `support_flags` | u32 | The peer's [support flags](../common_types.md#support-flags) | + +--- + +[^handshake-id]: + +[^handshake-req]: + +[^handshake-res]: + +[^timed-sync-id]: + +[^timed-sync-req]: + +[^timed-sync-res]: + +[^ping-id]: + +[^ping-req]: + +[^ping-res]: + +[^support-flags]: + +[^sf-req]: + +[^sf-res]: diff --git a/books/protocol/src/p2p_network/levin/protocol.md b/books/protocol/src/p2p_network/levin/protocol.md new file mode 100644 index 0000000..a52ca1d --- /dev/null +++ b/books/protocol/src/p2p_network/levin/protocol.md @@ -0,0 +1,121 @@ +# Protocol Messages + +This chapter describes protocol messages, and documents the current protocol messages. Protocol messages are used to share protocol data +like blocks and transactions. + +## Levin + +All protocol messages are in the notification levin format. Although there are some messages that fall under requests/responses, levin will treat them as notifications. + +All admin messages are in the request/response levin format. This means requests will set the [expect response bit](../levin.md#expect-response) and +responses will set the return code to [`1`](../levin.md#return-code). + +## Messages + +### Notify New Block + +ID: `2001`[^notify-new-block-id] + +| Fields | Type | Description | +| --------------------------- | --------------------------------------------------------------- | ------------------------ | +| `b` | [Block Complete Entry](../common_types.md#block-complete-entry) | The full block | +| `current_blockchain_height` | u64 | The current chain height | + +### Notify New Transactions + +ID: `2002`[^notify-new-transactions-id] + +| Fields | Type | Description | +| ------------------- | ----------------- | ------------------------------------------------------ | +| `txs` | A vector of bytes | The txs | +| `_` | Bytes | Padding to prevent traffic volume analysis | +| `dandelionpp_fluff` | bool | True if this message contains fluff txs, false if stem | + +### Notify Request Get Objects + +ID: `2003`[^notify-request-get-objects-id] + +| Fields | Type | Description | +|----------|----------------------------------------------------|------------------------------------------------------------| +| `blocks` | A vector of [u8; 32] serialized as a single string | The block IDs requested | +| `prune` | bool | True if we want the blocks in pruned form, false otherwise | + +### Notify Response Get Objects + +ID: `2004`[^notify-response-get-objects-id] + +| Fields | Type | Description | +| --------------------------- | --------------------------------------------------------------------------- | ------------------------------ | +| `blocks` | A vector of [Block Complete Entry](../common_types.md#block-complete-entry) | The blocks that were requested | +| `missed_ids` | A vector of [u8; 32] serialized as a single string | IDs of any missed blocks | +| `current_blockchain_height` | u64 | The current blockchain height | + +### Notify Request Chain + +ID: `2006`[^notify-request-chain-id] + +| Fields | Type | Description | +|-------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------| +| `block_ids` | A vector of [u8; 32] serialized as a single string | A list of block IDs in reverse chronological order, the top and genesis block will always be included | +| `prune` | bool | True if we want the response to contain pruned blocks, false otherwise | + +### Notify Response Chain Entry + +ID: `2007`[^notify-response-chain-entry-id] + +| Fields | Type | Description | +|-------------------------------|----------------------------------------------------|------------------------------------------------| +| `start_height` | u64 | The start height of the entry | +| `total_height` | u64 | The height of the peer's blockchain | +| `cumulative_difficulty` | u64 | The low 64 bits of the cumulative difficulty | +| `cumulative_difficulty_top64` | u64 | The high 64 bits of the cumulative difficulty | +| `m_block_ids` | A vector of [u8; 32] serialized as a single string | The block IDs in this entry | +| `m_block_weights` | A vector of u64 serialized as a single string | The block weights | +| `first_block` | bytes (epee string) | The header of the first block in `m_block_ids` | + +### Notify New Fluffy Block + +ID: `2008`[^notify-new-fluffy-block-id] + +| Fields | Type | Description | +| --------------------------- | --------------------------------------------------------------- | ------------------------------------- | +| `b` | [Block Complete Entry](../common_types.md#block-complete-entry) | The block, may or may not contain txs | +| `current_blockchain_height` | u64 | The current chain height | + +### Notify Request Fluffy Missing Tx + +ID: `2009`[^notify-request-fluffy-missing-tx-id] + +| Fields | Type | Description | +|-----------------------------|-----------------------------------------------|--------------------------------------------| +| `block_hash` | [u8; 32] serialized as a string | The block hash txs are needed from | +| `current_blockchain_height` | u64 | The current chain height | +| `missing_tx_indices` | A vector of u64 serialized as a single string | The indices of the needed txs in the block | + +### Notify Get Txpool Compliment + +ID: `2010`[^notify-get-txpool-compliment-id] + +| Fields | Type | Description | +| -------- | ------------------------------------------- | ---------------------- | +| `hashes` | A vector of [u8; 32] serialized as a string | The current txpool txs | + +--- + +[^notify-new-block-id]: + +[^notify-new-transactions-id]: + +[^notify-request-get-objects-id]: + +[^notify-response-get-objects-id]: + +[^notify-request-chain-id]: + +[^notify-response-chain-entry-id]: + +[^notify-new-fluffy-block-id]: + +[^notify-request-fluffy-missing-tx-id]: + +[^notify-get-txpool-compliment-id]: diff --git a/books/protocol/src/p2p_network/message_flows.md b/books/protocol/src/p2p_network/message_flows.md new file mode 100644 index 0000000..8f1004c --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows.md @@ -0,0 +1,19 @@ +# Message Flows + +Message flows are sets of messages sent between peers, that achieve an identifiable goal, like a handshake. +Some message flows are complex, involving many message types, whereas others are simple, requiring only 1. + +The message flows here are not every possible request/response. + +When documenting checks on the messages, not all checks are documented, only the ones notable. This should help +to reduce the maintenance burden. + +## Different Flows + +- [Handshakes](./message_flows/handshake.md) +- [Timed Sync](./message_flows/timed_sync.md) +- [New Block](./message_flows/new_block.md) +- [New Transactions](./message_flows/new_transactions.md) +- [Chain Sync](./message_flows/chain_sync.md) +- [Get Blocks](./message_flows/get_blocks.md) + diff --git a/books/protocol/src/p2p_network/message_flows/chain_sync.md b/books/protocol/src/p2p_network/message_flows/chain_sync.md new file mode 100644 index 0000000..1b66132 --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows/chain_sync.md @@ -0,0 +1,28 @@ +# Chain Sync + +Chain sync is the first step in syncing a peer's blockchain, it allows a peers to find the split point in their chains and for the peer +to learn about the missing block IDs. + +## Flow + +The first step is for the initiating peer is to get its compact chain history. The compact chain history must be in reverse chronological +order, with the first block being the top block and the last the genesis, if the only block is the genesis then that only needs to be included +once. The blocks in the middle are not enforced to be at certain locations, however `monerod` will use the top 11 blocks and will then go power +of 2 offsets from then on, i.e. `{13, 17, 25, ...}` + +Then, with the compact history, the initiating peer will send a [request chain](../levin/protocol.md#notify-request-chain) message, the receiving +peer will then find the split point and return a [response chain entry](../levin/protocol.md#notify-response-chain-entry) message. + +The `response chain entry` will contain a list of block IDs with the first being a common ancestor and the rest being the next blocks that come after +that block in the peer's chain. + +### Response Checks + +- There must be an overlapping block.[^res-overlapping-block] +- The amount of returned block IDs must be less than `25,000`.[^res-max-blocks] + +--- + +[^res-overlapping-block]: + +[^res-max-blocks]: \ No newline at end of file diff --git a/books/protocol/src/p2p_network/message_flows/get_blocks.md b/books/protocol/src/p2p_network/message_flows/get_blocks.md new file mode 100644 index 0000000..eacca7f --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows/get_blocks.md @@ -0,0 +1,19 @@ +# Get Blocks + +The get block flow is used to download batches of blocks from a peer. + +## Flow + +The initiating peer needs a list of block IDs that the receiving peer has, this can be done with +the [chain sync flow](./chain_sync.md). + +With a list a block IDs the initiating peer will send a [get objects request](../levin/protocol.md#notify-request-get-objects) message, the receiving +peer will then respond with [get objects response](../levin/protocol.md#notify-response-get-objects). + +### Request Checks + +- The amount of blocks must be less than `100`.[^max-block-requests] + +--- + +[^max-block-requests]: diff --git a/books/protocol/src/p2p_network/message_flows/handshake.md b/books/protocol/src/p2p_network/message_flows/handshake.md new file mode 100644 index 0000000..2a4abe1 --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows/handshake.md @@ -0,0 +1,51 @@ +# Handshakes + +Handshakes are used to establish connections to peers. + +## Flow + +The default handshake flow is made up of the connecting peer sending a [handshake request](../levin/admin.md#handshake-request) and the +receiving peer responding with a [handshake response](../levin/admin.md#handshake-response). + +It should be noted that not all other messages are banned during handshakes, for example, support flag requests and even some protocol +requests can be sent. + +### Handshake Request Checks + +The receiving peer will check: + +- The `network_id` is network ID expected.[^network-id] +- The connection is an incoming connection.[^req-incoming-only] +- The peer hasn't already completed a handshake.[^double-handshake] +- If the network zone is public, then the `peer_id` must not be the same as ours.[^same-peer-id] +- The core sync data is not malformed.[^core-sync-data-checks] + +### Handshake Response Checks + +The initiating peer will check: + +- The `network_id` is network ID expected.[^res-network-id] +- The number of peers in the peer list is less than `250`.[^max-peer-list-res] +- All peers in the peer list are in the same zone.[^peers-all-in-same-zone] +- The core sync data is not malformed.[^core-sync-data-checks] +- If the network zone is public, then the `peer_id` must not be the same as ours.[^same-peer-id-res] + +--- + +[^network-id]: + +[^req-incoming-only]: + +[^double-handshake]: + +[^same-peer-id]: + +[^core-sync-data-checks]: + +[^res-network-id]: + +[^max-peer-list-res]: + +[^peers-all-in-same-zone]: + +[^same-peer-id-res]: diff --git a/books/protocol/src/p2p_network/message_flows/new_block.md b/books/protocol/src/p2p_network/message_flows/new_block.md new file mode 100644 index 0000000..452aa44 --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows/new_block.md @@ -0,0 +1,29 @@ +# New Block + +This is used whenever a new block is to be sent to peers. Only the fluffy block flow is described here, as the other method is deprecated. + +## Flow + +First the peer with the new block will send a [new fluffy block](../levin/protocol.md#notify-new-fluffy-block) notification, if the receiving +peer has all the txs in the block then the flow is complete. Otherwise the peer sends a [fluffy missing transactions request](../levin/protocol.md#notify-request-fluffy-missing-tx) +to the first peer, the first peer will then respond with again a [new fluffy block](../levin/protocol.md#notify-new-fluffy-block) notification but +with the transactions requested. + +```bob + + ,-----------. ,----------. + | Initiator | | Receiver | + `-----+-----' `-----+----' + | New Fluffy Block | + |-------------------->| + | | + | Missing Txs Request | + |<- - - - - - - - - - | + | | + | New Fluffy Block | + | - - - - - - - - - ->| + | | + | | + V v +``` + diff --git a/books/protocol/src/p2p_network/message_flows/new_transactions.md b/books/protocol/src/p2p_network/message_flows/new_transactions.md new file mode 100644 index 0000000..2a90a3f --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows/new_transactions.md @@ -0,0 +1,16 @@ +# New Transactions + +Monero uses the dandelion++ protocol to pass transactions around the network, this flow just describes the actual tx passing between nodes part. + +## Flow + +This flow is pretty simple, the txs are put into a [new transactions](../levin/protocol.md#notify-new-transactions) notification and sent to +peers. + +Hopefully in the future [this is changed](https://github.com/monero-project/monero/issues/9334). + +There must be no duplicate txs in the notification.[^duplicate-txs] + +--- + +[^duplicate-txs]: \ No newline at end of file diff --git a/books/protocol/src/p2p_network/message_flows/timed_sync.md b/books/protocol/src/p2p_network/message_flows/timed_sync.md new file mode 100644 index 0000000..4d258d7 --- /dev/null +++ b/books/protocol/src/p2p_network/message_flows/timed_sync.md @@ -0,0 +1,28 @@ +# Timed Syncs + +A timed sync request is sent every 60 seconds to make sure the connection is still live. + +## Flow + +First the timed sync initiator will send a [timed sync request](../levin/admin.md#timed-sync-request), the receiver will then +respond with a [timed sync response](../levin/admin.md#timed-sync-response) + +### Timed Sync Request Checks + +- The core sync data is not malformed.[^core-sync-data-checks] + + +### Timed Sync Response Checks + +- The core sync data is not malformed.[^core-sync-data-checks] +- The number of peers in the peer list is less than `250`.[^max-peer-list-res] +- All peers in the peer list are in the same zone.[^peers-all-in-same-zone] + +--- + +[^core-sync-data-checks]: + +[^max-peer-list-res]: + +[^peers-all-in-same-zone]: + diff --git a/books/protocol/src/p2p_network/messages.md b/books/protocol/src/p2p_network/messages.md deleted file mode 100644 index c3f1828..0000000 --- a/books/protocol/src/p2p_network/messages.md +++ /dev/null @@ -1,37 +0,0 @@ -# P2P Messages - -This chapter contains every P2P message. - -## Index - -## Types - -Types used in multiple P2P messages. - -### Support Flags - -Support flags specify any protocol extensions the peer supports, currently only the first bit is used: - -`FLUFFY_BLOCKS = 1` - for if the peer supports receiving fluffy blocks. - -### Basic Node Data - -| Fields | Type (Epee Type) | Description | -| ---------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- | -| `network_id` | A UUID (String) | A fixed constant value for a specific network (mainnet,testnet,stagenet) | -| `my_port` | u32 (u32) | The peer's inbound port, if the peer does not want inbound connections this should be `0` | -| `rpc_port` | u16 (u16) | The peer's RPC port, if the peer does not want inbound connections this should be `0` | -| `rpc_credits_per_hash` | u32 (u32) | TODO | -| `peer_id` | u64 (u64) | A fixed ID for the node, set to 1 for anonymity networks | -| `support_flags` | [support flags](#support-flags) (u32) | Specifies any protocol extensions the peer supports | - -## Messages - -### Handshake Requests - -levin command: 1001 - -| Fields | Type (Epee Type) | Description | -| ----------- | -------------------------------------------- | ----------- | -| `node_data` | [basic node data](#basic-node-data) (Object) | | -| | | | diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..cc94ec5 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +upper-case-acronyms-aggressive = true diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 624eb63..8b732a0 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -8,34 +8,36 @@ authors = ["Boog900"] repository = "https://github.com/Cuprate/cuprate/tree/main/consensus" [dependencies] -cuprate-helper = { path = "../helper", default-features = false, features = ["std", "asynch", "num"] } -cuprate-consensus-rules = { path = "./rules", features = ["rayon"] } -cuprate-types = { path = "../types" } +cuprate-helper = { workspace = true, default-features = false, features = ["std", "asynch", "num"] } +cuprate-consensus-rules = { workspace = true, features = ["rayon"] } +cuprate-types = { workspace = true } +cuprate-consensus-context = { workspace = true } +cfg-if = { workspace = true } thiserror = { workspace = true } tower = { workspace = true, features = ["util"] } tracing = { workspace = true, features = ["std", "attributes"] } futures = { workspace = true, features = ["std", "async-await"] } -randomx-rs = { workspace = true } monero-serai = { workspace = true, features = ["std"] } -multiexp = { workspace = true } -dalek-ff-group = { workspace = true } -curve25519-dalek = { workspace = true } rayon = { workspace = true } thread_local = { workspace = true } -tokio = { workspace = true, features = ["rt"] } -tokio-util = { workspace = true } hex = { workspace = true } +rand = { workspace = true } [dev-dependencies] -cuprate-test-utils = { path = "../test-utils" } -cuprate-consensus-rules = {path = "./rules", features = ["proptest"]} +cuprate-test-utils = { workspace = true } +cuprate-consensus-rules = { workspace = true, features = ["proptest"]} hex-literal = { workspace = true } +curve25519-dalek = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"]} +tokio-test = { workspace = true } proptest = { workspace = true } -proptest-derive = { workspace = true } \ No newline at end of file +proptest-derive = { workspace = true } + +[lints] +workspace = true diff --git a/consensus/context/Cargo.toml b/consensus/context/Cargo.toml new file mode 100644 index 0000000..7679046 --- /dev/null +++ b/consensus/context/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cuprate-consensus-context" +version = "0.1.0" +edition = "2021" +license = "MIT" +authors = ["SyntheticBird","Boog900"] + +[dependencies] +cuprate-consensus-rules = { workspace = true, features = ["proptest"]} +cuprate-helper = { workspace = true, default-features = false, features = ["std", "cast", "num", "asynch"] } +cuprate-types = { workspace = true, default-features = false, features = ["blockchain"] } + +futures = { workspace = true, features = ["std", "async-await"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"]} +tokio-util = { workspace = true } +tower = { workspace = true, features = ["util"] } +tracing = { workspace = true, features = ["std", "attributes"] } +thiserror = { workspace = true } + +monero-serai = { workspace = true, features = ["std"] } +randomx-rs = { workspace = true } +rayon = { workspace = true } +thread_local = { workspace = true } +hex = { workspace = true } + +[lints] +workspace = true diff --git a/consensus/context/src/alt_chains.rs b/consensus/context/src/alt_chains.rs new file mode 100644 index 0000000..df82ef3 --- /dev/null +++ b/consensus/context/src/alt_chains.rs @@ -0,0 +1,219 @@ +use std::{collections::HashMap, sync::Arc}; + +use tower::ServiceExt; + +use cuprate_consensus_rules::{blocks::BlockError, ConsensusError}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, ChainId, +}; + +use crate::{ + ContextCacheError, __private::Database, difficulty::DifficultyCache, rx_vms::RandomXVm, + weight::BlockWeightsCache, +}; + +pub(crate) mod sealed { + /// A token that should be hard to create from outside this crate. + /// + /// It is currently possible to safely create this from outside this crate, **DO NOT** rely on this + /// as it will be broken once we find a way to completely seal this. + #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] + pub struct AltChainRequestToken; +} + +/// The context cache of an alternative chain. +#[derive(Debug, Clone)] +pub struct AltChainContextCache { + /// The alt chain weight cache, [`None`] if it has not been built yet. + pub weight_cache: Option, + /// The alt chain difficulty cache, [`None`] if it has not been built yet. + pub difficulty_cache: Option, + + /// A cached RX VM. + pub cached_rx_vm: Option<(usize, Arc)>, + + /// The chain height of the alt chain. + pub chain_height: usize, + /// The top hash of the alt chain. + pub top_hash: [u8; 32], + /// The [`ChainId`] of the alt chain. + pub chain_id: Option, + /// The parent [`Chain`] of this alt chain. + pub parent_chain: Chain, +} + +impl AltChainContextCache { + /// Add a new block to the cache. + pub fn add_new_block( + &mut self, + height: usize, + block_hash: [u8; 32], + block_weight: usize, + long_term_block_weight: usize, + timestamp: u64, + ) { + if let Some(difficulty_cache) = &mut self.difficulty_cache { + difficulty_cache.new_block(height, timestamp, difficulty_cache.cumulative_difficulty()); + } + + if let Some(weight_cache) = &mut self.weight_cache { + weight_cache.new_block(height, block_weight, long_term_block_weight); + } + + self.chain_height += 1; + self.top_hash = block_hash; + } +} + +/// A map of top IDs to alt chains. +pub(crate) struct AltChainMap { + alt_cache_map: HashMap<[u8; 32], Box>, +} + +impl AltChainMap { + pub(crate) fn new() -> Self { + Self { + alt_cache_map: HashMap::new(), + } + } + + pub(crate) fn clear(&mut self) { + self.alt_cache_map.clear(); + } + + /// Add an alt chain cache to the map. + pub(crate) fn add_alt_cache( + &mut self, + prev_id: [u8; 32], + alt_cache: Box, + ) { + self.alt_cache_map.insert(prev_id, alt_cache); + } + + /// Attempts to take an [`AltChainContextCache`] from the map, returning [`None`] if no cache is + /// present. + pub(crate) async fn get_alt_chain_context( + &mut self, + prev_id: [u8; 32], + database: D, + ) -> Result, ContextCacheError> { + if let Some(cache) = self.alt_cache_map.remove(&prev_id) { + return Ok(cache); + } + + // find the block with hash == prev_id. + let BlockchainResponse::FindBlock(res) = database + .oneshot(BlockchainReadRequest::FindBlock(prev_id)) + .await? + else { + panic!("Database returned wrong response"); + }; + + let Some((parent_chain, top_height)) = res else { + // Couldn't find prev_id + return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into()); + }; + + Ok(Box::new(AltChainContextCache { + weight_cache: None, + difficulty_cache: None, + cached_rx_vm: None, + chain_height: top_height, + top_hash: prev_id, + chain_id: None, + parent_chain, + })) + } +} + +/// Builds a [`DifficultyCache`] for an alt chain. +pub(crate) async fn get_alt_chain_difficulty_cache( + prev_id: [u8; 32], + main_chain_difficulty_cache: &DifficultyCache, + mut database: D, +) -> Result { + // find the block with hash == prev_id. + let BlockchainResponse::FindBlock(res) = database + .ready() + .await? + .call(BlockchainReadRequest::FindBlock(prev_id)) + .await? + else { + panic!("Database returned wrong response"); + }; + + let Some((chain, top_height)) = res else { + // Can't find prev_id + return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into()); + }; + + Ok(match chain { + Chain::Main => { + // prev_id is in main chain, we can use the fast path and clone the main chain cache. + let mut difficulty_cache = main_chain_difficulty_cache.clone(); + difficulty_cache + .pop_blocks_main_chain( + difficulty_cache.last_accounted_height - top_height, + database, + ) + .await?; + + difficulty_cache + } + Chain::Alt(_) => { + // prev_id is in an alt chain, completely rebuild the cache. + DifficultyCache::init_from_chain_height( + top_height + 1, + main_chain_difficulty_cache.config, + database, + chain, + ) + .await? + } + }) +} + +/// Builds a [`BlockWeightsCache`] for an alt chain. +pub(crate) async fn get_alt_chain_weight_cache( + prev_id: [u8; 32], + main_chain_weight_cache: &BlockWeightsCache, + mut database: D, +) -> Result { + // find the block with hash == prev_id. + let BlockchainResponse::FindBlock(res) = database + .ready() + .await? + .call(BlockchainReadRequest::FindBlock(prev_id)) + .await? + else { + panic!("Database returned wrong response"); + }; + + let Some((chain, top_height)) = res else { + // Can't find prev_id + return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into()); + }; + + Ok(match chain { + Chain::Main => { + // prev_id is in main chain, we can use the fast path and clone the main chain cache. + let mut weight_cache = main_chain_weight_cache.clone(); + weight_cache + .pop_blocks_main_chain(weight_cache.tip_height - top_height, database) + .await?; + + weight_cache + } + Chain::Alt(_) => { + // prev_id is in an alt chain, completely rebuild the cache. + BlockWeightsCache::init_from_chain_height( + top_height + 1, + main_chain_weight_cache.config, + database, + chain, + ) + .await? + } + }) +} diff --git a/consensus/src/context/difficulty.rs b/consensus/context/src/difficulty.rs similarity index 61% rename from consensus/src/context/difficulty.rs rename to consensus/context/src/difficulty.rs index 9c8321f..3bbcb05 100644 --- a/consensus/src/context/difficulty.rs +++ b/consensus/context/src/difficulty.rs @@ -12,9 +12,12 @@ use tower::ServiceExt; use tracing::instrument; use cuprate_helper::num::median; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, +}; -use crate::{Database, ExtendedConsensusError, HardFork}; +use crate::{ContextCacheError, Database, HardFork}; /// The amount of blocks we account for to calculate difficulty const DIFFICULTY_WINDOW: usize = 720; @@ -28,11 +31,11 @@ const DIFFICULTY_LAG: usize = 15; /// Configuration for the difficulty cache. /// -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct DifficultyCacheConfig { - pub(crate) window: usize, - pub(crate) cut: usize, - pub(crate) lag: usize, + pub window: usize, + pub cut: usize, + pub lag: usize, } impl DifficultyCacheConfig { @@ -40,24 +43,24 @@ impl DifficultyCacheConfig { /// /// # Notes /// You probably do not need this, use [`DifficultyCacheConfig::main_net`] instead. - pub const fn new(window: usize, cut: usize, lag: usize) -> DifficultyCacheConfig { - DifficultyCacheConfig { window, cut, lag } + pub const fn new(window: usize, cut: usize, lag: usize) -> Self { + Self { window, cut, lag } } /// Returns the total amount of blocks we need to track to calculate difficulty - pub fn total_block_count(&self) -> u64 { - (self.window + self.lag).try_into().unwrap() + pub const fn total_block_count(&self) -> usize { + self.window + self.lag } /// The amount of blocks we account for after removing the outliers. - pub fn accounted_window_len(&self) -> usize { + pub const fn accounted_window_len(&self) -> usize { self.window - 2 * self.cut } /// Returns the config needed for [`Mainnet`](cuprate_helper::network::Network::Mainnet). This is also the /// config for all other current networks. - pub const fn main_net() -> DifficultyCacheConfig { - DifficultyCacheConfig { + pub const fn main_net() -> Self { + Self { window: DIFFICULTY_WINDOW, cut: DIFFICULTY_CUT, lag: DIFFICULTY_LAG, @@ -68,26 +71,26 @@ impl DifficultyCacheConfig { /// This struct is able to calculate difficulties from blockchain information. /// #[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct DifficultyCache { +pub struct DifficultyCache { /// The list of timestamps in the window. - /// len <= [`DIFFICULTY_BLOCKS_COUNT`] - pub(crate) timestamps: VecDeque, + pub timestamps: VecDeque, /// The current cumulative difficulty of the chain. - pub(crate) cumulative_difficulties: VecDeque, + pub cumulative_difficulties: VecDeque, /// The last height we accounted for. - pub(crate) last_accounted_height: u64, + pub last_accounted_height: usize, /// The config - pub(crate) config: DifficultyCacheConfig, + pub config: DifficultyCacheConfig, } impl DifficultyCache { /// Initialize the difficulty cache from the specified chain height. #[instrument(name = "init_difficulty_cache", level = "info", skip(database, config))] pub async fn init_from_chain_height( - chain_height: u64, + chain_height: usize, config: DifficultyCacheConfig, database: D, - ) -> Result { + chain: Chain, + ) -> Result { tracing::info!("Initializing difficulty cache this may take a while."); let mut block_start = chain_height.saturating_sub(config.total_block_count()); @@ -98,7 +101,9 @@ impl DifficultyCache { } let (timestamps, cumulative_difficulties) = - get_blocks_in_pow_info(database.clone(), block_start..chain_height).await?; + get_blocks_in_pow_info(database.clone(), block_start..chain_height, chain).await?; + + debug_assert_eq!(timestamps.len(), chain_height - block_start); tracing::info!( "Current chain height: {}, accounting for {} blocks timestamps", @@ -106,7 +111,7 @@ impl DifficultyCache { timestamps.len() ); - let diff = DifficultyCache { + let diff = Self { timestamps, cumulative_difficulties, last_accounted_height: chain_height - 1, @@ -116,8 +121,68 @@ impl DifficultyCache { Ok(diff) } + /// Pop some blocks from the top of the cache. + /// + /// The cache will be returned to the state it would have been in `numb_blocks` ago. + /// + /// # Invariant + /// + /// This _must_ only be used on a main-chain cache. + #[instrument(name = "pop_blocks_diff_cache", skip_all, fields(numb_blocks = numb_blocks))] + pub async fn pop_blocks_main_chain( + &mut self, + numb_blocks: usize, + database: D, + ) -> Result<(), ContextCacheError> { + let Some(retained_blocks) = self.timestamps.len().checked_sub(numb_blocks) else { + // More blocks to pop than we have in the cache, so just restart a new cache. + *self = Self::init_from_chain_height( + self.last_accounted_height - numb_blocks + 1, + self.config, + database, + Chain::Main, + ) + .await?; + + return Ok(()); + }; + + let current_chain_height = self.last_accounted_height + 1; + + let mut new_start_height = current_chain_height + .saturating_sub(self.config.total_block_count()) + .saturating_sub(numb_blocks); + + // skip the genesis block. + if new_start_height == 0 { + new_start_height = 1; + } + + let (mut timestamps, mut cumulative_difficulties) = get_blocks_in_pow_info( + database, + new_start_height + // current_chain_height - self.timestamps.len() blocks are already in the cache. + ..(current_chain_height - self.timestamps.len()), + Chain::Main, + ) + .await?; + + self.timestamps.drain(retained_blocks..); + self.cumulative_difficulties.drain(retained_blocks..); + timestamps.append(&mut self.timestamps); + cumulative_difficulties.append(&mut self.cumulative_difficulties); + + self.timestamps = timestamps; + self.cumulative_difficulties = cumulative_difficulties; + self.last_accounted_height -= numb_blocks; + + assert_eq!(self.timestamps.len(), self.cumulative_difficulties.len()); + + Ok(()) + } + /// Add a new block to the difficulty cache. - pub fn new_block(&mut self, height: u64, timestamp: u64, cumulative_difficulty: u128) { + pub fn new_block(&mut self, height: usize, timestamp: u64, cumulative_difficulty: u128) { assert_eq!(self.last_accounted_height + 1, height); self.last_accounted_height += 1; @@ -129,7 +194,7 @@ impl DifficultyCache { self.cumulative_difficulties .push_back(cumulative_difficulty); - if u64::try_from(self.timestamps.len()).unwrap() > self.config.total_block_count() { + if self.timestamps.len() > self.config.total_block_count() { self.timestamps.pop_front(); self.cumulative_difficulties.pop_front(); } @@ -137,8 +202,8 @@ impl DifficultyCache { /// Returns the required difficulty for the next block. /// - /// See: https://cuprate.github.io/monero-book/consensus_rules/blocks/difficulty.html#calculating-difficulty - pub fn next_difficulty(&self, hf: &HardFork) -> u128 { + /// See: + pub fn next_difficulty(&self, hf: HardFork) -> u128 { next_difficulty( &self.config, &self.timestamps, @@ -157,7 +222,7 @@ impl DifficultyCache { pub fn next_difficulties( &self, blocks: Vec<(u64, HardFork)>, - current_hf: &HardFork, + current_hf: HardFork, ) -> Vec { let mut timestamps = self.timestamps.clone(); let mut cumulative_difficulties = self.cumulative_difficulties.clone(); @@ -166,26 +231,22 @@ impl DifficultyCache { difficulties.push(self.next_difficulty(current_hf)); - let mut diff_info_popped = Vec::new(); - for (new_timestamp, hf) in blocks { timestamps.push_back(new_timestamp); let last_cum_diff = cumulative_difficulties.back().copied().unwrap_or(1); cumulative_difficulties.push_back(last_cum_diff + *difficulties.last().unwrap()); - if u64::try_from(timestamps.len()).unwrap() > self.config.total_block_count() { - diff_info_popped.push(( - timestamps.pop_front().unwrap(), - cumulative_difficulties.pop_front().unwrap(), - )); + if timestamps.len() > self.config.total_block_count() { + timestamps.pop_front().unwrap(); + cumulative_difficulties.pop_front().unwrap(); } difficulties.push(next_difficulty( &self.config, ×tamps, &cumulative_difficulties, - &hf, + hf, )); } @@ -196,22 +257,21 @@ impl DifficultyCache { /// /// Will return [`None`] if there aren't enough blocks. pub fn median_timestamp(&self, numb_blocks: usize) -> Option { - let mut timestamps = - if self.last_accounted_height + 1 == u64::try_from(numb_blocks).unwrap() { - // if the chain height is equal to `numb_blocks` add the genesis block. - // otherwise if the chain height is less than `numb_blocks` None is returned - // and if its more than it would be excluded from calculations. - let mut timestamps = self.timestamps.clone(); - // all genesis blocks have a timestamp of 0. - // https://cuprate.github.io/monero-book/consensus_rules/genesis_block.html - timestamps.push_front(0); - timestamps.into() - } else { - self.timestamps - .range(self.timestamps.len().checked_sub(numb_blocks)?..) - .copied() - .collect::>() - }; + let mut timestamps = if self.last_accounted_height + 1 == numb_blocks { + // if the chain height is equal to `numb_blocks` add the genesis block. + // otherwise if the chain height is less than `numb_blocks` None is returned + // and if it's more it would be excluded from calculations. + let mut timestamps = self.timestamps.clone(); + // all genesis blocks have a timestamp of 0. + // https://cuprate.github.io/monero-book/consensus_rules/genesis_block.html + timestamps.push_front(0); + timestamps.into() + } else { + self.timestamps + .range(self.timestamps.len().checked_sub(numb_blocks)?..) + .copied() + .collect::>() + }; timestamps.sort_unstable(); debug_assert_eq!(timestamps.len(), numb_blocks); @@ -230,12 +290,12 @@ impl DifficultyCache { } } -/// Calculates the next difficulty with the inputted config/timestamps/cumulative_difficulties. +/// Calculates the next difficulty with the inputted `config/timestamps/cumulative_difficulties`. fn next_difficulty( config: &DifficultyCacheConfig, timestamps: &VecDeque, cumulative_difficulties: &VecDeque, - hf: &HardFork, + hf: HardFork, ) -> u128 { if timestamps.len() <= 1 { return 1; @@ -268,8 +328,8 @@ fn next_difficulty( time_span = 1; } - // TODO: do checked operations here and unwrap so we don't silently overflow? - (windowed_work * hf.block_time().as_secs() as u128 + time_span - 1) / time_span + // TODO: do `checked_mul` here and unwrap so we don't silently overflow? + (windowed_work * u128::from(hf.block_time().as_secs())).div_ceil(time_span) } /// Get the start and end of the window to calculate difficulty. @@ -298,12 +358,16 @@ fn get_window_start_and_end( #[instrument(name = "get_blocks_timestamps", skip(database), level = "info")] async fn get_blocks_in_pow_info( database: D, - block_heights: Range, -) -> Result<(VecDeque, VecDeque), ExtendedConsensusError> { + block_heights: Range, + chain: Chain, +) -> Result<(VecDeque, VecDeque), ContextCacheError> { tracing::info!("Getting blocks timestamps"); - let BCResponse::BlockExtendedHeaderInRange(ext_header) = database - .oneshot(BCReadRequest::BlockExtendedHeaderInRange(block_heights)) + let BlockchainResponse::BlockExtendedHeaderInRange(ext_header) = database + .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange( + block_heights, + chain, + )) .await? else { panic!("Database sent incorrect response"); diff --git a/consensus/src/context/hardforks.rs b/consensus/context/src/hardforks.rs similarity index 57% rename from consensus/src/context/hardforks.rs rename to consensus/context/src/hardforks.rs index 92182c7..e6af492 100644 --- a/consensus/src/context/hardforks.rs +++ b/consensus/context/src/hardforks.rs @@ -4,28 +4,31 @@ use tower::ServiceExt; use tracing::instrument; use cuprate_consensus_rules::{HFVotes, HFsInfo, HardFork}; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, +}; -use crate::{Database, ExtendedConsensusError}; +use crate::{ContextCacheError, Database}; /// The default amount of hard-fork votes to track to decide on activation of a hard-fork. /// /// ref: -const DEFAULT_WINDOW_SIZE: u64 = 10080; // supermajority window check length - a week +const DEFAULT_WINDOW_SIZE: usize = 10080; // supermajority window check length - a week /// Configuration for hard-forks. /// -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct HardForkConfig { /// The network we are on. - pub(crate) info: HFsInfo, + pub info: HFsInfo, /// The amount of votes we are taking into account to decide on a fork activation. - pub(crate) window: u64, + pub window: usize, } impl HardForkConfig { /// Config for main-net. - pub const fn main_net() -> HardForkConfig { + pub const fn main_net() -> Self { Self { info: HFsInfo::main_net(), window: DEFAULT_WINDOW_SIZE, @@ -33,7 +36,7 @@ impl HardForkConfig { } /// Config for stage-net. - pub const fn stage_net() -> HardForkConfig { + pub const fn stage_net() -> Self { Self { info: HFsInfo::stage_net(), window: DEFAULT_WINDOW_SIZE, @@ -41,7 +44,7 @@ impl HardForkConfig { } /// Config for test-net. - pub const fn test_net() -> HardForkConfig { + pub const fn test_net() -> Self { Self { info: HFsInfo::test_net(), window: DEFAULT_WINDOW_SIZE, @@ -50,56 +53,51 @@ impl HardForkConfig { } /// A struct that keeps track of the current hard-fork and current votes. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct HardForkState { /// The current active hard-fork. - pub(crate) current_hardfork: HardFork, + pub current_hardfork: HardFork, /// The hard-fork config. - pub(crate) config: HardForkConfig, + pub config: HardForkConfig, /// The votes in the current window. - pub(crate) votes: HFVotes, + pub votes: HFVotes, /// The last block height accounted for. - pub(crate) last_height: u64, + pub last_height: usize, } impl HardForkState { /// Initialize the [`HardForkState`] from the specified chain height. #[instrument(name = "init_hardfork_state", skip(config, database), level = "info")] pub async fn init_from_chain_height( - chain_height: u64, + chain_height: usize, config: HardForkConfig, mut database: D, - ) -> Result { + ) -> Result { tracing::info!("Initializing hard-fork state this may take a while."); let block_start = chain_height.saturating_sub(config.window); - let votes = get_votes_in_range( - database.clone(), - block_start..chain_height, - usize::try_from(config.window).unwrap(), - ) - .await?; + let votes = + get_votes_in_range(database.clone(), block_start..chain_height, config.window).await?; if chain_height > config.window { - debug_assert_eq!(votes.total_votes(), config.window) + debug_assert_eq!(votes.total_votes(), config.window); } - let BCResponse::BlockExtendedHeader(ext_header) = database + let BlockchainResponse::BlockExtendedHeader(ext_header) = database .ready() .await? - .call(BCReadRequest::BlockExtendedHeader(chain_height - 1)) + .call(BlockchainReadRequest::BlockExtendedHeader(chain_height - 1)) .await? else { panic!("Database sent incorrect response!"); }; - let current_hardfork = - HardFork::from_version(ext_header.version).expect("Stored block has invalid hardfork"); + let current_hardfork = ext_header.version; - let mut hfs = HardForkState { + let mut hfs = Self { config, current_hardfork, votes, @@ -117,8 +115,51 @@ impl HardForkState { Ok(hfs) } + /// Pop some blocks from the top of the cache. + /// + /// The cache will be returned to the state it would have been in `numb_blocks` ago. + /// + /// # Invariant + /// + /// This _must_ only be used on a main-chain cache. + pub async fn pop_blocks_main_chain( + &mut self, + numb_blocks: usize, + database: D, + ) -> Result<(), ContextCacheError> { + let Some(retained_blocks) = self.votes.total_votes().checked_sub(self.config.window) else { + *self = Self::init_from_chain_height( + self.last_height + 1 - numb_blocks, + self.config, + database, + ) + .await?; + + return Ok(()); + }; + + let current_chain_height = self.last_height + 1; + + let oldest_votes = get_votes_in_range( + database, + current_chain_height + .saturating_sub(self.config.window) + .saturating_sub(numb_blocks) + ..current_chain_height + .saturating_sub(numb_blocks) + .saturating_sub(retained_blocks), + numb_blocks, + ) + .await?; + + self.votes.reverse_blocks(numb_blocks, oldest_votes); + self.last_height -= numb_blocks; + + Ok(()) + } + /// Add a new block to the cache. - pub fn new_block(&mut self, vote: HardFork, height: u64) { + pub fn new_block(&mut self, vote: HardFork, height: usize) { // We don't _need_ to take in `height` but it's for safety, so we don't silently loose track // of blocks. assert_eq!(self.last_height + 1, height); @@ -142,7 +183,7 @@ impl HardForkState { /// Checks if the next hard-fork should be activated and activates it if it should. /// - /// https://cuprate.github.io/monero-docs/consensus_rules/hardforks.html#accepting-a-fork + /// fn check_set_new_hf(&mut self) { self.current_hardfork = self.votes.current_fork( &self.current_hardfork, @@ -153,7 +194,7 @@ impl HardForkState { } /// Returns the current hard-fork. - pub fn current_hardfork(&self) -> HardFork { + pub const fn current_hardfork(&self) -> HardFork { self.current_hardfork } } @@ -162,19 +203,22 @@ impl HardForkState { #[instrument(name = "get_votes", skip(database))] async fn get_votes_in_range( database: D, - block_heights: Range, + block_heights: Range, window_size: usize, -) -> Result { +) -> Result { let mut votes = HFVotes::new(window_size); - let BCResponse::BlockExtendedHeaderInRange(vote_list) = database - .oneshot(BCReadRequest::BlockExtendedHeaderInRange(block_heights)) + let BlockchainResponse::BlockExtendedHeaderInRange(vote_list) = database + .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange( + block_heights, + Chain::Main, + )) .await? else { panic!("Database sent incorrect response!"); }; - for hf_info in vote_list.into_iter() { + for hf_info in vote_list { votes.add_vote_for_hf(&HardFork::from_vote(hf_info.vote)); } diff --git a/consensus/src/context.rs b/consensus/context/src/lib.rs similarity index 51% rename from consensus/src/context.rs rename to consensus/context/src/lib.rs index 0752b8b..acc4d23 100644 --- a/consensus/src/context.rs +++ b/consensus/context/src/lib.rs @@ -1,9 +1,13 @@ //! # Blockchain Context //! -//! This module contains a service to get cached context from the blockchain: [`BlockChainContext`]. +//! This crate contains a service to get cached context from the blockchain: [`BlockChainContext`]. //! This is used during contextual validation, this does not have all the data for contextual validation //! (outputs) for that you will need a [`Database`]. -//! + +// Used in documentation references for [`BlockChainContextRequest`] +// FIXME: should we pull in a dependency just to link docs? +use monero_serai as _; + use std::{ cmp::min, collections::HashMap, @@ -14,29 +18,36 @@ use std::{ }; use futures::{channel::oneshot, FutureExt}; +use monero_serai::block::Block; use tokio::sync::mpsc; use tokio_util::sync::PollSender; use tower::Service; -use cuprate_consensus_rules::{blocks::ContextToVerifyBlock, current_unix_timestamp, HardFork}; +use cuprate_consensus_rules::{ + blocks::ContextToVerifyBlock, current_unix_timestamp, ConsensusError, HardFork, +}; -use crate::{Database, ExtendedConsensusError}; - -pub(crate) mod difficulty; -pub(crate) mod hardforks; -pub(crate) mod rx_vms; -pub(crate) mod weight; +pub mod difficulty; +pub mod hardforks; +pub mod rx_vms; +pub mod weight; +mod alt_chains; mod task; mod tokens; +use cuprate_types::{Chain, ChainInfo, FeeEstimate, HardForkInfo}; +use difficulty::DifficultyCache; +use rx_vms::RandomXVm; +use weight::BlockWeightsCache; + +pub use alt_chains::{sealed::AltChainRequestToken, AltChainContextCache}; pub use difficulty::DifficultyCacheConfig; pub use hardforks::HardForkConfig; -use rx_vms::RandomXVM; pub use tokens::*; pub use weight::BlockWeightsCacheConfig; -const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60; +pub const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60; /// Config for the context service. pub struct ContextConfig { @@ -50,8 +61,8 @@ pub struct ContextConfig { impl ContextConfig { /// Get the config for main-net. - pub fn main_net() -> ContextConfig { - ContextConfig { + pub const fn main_net() -> Self { + Self { hard_fork_cfg: HardForkConfig::main_net(), difficulty_cfg: DifficultyCacheConfig::main_net(), weights_config: BlockWeightsCacheConfig::main_net(), @@ -59,8 +70,8 @@ impl ContextConfig { } /// Get the config for stage-net. - pub fn stage_net() -> ContextConfig { - ContextConfig { + pub const fn stage_net() -> Self { + Self { hard_fork_cfg: HardForkConfig::stage_net(), // These 2 have the same config as main-net. difficulty_cfg: DifficultyCacheConfig::main_net(), @@ -69,8 +80,8 @@ impl ContextConfig { } /// Get the config for test-net. - pub fn test_net() -> ContextConfig { - ContextConfig { + pub const fn test_net() -> Self { + Self { hard_fork_cfg: HardForkConfig::test_net(), // These 2 have the same config as main-net. difficulty_cfg: DifficultyCacheConfig::main_net(), @@ -85,7 +96,7 @@ impl ContextConfig { pub async fn initialize_blockchain_context( cfg: ContextConfig, database: D, -) -> Result +) -> Result where D: Database + Clone + Send + Sync + 'static, D::Future: Send + 'static, @@ -149,7 +160,7 @@ impl RawBlockChainContext { /// Returns the next blocks long term weight from its block weight. pub fn next_block_long_term_weight(&self, block_weight: usize) -> usize { weight::calculate_block_long_term_weight( - &self.current_hf, + self.current_hf, block_weight, self.median_long_term_weight, ) @@ -185,7 +196,7 @@ impl BlockChainContext { } /// Returns the blockchain context without checking the validity token. - pub fn unchecked_blockchain_context(&self) -> &RawBlockChainContext { + pub const fn unchecked_blockchain_context(&self) -> &RawBlockChainContext { &self.raw } } @@ -196,7 +207,7 @@ pub struct NewBlockData { /// The blocks hash. pub block_hash: [u8; 32], /// The blocks height. - pub height: u64, + pub height: usize, /// The blocks timestamp. pub timestamp: u64, /// The blocks weight. @@ -215,35 +226,179 @@ pub struct NewBlockData { #[derive(Debug, Clone)] pub enum BlockChainContextRequest { /// Get the current blockchain context. - GetContext, - /// Gets the current RandomX VM. - GetCurrentRxVm, + Context, + + /// Gets all the current `RandomX` VMs. + CurrentRxVms, + /// Get the next difficulties for these blocks. /// /// Inputs: a list of block timestamps and hfs /// /// The number of difficulties returned will be one more than the number of timestamps/ hfs. BatchGetDifficulties(Vec<(u64, HardFork)>), + /// Add a VM that has been created outside of the blockchain context service to the blockchain context. /// This is useful when batch calculating POW as you may need to create a new VM if you batch a lot of blocks together, /// it would be wasteful to then not give this VM to the context service to then use when it needs to init a VM with the same /// seed. /// /// This should include the seed used to init this VM and the VM. - NewRXVM(([u8; 32], Arc)), + NewRXVM(([u8; 32], Arc)), + /// A request to add a new block to the cache. Update(NewBlockData), + + /// Pop blocks from the cache to the specified height. + PopBlocks { + /// The number of blocks to pop from the top of the chain. + /// + /// # Panics + /// + /// This will panic if the number of blocks will pop the genesis block. + numb_blocks: usize, + }, + + /// Get information on a certain hardfork. + HardForkInfo(HardFork), + + /// Get the current fee estimate. + FeeEstimate { + /// TODO + grace_blocks: u64, + }, + + /// Calculate proof-of-work for this block. + CalculatePow { + /// The hardfork of the protocol at this block height. + hardfork: HardFork, + /// The height of the block. + height: usize, + /// The block data. + /// + /// This is boxed because [`Block`] causes this enum to be 1200 bytes, + /// where the 2nd variant is only 96 bytes. + block: Box, + /// The seed hash for the proof-of-work. + seed_hash: [u8; 32], + }, + + /// Clear the alt chain context caches. + ClearAltCache, + + /// Get information on all the current alternate chains. + AltChains, + + //----------------------------------------------------------------------------------------------------------- AltChainRequests + /// A request for an alt chain context cache. + /// + /// This variant is private and is not callable from outside this crate, the block verifier service will + /// handle getting the alt cache. + AltChainContextCache { + /// The previous block field in a [`BlockHeader`](monero_serai::block::BlockHeader). + prev_id: [u8; 32], + /// An internal token to prevent external crates calling this request. + _token: AltChainRequestToken, + }, + + /// A request for a difficulty cache of an alternative chin. + /// + /// This variant is private and is not callable from outside this crate, the block verifier service will + /// handle getting the difficulty cache of an alt chain. + AltChainDifficultyCache { + /// The previous block field in a [`BlockHeader`](monero_serai::block::BlockHeader). + prev_id: [u8; 32], + /// An internal token to prevent external crates calling this request. + _token: AltChainRequestToken, + }, + + /// A request for a block weight cache of an alternative chin. + /// + /// This variant is private and is not callable from outside this crate, the block verifier service will + /// handle getting the weight cache of an alt chain. + AltChainWeightCache { + /// The previous block field in a [`BlockHeader`](monero_serai::block::BlockHeader). + prev_id: [u8; 32], + /// An internal token to prevent external crates calling this request. + _token: AltChainRequestToken, + }, + + /// A request for a RX VM for an alternative chin. + /// + /// Response variant: [`BlockChainContextResponse::AltChainRxVM`]. + /// + /// This variant is private and is not callable from outside this crate, the block verifier service will + /// handle getting the randomX VM of an alt chain. + AltChainRxVM { + /// The height the `RandomX` VM is needed for. + height: usize, + /// The chain to look in for the seed. + chain: Chain, + /// An internal token to prevent external crates calling this request. + _token: AltChainRequestToken, + }, + + /// A request to add an alt chain context cache to the context cache. + /// + /// This variant is private and is not callable from outside this crate, the block verifier service will + /// handle returning the alt cache to the context service. + AddAltChainContextCache { + /// The previous block field in a [`BlockHeader`](monero_serai::block::BlockHeader). + prev_id: [u8; 32], + /// The cache. + cache: Box, + /// An internal token to prevent external crates calling this request. + _token: AltChainRequestToken, + }, } pub enum BlockChainContextResponse { - /// Blockchain context response. + /// A generic Ok response. + /// + /// Response to: + /// - [`BlockChainContextRequest::NewRXVM`] + /// - [`BlockChainContextRequest::Update`] + /// - [`BlockChainContextRequest::PopBlocks`] + /// - [`BlockChainContextRequest::ClearAltCache`] + /// - [`BlockChainContextRequest::AddAltChainContextCache`] + Ok, + + /// Response to [`BlockChainContextRequest::Context`] Context(BlockChainContext), - /// A map of seed height to RandomX VMs. - RxVms(HashMap>), + + /// Response to [`BlockChainContextRequest::CurrentRxVms`] + /// + /// A map of seed height to `RandomX` VMs. + RxVms(HashMap>), + /// A list of difficulties. BatchDifficulties(Vec), - /// Ok response. - Ok, + + /// Response to [`BlockChainContextRequest::HardForkInfo`] + HardForkInfo(HardForkInfo), + + /// Response to [`BlockChainContextRequest::FeeEstimate`] + FeeEstimate(FeeEstimate), + + /// Response to [`BlockChainContextRequest::CalculatePow`] + CalculatePow([u8; 32]), + + /// Response to [`BlockChainContextRequest::AltChains`] + /// + /// If the inner [`Vec::is_empty`], there were no alternate chains. + AltChains(Vec), + + /// An alt chain context cache. + AltChainContextCache(Box), + + /// A difficulty cache for an alt chain. + AltChainDifficultyCache(DifficultyCache), + + /// A randomX VM for an alt chain. + AltChainRxVM(Arc), + + /// A weight cache for an alt chain + AltChainWeightCache(BlockWeightsCache), } /// The blockchain context service. @@ -282,3 +437,52 @@ impl Service for BlockChainContextService { .boxed() } } + +#[derive(Debug, thiserror::Error)] +pub enum ContextCacheError { + /// A consensus error. + #[error("{0}")] + ConErr(#[from] ConsensusError), + /// A database error. + #[error("Database error: {0}")] + DBErr(#[from] tower::BoxError), +} + +use __private::Database; + +pub mod __private { + use std::future::Future; + + use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse}; + + /// A type alias trait used to represent a database, so we don't have to write [`tower::Service`] bounds + /// everywhere. + /// + /// Automatically implemented for: + /// ```ignore + /// tower::Service + /// ``` + pub trait Database: + tower::Service< + BlockchainReadRequest, + Response = BlockchainResponse, + Error = tower::BoxError, + Future = Self::Future2, + > + { + type Future2: Future> + Send + 'static; + } + + impl< + T: tower::Service< + BlockchainReadRequest, + Response = BlockchainResponse, + Error = tower::BoxError, + >, + > Database for T + where + T::Future: Future> + Send + 'static, + { + type Future2 = T::Future; + } +} diff --git a/consensus/src/context/rx_vms.rs b/consensus/context/src/rx_vms.rs similarity index 65% rename from consensus/src/context/rx_vms.rs rename to consensus/context/src/rx_vms.rs index 08ecb95..803bb32 100644 --- a/consensus/src/context/rx_vms.rs +++ b/consensus/context/src/rx_vms.rs @@ -1,6 +1,6 @@ -//! RandomX VM Cache +//! `RandomX` VM Cache //! -//! This module keeps track of the RandomX VM to calculate the next blocks PoW, if the block needs a randomX VM and potentially +//! This module keeps track of the `RandomX` VM to calculate the next blocks proof-of-work, if the block needs a randomX VM and potentially //! more VMs around this height. //! use std::{ @@ -9,36 +9,40 @@ use std::{ }; use futures::{stream::FuturesOrdered, StreamExt}; -use randomx_rs::{RandomXCache, RandomXError, RandomXFlag, RandomXVM as VMInner}; +use randomx_rs::{RandomXCache, RandomXError, RandomXFlag, RandomXVM as VmInner}; use rayon::prelude::*; use thread_local::ThreadLocal; use tower::ServiceExt; use tracing::instrument; +use cuprate_consensus_rules::blocks::randomx_seed_height; use cuprate_consensus_rules::{ blocks::{is_randomx_seed_height, RandomX, RX_SEEDHASH_EPOCH_BLOCKS}, HardFork, }; use cuprate_helper::asynch::rayon_spawn_async; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, +}; -use crate::{Database, ExtendedConsensusError}; +use crate::{ContextCacheError, Database}; /// The amount of randomX VMs to keep in the cache. -const RX_SEEDS_CACHED: usize = 2; +pub const RX_SEEDS_CACHED: usize = 2; /// A multithreaded randomX VM. #[derive(Debug)] -pub struct RandomXVM { - /// These RandomX VMs all share the same cache. - vms: ThreadLocal, - /// The RandomX cache. +pub struct RandomXVm { + /// These `RandomX` VMs all share the same cache. + vms: ThreadLocal, + /// The `RandomX` cache. cache: RandomXCache, - /// The flags used to start the RandomX VMs. + /// The flags used to start the `RandomX` VMs. flags: RandomXFlag, } -impl RandomXVM { +impl RandomXVm { /// Create a new multithreaded randomX VM with the provided seed. pub fn new(seed: &[u8; 32]) -> Result { // TODO: allow passing in flags. @@ -46,7 +50,7 @@ impl RandomXVM { let cache = RandomXCache::new(flags, seed.as_slice())?; - Ok(RandomXVM { + Ok(Self { vms: ThreadLocal::new(), cache, flags, @@ -54,43 +58,44 @@ impl RandomXVM { } } -impl RandomX for RandomXVM { +impl RandomX for RandomXVm { type Error = RandomXError; fn calculate_hash(&self, buf: &[u8]) -> Result<[u8; 32], Self::Error> { self.vms - .get_or_try(|| VMInner::new(self.flags, Some(self.cache.clone()), None))? + .get_or_try(|| VmInner::new(self.flags, Some(self.cache.clone()), None))? .calculate_hash(buf) .map(|out| out.try_into().unwrap()) } } -/// The randomX VMs cache, keeps the VM needed to calculate the current block's PoW hash (if a VM is needed) and a +/// The randomX VMs cache, keeps the VM needed to calculate the current block's proof-of-work hash (if a VM is needed) and a /// couple more around this VM. #[derive(Clone, Debug)] -pub struct RandomXVMCache { +pub struct RandomXVmCache { /// The top [`RX_SEEDS_CACHED`] RX seeds. - pub(crate) seeds: VecDeque<(u64, [u8; 32])>, + pub seeds: VecDeque<(usize, [u8; 32])>, /// The VMs for `seeds` (if after hf 12, otherwise this will be empty). - pub(crate) vms: HashMap>, + pub vms: HashMap>, /// A single cached VM that was given to us from a part of Cuprate. - pub(crate) cached_vm: Option<([u8; 32], Arc)>, + pub cached_vm: Option<([u8; 32], Arc)>, } -impl RandomXVMCache { +impl RandomXVmCache { #[instrument(name = "init_rx_vm_cache", level = "info", skip(database))] pub async fn init_from_chain_height( - chain_height: u64, + chain_height: usize, hf: &HardFork, database: D, - ) -> Result { + ) -> Result { let seed_heights = get_last_rx_seed_heights(chain_height - 1, RX_SEEDS_CACHED); let seed_hashes = get_block_hashes(seed_heights.clone(), database).await?; tracing::debug!("last {RX_SEEDS_CACHED} randomX seed heights: {seed_heights:?}",); - let seeds: VecDeque<(u64, [u8; 32])> = seed_heights.into_iter().zip(seed_hashes).collect(); + let seeds: VecDeque<(usize, [u8; 32])> = + seed_heights.into_iter().zip(seed_hashes).collect(); let vms = if hf >= &HardFork::V12 { tracing::debug!("Creating RandomX VMs"); @@ -101,7 +106,7 @@ impl RandomXVMCache { .map(|(height, seed)| { ( *height, - Arc::new(RandomXVM::new(seed).expect("Failed to create RandomX VM!")), + Arc::new(RandomXVm::new(seed).expect("Failed to create RandomX VM!")), ) }) .collect() @@ -112,7 +117,7 @@ impl RandomXVMCache { HashMap::new() }; - Ok(RandomXVMCache { + Ok(Self { seeds, vms, cached_vm: None, @@ -120,12 +125,44 @@ impl RandomXVMCache { } /// Add a randomX VM to the cache, with the seed it was created with. - pub fn add_vm(&mut self, vm: ([u8; 32], Arc)) { + pub fn add_vm(&mut self, vm: ([u8; 32], Arc)) { self.cached_vm.replace(vm); } - /// Get the RandomX VMs. - pub async fn get_vms(&mut self) -> HashMap> { + /// Creates a RX VM for an alt chain, looking at the main chain RX VMs to see if we can use one + /// of them first. + pub async fn get_alt_vm( + &self, + height: usize, + chain: Chain, + database: D, + ) -> Result, ContextCacheError> { + let seed_height = randomx_seed_height(height); + + let BlockchainResponse::BlockHash(seed_hash) = database + .oneshot(BlockchainReadRequest::BlockHash(seed_height, chain)) + .await? + else { + panic!("Database returned wrong response!"); + }; + + for (vm_main_chain_height, vm_seed_hash) in &self.seeds { + if vm_seed_hash == &seed_hash { + let Some(vm) = self.vms.get(vm_main_chain_height) else { + break; + }; + + return Ok(Arc::clone(vm)); + } + } + + let alt_vm = rayon_spawn_async(move || Arc::new(RandomXVm::new(&seed_hash).unwrap())).await; + + Ok(alt_vm) + } + + /// Get the main-chain `RandomX` VMs. + pub async fn get_vms(&mut self) -> HashMap> { match self.seeds.len().checked_sub(self.vms.len()) { // No difference in the amount of seeds to VMs. Some(0) => (), @@ -147,7 +184,7 @@ impl RandomXVMCache { } }; - rayon_spawn_async(move || Arc::new(RandomXVM::new(&next_seed_hash).unwrap())) + rayon_spawn_async(move || Arc::new(RandomXVm::new(&next_seed_hash).unwrap())) .await }; @@ -163,23 +200,29 @@ impl RandomXVMCache { seeds_clone .par_iter() .map(|(height, seed)| { - let vm = RandomXVM::new(seed).expect("Failed to create RandomX VM!"); + let vm = RandomXVm::new(seed).expect("Failed to create RandomX VM!"); let vm = Arc::new(vm); (*height, vm) }) .collect() }) - .await + .await; } } self.vms.clone() } + /// Removes all the `RandomX` VMs above the `new_height`. + pub fn pop_blocks_main_chain(&mut self, new_height: usize) { + self.seeds.retain(|(height, _)| *height < new_height); + self.vms.retain(|height, _| *height < new_height); + } + /// Add a new block to the VM cache. /// - /// hash is the block hash not the blocks PoW hash. - pub fn new_block(&mut self, height: u64, hash: &[u8; 32]) { + /// hash is the block hash not the blocks proof-of-work hash. + pub fn new_block(&mut self, height: usize, hash: &[u8; 32]) { if is_randomx_seed_height(height) { tracing::debug!("Block {height} is a randomX seed height, adding it to the cache.",); @@ -192,7 +235,7 @@ impl RandomXVMCache { self.seeds .iter() .any(|(cached_height, _)| height == cached_height) - }) + }); } } } @@ -200,7 +243,7 @@ impl RandomXVMCache { /// Get the last `amount` of RX seeds, the top height returned here will not necessarily be the RX VM for the top block /// in the chain as VMs include some lag before a seed activates. -pub(crate) fn get_last_rx_seed_heights(mut last_height: u64, mut amount: usize) -> Vec { +pub fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize) -> Vec { let mut seeds = Vec::with_capacity(amount); if is_randomx_seed_height(last_height) { seeds.push(last_height); @@ -215,7 +258,7 @@ pub(crate) fn get_last_rx_seed_heights(mut last_height: u64, mut amount: usize) // We don't include the lag as we only want seeds not the specific seed for this height. let seed_height = (last_height - 1) & !(RX_SEEDHASH_EPOCH_BLOCKS - 1); seeds.push(seed_height); - last_height = seed_height + last_height = seed_height; } seeds @@ -223,20 +266,22 @@ pub(crate) fn get_last_rx_seed_heights(mut last_height: u64, mut amount: usize) /// Gets the block hashes for the heights specified. async fn get_block_hashes( - heights: Vec, + heights: Vec, database: D, -) -> Result, ExtendedConsensusError> { +) -> Result, ContextCacheError> { let mut fut = FuturesOrdered::new(); for height in heights { let db = database.clone(); fut.push_back(async move { - let BCResponse::BlockHash(hash) = - db.clone().oneshot(BCReadRequest::BlockHash(height)).await? + let BlockchainResponse::BlockHash(hash) = db + .clone() + .oneshot(BlockchainReadRequest::BlockHash(height, Chain::Main)) + .await? else { panic!("Database sent incorrect response!"); }; - Result::<_, ExtendedConsensusError>::Ok(hash) + Result::<_, ContextCacheError>::Ok(hash) }); } diff --git a/consensus/src/context/task.rs b/consensus/context/src/task.rs similarity index 52% rename from consensus/src/context/task.rs rename to consensus/context/src/task.rs index 108922d..b075995 100644 --- a/consensus/src/context/task.rs +++ b/consensus/context/src/task.rs @@ -9,14 +9,18 @@ use tower::ServiceExt; use tracing::Instrument; use cuprate_consensus_rules::blocks::ContextToVerifyBlock; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; - -use super::{ - difficulty, hardforks, rx_vms, weight, BlockChainContext, BlockChainContextRequest, - BlockChainContextResponse, ContextConfig, RawBlockChainContext, ValidityToken, - BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW, +use cuprate_helper::cast::u64_to_usize; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, +}; + +use crate::{ + alt_chains::{get_alt_chain_difficulty_cache, get_alt_chain_weight_cache, AltChainMap}, + difficulty, hardforks, rx_vms, weight, BlockChainContext, BlockChainContextRequest, + BlockChainContextResponse, ContextCacheError, ContextConfig, Database, RawBlockChainContext, + ValidityToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW, }; -use crate::{Database, ExtendedConsensusError}; /// A request from the context service to the context task. pub(super) struct ContextTaskRequest { @@ -29,7 +33,7 @@ pub(super) struct ContextTaskRequest { } /// The Context task that keeps the blockchain context and handles requests. -pub struct ContextTask { +pub(crate) struct ContextTask { /// A token used to invalidate previous contexts when a new /// block is added to the chain. current_validity_token: ValidityToken, @@ -39,29 +43,29 @@ pub struct ContextTask { /// The weight cache. weight_cache: weight::BlockWeightsCache, /// The RX VM cache. - rx_vm_cache: rx_vms::RandomXVMCache, + rx_vm_cache: rx_vms::RandomXVmCache, /// The hard-fork state cache. hardfork_state: hardforks::HardForkState, + alt_chain_cache_map: AltChainMap, + /// The current chain height. - chain_height: u64, + chain_height: usize, /// The top block hash. top_block_hash: [u8; 32], /// The total amount of coins generated. already_generated_coins: u64, + + database: D, } -impl ContextTask { +impl ContextTask { /// Initialize the [`ContextTask`], this will need to pull a lot of data from the database so may take a /// while to complete. - pub async fn init_context( + pub(crate) async fn init_context( cfg: ContextConfig, mut database: D, - ) -> Result - where - D: Database + Clone + Send + Sync + 'static, - D::Future: Send + 'static, - { + ) -> Result { let ContextConfig { difficulty_cfg, weights_config, @@ -70,19 +74,19 @@ impl ContextTask { tracing::debug!("Initialising blockchain context"); - let BCResponse::ChainHeight(chain_height, top_block_hash) = database + let BlockchainResponse::ChainHeight(chain_height, top_block_hash) = database .ready() .await? - .call(BCReadRequest::ChainHeight) + .call(BlockchainReadRequest::ChainHeight) .await? else { panic!("Database sent incorrect response!"); }; - let BCResponse::GeneratedCoins(already_generated_coins) = database + let BlockchainResponse::GeneratedCoins(already_generated_coins) = database .ready() .await? - .call(BCReadRequest::GeneratedCoins) + .call(BlockchainReadRequest::GeneratedCoins(chain_height - 1)) .await? else { panic!("Database sent incorrect response!"); @@ -95,14 +99,24 @@ impl ContextTask { let db = database.clone(); let difficulty_cache_handle = tokio::spawn(async move { - difficulty::DifficultyCache::init_from_chain_height(chain_height, difficulty_cfg, db) - .await + difficulty::DifficultyCache::init_from_chain_height( + chain_height, + difficulty_cfg, + db, + Chain::Main, + ) + .await }); let db = database.clone(); let weight_cache_handle = tokio::spawn(async move { - weight::BlockWeightsCache::init_from_chain_height(chain_height, weights_config, db) - .await + weight::BlockWeightsCache::init_from_chain_height( + chain_height, + weights_config, + db, + Chain::Main, + ) + .await }); // Wait for the hardfork state to finish first as we need it to start the randomX VM cache. @@ -111,30 +125,32 @@ impl ContextTask { let db = database.clone(); let rx_seed_handle = tokio::spawn(async move { - rx_vms::RandomXVMCache::init_from_chain_height(chain_height, ¤t_hf, db).await + rx_vms::RandomXVmCache::init_from_chain_height(chain_height, ¤t_hf, db).await }); - let context_svc = ContextTask { + let context_svc = Self { current_validity_token: ValidityToken::new(), difficulty_cache: difficulty_cache_handle.await.unwrap()?, weight_cache: weight_cache_handle.await.unwrap()?, rx_vm_cache: rx_seed_handle.await.unwrap()?, hardfork_state, + alt_chain_cache_map: AltChainMap::new(), chain_height, already_generated_coins, top_block_hash, + database, }; Ok(context_svc) } /// Handles a [`BlockChainContextRequest`] and returns a [`BlockChainContextResponse`]. - pub async fn handle_req( + pub(crate) async fn handle_req( &mut self, req: BlockChainContextRequest, ) -> Result { Ok(match req { - BlockChainContextRequest::GetContext => { + BlockChainContextRequest::Context => { tracing::debug!("Getting blockchain context"); let current_hf = self.hardfork_state.current_hardfork(); @@ -145,17 +161,17 @@ impl ContextTask { context_to_verify_block: ContextToVerifyBlock { median_weight_for_block_reward: self .weight_cache - .median_for_block_reward(¤t_hf), + .median_for_block_reward(current_hf), effective_median_weight: self .weight_cache - .effective_median_block_weight(¤t_hf), + .effective_median_block_weight(current_hf), top_hash: self.top_block_hash, - median_block_timestamp: self.difficulty_cache.median_timestamp( - usize::try_from(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW).unwrap(), - ), + median_block_timestamp: self + .difficulty_cache + .median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)), chain_height: self.chain_height, current_hf, - next_difficulty: self.difficulty_cache.next_difficulty(¤t_hf), + next_difficulty: self.difficulty_cache.next_difficulty(current_hf), already_generated_coins: self.already_generated_coins, }, cumulative_difficulty: self.difficulty_cache.cumulative_difficulty(), @@ -164,7 +180,7 @@ impl ContextTask { }, }) } - BlockChainContextRequest::GetCurrentRxVm => { + BlockChainContextRequest::CurrentRxVms => { BlockChainContextResponse::RxVms(self.rx_vm_cache.get_vms().await) } BlockChainContextRequest::BatchGetDifficulties(blocks) => { @@ -172,7 +188,7 @@ impl ContextTask { let next_diffs = self .difficulty_cache - .next_difficulties(blocks, &self.hardfork_state.current_hardfork()); + .next_difficulties(blocks, self.hardfork_state.current_hardfork()); BlockChainContextResponse::BatchDifficulties(next_diffs) } BlockChainContextRequest::NewRXVM(vm) => { @@ -211,15 +227,116 @@ impl ContextTask { BlockChainContextResponse::Ok } + BlockChainContextRequest::PopBlocks { numb_blocks } => { + assert!(numb_blocks < self.chain_height); + + self.difficulty_cache + .pop_blocks_main_chain(numb_blocks, self.database.clone()) + .await?; + self.weight_cache + .pop_blocks_main_chain(numb_blocks, self.database.clone()) + .await?; + self.rx_vm_cache + .pop_blocks_main_chain(self.chain_height - numb_blocks - 1); + self.hardfork_state + .pop_blocks_main_chain(numb_blocks, self.database.clone()) + .await?; + + self.alt_chain_cache_map.clear(); + + self.chain_height -= numb_blocks; + + let BlockchainResponse::GeneratedCoins(already_generated_coins) = self + .database + .ready() + .await? + .call(BlockchainReadRequest::GeneratedCoins(self.chain_height - 1)) + .await? + else { + panic!("Database sent incorrect response!"); + }; + + let BlockchainResponse::BlockHash(top_block_hash) = self + .database + .ready() + .await? + .call(BlockchainReadRequest::BlockHash( + self.chain_height - 1, + Chain::Main, + )) + .await? + else { + panic!("Database returned incorrect response!"); + }; + + self.already_generated_coins = already_generated_coins; + self.top_block_hash = top_block_hash; + + std::mem::replace(&mut self.current_validity_token, ValidityToken::new()) + .set_data_invalid(); + + BlockChainContextResponse::Ok + } + BlockChainContextRequest::ClearAltCache => { + self.alt_chain_cache_map.clear(); + + BlockChainContextResponse::Ok + } + BlockChainContextRequest::AltChainContextCache { prev_id, _token } => { + BlockChainContextResponse::AltChainContextCache( + self.alt_chain_cache_map + .get_alt_chain_context(prev_id, &mut self.database) + .await?, + ) + } + BlockChainContextRequest::AltChainDifficultyCache { prev_id, _token } => { + BlockChainContextResponse::AltChainDifficultyCache( + get_alt_chain_difficulty_cache( + prev_id, + &self.difficulty_cache, + self.database.clone(), + ) + .await?, + ) + } + BlockChainContextRequest::AltChainWeightCache { prev_id, _token } => { + BlockChainContextResponse::AltChainWeightCache( + get_alt_chain_weight_cache(prev_id, &self.weight_cache, self.database.clone()) + .await?, + ) + } + BlockChainContextRequest::AltChainRxVM { + height, + chain, + _token, + } => BlockChainContextResponse::AltChainRxVM( + self.rx_vm_cache + .get_alt_vm(height, chain, &mut self.database) + .await?, + ), + BlockChainContextRequest::AddAltChainContextCache { + prev_id, + cache, + _token, + } => { + self.alt_chain_cache_map.add_alt_cache(prev_id, cache); + BlockChainContextResponse::Ok + } + BlockChainContextRequest::HardForkInfo(_) + | BlockChainContextRequest::FeeEstimate { .. } + | BlockChainContextRequest::AltChains + | BlockChainContextRequest::CalculatePow { .. } => { + todo!("finish https://github.com/Cuprate/cuprate/pull/297") + } }) } /// Run the [`ContextTask`], the task will listen for requests on the passed in channel. When the channel closes the /// task will finish. - pub async fn run(mut self, mut rx: mpsc::Receiver) { + pub(crate) async fn run(mut self, mut rx: mpsc::Receiver) { while let Some(req) = rx.recv().await { let res = self.handle_req(req.req).instrument(req.span).await; - let _ = req.tx.send(res); + drop(req.tx.send(res)); } tracing::info!("Shutting down blockchain context task."); diff --git a/consensus/src/context/tokens.rs b/consensus/context/src/tokens.rs similarity index 88% rename from consensus/src/context/tokens.rs rename to consensus/context/src/tokens.rs index 882d3b5..d222303 100644 --- a/consensus/src/context/tokens.rs +++ b/consensus/context/src/tokens.rs @@ -15,8 +15,8 @@ pub struct ValidityToken { impl ValidityToken { /// Creates a new [`ValidityToken`] - pub fn new() -> ValidityToken { - ValidityToken { + pub fn new() -> Self { + Self { token: CancellationToken::new(), } } @@ -28,6 +28,6 @@ impl ValidityToken { /// Sets the data to invalid. pub fn set_data_invalid(self) { - self.token.cancel() + self.token.cancel(); } } diff --git a/consensus/src/context/weight.rs b/consensus/context/src/weight.rs similarity index 53% rename from consensus/src/context/weight.rs rename to consensus/context/src/weight.rs index 2511c59..7f72599 100644 --- a/consensus/src/context/weight.rs +++ b/consensus/context/src/weight.rs @@ -8,45 +8,46 @@ //! use std::{ cmp::{max, min}, - collections::VecDeque, ops::Range, }; -use rayon::prelude::*; use tower::ServiceExt; use tracing::instrument; use cuprate_consensus_rules::blocks::{penalty_free_zone, PENALTY_FREE_ZONE_5}; -use cuprate_helper::{asynch::rayon_spawn_async, num::median}; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; +use cuprate_helper::{asynch::rayon_spawn_async, num::RollingMedian}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, +}; -use crate::{Database, ExtendedConsensusError, HardFork}; +use crate::{ContextCacheError, Database, HardFork}; /// The short term block weight window. -const SHORT_TERM_WINDOW: u64 = 100; +pub const SHORT_TERM_WINDOW: usize = 100; /// The long term block weight window. -const LONG_TERM_WINDOW: u64 = 100000; +pub const LONG_TERM_WINDOW: usize = 100000; /// Configuration for the block weight cache. /// -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct BlockWeightsCacheConfig { - short_term_window: u64, - long_term_window: u64, + short_term_window: usize, + long_term_window: usize, } impl BlockWeightsCacheConfig { /// Creates a new [`BlockWeightsCacheConfig`] - pub const fn new(short_term_window: u64, long_term_window: u64) -> BlockWeightsCacheConfig { - BlockWeightsCacheConfig { + pub const fn new(short_term_window: usize, long_term_window: usize) -> Self { + Self { short_term_window, long_term_window, } } /// Returns the [`BlockWeightsCacheConfig`] for all networks (They are all the same as mainnet). - pub fn main_net() -> BlockWeightsCacheConfig { - BlockWeightsCacheConfig { + pub const fn main_net() -> Self { + Self { short_term_window: SHORT_TERM_WINDOW, long_term_window: LONG_TERM_WINDOW, } @@ -58,78 +59,128 @@ impl BlockWeightsCacheConfig { /// /// These calculations require a lot of data from the database so by caching /// this data it reduces the load on the database. -#[derive(Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct BlockWeightsCache { /// The short term block weights. - short_term_block_weights: VecDeque, + short_term_block_weights: RollingMedian, /// The long term block weights. - long_term_weights: VecDeque, - - /// The short term block weights sorted so we don't have to sort them every time we need - /// the median. - cached_sorted_long_term_weights: Vec, - /// The long term block weights sorted so we don't have to sort them every time we need - /// the median. - cached_sorted_short_term_weights: Vec, + long_term_weights: RollingMedian, /// The height of the top block. - tip_height: u64, + pub(crate) tip_height: usize, - /// The block weight config. - config: BlockWeightsCacheConfig, + pub(crate) config: BlockWeightsCacheConfig, } impl BlockWeightsCache { /// Initialize the [`BlockWeightsCache`] at the the given chain height. #[instrument(name = "init_weight_cache", level = "info", skip(database, config))] pub async fn init_from_chain_height( - chain_height: u64, + chain_height: usize, config: BlockWeightsCacheConfig, database: D, - ) -> Result { + chain: Chain, + ) -> Result { tracing::info!("Initializing weight cache this may take a while."); let long_term_weights = get_long_term_weight_in_range( chain_height.saturating_sub(config.long_term_window)..chain_height, database.clone(), + chain, ) .await?; let short_term_block_weights = get_blocks_weight_in_range( chain_height.saturating_sub(config.short_term_window)..chain_height, database, + chain, ) .await?; tracing::info!("Initialized block weight cache, chain-height: {:?}, long term weights length: {:?}, short term weights length: {:?}", chain_height, long_term_weights.len(), short_term_block_weights.len()); - let mut cloned_short_term_weights = short_term_block_weights.clone(); - let mut cloned_long_term_weights = long_term_weights.clone(); - Ok(BlockWeightsCache { - short_term_block_weights: short_term_block_weights.into(), - long_term_weights: long_term_weights.into(), - - cached_sorted_long_term_weights: rayon_spawn_async(|| { - cloned_long_term_weights.par_sort_unstable(); - cloned_long_term_weights + Ok(Self { + short_term_block_weights: rayon_spawn_async(move || { + RollingMedian::from_vec(short_term_block_weights, config.short_term_window) }) .await, - cached_sorted_short_term_weights: rayon_spawn_async(|| { - cloned_short_term_weights.par_sort_unstable(); - cloned_short_term_weights + long_term_weights: rayon_spawn_async(move || { + RollingMedian::from_vec(long_term_weights, config.long_term_window) }) .await, - tip_height: chain_height - 1, config, }) } + /// Pop some blocks from the top of the cache. + /// + /// The cache will be returned to the state it would have been in `numb_blocks` ago. + #[instrument(name = "pop_blocks_weight_cache", skip_all, fields(numb_blocks = numb_blocks))] + pub async fn pop_blocks_main_chain( + &mut self, + numb_blocks: usize, + database: D, + ) -> Result<(), ContextCacheError> { + if self.long_term_weights.window_len() <= numb_blocks { + // More blocks to pop than we have in the cache, so just restart a new cache. + *self = Self::init_from_chain_height( + self.tip_height - numb_blocks + 1, + self.config, + database, + Chain::Main, + ) + .await?; + + return Ok(()); + } + + let chain_height = self.tip_height + 1; + + let new_long_term_start_height = chain_height + .saturating_sub(self.config.long_term_window) + .saturating_sub(numb_blocks); + + let old_long_term_weights = get_long_term_weight_in_range( + new_long_term_start_height + // current_chain_height - self.long_term_weights.len() blocks are already in the cache. + ..(chain_height - self.long_term_weights.window_len()), + database.clone(), + Chain::Main, + ) + .await?; + + let new_short_term_start_height = chain_height + .saturating_sub(self.config.short_term_window) + .saturating_sub(numb_blocks); + + let old_short_term_weights = get_blocks_weight_in_range( + new_short_term_start_height + // current_chain_height - self.long_term_weights.len() blocks are already in the cache. + ..(chain_height - self.short_term_block_weights.window_len()), + database, + Chain::Main, + ) + .await?; + + for _ in 0..numb_blocks { + self.short_term_block_weights.pop_back(); + self.long_term_weights.pop_back(); + } + + self.long_term_weights.append_front(old_long_term_weights); + self.short_term_block_weights + .append_front(old_short_term_weights); + self.tip_height -= numb_blocks; + + Ok(()) + } + /// Add a new block to the cache. /// - /// The block_height **MUST** be one more than the last height the cache has + /// The `block_height` **MUST** be one more than the last height the cache has /// seen. - pub fn new_block(&mut self, block_height: u64, block_weight: usize, long_term_weight: usize) { + pub fn new_block(&mut self, block_height: usize, block_weight: usize, long_term_weight: usize) { assert_eq!(self.tip_height + 1, block_height); self.tip_height += 1; tracing::debug!( @@ -139,81 +190,26 @@ impl BlockWeightsCache { long_term_weight ); - // add the new block to the `long_term_weights` list and the sorted `cached_sorted_long_term_weights` list. - self.long_term_weights.push_back(long_term_weight); - match self - .cached_sorted_long_term_weights - .binary_search(&long_term_weight) - { - Ok(idx) | Err(idx) => self - .cached_sorted_long_term_weights - .insert(idx, long_term_weight), - } + self.long_term_weights.push(long_term_weight); - // If the list now has too many entries remove the oldest. - if u64::try_from(self.long_term_weights.len()).unwrap() > self.config.long_term_window { - let val = self - .long_term_weights - .pop_front() - .expect("long term window can't be negative"); - - match self.cached_sorted_long_term_weights.binary_search(&val) { - Ok(idx) => self.cached_sorted_long_term_weights.remove(idx), - Err(_) => panic!("Long term cache has incorrect values!"), - }; - } - - // add the block to the short_term_block_weights and the sorted cached_sorted_short_term_weights list. - self.short_term_block_weights.push_back(block_weight); - match self - .cached_sorted_short_term_weights - .binary_search(&block_weight) - { - Ok(idx) | Err(idx) => self - .cached_sorted_short_term_weights - .insert(idx, block_weight), - } - - // If there are now too many entries remove the oldest. - if u64::try_from(self.short_term_block_weights.len()).unwrap() - > self.config.short_term_window - { - let val = self - .short_term_block_weights - .pop_front() - .expect("short term window can't be negative"); - - match self.cached_sorted_short_term_weights.binary_search(&val) { - Ok(idx) => self.cached_sorted_short_term_weights.remove(idx), - Err(_) => panic!("Short term cache has incorrect values"), - }; - } - - debug_assert_eq!( - self.cached_sorted_long_term_weights.len(), - self.long_term_weights.len() - ); - debug_assert_eq!( - self.cached_sorted_short_term_weights.len(), - self.short_term_block_weights.len() - ); + self.short_term_block_weights.push(block_weight); } /// Returns the median long term weight over the last [`LONG_TERM_WINDOW`] blocks, or custom amount of blocks in the config. pub fn median_long_term_weight(&self) -> usize { - median(&self.cached_sorted_long_term_weights) + self.long_term_weights.median() } /// Returns the median weight over the last [`SHORT_TERM_WINDOW`] blocks, or custom amount of blocks in the config. pub fn median_short_term_weight(&self) -> usize { - median(&self.cached_sorted_short_term_weights) + self.short_term_block_weights.median() } /// Returns the effective median weight, used for block reward calculations and to calculate /// the block weight limit. /// - /// See: https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#calculating-effective-median-weight - pub fn effective_median_block_weight(&self, hf: &HardFork) -> usize { + /// See: + pub fn effective_median_block_weight(&self, hf: HardFork) -> usize { calculate_effective_median_block_weight( hf, self.median_short_term_weight(), @@ -223,9 +219,9 @@ impl BlockWeightsCache { /// Returns the median weight used to calculate block reward punishment. /// - /// https://cuprate.github.io/monero-book/consensus_rules/blocks/reward.html#calculating-block-reward - pub fn median_for_block_reward(&self, hf: &HardFork) -> usize { - if hf < &HardFork::V12 { + /// + pub fn median_for_block_reward(&self, hf: HardFork) -> usize { + if hf < HardFork::V12 { self.median_short_term_weight() } else { self.effective_median_block_weight(hf) @@ -236,17 +232,17 @@ impl BlockWeightsCache { /// Calculates the effective median with the long term and short term median. fn calculate_effective_median_block_weight( - hf: &HardFork, + hf: HardFork, median_short_term_weight: usize, median_long_term_weight: usize, ) -> usize { - if hf < &HardFork::V10 { + if hf < HardFork::V10 { return median_short_term_weight.max(penalty_free_zone(hf)); } let long_term_median = median_long_term_weight.max(PENALTY_FREE_ZONE_5); let short_term_median = median_short_term_weight; - let effective_median = if hf >= &HardFork::V10 && hf < &HardFork::V15 { + let effective_median = if hf >= HardFork::V10 && hf < HardFork::V15 { min( max(PENALTY_FREE_ZONE_5, short_term_median), 50 * long_term_median, @@ -263,18 +259,18 @@ fn calculate_effective_median_block_weight( /// Calculates a blocks long term weight. pub fn calculate_block_long_term_weight( - hf: &HardFork, + hf: HardFork, block_weight: usize, long_term_median: usize, ) -> usize { - if hf < &HardFork::V10 { + if hf < HardFork::V10 { return block_weight; } let long_term_median = max(penalty_free_zone(hf), long_term_median); let (short_term_constraint, adjusted_block_weight) = - if hf >= &HardFork::V10 && hf < &HardFork::V15 { + if hf >= HardFork::V10 && hf < HardFork::V15 { let stc = long_term_median + long_term_median * 2 / 5; (stc, block_weight) } else { @@ -288,13 +284,16 @@ pub fn calculate_block_long_term_weight( /// Gets the block weights from the blocks with heights in the range provided. #[instrument(name = "get_block_weights", skip(database))] async fn get_blocks_weight_in_range( - range: Range, + range: Range, database: D, -) -> Result, ExtendedConsensusError> { + chain: Chain, +) -> Result, ContextCacheError> { tracing::info!("getting block weights."); - let BCResponse::BlockExtendedHeaderInRange(ext_headers) = database - .oneshot(BCReadRequest::BlockExtendedHeaderInRange(range)) + let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database + .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange( + range, chain, + )) .await? else { panic!("Database sent incorrect response!") @@ -309,13 +308,16 @@ async fn get_blocks_weight_in_range( /// Gets the block long term weights from the blocks with heights in the range provided. #[instrument(name = "get_long_term_weights", skip(database), level = "info")] async fn get_long_term_weight_in_range( - range: Range, + range: Range, database: D, -) -> Result, ExtendedConsensusError> { + chain: Chain, +) -> Result, ContextCacheError> { tracing::info!("getting block long term weights."); - let BCResponse::BlockExtendedHeaderInRange(ext_headers) = database - .oneshot(BCReadRequest::BlockExtendedHeaderInRange(range)) + let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database + .oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange( + range, chain, + )) .await? else { panic!("Database sent incorrect response!") diff --git a/consensus/fast-sync/Cargo.toml b/consensus/fast-sync/Cargo.toml index 32fce11..8e732a6 100644 --- a/consensus/fast-sync/Cargo.toml +++ b/consensus/fast-sync/Cargo.toml @@ -9,19 +9,23 @@ name = "cuprate-fast-sync-create-hashes" path = "src/create.rs" [dependencies] -clap = { workspace = true, features = ["derive", "std"] } -cuprate-blockchain = { path = "../../storage/blockchain" } -cuprate-consensus = { path = ".." } -cuprate-consensus-rules = { path = "../rules" } -cuprate-types = { path = "../../types" } -hex.workspace = true -hex-literal.workspace = true -monero-serai.workspace = true -rayon.workspace = true -sha3 = "0.10.8" -thiserror.workspace = true -tokio = { workspace = true, features = ["full"] } -tower.workspace = true +cuprate-blockchain = { workspace = true } +cuprate-consensus = { workspace = true } +cuprate-consensus-rules = { workspace = true } +cuprate-consensus-context = { workspace = true } +cuprate-types = { workspace = true } +cuprate-helper = { workspace = true, features = ["cast"] } + +clap = { workspace = true, features = ["derive", "std"] } +hex = { workspace = true } +hex-literal = { workspace = true } +monero-serai = { workspace = true } +sha3 = { version = "0.10.8" } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tower = { workspace = true } [dev-dependencies] -tokio-test = "0.4.4" + +[lints] +workspace = true diff --git a/consensus/fast-sync/src/create.rs b/consensus/fast-sync/src/create.rs index dc2311f..9410f60 100644 --- a/consensus/fast-sync/src/create.rs +++ b/consensus/fast-sync/src/create.rs @@ -1,30 +1,38 @@ +#![expect( + unused_crate_dependencies, + reason = "binary shares same Cargo.toml as library" +)] + use std::{fmt::Write, fs::write}; use clap::Parser; use tower::{Service, ServiceExt}; use cuprate_blockchain::{ - config::ConfigBuilder, cuprate_database::RuntimeError, service::DatabaseReadHandle, + config::ConfigBuilder, cuprate_database::DbResult, service::BlockchainReadHandle, +}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, }; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; use cuprate_fast_sync::{hash_of_hashes, BlockId, HashOfHashes}; -const BATCH_SIZE: u64 = 512; +const BATCH_SIZE: usize = 512; async fn read_batch( - handle: &mut DatabaseReadHandle, - height_from: u64, -) -> Result, RuntimeError> { - let mut block_ids = Vec::::with_capacity(BATCH_SIZE as usize); + handle: &mut BlockchainReadHandle, + height_from: usize, +) -> DbResult> { + let mut block_ids = Vec::::with_capacity(BATCH_SIZE); for height in height_from..(height_from + BATCH_SIZE) { - let request = BCReadRequest::BlockHash(height); + let request = BlockchainReadRequest::BlockHash(height, Chain::Main); let response_channel = handle.ready().await?.call(request); let response = response_channel.await?; match response { - BCResponse::BlockHash(block_id) => block_ids.push(block_id), + BlockchainResponse::BlockHash(block_id) => block_ids.push(block_id), _ => unreachable!(), } } @@ -50,7 +58,7 @@ fn generate_hex(hashes: &[HashOfHashes]) -> String { #[command(version, about, long_about = None)] struct Args { #[arg(short, long)] - height: u64, + height: usize, } #[tokio::main] @@ -60,22 +68,19 @@ async fn main() { let config = ConfigBuilder::new().build(); - let (mut read_handle, _) = cuprate_blockchain::service::init(config).unwrap(); + let (mut read_handle, _, _) = cuprate_blockchain::service::init(config).unwrap(); let mut hashes_of_hashes = Vec::new(); - let mut height = 0u64; + let mut height = 0_usize; while height < height_target { - match read_batch(&mut read_handle, height).await { - Ok(block_ids) => { - let hash = hash_of_hashes(block_ids.as_slice()); - hashes_of_hashes.push(hash); - } - Err(_) => { - println!("Failed to read next batch from database"); - break; - } + if let Ok(block_ids) = read_batch(&mut read_handle, height).await { + let hash = hash_of_hashes(block_ids.as_slice()); + hashes_of_hashes.push(hash); + } else { + println!("Failed to read next batch from database"); + break; } height += BATCH_SIZE; } @@ -85,5 +90,5 @@ async fn main() { let generated = generate_hex(&hashes_of_hashes); write("src/data/hashes_of_hashes", generated).expect("Could not write file"); - println!("Generated hashes up to block height {}", height); + println!("Generated hashes up to block height {height}"); } diff --git a/consensus/fast-sync/src/data/hashes_of_hashes b/consensus/fast-sync/src/data/hashes_of_hashes index 74fec4c..2e5e99a 100644 --- a/consensus/fast-sync/src/data/hashes_of_hashes +++ b/consensus/fast-sync/src/data/hashes_of_hashes @@ -1,12 +1,12 @@ [ - hex!("1adffbaf832784406018009e07d3dc3a39da7edb6632523c119ed8acb32eb934"), - hex!("ae960265e3398d04f3cd4f949ed13c2689424887c71c1441a03d900a9d3a777f"), - hex!("938c72d267bbd3a17cdecbe02443d00012ee62d6e9f3524f5a914192110b1798"), - hex!("de0c82e51549b6514b42a591fd5440dddb5cc0118ec461459a99017bf06a0a0a"), - hex!("9a50f4586ec7e0fb58c6383048d3b334180235fd34bb714af20f1a3ebce4c911"), - hex!("5a3942f9bb318d65997bf57c40e045d62e7edbe35f3dae57499c2c5554896543"), - hex!("9dccee3b094cdd1b98e357c2c81bfcea798ea75efd94e67c6f5e86f428c5ec2c"), - hex!("620397540d44f21c3c57c20e9d47c6aaf0b1bf4302a4d43e75f2e33edd1a4032"), - hex!("ef6c612fb17bd70ac2ac69b2f85a421b138cc3a81daf622b077cb402dbf68377"), - hex!("6815ecb2bd73a3ba5f20558bfe1b714c30d6892b290e0d6f6cbf18237cedf75a"), + hex_literal::hex!("1adffbaf832784406018009e07d3dc3a39da7edb6632523c119ed8acb32eb934"), + hex_literal::hex!("ae960265e3398d04f3cd4f949ed13c2689424887c71c1441a03d900a9d3a777f"), + hex_literal::hex!("938c72d267bbd3a17cdecbe02443d00012ee62d6e9f3524f5a914192110b1798"), + hex_literal::hex!("de0c82e51549b6514b42a591fd5440dddb5cc0118ec461459a99017bf06a0a0a"), + hex_literal::hex!("9a50f4586ec7e0fb58c6383048d3b334180235fd34bb714af20f1a3ebce4c911"), + hex_literal::hex!("5a3942f9bb318d65997bf57c40e045d62e7edbe35f3dae57499c2c5554896543"), + hex_literal::hex!("9dccee3b094cdd1b98e357c2c81bfcea798ea75efd94e67c6f5e86f428c5ec2c"), + hex_literal::hex!("620397540d44f21c3c57c20e9d47c6aaf0b1bf4302a4d43e75f2e33edd1a4032"), + hex_literal::hex!("ef6c612fb17bd70ac2ac69b2f85a421b138cc3a81daf622b077cb402dbf68377"), + hex_literal::hex!("6815ecb2bd73a3ba5f20558bfe1b714c30d6892b290e0d6f6cbf18237cedf75a"), ] diff --git a/consensus/fast-sync/src/fast_sync.rs b/consensus/fast-sync/src/fast_sync.rs index a97040a..3764e21 100644 --- a/consensus/fast-sync/src/fast_sync.rs +++ b/consensus/fast-sync/src/fast_sync.rs @@ -6,19 +6,16 @@ use std::{ task::{Context, Poll}, }; -#[allow(unused_imports)] -use hex_literal::hex; use monero_serai::{ block::Block, transaction::{Input, Transaction}, }; use tower::{Service, ServiceExt}; -use cuprate_consensus::{ - context::{BlockChainContextRequest, BlockChainContextResponse}, - transactions::TransactionVerificationData, -}; +use cuprate_consensus::transactions::new_tx_verification_data; +use cuprate_consensus_context::{BlockChainContextRequest, BlockChainContextResponse}; use cuprate_consensus_rules::{miner_tx::MinerTxError, ConsensusError}; +use cuprate_helper::cast::u64_to_usize; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; use crate::{hash_of_hashes, BlockId, HashOfHashes}; @@ -31,9 +28,9 @@ const BATCH_SIZE: usize = 512; #[cfg(test)] static HASHES_OF_HASHES: &[HashOfHashes] = &[ - hex!("3fdc9032c16d440f6c96be209c36d3d0e1aed61a2531490fe0ca475eb615c40a"), - hex!("0102030405060708010203040506070801020304050607080102030405060708"), - hex!("0102030405060708010203040506070801020304050607080102030405060708"), + hex_literal::hex!("3fdc9032c16d440f6c96be209c36d3d0e1aed61a2531490fe0ca475eb615c40a"), + hex_literal::hex!("0102030405060708010203040506070801020304050607080102030405060708"), + hex_literal::hex!("0102030405060708010203040506070801020304050607080102030405060708"), ]; #[cfg(test)] @@ -44,14 +41,14 @@ fn max_height() -> u64 { (HASHES_OF_HASHES.len() * BATCH_SIZE) as u64 } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub struct ValidBlockId(BlockId); fn valid_block_ids(block_ids: &[BlockId]) -> Vec { block_ids.iter().map(|b| ValidBlockId(*b)).collect() } -#[allow(clippy::large_enum_variant)] +#[expect(clippy::large_enum_variant)] pub enum FastSyncRequest { ValidateHashes { start_height: u64, @@ -64,8 +61,8 @@ pub enum FastSyncRequest { }, } -#[allow(clippy::large_enum_variant)] -#[derive(Debug, PartialEq)] +#[expect(clippy::large_enum_variant)] +#[derive(Debug, PartialEq, Eq)] pub enum FastSyncResponse { ValidateHashes { validated_hashes: Vec, @@ -74,7 +71,7 @@ pub enum FastSyncResponse { ValidateBlock(VerifiedBlockInformation), } -#[derive(thiserror::Error, Debug, PartialEq)] +#[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum FastSyncError { #[error("Block does not match its expected hash")] BlockHashMismatch, @@ -127,9 +124,9 @@ where + Send + 'static, { - #[allow(dead_code)] - pub(crate) fn new(context_svc: C) -> FastSyncService { - FastSyncService { context_svc } + #[expect(dead_code)] + pub(crate) const fn new(context_svc: C) -> Self { + Self { context_svc } } } @@ -161,7 +158,7 @@ where FastSyncRequest::ValidateHashes { start_height, block_ids, - } => validate_hashes(start_height, &block_ids).await, + } => validate_hashes(start_height, &block_ids), FastSyncRequest::ValidateBlock { block, txs, token } => { validate_block(context_svc, block, txs, token).await } @@ -170,11 +167,13 @@ where } } -async fn validate_hashes( +fn validate_hashes( start_height: u64, block_ids: &[BlockId], ) -> Result { - if start_height as usize % BATCH_SIZE != 0 { + let start_height_usize = u64_to_usize(start_height); + + if start_height_usize % BATCH_SIZE != 0 { return Err(FastSyncError::InvalidStartHeight); } @@ -182,9 +181,9 @@ async fn validate_hashes( return Err(FastSyncError::OutOfRange); } - let stop_height = start_height as usize + block_ids.len(); + let stop_height = start_height_usize + block_ids.len(); - let batch_from = start_height as usize / BATCH_SIZE; + let batch_from = start_height_usize / BATCH_SIZE; let batch_to = cmp::min(stop_height / BATCH_SIZE, HASHES_OF_HASHES.len()); let n_batches = batch_to - batch_from; @@ -229,7 +228,7 @@ where let BlockChainContextResponse::Context(checked_context) = context_svc .ready() .await? - .call(BlockChainContextRequest::GetContext) + .call(BlockChainContextRequest::Context) .await? else { panic!("Context service returned wrong response!"); @@ -244,7 +243,7 @@ where let block_blob = block.serialize(); - let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else { + let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else { return Err(FastSyncError::MinerTx(MinerTxError::InputNotOfTypeGen)); }; if *height != block_chain_ctx.chain_height { @@ -252,12 +251,12 @@ where } let mut verified_txs = Vec::with_capacity(txs.len()); - for tx in &block.txs { + for tx in &block.transactions { let tx = txs .remove(tx) .ok_or(FastSyncError::TxsIncludedWithBlockIncorrect)?; - let data = TransactionVerificationData::new(tx)?; + let data = new_tx_verification_data(tx)?; verified_txs.push(VerifiedTransactionInformation { tx_blob: data.tx_blob, tx_weight: data.tx_weight, @@ -269,8 +268,8 @@ where let total_fees = verified_txs.iter().map(|tx| tx.fee).sum::(); let total_outputs = block - .miner_tx - .prefix + .miner_transaction + .prefix() .outputs .iter() .map(|output| output.amount.unwrap_or(0)) @@ -278,14 +277,14 @@ where let generated_coins = total_outputs - total_fees; - let weight = - block.miner_tx.weight() + verified_txs.iter().map(|tx| tx.tx_weight).sum::(); + let weight = block.miner_transaction.weight() + + verified_txs.iter().map(|tx| tx.tx_weight).sum::(); Ok(FastSyncResponse::ValidateBlock(VerifiedBlockInformation { block_blob, txs: verified_txs, block_hash, - pow_hash: [0u8; 32], + pow_hash: [0_u8; 32], height: *height, generated_coins, weight, @@ -299,46 +298,36 @@ where #[cfg(test)] mod tests { use super::*; - use tokio_test::block_on; #[test] fn test_validate_hashes_errors() { - let ids = [[1u8; 32], [2u8; 32], [3u8; 32], [4u8; 32], [5u8; 32]]; + let ids = [[1_u8; 32], [2_u8; 32], [3_u8; 32], [4_u8; 32], [5_u8; 32]]; assert_eq!( - block_on(validate_hashes(3, &[])), + validate_hashes(3, &[]), Err(FastSyncError::InvalidStartHeight) ); assert_eq!( - block_on(validate_hashes(3, &ids)), + validate_hashes(3, &ids), Err(FastSyncError::InvalidStartHeight) ); - assert_eq!( - block_on(validate_hashes(20, &[])), - Err(FastSyncError::OutOfRange) - ); - assert_eq!( - block_on(validate_hashes(20, &ids)), - Err(FastSyncError::OutOfRange) - ); + assert_eq!(validate_hashes(20, &[]), Err(FastSyncError::OutOfRange)); + assert_eq!(validate_hashes(20, &ids), Err(FastSyncError::OutOfRange)); + assert_eq!(validate_hashes(4, &[]), Err(FastSyncError::NothingToDo)); assert_eq!( - block_on(validate_hashes(4, &[])), - Err(FastSyncError::NothingToDo) - ); - assert_eq!( - block_on(validate_hashes(4, &ids[..3])), + validate_hashes(4, &ids[..3]), Err(FastSyncError::NothingToDo) ); } #[test] fn test_validate_hashes_success() { - let ids = [[1u8; 32], [2u8; 32], [3u8; 32], [4u8; 32], [5u8; 32]]; + let ids = [[1_u8; 32], [2_u8; 32], [3_u8; 32], [4_u8; 32], [5_u8; 32]]; let validated_hashes = valid_block_ids(&ids[0..4]); let unknown_hashes = ids[4..].to_vec(); assert_eq!( - block_on(validate_hashes(0, &ids)), + validate_hashes(0, &ids), Ok(FastSyncResponse::ValidateHashes { validated_hashes, unknown_hashes @@ -349,15 +338,10 @@ mod tests { #[test] fn test_validate_hashes_mismatch() { let ids = [ - [1u8; 32], [2u8; 32], [3u8; 32], [5u8; 32], [1u8; 32], [2u8; 32], [3u8; 32], [4u8; 32], + [1_u8; 32], [2_u8; 32], [3_u8; 32], [5_u8; 32], [1_u8; 32], [2_u8; 32], [3_u8; 32], + [4_u8; 32], ]; - assert_eq!( - block_on(validate_hashes(0, &ids)), - Err(FastSyncError::Mismatch) - ); - assert_eq!( - block_on(validate_hashes(4, &ids)), - Err(FastSyncError::Mismatch) - ); + assert_eq!(validate_hashes(0, &ids), Err(FastSyncError::Mismatch)); + assert_eq!(validate_hashes(4, &ids), Err(FastSyncError::Mismatch)); } } diff --git a/consensus/fast-sync/src/lib.rs b/consensus/fast-sync/src/lib.rs index f82b163..8dbdc64 100644 --- a/consensus/fast-sync/src/lib.rs +++ b/consensus/fast-sync/src/lib.rs @@ -1,3 +1,9 @@ +// Used in `create.rs` +use clap as _; +use cuprate_blockchain as _; +use hex as _; +use tokio as _; + pub mod fast_sync; pub mod util; diff --git a/consensus/rules/Cargo.toml b/consensus/rules/Cargo.toml index fd86a61..8999cbc 100644 --- a/consensus/rules/Cargo.toml +++ b/consensus/rules/Cargo.toml @@ -7,16 +7,16 @@ authors = ["Boog900"] [features] default = [] -proptest = ["dep:proptest", "dep:proptest-derive"] +proptest = ["cuprate-types/proptest"] rayon = ["dep:rayon"] [dependencies] -cuprate-helper = { path = "../../helper", default-features = false, features = ["std"] } -cuprate-cryptonight = {path = "../../cryptonight"} +cuprate-constants = { workspace = true, default-features = false, features = ["block"] } +cuprate-helper = { workspace = true, default-features = false, features = ["std", "cast"] } +cuprate-types = { workspace = true, default-features = false } +cuprate-cryptonight = { workspace = true } monero-serai = { workspace = true, features = ["std"] } -multiexp = { workspace = true, features = ["std", "batch"] } -dalek-ff-group = { workspace = true, features = ["std"] } curve25519-dalek = { workspace = true, features = ["alloc", "zeroize", "precomputed-tables"] } rand = { workspace = true, features = ["std", "std_rng"] } @@ -25,15 +25,16 @@ hex = { workspace = true, features = ["std"] } hex-literal = { workspace = true } crypto-bigint = { workspace = true } +cfg-if = { workspace = true } tracing = { workspace = true, features = ["std"] } thiserror = { workspace = true } rayon = { workspace = true, optional = true } -proptest = {workspace = true, optional = true} -proptest-derive = {workspace = true, optional = true} - [dev-dependencies] -proptest = {workspace = true} -proptest-derive = {workspace = true} -tokio = {version = "1.35.0", features = ["rt-multi-thread", "macros"]} \ No newline at end of file +proptest = { workspace = true } +proptest-derive = { workspace = true } +tokio = { version = "1.35.0", features = ["rt-multi-thread", "macros"] } + +[lints] +workspace = true diff --git a/consensus/rules/src/batch_verifier.rs b/consensus/rules/src/batch_verifier.rs index c8d3f10..bce6eb9 100644 --- a/consensus/rules/src/batch_verifier.rs +++ b/consensus/rules/src/batch_verifier.rs @@ -1,4 +1,4 @@ -use multiexp::BatchVerifier as InternalBatchVerifier; +use monero_serai::ringct::bulletproofs::BatchVerifier as InternalBatchVerifier; /// This trait represents a batch verifier. /// @@ -12,18 +12,12 @@ pub trait BatchVerifier { /// # Panics /// This function may panic if `stmt` contains calls to `rayon`'s parallel iterators, e.g. `par_iter()`. // TODO: remove the panics by adding a generic API upstream. - fn queue_statement( - &mut self, - stmt: impl FnOnce(&mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>) -> R, - ) -> R; + fn queue_statement(&mut self, stmt: impl FnOnce(&mut InternalBatchVerifier) -> R) -> R; } // impl this for a single threaded batch verifier. -impl BatchVerifier for &'_ mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint> { - fn queue_statement( - &mut self, - stmt: impl FnOnce(&mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>) -> R, - ) -> R { +impl BatchVerifier for &'_ mut InternalBatchVerifier { + fn queue_statement(&mut self, stmt: impl FnOnce(&mut InternalBatchVerifier) -> R) -> R { stmt(self) } } diff --git a/consensus/rules/src/blocks.rs b/consensus/rules/src/blocks.rs index cb0e3e4..5e55ce2 100644 --- a/consensus/rules/src/blocks.rs +++ b/consensus/rules/src/blocks.rs @@ -6,7 +6,7 @@ use monero_serai::block::Block; use cuprate_cryptonight::*; use crate::{ - current_unix_timestamp, + check_block_version_vote, current_unix_timestamp, hard_forks::HardForkError, miner_tx::{check_miner_tx, MinerTxError}, HardFork, @@ -21,8 +21,8 @@ pub const PENALTY_FREE_ZONE_1: usize = 20000; pub const PENALTY_FREE_ZONE_2: usize = 60000; pub const PENALTY_FREE_ZONE_5: usize = 300000; -pub const RX_SEEDHASH_EPOCH_BLOCKS: u64 = 2048; -pub const RX_SEEDHASH_EPOCH_LAG: u64 = 64; +pub const RX_SEEDHASH_EPOCH_BLOCKS: usize = 2048; +pub const RX_SEEDHASH_EPOCH_LAG: usize = 64; #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] pub enum BlockError { @@ -44,22 +44,22 @@ pub enum BlockError { MinerTxError(#[from] MinerTxError), } -/// A trait to represent the RandomX VM. +/// A trait to represent the `RandomX` VM. pub trait RandomX { type Error; fn calculate_hash(&self, buf: &[u8]) -> Result<[u8; 32], Self::Error>; } -/// Returns if this height is a RandomX seed height. -pub fn is_randomx_seed_height(height: u64) -> bool { +/// Returns if this height is a `RandomX` seed height. +pub const fn is_randomx_seed_height(height: usize) -> bool { height % RX_SEEDHASH_EPOCH_BLOCKS == 0 } -/// Returns the RandomX seed height for this block. +/// Returns the `RandomX` seed height for this block. /// /// ref: -pub fn randomx_seed_height(height: u64) -> u64 { +pub const fn randomx_seed_height(height: usize) -> usize { if height <= RX_SEEDHASH_EPOCH_BLOCKS + RX_SEEDHASH_EPOCH_LAG { 0 } else { @@ -75,7 +75,7 @@ pub fn randomx_seed_height(height: u64) -> u64 { pub fn calculate_pow_hash( randomx_vm: Option<&R>, buf: &[u8], - height: u64, + height: usize, hf: &HardFork, ) -> Result<[u8; 32], BlockError> { if height == 202612 { @@ -89,7 +89,8 @@ pub fn calculate_pow_hash( } else if hf < &HardFork::V10 { cryptonight_hash_v2(buf) } else if hf < &HardFork::V12 { - cryptonight_hash_r(buf, height) + // FIXME: https://github.com/Cuprate/cuprate/issues/167. + cryptonight_hash_r(buf, height as u64) } else { randomx_vm .expect("RandomX VM needed from hf 12") @@ -121,10 +122,10 @@ pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockErr /// Returns the penalty free zone /// /// -pub fn penalty_free_zone(hf: &HardFork) -> usize { - if hf == &HardFork::V1 { +pub fn penalty_free_zone(hf: HardFork) -> usize { + if hf == HardFork::V1 { PENALTY_FREE_ZONE_1 - } else if hf >= &HardFork::V2 && hf < &HardFork::V5 { + } else if hf >= HardFork::V2 && hf < HardFork::V5 { PENALTY_FREE_ZONE_2 } else { PENALTY_FREE_ZONE_5 @@ -134,7 +135,7 @@ pub fn penalty_free_zone(hf: &HardFork) -> usize { /// Sanity check on the block blob size. /// /// ref: -fn block_size_sanity_check( +const fn block_size_sanity_check( block_blob_len: usize, effective_median: usize, ) -> Result<(), BlockError> { @@ -148,7 +149,7 @@ fn block_size_sanity_check( /// Sanity check on the block weight. /// /// ref: -fn check_block_weight( +pub const fn check_block_weight( block_weight: usize, median_for_block_reward: usize, ) -> Result<(), BlockError> { @@ -162,7 +163,7 @@ fn check_block_weight( /// Sanity check on number of txs in the block. /// /// ref: -fn check_amount_txs(number_none_miner_txs: usize) -> Result<(), BlockError> { +const fn check_amount_txs(number_none_miner_txs: usize) -> Result<(), BlockError> { if number_none_miner_txs + 1 > 0x10000000 { Err(BlockError::TooManyTxs) } else { @@ -174,17 +175,17 @@ fn check_amount_txs(number_none_miner_txs: usize) -> Result<(), BlockError> { /// /// ref: fn check_prev_id(block: &Block, top_hash: &[u8; 32]) -> Result<(), BlockError> { - if &block.header.previous != top_hash { - Err(BlockError::PreviousIDIncorrect) - } else { + if &block.header.previous == top_hash { Ok(()) + } else { + Err(BlockError::PreviousIDIncorrect) } } /// Checks the blocks timestamp is in the valid range. /// /// ref: -fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockError> { +pub fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockError> { if block.header.timestamp < median_timestamp || block.header.timestamp > current_unix_timestamp() + BLOCK_FUTURE_TIME_LIMIT { @@ -220,7 +221,7 @@ pub struct ContextToVerifyBlock { /// Contains the median timestamp over the last 60 blocks, if there is less than 60 blocks this should be [`None`] pub median_block_timestamp: Option, /// The current chain height. - pub chain_height: u64, + pub chain_height: usize, /// The current hard-fork. pub current_hf: HardFork, /// ref: @@ -248,11 +249,10 @@ pub fn check_block( block_blob_len: usize, block_chain_ctx: &ContextToVerifyBlock, ) -> Result<(HardFork, u64), BlockError> { - let (version, vote) = HardFork::from_block_header(&block.header)?; + let (version, vote) = + HardFork::from_block_header(&block.header).map_err(|_| HardForkError::HardForkUnknown)?; - block_chain_ctx - .current_hf - .check_block_version_vote(&version, &vote)?; + check_block_version_vote(&block_chain_ctx.current_hf, &version, &vote)?; if let Some(median_timestamp) = block_chain_ctx.median_block_timestamp { check_timestamp(block, median_timestamp)?; @@ -263,17 +263,17 @@ pub fn check_block( check_block_weight(block_weight, block_chain_ctx.median_weight_for_block_reward)?; block_size_sanity_check(block_blob_len, block_chain_ctx.effective_median_weight)?; - check_amount_txs(block.txs.len())?; - check_txs_unique(&block.txs)?; + check_amount_txs(block.transactions.len())?; + check_txs_unique(&block.transactions)?; let generated_coins = check_miner_tx( - &block.miner_tx, + &block.miner_transaction, total_fees, block_chain_ctx.chain_height, block_weight, block_chain_ctx.median_weight_for_block_reward, block_chain_ctx.already_generated_coins, - &block_chain_ctx.current_hf, + block_chain_ctx.current_hf, )?; Ok((vote, generated_coins)) diff --git a/consensus/rules/src/decomposed_amount.rs b/consensus/rules/src/decomposed_amount.rs index 5934814..ebed8b0 100644 --- a/consensus/rules/src/decomposed_amount.rs +++ b/consensus/rules/src/decomposed_amount.rs @@ -1,36 +1,27 @@ -use std::sync::OnceLock; - -/// Decomposed amount table. -/// -static DECOMPOSED_AMOUNTS: OnceLock<[u64; 172]> = OnceLock::new(); - #[rustfmt::skip] -pub fn decomposed_amounts() -> &'static [u64; 172] { - DECOMPOSED_AMOUNTS.get_or_init(|| { - [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 20, 30, 40, 50, 60, 70, 80, 90, - 100, 200, 300, 400, 500, 600, 700, 800, 900, - 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, - 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, - 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, - 1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000, - 10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000, - 100000000, 200000000, 300000000, 400000000, 500000000, 600000000, 700000000, 800000000, 900000000, - 1000000000, 2000000000, 3000000000, 4000000000, 5000000000, 6000000000, 7000000000, 8000000000, 9000000000, - 10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000, 70000000000, 80000000000, 90000000000, - 100000000000, 200000000000, 300000000000, 400000000000, 500000000000, 600000000000, 700000000000, 800000000000, 900000000000, - 1000000000000, 2000000000000, 3000000000000, 4000000000000, 5000000000000, 6000000000000, 7000000000000, 8000000000000, 9000000000000, - 10000000000000, 20000000000000, 30000000000000, 40000000000000, 50000000000000, 60000000000000, 70000000000000, 80000000000000, 90000000000000, - 100000000000000, 200000000000000, 300000000000000, 400000000000000, 500000000000000, 600000000000000, 700000000000000, 800000000000000, 900000000000000, - 1000000000000000, 2000000000000000, 3000000000000000, 4000000000000000, 5000000000000000, 6000000000000000, 7000000000000000, 8000000000000000, 9000000000000000, - 10000000000000000, 20000000000000000, 30000000000000000, 40000000000000000, 50000000000000000, 60000000000000000, 70000000000000000, 80000000000000000, 90000000000000000, - 100000000000000000, 200000000000000000, 300000000000000000, 400000000000000000, 500000000000000000, 600000000000000000, 700000000000000000, 800000000000000000, 900000000000000000, - 1000000000000000000, 2000000000000000000, 3000000000000000000, 4000000000000000000, 5000000000000000000, 6000000000000000000, 7000000000000000000, 8000000000000000000, 9000000000000000000, - 10000000000000000000 - ] - }) -} +/// Decomposed amount table. +pub(crate) static DECOMPOSED_AMOUNTS: [u64; 172] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 20, 30, 40, 50, 60, 70, 80, 90, + 100, 200, 300, 400, 500, 600, 700, 800, 900, + 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, + 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, + 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, + 1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000, + 10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000, + 100000000, 200000000, 300000000, 400000000, 500000000, 600000000, 700000000, 800000000, 900000000, + 1000000000, 2000000000, 3000000000, 4000000000, 5000000000, 6000000000, 7000000000, 8000000000, 9000000000, + 10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000, 70000000000, 80000000000, 90000000000, + 100000000000, 200000000000, 300000000000, 400000000000, 500000000000, 600000000000, 700000000000, 800000000000, 900000000000, + 1000000000000, 2000000000000, 3000000000000, 4000000000000, 5000000000000, 6000000000000, 7000000000000, 8000000000000, 9000000000000, + 10000000000000, 20000000000000, 30000000000000, 40000000000000, 50000000000000, 60000000000000, 70000000000000, 80000000000000, 90000000000000, + 100000000000000, 200000000000000, 300000000000000, 400000000000000, 500000000000000, 600000000000000, 700000000000000, 800000000000000, 900000000000000, + 1000000000000000, 2000000000000000, 3000000000000000, 4000000000000000, 5000000000000000, 6000000000000000, 7000000000000000, 8000000000000000, 9000000000000000, + 10000000000000000, 20000000000000000, 30000000000000000, 40000000000000000, 50000000000000000, 60000000000000000, 70000000000000000, 80000000000000000, 90000000000000000, + 100000000000000000, 200000000000000000, 300000000000000000, 400000000000000000, 500000000000000000, 600000000000000000, 700000000000000000, 800000000000000000, 900000000000000000, + 1000000000000000000, 2000000000000000000, 3000000000000000000, 4000000000000000000, 5000000000000000000, 6000000000000000000, 7000000000000000000, 8000000000000000000, 9000000000000000000, + 10000000000000000000 +]; /// Checks that an output amount is decomposed. /// @@ -40,7 +31,7 @@ pub fn decomposed_amounts() -> &'static [u64; 172] { /// ref: #[inline] pub fn is_decomposed_amount(amount: &u64) -> bool { - decomposed_amounts().binary_search(amount).is_ok() + DECOMPOSED_AMOUNTS.binary_search(amount).is_ok() } #[cfg(test)] @@ -49,8 +40,8 @@ mod tests { #[test] fn decomposed_amounts_return_decomposed() { - for amount in decomposed_amounts() { - assert!(is_decomposed_amount(amount)) + for amount in &DECOMPOSED_AMOUNTS { + assert!(is_decomposed_amount(amount)); } } diff --git a/consensus/rules/src/genesis.rs b/consensus/rules/src/genesis.rs index 73bc951..e1cf4f8 100644 --- a/consensus/rules/src/genesis.rs +++ b/consensus/rules/src/genesis.rs @@ -8,7 +8,7 @@ use monero_serai::{ use cuprate_helper::network::Network; -const fn genesis_nonce(network: &Network) -> u32 { +const fn genesis_nonce(network: Network) -> u32 { match network { Network::Mainnet => 10000, Network::Testnet => 10001, @@ -16,7 +16,7 @@ const fn genesis_nonce(network: &Network) -> u32 { } } -fn genesis_miner_tx(network: &Network) -> Transaction { +fn genesis_miner_tx(network: Network) -> Transaction { Transaction::read(&mut hex::decode(match network { Network::Mainnet | Network::Testnet => "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1", Network::Stagenet => "013c01ff0001ffffffffffff0302df5d56da0c7d643ddd1ce61901c7bdc5fb1738bfe39fbe69c28a3a7032729c0f2101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b" @@ -26,17 +26,17 @@ fn genesis_miner_tx(network: &Network) -> Transaction { /// Generates the Monero genesis block. /// /// ref: -pub fn generate_genesis_block(network: &Network) -> Block { +pub fn generate_genesis_block(network: Network) -> Block { Block { header: BlockHeader { - major_version: 1, - minor_version: 0, + hardfork_version: 1, + hardfork_signal: 0, timestamp: 0, previous: [0; 32], nonce: genesis_nonce(network), }, - miner_tx: genesis_miner_tx(network), - txs: vec![], + miner_transaction: genesis_miner_tx(network), + transactions: vec![], } } @@ -47,19 +47,19 @@ mod tests { #[test] fn generate_genesis_blocks() { assert_eq!( - &generate_genesis_block(&Network::Mainnet).hash(), + &generate_genesis_block(Network::Mainnet).hash(), hex::decode("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3") .unwrap() .as_slice() ); assert_eq!( - &generate_genesis_block(&Network::Testnet).hash(), + &generate_genesis_block(Network::Testnet).hash(), hex::decode("48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b") .unwrap() .as_slice() ); assert_eq!( - &generate_genesis_block(&Network::Stagenet).hash(), + &generate_genesis_block(Network::Stagenet).hash(), hex::decode("76ee3cc98646292206cd3e86f74d88b4dcc1d937088645e9b0cbca84b7ce74eb") .unwrap() .as_slice() diff --git a/consensus/rules/src/hard_forks.rs b/consensus/rules/src/hard_forks.rs index b34b93d..7e9a881 100644 --- a/consensus/rules/src/hard_forks.rs +++ b/consensus/rules/src/hard_forks.rs @@ -1,60 +1,57 @@ //! # Hard-Forks //! -//! Monero use hard-forks to update it's protocol, this module contains a [`HardFork`] enum which is -//! an identifier for every current hard-fork. -//! -//! This module also contains a [`HFVotes`] struct which keeps track of current blockchain voting, and -//! has a method [`HFVotes::current_fork`] to check if the next hard-fork should be activated. -//! -use monero_serai::block::BlockHeader; +//! Monero use hard-forks to update it's protocol, this module contains a [`HFVotes`] struct which +//! keeps track of current blockchain voting, and has a method [`HFVotes::current_fork`] to check +//! if the next hard-fork should be activated. use std::{ collections::VecDeque, fmt::{Display, Formatter}, - time::Duration, }; +pub use cuprate_types::{HardFork, HardForkError}; + #[cfg(test)] mod tests; -/// Target block time for hf 1. -/// -/// ref: -const BLOCK_TIME_V1: Duration = Duration::from_secs(60); -/// Target block time from v2. -/// -/// ref: -const BLOCK_TIME_V2: Duration = Duration::from_secs(120); - pub const NUMB_OF_HARD_FORKS: usize = 16; -#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] -pub enum HardForkError { - #[error("The hard-fork is unknown")] - HardForkUnknown, - #[error("The block is on an incorrect hard-fork")] - VersionIncorrect, - #[error("The block's vote is for a previous hard-fork")] - VoteTooLow, +/// Checks a blocks version and vote, assuming that `hf` is the current hard-fork. +/// +/// ref: +pub fn check_block_version_vote( + hf: &HardFork, + version: &HardFork, + vote: &HardFork, +) -> Result<(), HardForkError> { + // self = current hf + if hf != version { + return Err(HardForkError::VersionIncorrect); + } + if hf > vote { + return Err(HardForkError::VoteTooLow); + } + + Ok(()) } /// Information about a given hard-fork. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct HFInfo { - height: u64, - threshold: u64, + height: usize, + threshold: usize, } impl HFInfo { - pub const fn new(height: u64, threshold: u64) -> HFInfo { - HFInfo { height, threshold } + pub const fn new(height: usize, threshold: usize) -> Self { + Self { height, threshold } } } /// Information about every hard-fork Monero has had. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct HFsInfo([HFInfo; NUMB_OF_HARD_FORKS]); impl HFsInfo { - pub fn info_for_hf(&self, hf: &HardFork) -> HFInfo { + pub const fn info_for_hf(&self, hf: &HardFork) -> HFInfo { self.0[*hf as usize - 1] } @@ -65,7 +62,7 @@ impl HFsInfo { /// Returns the main-net hard-fork information. /// /// ref: - pub const fn main_net() -> HFsInfo { + pub const fn main_net() -> Self { Self([ HFInfo::new(0, 0), HFInfo::new(1009827, 0), @@ -89,7 +86,7 @@ impl HFsInfo { /// Returns the test-net hard-fork information. /// /// ref: - pub const fn test_net() -> HFsInfo { + pub const fn test_net() -> Self { Self([ HFInfo::new(0, 0), HFInfo::new(624634, 0), @@ -113,7 +110,7 @@ impl HFsInfo { /// Returns the test-net hard-fork information. /// /// ref: - pub const fn stage_net() -> HFsInfo { + pub const fn stage_net() -> Self { Self([ HFInfo::new(0, 0), HFInfo::new(32000, 0), @@ -135,117 +132,10 @@ impl HFsInfo { } } -/// An identifier for every hard-fork Monero has had. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -#[cfg_attr(any(feature = "proptest", test), derive(proptest_derive::Arbitrary))] -#[repr(u8)] -pub enum HardFork { - V1 = 1, - V2, - V3, - V4, - V5, - V6, - V7, - V8, - V9, - V10, - V11, - V12, - V13, - V14, - V15, - // remember to update from_vote! - V16, -} - -impl HardFork { - /// Returns the hard-fork for a blocks `major_version` field. - /// - /// - #[inline] - pub fn from_version(version: u8) -> Result { - Ok(match version { - 1 => HardFork::V1, - 2 => HardFork::V2, - 3 => HardFork::V3, - 4 => HardFork::V4, - 5 => HardFork::V5, - 6 => HardFork::V6, - 7 => HardFork::V7, - 8 => HardFork::V8, - 9 => HardFork::V9, - 10 => HardFork::V10, - 11 => HardFork::V11, - 12 => HardFork::V12, - 13 => HardFork::V13, - 14 => HardFork::V14, - 15 => HardFork::V15, - 16 => HardFork::V16, - _ => return Err(HardForkError::HardForkUnknown), - }) - } - - /// Returns the hard-fork for a blocks `minor_version` (vote) field. - /// - /// - #[inline] - pub fn from_vote(vote: u8) -> HardFork { - if vote == 0 { - // A vote of 0 is interpreted as 1 as that's what Monero used to default to. - return HardFork::V1; - } - // This must default to the latest hard-fork! - Self::from_version(vote).unwrap_or(HardFork::V16) - } - - #[inline] - pub fn from_block_header(header: &BlockHeader) -> Result<(HardFork, HardFork), HardForkError> { - Ok(( - HardFork::from_version(header.major_version)?, - HardFork::from_vote(header.minor_version), - )) - } - - /// Returns the next hard-fork. - pub fn next_fork(&self) -> Option { - HardFork::from_version(*self as u8 + 1).ok() - } - - /// Returns the target block time for this hardfork. - /// - /// ref: - pub fn block_time(&self) -> Duration { - match self { - HardFork::V1 => BLOCK_TIME_V1, - _ => BLOCK_TIME_V2, - } - } - - /// Checks a blocks version and vote, assuming that `self` is the current hard-fork. - /// - /// ref: - pub fn check_block_version_vote( - &self, - version: &HardFork, - vote: &HardFork, - ) -> Result<(), HardForkError> { - // self = current hf - if self != version { - Err(HardForkError::VersionIncorrect)?; - } - if self > vote { - Err(HardForkError::VoteTooLow)?; - } - - Ok(()) - } -} - /// A struct holding the current voting state of the blockchain. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct HFVotes { - votes: [u64; NUMB_OF_HARD_FORKS], + votes: [usize; NUMB_OF_HARD_FORKS], vote_list: VecDeque, window_size: usize, } @@ -275,8 +165,8 @@ impl Display for HFVotes { } impl HFVotes { - pub fn new(window_size: usize) -> HFVotes { - HFVotes { + pub fn new(window_size: usize) -> Self { + Self { votes: [0; NUMB_OF_HARD_FORKS], vote_list: VecDeque::with_capacity(window_size), window_size, @@ -293,16 +183,38 @@ impl HFVotes { } } + /// Pop a number of blocks from the top of the cache and push some values into the front of the cache, + /// i.e. the oldest blocks. + /// + /// `old_block_votes` should contain the HFs below the window that now will be in the window after popping + /// blocks from the top. + /// + /// # Panics + /// + /// This will panic if `old_block_votes` contains more HFs than `numb_blocks`. + pub fn reverse_blocks(&mut self, numb_blocks: usize, old_block_votes: Self) { + assert!(old_block_votes.vote_list.len() <= numb_blocks); + + for hf in self.vote_list.drain(self.vote_list.len() - numb_blocks..) { + self.votes[hf as usize - 1] -= 1; + } + + for old_vote in old_block_votes.vote_list.into_iter().rev() { + self.vote_list.push_front(old_vote); + self.votes[old_vote as usize - 1] += 1; + } + } + /// Returns the total votes for a hard-fork. /// /// ref: - pub fn votes_for_hf(&self, hf: &HardFork) -> u64 { + pub fn votes_for_hf(&self, hf: &HardFork) -> usize { self.votes[*hf as usize - 1..].iter().sum() } /// Returns the total amount of votes being tracked - pub fn total_votes(&self) -> u64 { - self.votes.iter().sum() + pub fn total_votes(&self) -> usize { + self.vote_list.len() } /// Checks if a future hard fork should be activated, returning the next hard-fork that should be @@ -312,8 +224,8 @@ impl HFVotes { pub fn current_fork( &self, current_hf: &HardFork, - current_height: u64, - window: u64, + current_height: usize, + window: usize, hfs_info: &HFsInfo, ) -> HardFork { let mut current_hf = *current_hf; @@ -339,6 +251,6 @@ impl HFVotes { /// Returns the votes needed for a hard-fork. /// /// ref: -pub fn votes_needed(threshold: u64, window: u64) -> u64 { +pub const fn votes_needed(threshold: usize, window: usize) -> usize { (threshold * window).div_ceil(100) } diff --git a/consensus/rules/src/hard_forks/tests.rs b/consensus/rules/src/hard_forks/tests.rs index 77ed751..1a24627 100644 --- a/consensus/rules/src/hard_forks/tests.rs +++ b/consensus/rules/src/hard_forks/tests.rs @@ -4,7 +4,7 @@ use proptest::{arbitrary::any, prop_assert_eq, prop_compose, proptest}; use crate::hard_forks::{HFVotes, HardFork, NUMB_OF_HARD_FORKS}; -const TEST_WINDOW_SIZE: u64 = 25; +const TEST_WINDOW_SIZE: usize = 25; #[test] fn target_block_time() { @@ -35,9 +35,9 @@ prop_compose! { fn arb_full_hf_votes() ( // we can't use HardFork as for some reason it overflows the stack, so we use u8. - votes in any::<[u8; TEST_WINDOW_SIZE as usize]>() + votes in any::<[u8; TEST_WINDOW_SIZE]>() ) -> HFVotes { - let mut vote_count = HFVotes::new(TEST_WINDOW_SIZE as usize); + let mut vote_count = HFVotes::new(TEST_WINDOW_SIZE); for vote in votes { vote_count.add_vote_for_hf(&HardFork::from_vote(vote % 17)); } @@ -48,10 +48,10 @@ prop_compose! { proptest! { #[test] fn hf_vote_counter_total_correct(hf_votes in arb_full_hf_votes()) { - prop_assert_eq!(hf_votes.total_votes(), u64::try_from(hf_votes.vote_list.len()).unwrap()); + prop_assert_eq!(hf_votes.total_votes(), hf_votes.vote_list.len()); - let mut votes = [0_u64; NUMB_OF_HARD_FORKS]; - for vote in hf_votes.vote_list.iter() { + let mut votes = [0_usize; NUMB_OF_HARD_FORKS]; + for vote in &hf_votes.vote_list { // manually go through the list of votes tallying votes[*vote as usize - 1] += 1; } @@ -61,9 +61,9 @@ proptest! { #[test] fn window_size_kept_constant(mut hf_votes in arb_full_hf_votes(), new_votes in any::>()) { - for new_vote in new_votes.into_iter() { + for new_vote in new_votes { hf_votes.add_vote_for_hf(&new_vote); - prop_assert_eq!(hf_votes.total_votes(), TEST_WINDOW_SIZE) + prop_assert_eq!(hf_votes.total_votes(), TEST_WINDOW_SIZE); } } diff --git a/consensus/rules/src/lib.rs b/consensus/rules/src/lib.rs index 3106cbb..eef20c1 100644 --- a/consensus/rules/src/lib.rs +++ b/consensus/rules/src/lib.rs @@ -1,3 +1,12 @@ +cfg_if::cfg_if! { + // Used in external `tests/`. + if #[cfg(test)] { + use proptest as _; + use proptest_derive as _; + use tokio as _; + } +} + use std::time::{SystemTime, UNIX_EPOCH}; pub mod batch_verifier; @@ -9,7 +18,7 @@ pub mod miner_tx; pub mod transactions; pub use decomposed_amount::is_decomposed_amount; -pub use hard_forks::{HFVotes, HFsInfo, HardFork}; +pub use hard_forks::{check_block_version_vote, HFVotes, HFsInfo, HardFork}; pub use transactions::TxVersion; #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] @@ -54,9 +63,9 @@ where /// An internal function that returns an iterator or a parallel iterator if the /// `rayon` feature is enabled. #[cfg(not(feature = "rayon"))] -fn try_par_iter(t: T) -> impl std::iter::Iterator +fn try_par_iter(t: T) -> impl Iterator where - T: std::iter::IntoIterator, + T: IntoIterator, { t.into_iter() } diff --git a/consensus/rules/src/miner_tx.rs b/consensus/rules/src/miner_tx.rs index 90f1a7e..bb3b004 100644 --- a/consensus/rules/src/miner_tx.rs +++ b/consensus/rules/src/miner_tx.rs @@ -1,9 +1,9 @@ -use monero_serai::{ - ringct::RctType, - transaction::{Input, Output, Timelock, Transaction}, -}; +use monero_serai::transaction::{Input, Output, Timelock, Transaction}; -use crate::{is_decomposed_amount, transactions::check_output_types, HardFork, TxVersion}; +use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; +use cuprate_types::TxVersion; + +use crate::{is_decomposed_amount, transactions::check_output_types, HardFork}; #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] pub enum MinerTxError { @@ -35,13 +35,13 @@ const MONEY_SUPPLY: u64 = u64::MAX; /// The minimum block reward per minute, "tail-emission" const MINIMUM_REWARD_PER_MIN: u64 = 3 * 10_u64.pow(11); /// The value which `lock_time` should be for a coinbase output. -const MINER_TX_TIME_LOCKED_BLOCKS: u64 = 60; +const MINER_TX_TIME_LOCKED_BLOCKS: usize = 60; /// Calculates the base block reward without taking away the penalty for expanding /// the block. /// /// ref: -fn calculate_base_reward(already_generated_coins: u64, hf: &HardFork) -> u64 { +fn calculate_base_reward(already_generated_coins: u64, hf: HardFork) -> u64 { let target_mins = hf.block_time().as_secs() / 60; let emission_speed_factor = 20 - (target_mins - 1); ((MONEY_SUPPLY - already_generated_coins) >> emission_speed_factor) @@ -55,7 +55,7 @@ pub fn calculate_block_reward( block_weight: usize, median_bw: usize, already_generated_coins: u64, - hf: &HardFork, + hf: HardFork, ) -> u64 { let base_reward = calculate_base_reward(already_generated_coins, hf); @@ -68,7 +68,7 @@ pub fn calculate_block_reward( .unwrap(); let effective_median_bw: u128 = median_bw.try_into().unwrap(); - (((base_reward as u128 * multiplicand) / effective_median_bw) / effective_median_bw) + (((u128::from(base_reward) * multiplicand) / effective_median_bw) / effective_median_bw) .try_into() .unwrap() } @@ -76,9 +76,9 @@ pub fn calculate_block_reward( /// Checks the miner transactions version. /// /// ref: -fn check_miner_tx_version(tx_version: &TxVersion, hf: &HardFork) -> Result<(), MinerTxError> { +fn check_miner_tx_version(tx_version: TxVersion, hf: HardFork) -> Result<(), MinerTxError> { // The TxVersion enum checks if the version is not 1 or 2 - if hf >= &HardFork::V12 && tx_version != &TxVersion::RingCT { + if hf >= HardFork::V12 && tx_version != TxVersion::RingCT { Err(MinerTxError::VersionInvalid) } else { Ok(()) @@ -88,38 +88,38 @@ fn check_miner_tx_version(tx_version: &TxVersion, hf: &HardFork) -> Result<(), M /// Checks the miner transactions inputs. /// /// ref: -fn check_inputs(inputs: &[Input], chain_height: u64) -> Result<(), MinerTxError> { +fn check_inputs(inputs: &[Input], chain_height: usize) -> Result<(), MinerTxError> { if inputs.len() != 1 { return Err(MinerTxError::IncorrectNumbOfInputs); } match &inputs[0] { Input::Gen(height) => { - if height != &chain_height { - Err(MinerTxError::InputsHeightIncorrect) - } else { + if height == &chain_height { Ok(()) + } else { + Err(MinerTxError::InputsHeightIncorrect) } } - _ => Err(MinerTxError::InputNotOfTypeGen), + Input::ToKey { .. } => Err(MinerTxError::InputNotOfTypeGen), } } /// Checks the miner transaction has a correct time lock. /// /// ref: -fn check_time_lock(time_lock: &Timelock, chain_height: u64) -> Result<(), MinerTxError> { +const fn check_time_lock(time_lock: &Timelock, chain_height: usize) -> Result<(), MinerTxError> { match time_lock { - Timelock::Block(till_height) => { + &Timelock::Block(till_height) => { // Lock times above this amount are timestamps not blocks. // This is just for safety though and shouldn't actually be hit. - if till_height > &500_000_000 { - Err(MinerTxError::InvalidLockTime)?; + if till_height > MAX_BLOCK_HEIGHT_USIZE { + return Err(MinerTxError::InvalidLockTime); } - if u64::try_from(*till_height).unwrap() != chain_height + MINER_TX_TIME_LOCKED_BLOCKS { - Err(MinerTxError::InvalidLockTime) - } else { + if till_height == chain_height + MINER_TX_TIME_LOCKED_BLOCKS { Ok(()) + } else { + Err(MinerTxError::InvalidLockTime) } } _ => Err(MinerTxError::InvalidLockTime), @@ -132,18 +132,18 @@ fn check_time_lock(time_lock: &Timelock, chain_height: u64) -> Result<(), MinerT /// && fn sum_outputs( outputs: &[Output], - hf: &HardFork, - tx_version: &TxVersion, + hf: HardFork, + tx_version: TxVersion, ) -> Result { let mut sum: u64 = 0; for out in outputs { let amt = out.amount.unwrap_or(0); - if tx_version == &TxVersion::RingSignatures && amt == 0 { + if tx_version == TxVersion::RingSignatures && amt == 0 { return Err(MinerTxError::OutputAmountIncorrect); } - if hf == &HardFork::V3 && !is_decomposed_amount(&amt) { + if hf == HardFork::V3 && !is_decomposed_amount(&amt) { return Err(MinerTxError::OutputNotDecomposed); } sum = sum.checked_add(amt).ok_or(MinerTxError::OutputsOverflow)?; @@ -158,9 +158,9 @@ fn check_total_output_amt( total_output: u64, reward: u64, fees: u64, - hf: &HardFork, + hf: HardFork, ) -> Result { - if hf == &HardFork::V1 || hf >= &HardFork::V12 { + if hf == HardFork::V1 || hf >= HardFork::V12 { if total_output != reward + fees { return Err(MinerTxError::OutputAmountIncorrect); } @@ -182,28 +182,33 @@ fn check_total_output_amt( pub fn check_miner_tx( tx: &Transaction, total_fees: u64, - chain_height: u64, + chain_height: usize, block_weight: usize, median_bw: usize, already_generated_coins: u64, - hf: &HardFork, + hf: HardFork, ) -> Result { - let tx_version = TxVersion::from_raw(tx.prefix.version).ok_or(MinerTxError::VersionInvalid)?; - check_miner_tx_version(&tx_version, hf)?; + let tx_version = TxVersion::from_raw(tx.version()).ok_or(MinerTxError::VersionInvalid)?; + check_miner_tx_version(tx_version, hf)?; // ref: - if hf >= &HardFork::V12 && tx.rct_signatures.rct_type() != RctType::Null { - return Err(MinerTxError::RCTTypeNotNULL); + match tx { + Transaction::V1 { .. } => (), + Transaction::V2 { proofs, .. } => { + if hf >= HardFork::V12 && proofs.is_some() { + return Err(MinerTxError::RCTTypeNotNULL); + } + } } - check_time_lock(&tx.prefix.timelock, chain_height)?; + check_time_lock(&tx.prefix().additional_timelock, chain_height)?; - check_inputs(&tx.prefix.inputs, chain_height)?; + check_inputs(&tx.prefix().inputs, chain_height)?; - check_output_types(&tx.prefix.outputs, hf).map_err(|_| MinerTxError::InvalidOutputType)?; + check_output_types(&tx.prefix().outputs, hf).map_err(|_| MinerTxError::InvalidOutputType)?; let reward = calculate_block_reward(block_weight, median_bw, already_generated_coins, hf); - let total_outs = sum_outputs(&tx.prefix.outputs, hf, &tx_version)?; + let total_outs = sum_outputs(&tx.prefix().outputs, hf, tx_version)?; check_total_output_amt(total_outs, reward, total_fees, hf) } @@ -217,7 +222,7 @@ mod tests { proptest! { #[test] fn tail_emission(generated_coins in any::(), hf in any::()) { - prop_assert!(calculate_base_reward(generated_coins, &hf) >= MINIMUM_REWARD_PER_MIN * hf.block_time().as_secs() / 60) + prop_assert!(calculate_base_reward(generated_coins, hf) >= MINIMUM_REWARD_PER_MIN * hf.block_time().as_secs() / 60); } } } diff --git a/consensus/rules/src/transactions.rs b/consensus/rules/src/transactions.rs index 9169708..b4eac19 100644 --- a/consensus/rules/src/transactions.rs +++ b/consensus/rules/src/transactions.rs @@ -1,8 +1,11 @@ use std::cmp::Ordering; -use monero_serai::ringct::RctType; +use monero_serai::{ + ringct::RctType, + transaction::{Input, Output, Timelock, Transaction}, +}; -use monero_serai::transaction::{Input, Output, Timelock, Transaction}; +pub use cuprate_types::TxVersion; use crate::{ batch_verifier::BatchVerifier, blocks::penalty_free_zone, check_point_canonically_encoded, @@ -75,31 +78,6 @@ pub enum TransactionError { RingCTError(#[from] RingCTError), } -/// An enum representing all valid Monero transaction versions. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum TxVersion { - /// Legacy ring signatures. - RingSignatures, - /// RingCT - RingCT, -} - -impl TxVersion { - /// Converts a `raw` version value to a [`TxVersion`]. - /// - /// This will return `None` on invalid values. - /// - /// ref: - /// && - pub fn from_raw(version: u64) -> Option { - Some(match version { - 1 => TxVersion::RingSignatures, - 2 => TxVersion::RingCT, - _ => return None, - }) - } -} - //----------------------------------------------------------------------------------------------------------- OUTPUTS /// Checks the output keys are canonically encoded points. @@ -121,11 +99,8 @@ fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> { /// /// /// -pub(crate) fn check_output_types( - outputs: &[Output], - hf: &HardFork, -) -> Result<(), TransactionError> { - if hf == &HardFork::V15 { +pub(crate) fn check_output_types(outputs: &[Output], hf: HardFork) -> Result<(), TransactionError> { + if hf == HardFork::V15 { for outs in outputs.windows(2) { if outs[0].view_tag.is_some() != outs[1].view_tag.is_some() { return Err(TransactionError::OutputTypeInvalid); @@ -135,8 +110,8 @@ pub(crate) fn check_output_types( } for out in outputs { - if hf <= &HardFork::V14 && out.view_tag.is_some() - || hf >= &HardFork::V16 && out.view_tag.is_none() + if hf <= HardFork::V14 && out.view_tag.is_some() + || hf >= HardFork::V16 && out.view_tag.is_none() { return Err(TransactionError::OutputTypeInvalid); } @@ -147,12 +122,12 @@ pub(crate) fn check_output_types( /// Checks the individual outputs amount for version 1 txs. /// /// ref: -fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionError> { +fn check_output_amount_v1(amount: u64, hf: HardFork) -> Result<(), TransactionError> { if amount == 0 { return Err(TransactionError::ZeroOutputForV1); } - if hf >= &HardFork::V2 && !is_decomposed_amount(&amount) { + if hf >= HardFork::V2 && !is_decomposed_amount(&amount) { return Err(TransactionError::AmountNotDecomposed); } @@ -162,7 +137,7 @@ fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionE /// Checks the individual outputs amount for version 2 txs. /// /// ref: -fn check_output_amount_v2(amount: u64) -> Result<(), TransactionError> { +const fn check_output_amount_v2(amount: u64) -> Result<(), TransactionError> { if amount == 0 { Ok(()) } else { @@ -176,8 +151,8 @@ fn check_output_amount_v2(amount: u64) -> Result<(), TransactionError> { /// && fn sum_outputs( outputs: &[Output], - hf: &HardFork, - tx_version: &TxVersion, + hf: HardFork, + tx_version: TxVersion, ) -> Result { let mut sum: u64 = 0; @@ -203,30 +178,22 @@ fn sum_outputs( /// && fn check_number_of_outputs( outputs: usize, - hf: &HardFork, - tx_version: &TxVersion, - rct_type: &RctType, + hf: HardFork, + tx_version: TxVersion, + bp_or_bpp: bool, ) -> Result<(), TransactionError> { - if tx_version == &TxVersion::RingSignatures { + if tx_version == TxVersion::RingSignatures { return Ok(()); } - if hf >= &HardFork::V12 && outputs < 2 { + if hf >= HardFork::V12 && outputs < 2 { return Err(TransactionError::InvalidNumberOfOutputs); } - match rct_type { - RctType::Bulletproofs - | RctType::BulletproofsCompactAmount - | RctType::Clsag - | RctType::BulletproofsPlus => { - if outputs <= MAX_BULLETPROOFS_OUTPUTS { - Ok(()) - } else { - Err(TransactionError::InvalidNumberOfOutputs) - } - } - _ => Ok(()), + if bp_or_bpp && outputs > MAX_BULLETPROOFS_OUTPUTS { + Err(TransactionError::InvalidNumberOfOutputs) + } else { + Ok(()) } } @@ -237,13 +204,13 @@ fn check_number_of_outputs( /// && fn check_outputs_semantics( outputs: &[Output], - hf: &HardFork, - tx_version: &TxVersion, - rct_type: &RctType, + hf: HardFork, + tx_version: TxVersion, + bp_or_bpp: bool, ) -> Result { check_output_types(outputs, hf)?; check_output_keys(outputs)?; - check_number_of_outputs(outputs.len(), hf, tx_version, rct_type)?; + check_number_of_outputs(outputs.len(), hf, tx_version, bp_or_bpp)?; sum_outputs(outputs, hf, tx_version) } @@ -253,16 +220,16 @@ fn check_outputs_semantics( /// Checks if an outputs unlock time has passed. /// /// -pub fn output_unlocked( +pub const fn output_unlocked( time_lock: &Timelock, - current_chain_height: u64, + current_chain_height: usize, current_time_lock_timestamp: u64, - hf: &HardFork, + hf: HardFork, ) -> bool { match *time_lock { Timelock::None => true, Timelock::Block(unlock_height) => { - check_block_time_lock(unlock_height.try_into().unwrap(), current_chain_height) + check_block_time_lock(unlock_height, current_chain_height) } Timelock::Time(unlock_time) => { check_timestamp_time_lock(unlock_time, current_time_lock_timestamp, hf) @@ -273,7 +240,7 @@ pub fn output_unlocked( /// Returns if a locked output, which uses a block height, can be spent. /// /// ref: -fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool { +const fn check_block_time_lock(unlock_height: usize, current_chain_height: usize) -> bool { // current_chain_height = 1 + top height unlock_height <= current_chain_height } @@ -281,10 +248,10 @@ fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool /// Returns if a locked output, which uses a block height, can be spent. /// /// ref: -fn check_timestamp_time_lock( +const fn check_timestamp_time_lock( unlock_timestamp: u64, current_time_lock_timestamp: u64, - hf: &HardFork, + hf: HardFork, ) -> bool { current_time_lock_timestamp + hf.block_time().as_secs() >= unlock_timestamp } @@ -297,21 +264,21 @@ fn check_timestamp_time_lock( /// fn check_all_time_locks( time_locks: &[Timelock], - current_chain_height: u64, + current_chain_height: usize, current_time_lock_timestamp: u64, - hf: &HardFork, + hf: HardFork, ) -> Result<(), TransactionError> { time_locks.iter().try_for_each(|time_lock| { - if !output_unlocked( + if output_unlocked( time_lock, current_chain_height, current_time_lock_timestamp, hf, ) { + Ok(()) + } else { tracing::debug!("Transaction invalid: one or more inputs locked, lock: {time_lock:?}."); Err(TransactionError::OneOrMoreRingMembersLocked) - } else { - Ok(()) } }) } @@ -322,11 +289,11 @@ fn check_all_time_locks( /// /// ref: /// && -pub fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), TransactionError> { - if hf == &HardFork::V15 { +pub fn check_decoy_info(decoy_info: &DecoyInfo, hf: HardFork) -> Result<(), TransactionError> { + if hf == HardFork::V15 { // Hard-fork 15 allows both v14 and v16 rules - return check_decoy_info(decoy_info, &HardFork::V14) - .or_else(|_| check_decoy_info(decoy_info, &HardFork::V16)); + return check_decoy_info(decoy_info, HardFork::V14) + .or_else(|_| check_decoy_info(decoy_info, HardFork::V16)); } let current_minimum_decoys = minimum_decoys(hf); @@ -340,13 +307,13 @@ pub fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), Tra if decoy_info.mixable > 1 { return Err(TransactionError::MoreThanOneMixableInputWithUnmixable); } - } else if hf >= &HardFork::V8 && decoy_info.min_decoys != current_minimum_decoys { + } else if hf >= HardFork::V8 && decoy_info.min_decoys != current_minimum_decoys { // From V8 enforce the minimum used number of rings is the default minimum. return Err(TransactionError::InputDoesNotHaveExpectedNumbDecoys); } // From v12 all inputs must have the same number of decoys. - if hf >= &HardFork::V12 && decoy_info.min_decoys != decoy_info.max_decoys { + if hf >= HardFork::V12 && decoy_info.min_decoys != decoy_info.max_decoys { return Err(TransactionError::InputDoesNotHaveExpectedNumbDecoys); } @@ -364,19 +331,19 @@ fn check_key_images(input: &Input) -> Result<(), TransactionError> { return Err(TransactionError::KeyImageIsNotInPrimeSubGroup); } } - _ => Err(TransactionError::IncorrectInputType)?, + Input::Gen(_) => return Err(TransactionError::IncorrectInputType), } Ok(()) } -/// Checks that the input is of type [`Input::ToKey`] aka txin_to_key. +/// Checks that the input is of type [`Input::ToKey`] aka `txin_to_key`. /// /// ref: -fn check_input_type(input: &Input) -> Result<(), TransactionError> { +const fn check_input_type(input: &Input) -> Result<(), TransactionError> { match input { Input::ToKey { .. } => Ok(()), - _ => Err(TransactionError::IncorrectInputType)?, + Input::Gen(_) => Err(TransactionError::IncorrectInputType), } } @@ -392,15 +359,15 @@ fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> { Ok(()) } } - _ => Err(TransactionError::IncorrectInputType)?, + Input::Gen(_) => Err(TransactionError::IncorrectInputType), } } /// Checks that the ring members for the input are unique after hard-fork 6. /// /// ref: -fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), TransactionError> { - if hf >= &HardFork::V6 { +fn check_ring_members_unique(input: &Input, hf: HardFork) -> Result<(), TransactionError> { + if hf >= HardFork::V6 { match input { Input::ToKey { key_offsets, .. } => key_offsets.iter().skip(1).try_for_each(|offset| { if *offset == 0 { @@ -409,7 +376,7 @@ fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), Transac Ok(()) } }), - _ => Err(TransactionError::IncorrectInputType)?, + Input::Gen(_) => Err(TransactionError::IncorrectInputType), } } else { Ok(()) @@ -419,34 +386,33 @@ fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), Transac /// Checks that from hf 7 the inputs are sorted by key image. /// /// ref: -fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), TransactionError> { +fn check_inputs_sorted(inputs: &[Input], hf: HardFork) -> Result<(), TransactionError> { let get_ki = |inp: &Input| match inp { Input::ToKey { key_image, .. } => Ok(key_image.compress().to_bytes()), - _ => Err(TransactionError::IncorrectInputType), + Input::Gen(_) => Err(TransactionError::IncorrectInputType), }; - if hf >= &HardFork::V7 { + if hf >= HardFork::V7 { for inps in inputs.windows(2) { match get_ki(&inps[0])?.cmp(&get_ki(&inps[1])?) { Ordering::Greater => (), _ => return Err(TransactionError::InputsAreNotOrdered), } } - Ok(()) - } else { - Ok(()) } + + Ok(()) } /// Checks the youngest output is at least 10 blocks old. /// /// ref: fn check_10_block_lock( - youngest_used_out_height: u64, - current_chain_height: u64, - hf: &HardFork, + youngest_used_out_height: usize, + current_chain_height: usize, + hf: HardFork, ) -> Result<(), TransactionError> { - if hf >= &HardFork::V12 { + if hf >= HardFork::V12 { if youngest_used_out_height + 10 > current_chain_height { tracing::debug!( "Transaction invalid: One or more ring members younger than 10 blocks." @@ -472,7 +438,7 @@ fn sum_inputs_check_overflow(inputs: &[Input]) -> Result .checked_add(amount.unwrap_or(0)) .ok_or(TransactionError::InputsOverflow)?; } - _ => Err(TransactionError::IncorrectInputType)?, + Input::Gen(_) => return Err(TransactionError::IncorrectInputType), } } @@ -484,7 +450,7 @@ fn sum_inputs_check_overflow(inputs: &[Input]) -> Result /// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as: /// - The tx-pool will use the current hard-fork /// - When syncing the hard-fork is in the block header. -fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result { +fn check_inputs_semantics(inputs: &[Input], hf: HardFork) -> Result { // if inputs.is_empty() { return Err(TransactionError::NoInputs); @@ -510,15 +476,15 @@ fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result Result<(), TransactionError> { // This rule is not contained in monero-core explicitly, but it is enforced by how Monero picks ring members. // When picking ring members monerod will only look in the DB at past blocks so an output has to be younger // than this transaction to be used in this tx. if tx_ring_members_info.youngest_used_out_height >= current_chain_height { tracing::debug!("Transaction invalid: One or more ring members too young."); - Err(TransactionError::OneOrMoreRingMembersLocked)?; + return Err(TransactionError::OneOrMoreRingMembersLocked); } check_10_block_lock( @@ -530,7 +496,7 @@ fn check_inputs_contextual( if let Some(decoys_info) = &tx_ring_members_info.decoy_info { check_decoy_info(decoys_info, hf)?; } else { - assert_eq!(hf, &HardFork::V1); + assert_eq!(hf, HardFork::V1); } for input in inputs { @@ -547,22 +513,22 @@ fn check_inputs_contextual( /// fn check_tx_version( decoy_info: &Option, - version: &TxVersion, - hf: &HardFork, + version: TxVersion, + hf: HardFork, ) -> Result<(), TransactionError> { if let Some(decoy_info) = decoy_info { let max = max_tx_version(hf); - if version > &max { + if version > max { return Err(TransactionError::TransactionVersionInvalid); } let min = min_tx_version(hf); - if version < &min && decoy_info.not_mixable == 0 { + if version < min && decoy_info.not_mixable == 0 { return Err(TransactionError::TransactionVersionInvalid); } } else { // This will only happen for hard-fork 1 when only RingSignatures are allowed. - if version != &TxVersion::RingSignatures { + if version != TxVersion::RingSignatures { return Err(TransactionError::TransactionVersionInvalid); } } @@ -571,8 +537,8 @@ fn check_tx_version( } /// Returns the default maximum tx version for the given hard-fork. -fn max_tx_version(hf: &HardFork) -> TxVersion { - if hf <= &HardFork::V3 { +fn max_tx_version(hf: HardFork) -> TxVersion { + if hf <= HardFork::V3 { TxVersion::RingSignatures } else { TxVersion::RingCT @@ -580,15 +546,15 @@ fn max_tx_version(hf: &HardFork) -> TxVersion { } /// Returns the default minimum tx version for the given hard-fork. -fn min_tx_version(hf: &HardFork) -> TxVersion { - if hf >= &HardFork::V6 { +fn min_tx_version(hf: HardFork) -> TxVersion { + if hf >= HardFork::V6 { TxVersion::RingCT } else { TxVersion::RingSignatures } } -fn transaction_weight_limit(hf: &HardFork) -> usize { +fn transaction_weight_limit(hf: HardFork) -> usize { penalty_free_zone(hf) / 2 - 600 } @@ -605,38 +571,51 @@ pub fn check_transaction_semantic( tx_blob_size: usize, tx_weight: usize, tx_hash: &[u8; 32], - hf: &HardFork, + hf: HardFork, verifier: impl BatchVerifier, ) -> Result { // if tx_blob_size > MAX_TX_BLOB_SIZE - || (hf >= &HardFork::V8 && tx_weight > transaction_weight_limit(hf)) + || (hf >= HardFork::V8 && tx_weight > transaction_weight_limit(hf)) { - Err(TransactionError::TooBig)?; + return Err(TransactionError::TooBig); } - let tx_version = TxVersion::from_raw(tx.prefix.version) - .ok_or(TransactionError::TransactionVersionInvalid)?; + let tx_version = + TxVersion::from_raw(tx.version()).ok_or(TransactionError::TransactionVersionInvalid)?; - let outputs_sum = check_outputs_semantics( - &tx.prefix.outputs, - hf, - &tx_version, - &tx.rct_signatures.rct_type(), - )?; - let inputs_sum = check_inputs_semantics(&tx.prefix.inputs, hf)?; + let bp_or_bpp = match tx { + Transaction::V2 { + proofs: Some(proofs), + .. + } => match proofs.rct_type() { + RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => false, + RctType::MlsagBulletproofs + | RctType::MlsagBulletproofsCompactAmount + | RctType::ClsagBulletproof + | RctType::ClsagBulletproofPlus => true, + }, + Transaction::V2 { proofs: None, .. } | Transaction::V1 { .. } => false, + }; - let fee = match tx_version { - TxVersion::RingSignatures => { + let outputs_sum = check_outputs_semantics(&tx.prefix().outputs, hf, tx_version, bp_or_bpp)?; + let inputs_sum = check_inputs_semantics(&tx.prefix().inputs, hf)?; + + let fee = match tx { + Transaction::V1 { .. } => { if outputs_sum >= inputs_sum { - Err(TransactionError::OutputsTooHigh)?; + return Err(TransactionError::OutputsTooHigh); } inputs_sum - outputs_sum } - TxVersion::RingCT => { - ring_ct::ring_ct_semantic_checks(tx, tx_hash, verifier, hf)?; + Transaction::V2 { proofs, .. } => { + let proofs = proofs + .as_ref() + .ok_or(TransactionError::TransactionVersionInvalid)?; - tx.rct_signatures.base.fee + ring_ct::ring_ct_semantic_checks(proofs, tx_hash, verifier, hf)?; + + proofs.base.fee } }; @@ -650,24 +629,23 @@ pub fn check_transaction_semantic( /// This function also does _not_ check for duplicate key-images: . /// /// `current_time_lock_timestamp` must be: . - pub fn check_transaction_contextual( tx: &Transaction, tx_ring_members_info: &TxRingMembersInfo, - current_chain_height: u64, + current_chain_height: usize, current_time_lock_timestamp: u64, - hf: &HardFork, + hf: HardFork, ) -> Result<(), TransactionError> { - let tx_version = TxVersion::from_raw(tx.prefix.version) - .ok_or(TransactionError::TransactionVersionInvalid)?; + let tx_version = + TxVersion::from_raw(tx.version()).ok_or(TransactionError::TransactionVersionInvalid)?; check_inputs_contextual( - &tx.prefix.inputs, + &tx.prefix().inputs, tx_ring_members_info, current_chain_height, hf, )?; - check_tx_version(&tx_ring_members_info.decoy_info, &tx_version, hf)?; + check_tx_version(&tx_ring_members_info.decoy_info, tx_version, hf)?; check_all_time_locks( &tx_ring_members_info.time_locked_outs, @@ -676,17 +654,22 @@ pub fn check_transaction_contextual( hf, )?; - match tx_version { - TxVersion::RingSignatures => ring_signatures::check_input_signatures( - &tx.prefix.inputs, - &tx.signatures, + match &tx { + Transaction::V1 { prefix, signatures } => ring_signatures::check_input_signatures( + &prefix.inputs, + signatures, &tx_ring_members_info.rings, - &tx.signature_hash(), + // This will only return None on v2 miner txs. + &tx.signature_hash() + .ok_or(TransactionError::TransactionVersionInvalid)?, ), - TxVersion::RingCT => Ok(ring_ct::check_input_signatures( - &tx.signature_hash(), - &tx.prefix.inputs, - &tx.rct_signatures, + Transaction::V2 { prefix, proofs } => Ok(ring_ct::check_input_signatures( + &tx.signature_hash() + .ok_or(TransactionError::TransactionVersionInvalid)?, + &prefix.inputs, + proofs + .as_ref() + .ok_or(TransactionError::TransactionVersionInvalid)?, &tx_ring_members_info.rings, )?), } diff --git a/consensus/rules/src/transactions/contextual_data.rs b/consensus/rules/src/transactions/contextual_data.rs index 6af3ad3..73bc12e 100644 --- a/consensus/rules/src/transactions/contextual_data.rs +++ b/consensus/rules/src/transactions/contextual_data.rs @@ -26,7 +26,7 @@ pub fn get_absolute_offsets(relative_offsets: &[u64]) -> Result, Transa Ok(offsets) } -/// Inserts the output IDs that are needed to verify the transaction inputs into the provided HashMap. +/// Inserts the output IDs that are needed to verify the transaction inputs into the provided `HashMap`. /// /// This will error if the inputs are empty /// @@ -49,7 +49,7 @@ pub fn insert_ring_member_ids( .entry(amount.unwrap_or(0)) .or_default() .extend(get_absolute_offsets(key_offsets)?), - _ => return Err(TransactionError::IncorrectInputType), + Input::Gen(_) => return Err(TransactionError::IncorrectInputType), } } Ok(()) @@ -60,7 +60,7 @@ pub fn insert_ring_member_ids( pub enum Rings { /// Legacy, pre-ringCT, rings. Legacy(Vec>), - /// RingCT rings, (outkey, amount commitment). + /// `RingCT` rings, (outkey, amount commitment). RingCT(Vec>), } @@ -70,7 +70,7 @@ pub struct TxRingMembersInfo { pub rings: Rings, /// Information on the structure of the decoys, must be [`None`] for txs before [`HardFork::V1`] pub decoy_info: Option, - pub youngest_used_out_height: u64, + pub youngest_used_out_height: usize, pub time_locked_outs: Vec, } @@ -103,15 +103,15 @@ impl DecoyInfo { /// /// So: /// - /// amount_outs_on_chain(inputs`[X]`) == outputs_with_amount`[X]` + /// `amount_outs_on_chain(inputs[X]) == outputs_with_amount[X]` /// /// Do not rely on this function to do consensus checks! /// pub fn new( inputs: &[Input], outputs_with_amount: impl Fn(u64) -> usize, - hf: &HardFork, - ) -> Result { + hf: HardFork, + ) -> Result { let mut min_decoys = usize::MAX; let mut max_decoys = usize::MIN; let mut mixable = 0; @@ -119,7 +119,7 @@ impl DecoyInfo { let minimum_decoys = minimum_decoys(hf); - for inp in inputs.iter() { + for inp in inputs { match inp { Input::ToKey { key_offsets, @@ -149,11 +149,11 @@ impl DecoyInfo { min_decoys = min(min_decoys, numb_decoys); max_decoys = max(max_decoys, numb_decoys); } - _ => return Err(TransactionError::IncorrectInputType), + Input::Gen(_) => return Err(TransactionError::IncorrectInputType), } } - Ok(DecoyInfo { + Ok(Self { mixable, not_mixable, min_decoys, @@ -166,7 +166,7 @@ impl DecoyInfo { /// **There are exceptions to this always being the minimum decoys** /// /// ref: -pub(crate) fn minimum_decoys(hf: &HardFork) -> usize { +pub(crate) fn minimum_decoys(hf: HardFork) -> usize { use HardFork as HF; match hf { HF::V1 => panic!("hard-fork 1 does not use these rules!"), diff --git a/consensus/rules/src/transactions/ring_ct.rs b/consensus/rules/src/transactions/ring_ct.rs index 38b56eb..32cedd4 100644 --- a/consensus/rules/src/transactions/ring_ct.rs +++ b/consensus/rules/src/transactions/ring_ct.rs @@ -1,13 +1,13 @@ use curve25519_dalek::{EdwardsPoint, Scalar}; use hex_literal::hex; use monero_serai::{ + generators::H, ringct::{ clsag::ClsagError, mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix}, - RctPrunable, RctSignatures, RctType, + RctProofs, RctPrunable, RctType, }, - transaction::{Input, Transaction}, - H, + transaction::Input, }; use rand::thread_rng; #[cfg(feature = "rayon")] @@ -40,20 +40,20 @@ pub enum RingCTError { CLSAGError(#[from] ClsagError), } -/// Checks the RingCT type is allowed for the current hard fork. +/// Checks the `RingCT` type is allowed for the current hard fork. /// /// -fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(), RingCTError> { +fn check_rct_type(ty: RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(), RingCTError> { use HardFork as F; use RctType as T; match ty { - T::MlsagAggregate | T::MlsagIndividual if hf >= F::V4 && hf < F::V9 => Ok(()), - T::Bulletproofs if hf >= F::V8 && hf < F::V11 => Ok(()), - T::BulletproofsCompactAmount if hf >= F::V10 && hf < F::V14 => Ok(()), - T::BulletproofsCompactAmount if GRANDFATHERED_TRANSACTIONS.contains(tx_hash) => Ok(()), - T::Clsag if hf >= F::V13 && hf < F::V16 => Ok(()), - T::BulletproofsPlus if hf >= F::V15 => Ok(()), + T::AggregateMlsagBorromean | T::MlsagBorromean if hf >= F::V4 && hf < F::V9 => Ok(()), + T::MlsagBulletproofs if hf >= F::V8 && hf < F::V11 => Ok(()), + T::MlsagBulletproofsCompactAmount if hf >= F::V10 && hf < F::V14 => Ok(()), + T::MlsagBulletproofsCompactAmount if GRANDFATHERED_TRANSACTIONS.contains(tx_hash) => Ok(()), + T::ClsagBulletproof if hf >= F::V13 && hf < F::V16 => Ok(()), + T::ClsagBulletproofPlus if hf >= F::V15 => Ok(()), _ => Err(RingCTError::TypeNotAllowed), } } @@ -61,20 +61,22 @@ fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(), /// Checks that the pseudo-outs sum to the same point as the output commitments. /// /// -fn simple_type_balances(rct_sig: &RctSignatures) -> Result<(), RingCTError> { - let pseudo_outs = if rct_sig.rct_type() == RctType::MlsagIndividual { +fn simple_type_balances(rct_sig: &RctProofs) -> Result<(), RingCTError> { + let pseudo_outs = if rct_sig.rct_type() == RctType::MlsagBorromean { &rct_sig.base.pseudo_outs } else { match &rct_sig.prunable { RctPrunable::Clsag { pseudo_outs, .. } + | RctPrunable::MlsagBulletproofsCompactAmount { pseudo_outs, .. } | RctPrunable::MlsagBulletproofs { pseudo_outs, .. } => pseudo_outs, - _ => panic!("RingCT type is not simple!"), + RctPrunable::MlsagBorromean { .. } => &rct_sig.base.pseudo_outs, + RctPrunable::AggregateMlsagBorromean { .. } => panic!("RingCT type is not simple!"), } }; let sum_inputs = pseudo_outs.iter().sum::(); - let sum_outputs = rct_sig.base.commitments.iter().sum::() - + Scalar::from(rct_sig.base.fee) * H(); + let sum_outputs = + rct_sig.base.commitments.iter().sum::() + Scalar::from(rct_sig.base.fee) * *H; if sum_inputs == sum_outputs { Ok(()) @@ -89,13 +91,12 @@ fn simple_type_balances(rct_sig: &RctSignatures) -> Result<(), RingCTError> { /// /// fn check_output_range_proofs( - rct_sig: &RctSignatures, + proofs: &RctProofs, mut verifier: impl BatchVerifier, ) -> Result<(), RingCTError> { - let commitments = &rct_sig.base.commitments; + let commitments = &proofs.base.commitments; - match &rct_sig.prunable { - RctPrunable::Null => Err(RingCTError::TypeNotAllowed)?, + match &proofs.prunable { RctPrunable::MlsagBorromean { borromean, .. } | RctPrunable::AggregateMlsagBorromean { borromean, .. } => try_par_iter(borromean) .zip(commitments) @@ -106,10 +107,11 @@ fn check_output_range_proofs( Err(RingCTError::BorromeanRangeInvalid) } }), - RctPrunable::MlsagBulletproofs { bulletproofs, .. } - | RctPrunable::Clsag { bulletproofs, .. } => { + RctPrunable::MlsagBulletproofs { bulletproof, .. } + | RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } + | RctPrunable::Clsag { bulletproof, .. } => { if verifier.queue_statement(|verifier| { - bulletproofs.batch_verify(&mut thread_rng(), verifier, (), commitments) + bulletproof.batch_verify(&mut thread_rng(), verifier, commitments) }) { Ok(()) } else { @@ -120,18 +122,18 @@ fn check_output_range_proofs( } pub(crate) fn ring_ct_semantic_checks( - tx: &Transaction, + proofs: &RctProofs, tx_hash: &[u8; 32], verifier: impl BatchVerifier, - hf: &HardFork, + hf: HardFork, ) -> Result<(), RingCTError> { - let rct_type = tx.rct_signatures.rct_type(); + let rct_type = proofs.rct_type(); - check_rct_type(&rct_type, *hf, tx_hash)?; - check_output_range_proofs(&tx.rct_signatures, verifier)?; + check_rct_type(rct_type, hf, tx_hash)?; + check_output_range_proofs(proofs, verifier)?; - if rct_type != RctType::MlsagAggregate { - simple_type_balances(&tx.rct_signatures)?; + if rct_type != RctType::AggregateMlsagBorromean { + simple_type_balances(proofs)?; } Ok(()) @@ -144,7 +146,7 @@ pub(crate) fn ring_ct_semantic_checks( pub(crate) fn check_input_signatures( msg: &[u8; 32], inputs: &[Input], - rct_sig: &RctSignatures, + proofs: &RctProofs, rings: &Rings, ) -> Result<(), RingCTError> { let Rings::RingCT(rings) = rings else { @@ -152,18 +154,18 @@ pub(crate) fn check_input_signatures( }; if rings.is_empty() { - Err(RingCTError::RingInvalid)?; + return Err(RingCTError::RingInvalid); } - let pseudo_outs = match &rct_sig.prunable { + let pseudo_outs = match &proofs.prunable { RctPrunable::MlsagBulletproofs { pseudo_outs, .. } + | RctPrunable::MlsagBulletproofsCompactAmount { pseudo_outs, .. } | RctPrunable::Clsag { pseudo_outs, .. } => pseudo_outs.as_slice(), - RctPrunable::MlsagBorromean { .. } => rct_sig.base.pseudo_outs.as_slice(), - RctPrunable::AggregateMlsagBorromean { .. } | RctPrunable::Null => &[], + RctPrunable::MlsagBorromean { .. } => proofs.base.pseudo_outs.as_slice(), + RctPrunable::AggregateMlsagBorromean { .. } => &[], }; - match &rct_sig.prunable { - RctPrunable::Null => Err(RingCTError::TypeNotAllowed)?, + match &proofs.prunable { RctPrunable::AggregateMlsagBorromean { mlsag, .. } => { let key_images = inputs .iter() @@ -176,11 +178,14 @@ pub(crate) fn check_input_signatures( .collect::>(); let mut matrix = - AggregateRingMatrixBuilder::new(&rct_sig.base.commitments, rct_sig.base.fee); + AggregateRingMatrixBuilder::new(&proofs.base.commitments, proofs.base.fee); + rings.iter().try_for_each(|ring| matrix.push_ring(ring))?; + Ok(mlsag.verify(msg, &matrix.build()?, &key_images)?) } RctPrunable::MlsagBorromean { mlsags, .. } + | RctPrunable::MlsagBulletproofsCompactAmount { mlsags, .. } | RctPrunable::MlsagBulletproofs { mlsags, .. } => try_par_iter(mlsags) .zip(pseudo_outs) .zip(inputs) @@ -216,18 +221,21 @@ mod tests { #[test] fn grandfathered_bulletproofs2() { - assert!( - check_rct_type(&RctType::BulletproofsCompactAmount, HardFork::V14, &[0; 32]).is_err() - ); + assert!(check_rct_type( + RctType::MlsagBulletproofsCompactAmount, + HardFork::V14, + &[0; 32] + ) + .is_err()); assert!(check_rct_type( - &RctType::BulletproofsCompactAmount, + RctType::MlsagBulletproofsCompactAmount, HardFork::V14, &GRANDFATHERED_TRANSACTIONS[0] ) .is_ok()); assert!(check_rct_type( - &RctType::BulletproofsCompactAmount, + RctType::MlsagBulletproofsCompactAmount, HardFork::V14, &GRANDFATHERED_TRANSACTIONS[1] ) diff --git a/consensus/rules/src/transactions/ring_signatures.rs b/consensus/rules/src/transactions/ring_signatures.rs index 7d4b8f9..a226f5e 100644 --- a/consensus/rules/src/transactions/ring_signatures.rs +++ b/consensus/rules/src/transactions/ring_signatures.rs @@ -17,7 +17,7 @@ use crate::try_par_iter; /// Verifies the ring signature. /// /// ref: -pub fn check_input_signatures( +pub(crate) fn check_input_signatures( inputs: &[Input], signatures: &[RingSignature], rings: &Rings, @@ -45,7 +45,7 @@ pub fn check_input_signatures( Ok(()) })?; } - _ => panic!("tried to verify v1 tx with a non v1 ring"), + Rings::RingCT(_) => panic!("tried to verify v1 tx with a non v1 ring"), } Ok(()) } diff --git a/consensus/rules/src/transactions/tests.rs b/consensus/rules/src/transactions/tests.rs index 1d7591b..e154396 100644 --- a/consensus/rules/src/transactions/tests.rs +++ b/consensus/rules/src/transactions/tests.rs @@ -9,18 +9,21 @@ use proptest::{collection::vec, prelude::*}; use monero_serai::transaction::Output; +use cuprate_constants::block::MAX_BLOCK_HEIGHT; +use cuprate_helper::cast::u64_to_usize; + use super::*; -use crate::decomposed_amount::decomposed_amounts; +use crate::decomposed_amount::DECOMPOSED_AMOUNTS; #[test] fn test_check_output_amount_v1() { - for amount in decomposed_amounts() { - assert!(check_output_amount_v1(*amount, &HardFork::V2).is_ok()) + for amount in &DECOMPOSED_AMOUNTS { + assert!(check_output_amount_v1(*amount, HardFork::V2).is_ok()); } proptest!(|(amount in any::().prop_filter("value_decomposed", |val| !is_decomposed_amount(val)))| { - prop_assert!(check_output_amount_v1(amount, &HardFork::V2).is_err()); - prop_assert!(check_output_amount_v1(amount, &HardFork::V1).is_ok()) + prop_assert!(check_output_amount_v1(amount, HardFork::V2).is_err()); + prop_assert!(check_output_amount_v1(amount, HardFork::V1).is_ok()); }); } @@ -39,10 +42,10 @@ fn test_sum_outputs() { let outs = [output_10, outputs_20]; - let sum = sum_outputs(&outs, &HardFork::V16, &TxVersion::RingSignatures).unwrap(); + let sum = sum_outputs(&outs, HardFork::V16, TxVersion::RingSignatures).unwrap(); assert_eq!(sum, 30); - assert!(sum_outputs(&outs, &HardFork::V16, &TxVersion::RingCT).is_err()) + assert!(sum_outputs(&outs, HardFork::V16, TxVersion::RingCT).is_err()); } #[test] @@ -50,78 +53,53 @@ fn test_decoy_info() { let decoy_info = DecoyInfo { mixable: 0, not_mixable: 0, - min_decoys: minimum_decoys(&HardFork::V8), - max_decoys: minimum_decoys(&HardFork::V8) + 1, + min_decoys: minimum_decoys(HardFork::V8), + max_decoys: minimum_decoys(HardFork::V8) + 1, }; - assert!(check_decoy_info(&decoy_info, &HardFork::V8).is_ok()); - assert!(check_decoy_info(&decoy_info, &HardFork::V16).is_err()); + assert!(check_decoy_info(&decoy_info, HardFork::V8).is_ok()); + assert!(check_decoy_info(&decoy_info, HardFork::V16).is_err()); let mut decoy_info = DecoyInfo { mixable: 0, not_mixable: 0, - min_decoys: minimum_decoys(&HardFork::V8) - 1, - max_decoys: minimum_decoys(&HardFork::V8) + 1, + min_decoys: minimum_decoys(HardFork::V8) - 1, + max_decoys: minimum_decoys(HardFork::V8) + 1, }; - assert!(check_decoy_info(&decoy_info, &HardFork::V8).is_err()); + assert!(check_decoy_info(&decoy_info, HardFork::V8).is_err()); decoy_info.not_mixable = 1; - assert!(check_decoy_info(&decoy_info, &HardFork::V8).is_ok()); + assert!(check_decoy_info(&decoy_info, HardFork::V8).is_ok()); decoy_info.mixable = 2; - assert!(check_decoy_info(&decoy_info, &HardFork::V8).is_err()); + assert!(check_decoy_info(&decoy_info, HardFork::V8).is_err()); let mut decoy_info = DecoyInfo { mixable: 0, not_mixable: 0, - min_decoys: minimum_decoys(&HardFork::V12), - max_decoys: minimum_decoys(&HardFork::V12) + 1, + min_decoys: minimum_decoys(HardFork::V12), + max_decoys: minimum_decoys(HardFork::V12) + 1, }; - assert!(check_decoy_info(&decoy_info, &HardFork::V12).is_err()); + assert!(check_decoy_info(&decoy_info, HardFork::V12).is_err()); decoy_info.max_decoys = decoy_info.min_decoys; - assert!(check_decoy_info(&decoy_info, &HardFork::V12).is_ok()); + assert!(check_decoy_info(&decoy_info, HardFork::V12).is_ok()); } #[test] fn test_torsion_ki() { - for &key_image in EIGHT_TORSION[1..].iter() { + for &key_image in &EIGHT_TORSION[1..] { assert!(check_key_images(&Input::ToKey { key_image, amount: None, key_offsets: vec![], }) - .is_err()) + .is_err()); } } -/// Returns a strategy that resolves to a [`RctType`] that uses -/// BPs(+). -#[allow(unreachable_code)] -#[allow(clippy::diverging_sub_expression)] -fn bulletproof_rct_type() -> BoxedStrategy { - return prop_oneof![ - Just(RctType::Bulletproofs), - Just(RctType::BulletproofsCompactAmount), - Just(RctType::Clsag), - Just(RctType::BulletproofsPlus), - ] - .boxed(); - - // Here to make sure this is updated when needed. - match unreachable!() { - RctType::Null => {} - RctType::MlsagAggregate => {} - RctType::MlsagIndividual => {} - RctType::Bulletproofs => {} - RctType::BulletproofsCompactAmount => {} - RctType::Clsag => {} - RctType::BulletproofsPlus => {} - }; -} - prop_compose! { /// Returns a valid prime-order point. fn random_point()(bytes in any::<[u8; 32]>()) -> EdwardsPoint { @@ -132,7 +110,7 @@ prop_compose! { prop_compose! { /// Returns a valid torsioned point. fn random_torsioned_point()(point in random_point(), torsion in 1..8_usize ) -> EdwardsPoint { - point + curve25519_dalek::constants::EIGHT_TORSION[torsion] + point + EIGHT_TORSION[torsion] } } @@ -183,13 +161,13 @@ prop_compose! { /// Returns a [`Timelock`] that is locked given a height and time. fn locked_timelock(height: u64, time_for_time_lock: u64)( timebased in any::(), - lock_height in (height+1)..500_000_001, + lock_height in (height+1)..=MAX_BLOCK_HEIGHT, time_for_time_lock in (time_for_time_lock+121).., ) -> Timelock { - if timebased || lock_height > 500_000_000 { + if timebased || lock_height > MAX_BLOCK_HEIGHT { Timelock::Time(time_for_time_lock) } else { - Timelock::Block(usize::try_from(lock_height).unwrap()) + Timelock::Block(u64_to_usize(lock_height)) } } } @@ -198,13 +176,13 @@ prop_compose! { /// Returns a [`Timelock`] that is unlocked given a height and time. fn unlocked_timelock(height: u64, time_for_time_lock: u64)( ty in 0..3, - lock_height in 0..(height+1), + lock_height in 0..=height, time_for_time_lock in 0..(time_for_time_lock+121), ) -> Timelock { match ty { 0 => Timelock::None, 1 => Timelock::Time(time_for_time_lock), - _ => Timelock::Block(usize::try_from(lock_height).unwrap()) + _ => Timelock::Block(u64_to_usize(lock_height)) } } } @@ -226,47 +204,47 @@ proptest! { hf_no_view_tags in hf_in_range(1..14), hf_view_tags in hf_in_range(16..17), ) { - prop_assert!(check_output_types(&view_tag_outs, &hf_view_tags).is_ok()); - prop_assert!(check_output_types(&view_tag_outs, &hf_no_view_tags).is_err()); + prop_assert!(check_output_types(&view_tag_outs, hf_view_tags).is_ok()); + prop_assert!(check_output_types(&view_tag_outs, hf_no_view_tags).is_err()); - prop_assert!(check_output_types(&non_view_tag_outs, &hf_no_view_tags).is_ok()); - prop_assert!(check_output_types(&non_view_tag_outs, &hf_view_tags).is_err()); + prop_assert!(check_output_types(&non_view_tag_outs, hf_no_view_tags).is_ok()); + prop_assert!(check_output_types(&non_view_tag_outs, hf_view_tags).is_err()); - prop_assert!(check_output_types(&non_view_tag_outs, &HardFork::V15).is_ok()); - prop_assert!(check_output_types(&view_tag_outs, &HardFork::V15).is_ok()); + prop_assert!(check_output_types(&non_view_tag_outs, HardFork::V15).is_ok()); + prop_assert!(check_output_types(&view_tag_outs, HardFork::V15).is_ok()); view_tag_outs.append(&mut non_view_tag_outs); - prop_assert!(check_output_types(&view_tag_outs, &HardFork::V15).is_err()); + prop_assert!(check_output_types(&view_tag_outs, HardFork::V15).is_err()); } #[test] - fn test_valid_number_of_outputs(valid_numb_outs in 2..17_usize, rct_type in bulletproof_rct_type()) { - prop_assert!(check_number_of_outputs(valid_numb_outs, &HardFork::V16, &TxVersion::RingCT, &rct_type).is_ok()); + fn test_valid_number_of_outputs(valid_numb_outs in 2..17_usize) { + prop_assert!(check_number_of_outputs(valid_numb_outs, HardFork::V16, TxVersion::RingCT, true).is_ok()); } #[test] - fn test_invalid_number_of_outputs(numb_outs in 17..usize::MAX, rct_type in bulletproof_rct_type()) { - prop_assert!(check_number_of_outputs(numb_outs, &HardFork::V16, &TxVersion::RingCT, &rct_type).is_err()); + fn test_invalid_number_of_outputs(numb_outs in 17..usize::MAX) { + prop_assert!(check_number_of_outputs(numb_outs, HardFork::V16, TxVersion::RingCT, true).is_err()); } #[test] fn test_check_output_amount_v2(amt in 1..u64::MAX) { prop_assert!(check_output_amount_v2(amt).is_err()); - prop_assert!(check_output_amount_v2(0).is_ok()) + prop_assert!(check_output_amount_v2(0).is_ok()); } #[test] - fn test_block_unlock_time(height in 1..u64::MAX) { + fn test_block_unlock_time(height in 1..usize::MAX) { prop_assert!(check_block_time_lock(height, height)); prop_assert!(!check_block_time_lock(height, height - 1)); prop_assert!(check_block_time_lock(height, height+1)); } #[test] - fn test_timestamp_time_lock(timestamp in 500_000_001..u64::MAX) { - prop_assert!(check_timestamp_time_lock(timestamp, timestamp - 120, &HardFork::V16)); - prop_assert!(!check_timestamp_time_lock(timestamp, timestamp - 121, &HardFork::V16)); - prop_assert!(check_timestamp_time_lock(timestamp, timestamp, &HardFork::V16)); + fn test_timestamp_time_lock(timestamp in MAX_BLOCK_HEIGHT+1..u64::MAX) { + prop_assert!(check_timestamp_time_lock(timestamp, timestamp - 120, HardFork::V16)); + prop_assert!(!check_timestamp_time_lock(timestamp, timestamp - 121, HardFork::V16)); + prop_assert!(check_timestamp_time_lock(timestamp, timestamp, HardFork::V16)); } #[test] @@ -274,11 +252,11 @@ proptest! { mut locked_locks in vec(locked_timelock(5_000, 100_000_000), 1..50), mut unlocked_locks in vec(unlocked_timelock(5_000, 100_000_000), 1..50) ) { - assert!(check_all_time_locks(&locked_locks, 5_000, 100_000_000, &HardFork::V16).is_err()); - assert!(check_all_time_locks(&unlocked_locks, 5_000, 100_000_000, &HardFork::V16).is_ok()); + assert!(check_all_time_locks(&locked_locks, 5_000, 100_000_000, HardFork::V16).is_err()); + assert!(check_all_time_locks(&unlocked_locks, 5_000, 100_000_000, HardFork::V16).is_ok()); unlocked_locks.append(&mut locked_locks); - assert!(check_all_time_locks(&unlocked_locks, 5_000, 100_000_000, &HardFork::V16).is_err()); + assert!(check_all_time_locks(&unlocked_locks, 5_000, 100_000_000, HardFork::V16).is_err()); } #[test] diff --git a/consensus/src/batch_verifier.rs b/consensus/src/batch_verifier.rs index 44493a6..101f981 100644 --- a/consensus/src/batch_verifier.rs +++ b/consensus/src/batch_verifier.rs @@ -1,18 +1,20 @@ -use std::{cell::RefCell, ops::DerefMut}; +use std::cell::RefCell; -use multiexp::BatchVerifier as InternalBatchVerifier; +use monero_serai::ringct::bulletproofs::BatchVerifier as InternalBatchVerifier; use rayon::prelude::*; use thread_local::ThreadLocal; +use cuprate_consensus_rules::batch_verifier::BatchVerifier; + /// A multithreaded batch verifier. pub struct MultiThreadedBatchVerifier { - internal: ThreadLocal>>, + internal: ThreadLocal>, } impl MultiThreadedBatchVerifier { /// Create a new multithreaded batch verifier, - pub fn new(numb_threads: usize) -> MultiThreadedBatchVerifier { - MultiThreadedBatchVerifier { + pub fn new(numb_threads: usize) -> Self { + Self { internal: ThreadLocal::with_capacity(numb_threads), } } @@ -22,21 +24,24 @@ impl MultiThreadedBatchVerifier { .into_iter() .map(RefCell::into_inner) .par_bridge() - .find_any(|batch_verifier| !batch_verifier.verify_vartime()) - .is_none() + .try_for_each(|batch_verifier| { + if batch_verifier.verify() { + Ok(()) + } else { + Err(()) + } + }) + .is_ok() } } -impl cuprate_consensus_rules::batch_verifier::BatchVerifier for &'_ MultiThreadedBatchVerifier { - fn queue_statement( - &mut self, - stmt: impl FnOnce(&mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>) -> R, - ) -> R { +impl BatchVerifier for &'_ MultiThreadedBatchVerifier { + fn queue_statement(&mut self, stmt: impl FnOnce(&mut InternalBatchVerifier) -> R) -> R { let mut verifier = self .internal - .get_or(|| RefCell::new(InternalBatchVerifier::new(32))) + .get_or(|| RefCell::new(InternalBatchVerifier::new())) .borrow_mut(); - stmt(verifier.deref_mut()) + stmt(&mut verifier) } } diff --git a/consensus/src/block.rs b/consensus/src/block.rs index d3d0672..3f5d749 100644 --- a/consensus/src/block.rs +++ b/consensus/src/block.rs @@ -12,31 +12,39 @@ use monero_serai::{ block::Block, transaction::{Input, Transaction}, }; -use rayon::prelude::*; use tower::{Service, ServiceExt}; -use tracing::instrument; + +use cuprate_consensus_context::{ + BlockChainContextRequest, BlockChainContextResponse, RawBlockChainContext, +}; +use cuprate_helper::asynch::rayon_spawn_async; +use cuprate_types::{ + AltBlockInformation, TransactionVerificationData, VerifiedBlockInformation, + VerifiedTransactionInformation, +}; use cuprate_consensus_rules::{ blocks::{ - calculate_pow_hash, check_block, check_block_pow, is_randomx_seed_height, - randomx_seed_height, BlockError, RandomX, + calculate_pow_hash, check_block, check_block_pow, randomx_seed_height, BlockError, RandomX, }, hard_forks::HardForkError, miner_tx::MinerTxError, ConsensusError, HardFork, }; -use cuprate_helper::asynch::rayon_spawn_async; -use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; use crate::{ - context::{ - rx_vms::RandomXVM, BlockChainContextRequest, BlockChainContextResponse, - RawBlockChainContext, - }, - transactions::{TransactionVerificationData, VerifyTxRequest, VerifyTxResponse}, + transactions::{VerifyTxRequest, VerifyTxResponse}, Database, ExtendedConsensusError, }; +mod alt_block; +mod batch_prepare; +mod free; + +use alt_block::sanity_check_alt_block; +use batch_prepare::batch_prepare_main_chain_block; +use free::pull_ordered_transactions; + /// A pre-prepared block with all data needed to verify it, except the block's proof of work. #[derive(Debug)] pub struct PreparedBlockExPow { @@ -53,7 +61,7 @@ pub struct PreparedBlockExPow { /// The block's hash. pub block_hash: [u8; 32], /// The height of the block. - pub height: u64, + pub height: usize, /// The weight of the block's miner transaction. pub miner_tx_weight: usize, @@ -66,17 +74,17 @@ impl PreparedBlockExPow { /// This errors if either the `block`'s: /// - Hard-fork values are invalid /// - Miner transaction is missing a miner input - pub fn new(block: Block) -> Result { - let (hf_version, hf_vote) = - HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?; + pub fn new(block: Block) -> Result { + let (hf_version, hf_vote) = HardFork::from_block_header(&block.header) + .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?; - let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else { - Err(ConsensusError::Block(BlockError::MinerTxError( + let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else { + return Err(ConsensusError::Block(BlockError::MinerTxError( MinerTxError::InputNotOfTypeGen, - )))? + ))); }; - Ok(PreparedBlockExPow { + Ok(Self { block_blob: block.serialize(), hf_vote, hf_version, @@ -84,7 +92,7 @@ impl PreparedBlockExPow { block_hash: block.hash(), height: *height, - miner_tx_weight: block.miner_tx.weight(), + miner_tx_weight: block.miner_transaction.weight(), block, }) } @@ -117,20 +125,17 @@ impl PreparedBlock { /// /// The randomX VM must be Some if RX is needed or this will panic. /// The randomX VM must also be initialised with the correct seed. - fn new( - block: Block, - randomx_vm: Option<&R>, - ) -> Result { - let (hf_version, hf_vote) = - HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?; + pub fn new(block: Block, randomx_vm: Option<&R>) -> Result { + let (hf_version, hf_vote) = HardFork::from_block_header(&block.header) + .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?; - let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else { - Err(ConsensusError::Block(BlockError::MinerTxError( + let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else { + return Err(ConsensusError::Block(BlockError::MinerTxError( MinerTxError::InputNotOfTypeGen, - )))? + ))); }; - Ok(PreparedBlock { + Ok(Self { block_blob: block.serialize(), hf_vote, hf_version, @@ -138,29 +143,29 @@ impl PreparedBlock { block_hash: block.hash(), pow_hash: calculate_pow_hash( randomx_vm, - &block.serialize_hashable(), + &block.serialize_pow_hash(), *height, &hf_version, )?, - miner_tx_weight: block.miner_tx.weight(), + miner_tx_weight: block.miner_transaction.weight(), block, }) } /// Creates a new [`PreparedBlock`] from a [`PreparedBlockExPow`]. /// - /// This function will give an invalid PoW hash if `randomx_vm` is not initialised + /// This function will give an invalid proof-of-work hash if `randomx_vm` is not initialised /// with the correct seed. /// /// # Panics /// This function will panic if `randomx_vm` is - /// [`None`] even though RandomX is needed. + /// [`None`] even though `RandomX` is needed. fn new_prepped( block: PreparedBlockExPow, randomx_vm: Option<&R>, - ) -> Result { - Ok(PreparedBlock { + ) -> Result { + Ok(Self { block_blob: block.block_blob, hf_vote: block.hf_vote, hf_version: block.hf_version, @@ -168,12 +173,26 @@ impl PreparedBlock { block_hash: block.block_hash, pow_hash: calculate_pow_hash( randomx_vm, - &block.block.serialize_hashable(), + &block.block.serialize_pow_hash(), block.height, &block.hf_version, )?, - miner_tx_weight: block.block.miner_tx.weight(), + miner_tx_weight: block.block.miner_transaction.weight(), + block: block.block, + }) + } + + /// Creates a new [`PreparedBlock`] from an [`AltBlockInformation`]. + pub fn new_alt_block(block: AltBlockInformation) -> Result { + Ok(Self { + block_blob: block.block_blob, + hf_vote: HardFork::from_version(block.block.header.hardfork_version) + .map_err(|_| BlockError::HardForkError(HardForkError::HardForkUnknown))?, + hf_version: HardFork::from_vote(block.block.header.hardfork_signal), + block_hash: block.block_hash, + pow_hash: block.pow_hash, + miner_tx_weight: block.block.miner_transaction.weight(), block: block.block, }) } @@ -191,6 +210,7 @@ pub enum VerifyBlockRequest { /// The already prepared block. block: PreparedBlock, /// The full list of transactions for this block, in the order given in `block`. + // TODO: Remove the Arc here txs: Vec>, }, /// Batch prepares a list of blocks and transactions for verification. @@ -198,13 +218,24 @@ pub enum VerifyBlockRequest { /// The list of blocks and their transactions (not necessarily in the order given in the block). blocks: Vec<(Block, Vec)>, }, + /// A request to sanity check an alt block, also returning the cumulative difficulty of the alt chain. + /// + /// Unlike requests to verify main chain blocks, you do not need to add the returned block to the context + /// service, you will still have to add it to the database though. + AltChain { + /// The alt block to sanity check. + block: Block, + /// The alt transactions. + prepared_txs: HashMap<[u8; 32], TransactionVerificationData>, + }, } /// A response from a verify block request. -#[allow(clippy::large_enum_variant)] // The largest variant is most common ([`MainChain`]) pub enum VerifyBlockResponse { /// This block is valid. MainChain(VerifiedBlockInformation), + /// The sanity checked alt block. + AltChain(AltBlockInformation), /// A list of prepared blocks for verification, you should call [`VerifyBlockRequest::MainChainPrepped`] on each of the returned /// blocks to fully verify them. MainChainBatchPrepped(Vec<(PreparedBlock, Vec>)>), @@ -231,16 +262,12 @@ where + Clone + Send + 'static, - D: Database + Clone + Send + Sync + 'static, + D: Database + Clone + Send + 'static, D::Future: Send + 'static, { /// Creates a new block verifier. - pub(crate) fn new( - context_svc: C, - tx_verifier_svc: TxV, - database: D, - ) -> BlockVerifierService { - BlockVerifierService { + pub(crate) const fn new(context_svc: C, tx_verifier_svc: TxV, database: D) -> Self { + Self { context_svc, tx_verifier_svc, _database: database, @@ -265,7 +292,7 @@ where + 'static, TxV::Future: Send + 'static, - D: Database + Clone + Send + Sync + 'static, + D: Database + Clone + Send + 'static, D::Future: Send + 'static, { type Response = VerifyBlockResponse; @@ -296,206 +323,20 @@ where verify_prepped_main_chain_block(block, txs, context_svc, tx_verifier_svc, None) .await } + VerifyBlockRequest::AltChain { + block, + prepared_txs, + } => sanity_check_alt_block(block, prepared_txs, context_svc).await, } } .boxed() } } -/// Batch prepares a list of blocks for verification. -#[instrument(level = "debug", name = "batch_prep_blocks", skip_all, fields(amt = blocks.len()))] -async fn batch_prepare_main_chain_block( - blocks: Vec<(Block, Vec)>, - mut context_svc: C, -) -> Result -where - C: Service< - BlockChainContextRequest, - Response = BlockChainContextResponse, - Error = tower::BoxError, - > + Send - + 'static, - C::Future: Send + 'static, -{ - let (blocks, txs): (Vec<_>, Vec<_>) = blocks.into_iter().unzip(); - - tracing::debug!("Calculating block hashes."); - let blocks: Vec = rayon_spawn_async(|| { - blocks - .into_iter() - .map(PreparedBlockExPow::new) - .collect::, _>>() - }) - .await?; - - let Some(last_block) = blocks.last() else { - return Err(ExtendedConsensusError::NoBlocksToVerify); - }; - - // hard-forks cannot be reversed, so the last block will contain the highest hard fork (provided the - // batch is valid). - let top_hf_in_batch = last_block.hf_version; - - // A Vec of (timestamp, HF) for each block to calculate the expected difficulty for each block. - let mut timestamps_hfs = Vec::with_capacity(blocks.len()); - let mut new_rx_vm = None; - - tracing::debug!("Checking blocks follow each other."); - - // For every block make sure they have the correct height and previous ID - for window in blocks.windows(2) { - let block_0 = &window[0]; - let block_1 = &window[1]; - - // Make sure no blocks in the batch have a higher hard fork than the last block. - if block_0.hf_version > top_hf_in_batch { - Err(ConsensusError::Block(BlockError::HardForkError( - HardForkError::VersionIncorrect, - )))?; - } - - if block_0.block_hash != block_1.block.header.previous - || block_0.height != block_1.height - 1 - { - tracing::debug!("Blocks do not follow each other, verification failed."); - Err(ConsensusError::Block(BlockError::PreviousIDIncorrect))?; - } - - // Cache any potential RX VM seeds as we may need them for future blocks in the batch. - if is_randomx_seed_height(block_0.height) && top_hf_in_batch >= HardFork::V12 { - new_rx_vm = Some((block_0.height, block_0.block_hash)); - } - - timestamps_hfs.push((block_0.block.header.timestamp, block_0.hf_version)) - } - - // Get the current blockchain context. - let BlockChainContextResponse::Context(checked_context) = context_svc - .ready() - .await? - .call(BlockChainContextRequest::GetContext) - .await - .map_err(Into::::into)? - else { - panic!("Context service returned wrong response!"); - }; - - // Calculate the expected difficulties for each block in the batch. - let BlockChainContextResponse::BatchDifficulties(difficulties) = context_svc - .ready() - .await? - .call(BlockChainContextRequest::BatchGetDifficulties( - timestamps_hfs, - )) - .await - .map_err(Into::::into)? - else { - panic!("Context service returned wrong response!"); - }; - - let context = checked_context.unchecked_blockchain_context().clone(); - - // Make sure the blocks follow the main chain. - - if context.chain_height != blocks[0].height { - tracing::debug!("Blocks do not follow main chain, verification failed."); - - Err(ConsensusError::Block(BlockError::MinerTxError( - MinerTxError::InputsHeightIncorrect, - )))?; - } - - if context.top_hash != blocks[0].block.header.previous { - tracing::debug!("Blocks do not follow main chain, verification failed."); - - Err(ConsensusError::Block(BlockError::PreviousIDIncorrect))?; - } - - let mut rx_vms = if top_hf_in_batch < HardFork::V12 { - HashMap::new() - } else { - let BlockChainContextResponse::RxVms(rx_vms) = context_svc - .ready() - .await? - .call(BlockChainContextRequest::GetCurrentRxVm) - .await? - else { - panic!("Blockchain context service returned wrong response!"); - }; - - rx_vms - }; - - // If we have a RX seed in the batch calculate it. - if let Some((new_vm_height, new_vm_seed)) = new_rx_vm { - tracing::debug!("New randomX seed in batch, initialising VM"); - - let new_vm = rayon_spawn_async(move || { - Arc::new(RandomXVM::new(&new_vm_seed).expect("RandomX VM gave an error on set up!")) - }) - .await; - - context_svc - .oneshot(BlockChainContextRequest::NewRXVM(( - new_vm_seed, - new_vm.clone(), - ))) - .await - .map_err(Into::::into)?; - - rx_vms.insert(new_vm_height, new_vm); - } - - tracing::debug!("Calculating PoW and prepping transaction"); - - let blocks = rayon_spawn_async(move || { - blocks - .into_par_iter() - .zip(difficulties) - .zip(txs) - .map(|((block, difficultly), txs)| { - // Calculate the PoW for the block. - let height = block.height; - let block = PreparedBlock::new_prepped( - block, - rx_vms.get(&randomx_seed_height(height)).map(AsRef::as_ref), - )?; - - // Check the PoW - check_block_pow(&block.pow_hash, difficultly).map_err(ConsensusError::Block)?; - - // Now setup the txs. - let mut txs = txs - .into_par_iter() - .map(|tx| { - let tx = TransactionVerificationData::new(tx)?; - Ok::<_, ConsensusError>((tx.tx_hash, tx)) - }) - .collect::, _>>()?; - - // Order the txs correctly. - let mut ordered_txs = Vec::with_capacity(txs.len()); - - for tx_hash in &block.block.txs { - let tx = txs - .remove(tx_hash) - .ok_or(ExtendedConsensusError::TxsIncludedWithBlockIncorrect)?; - ordered_txs.push(Arc::new(tx)); - } - - Ok((block, ordered_txs)) - }) - .collect::, ExtendedConsensusError>>() - }) - .await?; - - Ok(VerifyBlockResponse::MainChainBatchPrepped(blocks)) -} - /// Verifies a prepared block. async fn verify_main_chain_block( block: Block, - mut txs: HashMap<[u8; 32], TransactionVerificationData>, + txs: HashMap<[u8; 32], TransactionVerificationData>, mut context_svc: C, tx_verifier_svc: TxV, ) -> Result @@ -512,7 +353,7 @@ where let BlockChainContextResponse::Context(checked_context) = context_svc .ready() .await? - .call(BlockChainContextRequest::GetContext) + .call(BlockChainContextRequest::Context) .await? else { panic!("Context service returned wrong response!"); @@ -528,14 +369,14 @@ where // Set up the block and just pass it to [`verify_prepped_main_chain_block`] - // We just use the raw `major_version` here, no need to turn it into a `HardFork`. - let rx_vms = if block.header.major_version < 12 { + // We just use the raw `hardfork_version` here, no need to turn it into a `HardFork`. + let rx_vms = if block.header.hardfork_version < 12 { HashMap::new() } else { let BlockChainContextResponse::RxVms(rx_vms) = context_svc .ready() .await? - .call(BlockChainContextRequest::GetCurrentRxVm) + .call(BlockChainContextRequest::CurrentRxVms) .await? else { panic!("Blockchain context service returned wrong response!"); @@ -557,20 +398,11 @@ where .map_err(ConsensusError::Block)?; // Check that the txs included are what we need and that there are not any extra. - - let mut ordered_txs = Vec::with_capacity(txs.len()); - - tracing::debug!("Ordering transactions for block."); - - if !prepped_block.block.txs.is_empty() { - for tx_hash in &prepped_block.block.txs { - let tx = txs - .remove(tx_hash) - .ok_or(ExtendedConsensusError::TxsIncludedWithBlockIncorrect)?; - ordered_txs.push(Arc::new(tx)); - } - drop(txs); - } + // TODO: Remove the Arc here + let ordered_txs = pull_ordered_transactions(&prepped_block.block, txs)? + .into_iter() + .map(Arc::new) + .collect(); verify_prepped_main_chain_block( prepped_block, @@ -603,9 +435,8 @@ where context } else { let BlockChainContextResponse::Context(checked_context) = context_svc - .oneshot(BlockChainContextRequest::GetContext) - .await - .map_err(Into::::into)? + .oneshot(BlockChainContextRequest::Context) + .await? else { panic!("Context service returned wrong response!"); }; @@ -622,12 +453,12 @@ where check_block_pow(&prepped_block.pow_hash, context.next_difficulty) .map_err(ConsensusError::Block)?; - if prepped_block.block.txs.len() != txs.len() { + if prepped_block.block.transactions.len() != txs.len() { return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect); } - if !prepped_block.block.txs.is_empty() { - for (expected_tx_hash, tx) in prepped_block.block.txs.iter().zip(txs.iter()) { + if !prepped_block.block.transactions.is_empty() { + for (expected_tx_hash, tx) in prepped_block.block.transactions.iter().zip(txs.iter()) { if expected_tx_hash != &tx.tx_hash { return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect); } diff --git a/consensus/src/block/alt_block.rs b/consensus/src/block/alt_block.rs new file mode 100644 index 0000000..18c2734 --- /dev/null +++ b/consensus/src/block/alt_block.rs @@ -0,0 +1,308 @@ +//! Alt Blocks +//! +//! Alt blocks are sanity checked by [`sanity_check_alt_block`], that function will also compute the cumulative +//! difficulty of the alt chain so callers will know if they should re-org to the alt chain. +use std::{collections::HashMap, sync::Arc}; + +use monero_serai::{block::Block, transaction::Input}; +use tower::{Service, ServiceExt}; + +use cuprate_consensus_context::{ + difficulty::DifficultyCache, + rx_vms::RandomXVm, + weight::{self, BlockWeightsCache}, + AltChainContextCache, AltChainRequestToken, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW, +}; +use cuprate_consensus_rules::{ + blocks::{ + check_block_pow, check_block_weight, check_timestamp, randomx_seed_height, BlockError, + }, + miner_tx::MinerTxError, + ConsensusError, +}; +use cuprate_helper::{asynch::rayon_spawn_async, cast::u64_to_usize}; +use cuprate_types::{ + AltBlockInformation, Chain, ChainId, TransactionVerificationData, + VerifiedTransactionInformation, +}; + +use crate::{ + block::{free::pull_ordered_transactions, PreparedBlock}, + BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError, + VerifyBlockResponse, +}; + +/// This function sanity checks an alt-block. +/// +/// Returns [`AltBlockInformation`], which contains the cumulative difficulty of the alt chain. +/// +/// This function only checks the block's proof-of-work and its weight. +pub(crate) async fn sanity_check_alt_block( + block: Block, + txs: HashMap<[u8; 32], TransactionVerificationData>, + mut context_svc: C, +) -> Result +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + > + Send + + 'static, + C::Future: Send + 'static, +{ + // Fetch the alt-chains context cache. + let BlockChainContextResponse::AltChainContextCache(mut alt_context_cache) = context_svc + .ready() + .await? + .call(BlockChainContextRequest::AltChainContextCache { + prev_id: block.header.previous, + _token: AltChainRequestToken, + }) + .await? + else { + panic!("Context service returned wrong response!"); + }; + + // Check if the block's miner input is formed correctly. + let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else { + return Err(ConsensusError::Block(BlockError::MinerTxError( + MinerTxError::InputNotOfTypeGen, + )) + .into()); + }; + + if *height != alt_context_cache.chain_height { + return Err(ConsensusError::Block(BlockError::MinerTxError( + MinerTxError::InputsHeightIncorrect, + )) + .into()); + } + + // prep the alt block. + let prepped_block = { + let rx_vm = alt_rx_vm( + alt_context_cache.chain_height, + block.header.hardfork_version, + alt_context_cache.parent_chain, + &mut alt_context_cache, + &mut context_svc, + ) + .await?; + + rayon_spawn_async(move || PreparedBlock::new(block, rx_vm.as_deref())).await? + }; + + // get the difficulty cache for this alt chain. + let difficulty_cache = alt_difficulty_cache( + prepped_block.block.header.previous, + &mut alt_context_cache, + &mut context_svc, + ) + .await?; + + // Check the alt block timestamp is in the correct range. + if let Some(median_timestamp) = + difficulty_cache.median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)) + { + check_timestamp(&prepped_block.block, median_timestamp).map_err(ConsensusError::Block)?; + }; + + let next_difficulty = difficulty_cache.next_difficulty(prepped_block.hf_version); + // make sure the block's PoW is valid for this difficulty. + check_block_pow(&prepped_block.pow_hash, next_difficulty).map_err(ConsensusError::Block)?; + + let cumulative_difficulty = difficulty_cache.cumulative_difficulty() + next_difficulty; + + let ordered_txs = pull_ordered_transactions(&prepped_block.block, txs)?; + + let block_weight = + prepped_block.miner_tx_weight + ordered_txs.iter().map(|tx| tx.tx_weight).sum::(); + + let alt_weight_cache = alt_weight_cache( + prepped_block.block.header.previous, + &mut alt_context_cache, + &mut context_svc, + ) + .await?; + + // Check the block weight is below the limit. + check_block_weight( + block_weight, + alt_weight_cache.median_for_block_reward(prepped_block.hf_version), + ) + .map_err(ConsensusError::Block)?; + + let long_term_weight = weight::calculate_block_long_term_weight( + prepped_block.hf_version, + block_weight, + alt_weight_cache.median_long_term_weight(), + ); + + // Get the chainID or generate a new one if this is the first alt block in this alt chain. + let chain_id = *alt_context_cache + .chain_id + .get_or_insert_with(|| ChainId(rand::random())); + + // Create the alt block info. + let block_info = AltBlockInformation { + block_hash: prepped_block.block_hash, + block: prepped_block.block, + block_blob: prepped_block.block_blob, + txs: ordered_txs + .into_iter() + .map(|tx| VerifiedTransactionInformation { + tx_blob: tx.tx_blob, + tx_weight: tx.tx_weight, + fee: tx.fee, + tx_hash: tx.tx_hash, + tx: tx.tx, + }) + .collect(), + pow_hash: prepped_block.pow_hash, + weight: block_weight, + height: alt_context_cache.chain_height, + long_term_weight, + cumulative_difficulty, + chain_id, + }; + + // Add this block to the cache. + alt_context_cache.add_new_block( + block_info.height, + block_info.block_hash, + block_info.weight, + block_info.long_term_weight, + block_info.block.header.timestamp, + ); + + // Add this alt cache back to the context service. + context_svc + .oneshot(BlockChainContextRequest::AddAltChainContextCache { + prev_id: block_info.block.header.previous, + cache: alt_context_cache, + _token: AltChainRequestToken, + }) + .await?; + + Ok(VerifyBlockResponse::AltChain(block_info)) +} + +/// Retrieves the alt RX VM for the chosen block height. +/// +/// If the `hf` is less than 12 (the height RX activates), then [`None`] is returned. +async fn alt_rx_vm( + block_height: usize, + hf: u8, + parent_chain: Chain, + alt_chain_context: &mut AltChainContextCache, + context_svc: C, +) -> Result>, ExtendedConsensusError> +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + > + Send, + C::Future: Send + 'static, +{ + if hf < 12 { + return Ok(None); + } + + let seed_height = randomx_seed_height(block_height); + + let cached_vm = match alt_chain_context.cached_rx_vm.take() { + // If the VM is cached and the height is the height we need, we can use this VM. + Some((cached_seed_height, vm)) if seed_height == cached_seed_height => { + (cached_seed_height, vm) + } + // Otherwise we need to make a new VM. + _ => { + let BlockChainContextResponse::AltChainRxVM(vm) = context_svc + .oneshot(BlockChainContextRequest::AltChainRxVM { + height: block_height, + chain: parent_chain, + _token: AltChainRequestToken, + }) + .await? + else { + panic!("Context service returned wrong response!"); + }; + + (seed_height, vm) + } + }; + + Ok(Some(Arc::clone( + &alt_chain_context.cached_rx_vm.insert(cached_vm).1, + ))) +} + +/// Returns the [`DifficultyCache`] for the alt chain. +async fn alt_difficulty_cache( + prev_id: [u8; 32], + alt_chain_context: &mut AltChainContextCache, + context_svc: C, +) -> Result<&mut DifficultyCache, ExtendedConsensusError> +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + > + Send, + C::Future: Send + 'static, +{ + // First look to see if the difficulty cache for this alt chain is already cached. + match &mut alt_chain_context.difficulty_cache { + Some(cache) => Ok(cache), + // Otherwise make a new one. + difficulty_cache => { + let BlockChainContextResponse::AltChainDifficultyCache(cache) = context_svc + .oneshot(BlockChainContextRequest::AltChainDifficultyCache { + prev_id, + _token: AltChainRequestToken, + }) + .await? + else { + panic!("Context service returned wrong response!"); + }; + + Ok(difficulty_cache.insert(cache)) + } + } +} + +/// Returns the [`BlockWeightsCache`] for the alt chain. +async fn alt_weight_cache( + prev_id: [u8; 32], + alt_chain_context: &mut AltChainContextCache, + context_svc: C, +) -> Result<&mut BlockWeightsCache, ExtendedConsensusError> +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + > + Send, + C::Future: Send + 'static, +{ + // First look to see if the weight cache for this alt chain is already cached. + match &mut alt_chain_context.weight_cache { + Some(cache) => Ok(cache), + // Otherwise make a new one. + weight_cache => { + let BlockChainContextResponse::AltChainWeightCache(cache) = context_svc + .oneshot(BlockChainContextRequest::AltChainWeightCache { + prev_id, + _token: AltChainRequestToken, + }) + .await? + else { + panic!("Context service returned wrong response!"); + }; + + Ok(weight_cache.insert(cache)) + } + } +} diff --git a/consensus/src/block/batch_prepare.rs b/consensus/src/block/batch_prepare.rs new file mode 100644 index 0000000..ef384f5 --- /dev/null +++ b/consensus/src/block/batch_prepare.rs @@ -0,0 +1,209 @@ +use std::{collections::HashMap, sync::Arc}; + +use monero_serai::{block::Block, transaction::Transaction}; +use rayon::prelude::*; +use tower::{Service, ServiceExt}; +use tracing::instrument; + +use cuprate_consensus_context::rx_vms::RandomXVm; +use cuprate_consensus_rules::{ + blocks::{check_block_pow, is_randomx_seed_height, randomx_seed_height, BlockError}, + hard_forks::HardForkError, + miner_tx::MinerTxError, + ConsensusError, HardFork, +}; +use cuprate_helper::asynch::rayon_spawn_async; + +use crate::{ + block::{free::pull_ordered_transactions, PreparedBlock, PreparedBlockExPow}, + transactions::new_tx_verification_data, + BlockChainContextRequest, BlockChainContextResponse, ExtendedConsensusError, + VerifyBlockResponse, +}; + +/// Batch prepares a list of blocks for verification. +#[instrument(level = "debug", name = "batch_prep_blocks", skip_all, fields(amt = blocks.len()))] +pub(crate) async fn batch_prepare_main_chain_block( + blocks: Vec<(Block, Vec)>, + mut context_svc: C, +) -> Result +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + > + Send + + 'static, + C::Future: Send + 'static, +{ + let (blocks, txs): (Vec<_>, Vec<_>) = blocks.into_iter().unzip(); + + tracing::debug!("Calculating block hashes."); + let blocks: Vec = rayon_spawn_async(|| { + blocks + .into_iter() + .map(PreparedBlockExPow::new) + .collect::, _>>() + }) + .await?; + + let Some(last_block) = blocks.last() else { + return Err(ExtendedConsensusError::NoBlocksToVerify); + }; + + // hard-forks cannot be reversed, so the last block will contain the highest hard fork (provided the + // batch is valid). + let top_hf_in_batch = last_block.hf_version; + + // A Vec of (timestamp, HF) for each block to calculate the expected difficulty for each block. + let mut timestamps_hfs = Vec::with_capacity(blocks.len()); + let mut new_rx_vm = None; + + tracing::debug!("Checking blocks follow each other."); + + // For every block make sure they have the correct height and previous ID + for window in blocks.windows(2) { + let block_0 = &window[0]; + let block_1 = &window[1]; + + // Make sure no blocks in the batch have a higher hard fork than the last block. + if block_0.hf_version > top_hf_in_batch { + return Err(ConsensusError::Block(BlockError::HardForkError( + HardForkError::VersionIncorrect, + )) + .into()); + } + + if block_0.block_hash != block_1.block.header.previous + || block_0.height != block_1.height - 1 + { + tracing::debug!("Blocks do not follow each other, verification failed."); + return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into()); + } + + // Cache any potential RX VM seeds as we may need them for future blocks in the batch. + if is_randomx_seed_height(block_0.height) && top_hf_in_batch >= HardFork::V12 { + new_rx_vm = Some((block_0.height, block_0.block_hash)); + } + + timestamps_hfs.push((block_0.block.header.timestamp, block_0.hf_version)); + } + + // Get the current blockchain context. + let BlockChainContextResponse::Context(checked_context) = context_svc + .ready() + .await? + .call(BlockChainContextRequest::Context) + .await? + else { + panic!("Context service returned wrong response!"); + }; + + // Calculate the expected difficulties for each block in the batch. + let BlockChainContextResponse::BatchDifficulties(difficulties) = context_svc + .ready() + .await? + .call(BlockChainContextRequest::BatchGetDifficulties( + timestamps_hfs, + )) + .await? + else { + panic!("Context service returned wrong response!"); + }; + + let context = checked_context.unchecked_blockchain_context().clone(); + + // Make sure the blocks follow the main chain. + + if context.chain_height != blocks[0].height { + tracing::debug!("Blocks do not follow main chain, verification failed."); + + return Err(ConsensusError::Block(BlockError::MinerTxError( + MinerTxError::InputsHeightIncorrect, + )) + .into()); + } + + if context.top_hash != blocks[0].block.header.previous { + tracing::debug!("Blocks do not follow main chain, verification failed."); + + return Err(ConsensusError::Block(BlockError::PreviousIDIncorrect).into()); + } + + let mut rx_vms = if top_hf_in_batch < HardFork::V12 { + HashMap::new() + } else { + let BlockChainContextResponse::RxVms(rx_vms) = context_svc + .ready() + .await? + .call(BlockChainContextRequest::CurrentRxVms) + .await? + else { + panic!("Blockchain context service returned wrong response!"); + }; + + rx_vms + }; + + // If we have a RX seed in the batch calculate it. + if let Some((new_vm_height, new_vm_seed)) = new_rx_vm { + tracing::debug!("New randomX seed in batch, initialising VM"); + + let new_vm = rayon_spawn_async(move || { + Arc::new(RandomXVm::new(&new_vm_seed).expect("RandomX VM gave an error on set up!")) + }) + .await; + + // Give the new VM to the context service, so it can cache it. + context_svc + .oneshot(BlockChainContextRequest::NewRXVM(( + new_vm_seed, + Arc::clone(&new_vm), + ))) + .await?; + + rx_vms.insert(new_vm_height, new_vm); + } + + tracing::debug!("Calculating PoW and prepping transaction"); + + let blocks = rayon_spawn_async(move || { + blocks + .into_par_iter() + .zip(difficulties) + .zip(txs) + .map(|((block, difficultly), txs)| { + // Calculate the PoW for the block. + let height = block.height; + let block = PreparedBlock::new_prepped( + block, + rx_vms.get(&randomx_seed_height(height)).map(AsRef::as_ref), + )?; + + // Check the PoW + check_block_pow(&block.pow_hash, difficultly).map_err(ConsensusError::Block)?; + + // Now setup the txs. + let txs = txs + .into_par_iter() + .map(|tx| { + let tx = new_tx_verification_data(tx)?; + Ok::<_, ConsensusError>((tx.tx_hash, tx)) + }) + .collect::, _>>()?; + + // Order the txs correctly. + // TODO: Remove the Arc here + let ordered_txs = pull_ordered_transactions(&block.block, txs)? + .into_iter() + .map(Arc::new) + .collect(); + + Ok((block, ordered_txs)) + }) + .collect::, ExtendedConsensusError>>() + }) + .await?; + + Ok(VerifyBlockResponse::MainChainBatchPrepped(blocks)) +} diff --git a/consensus/src/block/free.rs b/consensus/src/block/free.rs new file mode 100644 index 0000000..e122374 --- /dev/null +++ b/consensus/src/block/free.rs @@ -0,0 +1,34 @@ +//! Free functions for block verification +use std::collections::HashMap; + +use monero_serai::block::Block; + +use cuprate_types::TransactionVerificationData; + +use crate::ExtendedConsensusError; + +/// Returns a list of transactions, pulled from `txs` in the order they are in the [`Block`]. +/// +/// Will error if a tx need is not in `txs` or if `txs` contain more txs than needed. +pub(crate) fn pull_ordered_transactions( + block: &Block, + mut txs: HashMap<[u8; 32], TransactionVerificationData>, +) -> Result, ExtendedConsensusError> { + if block.transactions.len() != txs.len() { + return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect); + } + + let mut ordered_txs = Vec::with_capacity(txs.len()); + + if !block.transactions.is_empty() { + for tx_hash in &block.transactions { + let tx = txs + .remove(tx_hash) + .ok_or(ExtendedConsensusError::TxsIncludedWithBlockIncorrect)?; + ordered_txs.push(tx); + } + drop(txs); + } + + Ok(ordered_txs) +} diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 1edafdc..f21d00b 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -7,30 +7,44 @@ //! - [`TxVerifierService`] Which handles transaction verification. //! //! This crate is generic over the database which is implemented as a [`tower::Service`]. To -//! implement a database you need to have a service which accepts [`BCReadRequest`] and responds -//! with [`BCResponse`]. +//! implement a database you need to have a service which accepts [`BlockchainReadRequest`] and responds +//! with [`BlockchainResponse`]. //! -use cuprate_consensus_rules::{ConsensusError, HardFork}; + +cfg_if::cfg_if! { + // Used in external `tests/`. + if #[cfg(test)] { + use cuprate_test_utils as _; + use curve25519_dalek as _; + use hex_literal as _; + } +} + +use cuprate_consensus_rules::ConsensusError; mod batch_verifier; pub mod block; -pub mod context; #[cfg(test)] mod tests; pub mod transactions; pub use block::{BlockVerifierService, VerifyBlockRequest, VerifyBlockResponse}; -pub use context::{ +pub use cuprate_consensus_context::{ initialize_blockchain_context, BlockChainContext, BlockChainContextRequest, BlockChainContextResponse, BlockChainContextService, ContextConfig, }; pub use transactions::{TxVerifierService, VerifyTxRequest, VerifyTxResponse}; // re-export. -pub use cuprate_types::blockchain::{BCReadRequest, BCResponse}; +pub use cuprate_consensus_rules::genesis::generate_genesis_block; +pub use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + HardFork, +}; /// An Error returned from one of the consensus services. #[derive(Debug, thiserror::Error)] +#[expect(variant_size_differences)] pub enum ExtendedConsensusError { /// A consensus error. #[error("{0}")] @@ -50,16 +64,13 @@ pub enum ExtendedConsensusError { } /// Initialize the 2 verifier [`tower::Service`]s (block and transaction). -pub async fn initialize_verifier( +pub fn initialize_verifier( database: D, ctx_svc: Ctx, -) -> Result< - ( - BlockVerifierService, D>, - TxVerifierService, - ), - ConsensusError, -> +) -> ( + BlockVerifierService, D>, + TxVerifierService, +) where D: Database + Clone + Send + Sync + 'static, D::Future: Send + 'static, @@ -75,7 +86,7 @@ where { let tx_svc = TxVerifierService::new(database.clone()); let block_svc = BlockVerifierService::new(ctx_svc, tx_svc.clone(), database); - Ok((block_svc, tx_svc)) + (block_svc, tx_svc) } use __private::Database; @@ -83,7 +94,7 @@ use __private::Database; pub mod __private { use std::future::Future; - use cuprate_types::blockchain::{BCReadRequest, BCResponse}; + use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse}; /// A type alias trait used to represent a database, so we don't have to write [`tower::Service`] bounds /// everywhere. @@ -94,8 +105,8 @@ pub mod __private { /// ``` pub trait Database: tower::Service< - BCReadRequest, - Response = BCResponse, + BlockchainReadRequest, + Response = BlockchainResponse, Error = tower::BoxError, Future = Self::Future2, > @@ -103,8 +114,13 @@ pub mod __private { type Future2: Future> + Send + 'static; } - impl> - crate::Database for T + impl< + T: tower::Service< + BlockchainReadRequest, + Response = BlockchainResponse, + Error = tower::BoxError, + >, + > Database for T where T::Future: Future> + Send + 'static, { diff --git a/consensus/src/tests.rs b/consensus/src/tests.rs index 13598be..0efef82 100644 --- a/consensus/src/tests.rs +++ b/consensus/src/tests.rs @@ -1,2 +1,2 @@ mod context; -pub mod mock_db; +pub(crate) mod mock_db; diff --git a/consensus/src/tests/context.rs b/consensus/src/tests/context.rs index 8c3841e..b9c5217 100644 --- a/consensus/src/tests/context.rs +++ b/consensus/src/tests/context.rs @@ -2,15 +2,13 @@ use proptest::strategy::ValueTree; use proptest::{strategy::Strategy, test_runner::TestRunner}; use tower::ServiceExt; -use crate::{ - context::{ - initialize_blockchain_context, BlockChainContextRequest, BlockChainContextResponse, - ContextConfig, NewBlockData, - }, - tests::mock_db::*, - HardFork, +use cuprate_consensus_context::{ + initialize_blockchain_context, BlockChainContextRequest, BlockChainContextResponse, + ContextConfig, NewBlockData, }; +use crate::{tests::mock_db::*, HardFork}; + pub(crate) mod data; mod difficulty; mod hardforks; @@ -29,10 +27,10 @@ const TEST_CONTEXT_CONFIG: ContextConfig = ContextConfig { #[tokio::test] async fn context_invalidated_on_new_block() -> Result<(), tower::BoxError> { - const BLOCKCHAIN_HEIGHT: u64 = 6000; + const BLOCKCHAIN_HEIGHT: usize = 6000; let mut runner = TestRunner::default(); - let db = arb_dummy_database(BLOCKCHAIN_HEIGHT.try_into().unwrap()) + let db = arb_dummy_database(BLOCKCHAIN_HEIGHT) .new_tree(&mut runner) .unwrap() .current(); @@ -41,7 +39,7 @@ async fn context_invalidated_on_new_block() -> Result<(), tower::BoxError> { let BlockChainContextResponse::Context(context) = ctx_svc .clone() - .oneshot(BlockChainContextRequest::GetContext) + .oneshot(BlockChainContextRequest::Context) .await? else { panic!("Context service returned wrong response!"); @@ -71,19 +69,18 @@ async fn context_invalidated_on_new_block() -> Result<(), tower::BoxError> { #[tokio::test] async fn context_height_correct() -> Result<(), tower::BoxError> { - const BLOCKCHAIN_HEIGHT: u64 = 6000; + const BLOCKCHAIN_HEIGHT: usize = 6000; let mut runner = TestRunner::default(); - let db = arb_dummy_database(BLOCKCHAIN_HEIGHT.try_into().unwrap()) + let db = arb_dummy_database(BLOCKCHAIN_HEIGHT) .new_tree(&mut runner) .unwrap() .current(); let ctx_svc = initialize_blockchain_context(TEST_CONTEXT_CONFIG, db).await?; - let BlockChainContextResponse::Context(context) = ctx_svc - .oneshot(BlockChainContextRequest::GetContext) - .await? + let BlockChainContextResponse::Context(context) = + ctx_svc.oneshot(BlockChainContextRequest::Context).await? else { panic!("context service returned incorrect response!") }; diff --git a/consensus/src/tests/context/data.rs b/consensus/src/tests/context/data.rs index baa591c..28f61a4 100644 --- a/consensus/src/tests/context/data.rs +++ b/consensus/src/tests/context/data.rs @@ -1,11 +1,12 @@ use cuprate_consensus_rules::HardFork; -pub static HFS_2688888_2689608: [(HardFork, HardFork); 720] = +pub(crate) static HFS_2688888_2689608: [(HardFork, HardFork); 720] = include!("./data/hfs_2688888_2689608"); -pub static HFS_2678808_2688888: [(HardFork, HardFork); 10080] = +pub(crate) static HFS_2678808_2688888: [(HardFork, HardFork); 10080] = include!("./data/hfs_2678808_2688888"); -pub static BW_2850000_3050000: [(usize, usize); 200_000] = include!("./data/bw_2850000_3050000"); +pub(crate) static BW_2850000_3050000: [(usize, usize); 200_000] = + include!("./data/bw_2850000_3050000"); -pub static DIF_3000000_3002000: [(u128, u64); 2000] = include!("./data/dif_3000000_3002000"); +pub(crate) static DIF_3000000_3002000: [(u128, u64); 2000] = include!("./data/dif_3000000_3002000"); diff --git a/consensus/src/tests/context/difficulty.rs b/consensus/src/tests/context/difficulty.rs index c9886f3..f1c0fd9 100644 --- a/consensus/src/tests/context/difficulty.rs +++ b/consensus/src/tests/context/difficulty.rs @@ -1,15 +1,15 @@ use std::collections::VecDeque; -use proptest::collection::size_range; +use proptest::collection::{size_range, vec}; use proptest::{prelude::*, prop_assert_eq, prop_compose, proptest}; -use cuprate_helper::num::median; - use crate::{ - context::difficulty::*, tests::{context::data::DIF_3000000_3002000, mock_db::*}, HardFork, }; +use cuprate_consensus_context::difficulty::*; +use cuprate_helper::num::median; +use cuprate_types::Chain; const TEST_WINDOW: usize = 72; const TEST_CUT: usize = 6; @@ -17,7 +17,7 @@ const TEST_LAG: usize = 2; const TEST_TOTAL_ACCOUNTED_BLOCKS: usize = TEST_WINDOW + TEST_LAG; -pub const TEST_DIFFICULTY_CONFIG: DifficultyCacheConfig = +pub(crate) const TEST_DIFFICULTY_CONFIG: DifficultyCacheConfig = DifficultyCacheConfig::new(TEST_WINDOW, TEST_CUT, TEST_LAG); #[tokio::test] @@ -26,12 +26,16 @@ async fn first_3_blocks_fixed_difficulty() -> Result<(), tower::BoxError> { let genesis = DummyBlockExtendedHeader::default().with_difficulty_info(0, 1); db_builder.add_block(genesis); - let mut difficulty_cache = - DifficultyCache::init_from_chain_height(1, TEST_DIFFICULTY_CONFIG, db_builder.finish(None)) - .await?; + let mut difficulty_cache = DifficultyCache::init_from_chain_height( + 1, + TEST_DIFFICULTY_CONFIG, + db_builder.finish(None), + Chain::Main, + ) + .await?; for height in 1..3 { - assert_eq!(difficulty_cache.next_difficulty(&HardFork::V1), 1); + assert_eq!(difficulty_cache.next_difficulty(HardFork::V1), 1); difficulty_cache.new_block(height, 0, u128::MAX); } Ok(()) @@ -42,9 +46,13 @@ async fn genesis_block_skipped() -> Result<(), tower::BoxError> { let mut db_builder = DummyDatabaseBuilder::default(); let genesis = DummyBlockExtendedHeader::default().with_difficulty_info(0, 1); db_builder.add_block(genesis); - let diff_cache = - DifficultyCache::init_from_chain_height(1, TEST_DIFFICULTY_CONFIG, db_builder.finish(None)) - .await?; + let diff_cache = DifficultyCache::init_from_chain_height( + 1, + TEST_DIFFICULTY_CONFIG, + db_builder.finish(None), + Chain::Main, + ) + .await?; assert!(diff_cache.cumulative_difficulties.is_empty()); assert!(diff_cache.timestamps.is_empty()); Ok(()) @@ -55,32 +63,30 @@ async fn calculate_diff_3000000_3002000() -> Result<(), tower::BoxError> { let cfg = DifficultyCacheConfig::main_net(); let mut db_builder = DummyDatabaseBuilder::default(); - for (cum_dif, timestamp) in DIF_3000000_3002000 - .iter() - .take(cfg.total_block_count() as usize) - { + for (cum_dif, timestamp) in DIF_3000000_3002000.iter().take(cfg.total_block_count()) { db_builder.add_block( DummyBlockExtendedHeader::default().with_difficulty_info(*timestamp, *cum_dif), - ) + ); } let mut diff_cache = DifficultyCache::init_from_chain_height( 3_000_720, - cfg.clone(), + cfg, db_builder.finish(Some(3_000_720)), + Chain::Main, ) .await?; for (i, diff_info) in DIF_3000000_3002000 .windows(2) - .skip(cfg.total_block_count() as usize - 1) + .skip(cfg.total_block_count() - 1) .enumerate() { let diff = diff_info[1].0 - diff_info[0].0; - assert_eq!(diff_cache.next_difficulty(&HardFork::V16), diff); + assert_eq!(diff_cache.next_difficulty(HardFork::V16), diff); - diff_cache.new_block(3_000_720 + i as u64, diff_info[1].1, diff_info[1].0); + diff_cache.new_block(3_000_720 + i, diff_info[1].1, diff_info[1].0); } Ok(()) @@ -95,7 +101,7 @@ prop_compose! { let (timestamps, mut cumulative_difficulties): (Vec<_>, Vec<_>) = blocks.into_iter().unzip(); cumulative_difficulties.sort_unstable(); DifficultyCache { - last_accounted_height: timestamps.len().try_into().unwrap(), + last_accounted_height: timestamps.len(), config: TEST_DIFFICULTY_CONFIG, timestamps: timestamps.into(), // we generate cumulative_difficulties in range 0..u64::MAX as if the generated values are close to u128::MAX @@ -133,22 +139,22 @@ proptest! { no_lag_cache.cumulative_difficulties.pop_front(); } // get the difficulty - let next_diff_no_lag = no_lag_cache.next_difficulty(&hf); + let next_diff_no_lag = no_lag_cache.next_difficulty(hf); for _ in 0..TEST_LAG { // add new blocks to the lagged cache diff_cache.new_block(diff_cache.last_accounted_height+1, timestamp, cumulative_difficulty); } // they both should now be the same - prop_assert_eq!(diff_cache.next_difficulty(&hf), next_diff_no_lag) + prop_assert_eq!(diff_cache.next_difficulty(hf), next_diff_no_lag); } #[test] fn next_difficulty_consistent(diff_cache in arb_difficulty_cache(TEST_TOTAL_ACCOUNTED_BLOCKS), hf in any::()) { - let first_call = diff_cache.next_difficulty(&hf); - prop_assert_eq!(first_call, diff_cache.next_difficulty(&hf)); - prop_assert_eq!(first_call, diff_cache.next_difficulty(&hf)); - prop_assert_eq!(first_call, diff_cache.next_difficulty(&hf)); + let first_call = diff_cache.next_difficulty(hf); + prop_assert_eq!(first_call, diff_cache.next_difficulty(hf)); + prop_assert_eq!(first_call, diff_cache.next_difficulty(hf)); + prop_assert_eq!(first_call, diff_cache.next_difficulty(hf)); } #[test] @@ -156,7 +162,7 @@ proptest! { let mut timestamps: VecDeque = timestamps.into(); let diff_cache = DifficultyCache { - last_accounted_height: (TEST_WINDOW -1).try_into().unwrap(), + last_accounted_height: TEST_WINDOW -1, config: TEST_DIFFICULTY_CONFIG, timestamps: timestamps.clone(), // we dont need cumulative_difficulties @@ -172,7 +178,7 @@ proptest! { #[test] fn window_size_kept_constant(mut diff_cache in arb_difficulty_cache(TEST_TOTAL_ACCOUNTED_BLOCKS), new_blocks in any::>()) { - for (timestamp, cumulative_difficulty) in new_blocks.into_iter() { + for (timestamp, cumulative_difficulty) in new_blocks { diff_cache.new_block(diff_cache.last_accounted_height+1, timestamp, cumulative_difficulty); prop_assert_eq!(diff_cache.timestamps.len(), TEST_TOTAL_ACCOUNTED_BLOCKS); prop_assert_eq!(diff_cache.cumulative_difficulties.len(), TEST_TOTAL_ACCOUNTED_BLOCKS); @@ -187,7 +193,7 @@ proptest! { ) { let cache = diff_cache.clone(); - diff_cache.next_difficulties(timestamps.into_iter().zip([hf].into_iter().cycle()).collect(), &hf); + diff_cache.next_difficulties(timestamps.into_iter().zip(std::iter::once(hf).cycle()).collect(), hf); prop_assert_eq!(diff_cache, cache); } @@ -198,14 +204,62 @@ proptest! { timestamps in any_with::>(size_range(0..1000).lift()), hf in any::(), ) { - let timestamps: Vec<_> = timestamps.into_iter().zip([hf].into_iter().cycle()).collect(); + let timestamps: Vec<_> = timestamps.into_iter().zip(std::iter::once(hf).cycle()).collect(); - let diffs = diff_cache.next_difficulties(timestamps.clone(), &hf); + let diffs = diff_cache.next_difficulties(timestamps.clone(), hf); for (timestamp, diff) in timestamps.into_iter().zip(diffs.into_iter()) { - prop_assert_eq!(diff_cache.next_difficulty(×tamp.1), diff); + prop_assert_eq!(diff_cache.next_difficulty(timestamp.1), diff); diff_cache.new_block(diff_cache.last_accounted_height +1, timestamp.0, diff + diff_cache.cumulative_difficulty()); } } + + #[test] + fn pop_blocks_below_total_blocks( + mut database in arb_dummy_database(20), + new_blocks in vec(any::<(u64, u128)>(), 0..500) + ) { + tokio_test::block_on(async move { + let old_cache = DifficultyCache::init_from_chain_height(19, TEST_DIFFICULTY_CONFIG, database.clone(), Chain::Main).await.unwrap(); + + let blocks_to_pop = new_blocks.len(); + + let mut new_cache = old_cache.clone(); + for (timestamp, cumulative_difficulty) in new_blocks { + database.add_block(DummyBlockExtendedHeader::default().with_difficulty_info(timestamp, cumulative_difficulty)); + new_cache.new_block(new_cache.last_accounted_height+1, timestamp, cumulative_difficulty); + } + + new_cache.pop_blocks_main_chain(blocks_to_pop, database).await?; + + prop_assert_eq!(new_cache, old_cache); + + Ok::<_, TestCaseError>(()) + })?; + } + + #[test] + fn pop_blocks_above_total_blocks( + mut database in arb_dummy_database(2000), + new_blocks in vec(any::<(u64, u128)>(), 0..5_000) + ) { + tokio_test::block_on(async move { + let old_cache = DifficultyCache::init_from_chain_height(1999, TEST_DIFFICULTY_CONFIG, database.clone(), Chain::Main).await.unwrap(); + + let blocks_to_pop = new_blocks.len(); + + let mut new_cache = old_cache.clone(); + for (timestamp, cumulative_difficulty) in new_blocks { + database.add_block(DummyBlockExtendedHeader::default().with_difficulty_info(timestamp, cumulative_difficulty)); + new_cache.new_block(new_cache.last_accounted_height+1, timestamp, cumulative_difficulty); + } + + new_cache.pop_blocks_main_chain(blocks_to_pop, database).await?; + + prop_assert_eq!(new_cache, old_cache); + + Ok::<_, TestCaseError>(()) + })?; + } } diff --git a/consensus/src/tests/context/hardforks.rs b/consensus/src/tests/context/hardforks.rs index f6f0f23..f080023 100644 --- a/consensus/src/tests/context/hardforks.rs +++ b/consensus/src/tests/context/hardforks.rs @@ -1,14 +1,14 @@ +use proptest::{collection::vec, prelude::*}; + +use cuprate_consensus_context::{hardforks::HardForkState, HardForkConfig}; use cuprate_consensus_rules::hard_forks::{HFInfo, HFsInfo, HardFork, NUMB_OF_HARD_FORKS}; -use crate::{ - context::{hardforks::HardForkState, HardForkConfig}, - tests::{ - context::data::{HFS_2678808_2688888, HFS_2688888_2689608}, - mock_db::*, - }, +use crate::tests::{ + context::data::{HFS_2678808_2688888, HFS_2688888_2689608}, + mock_db::*, }; -const TEST_WINDOW_SIZE: u64 = 25; +const TEST_WINDOW_SIZE: usize = 25; const TEST_HFS: [HFInfo; NUMB_OF_HARD_FORKS] = [ HFInfo::new(0, 0), @@ -29,7 +29,7 @@ const TEST_HFS: [HFInfo; NUMB_OF_HARD_FORKS] = [ HFInfo::new(150, 0), ]; -pub const TEST_HARD_FORK_CONFIG: HardForkConfig = HardForkConfig { +pub(crate) const TEST_HARD_FORK_CONFIG: HardForkConfig = HardForkConfig { window: TEST_WINDOW_SIZE, info: HFsInfo::new(TEST_HFS), }; @@ -77,8 +77,49 @@ async fn hf_v15_v16_correct() { for (i, (_, vote)) in HFS_2688888_2689608.into_iter().enumerate() { assert_eq!(state.current_hardfork, HardFork::V15); - state.new_block(vote, (2688888 + i) as u64); + state.new_block(vote, 2688888 + i); } assert_eq!(state.current_hardfork, HardFork::V16); } + +proptest! { + fn pop_blocks( + hfs in vec(any::(), 0..100), + extra_hfs in vec(any::(), 0..100) + ) { + tokio_test::block_on(async move { + let numb_hfs = hfs.len(); + let numb_pop_blocks = extra_hfs.len(); + + let mut db_builder = DummyDatabaseBuilder::default(); + + for hf in hfs { + db_builder.add_block( + DummyBlockExtendedHeader::default().with_hard_fork_info(hf, hf), + ); + } + + let db = db_builder.finish(Some(numb_hfs )); + + let mut state = HardForkState::init_from_chain_height( + numb_hfs, + TEST_HARD_FORK_CONFIG, + db.clone(), + ) + .await?; + + let state_clone = state.clone(); + + for (i, hf) in extra_hfs.into_iter().enumerate() { + state.new_block(hf, state.last_height + i + 1); + } + + state.pop_blocks_main_chain(numb_pop_blocks, db).await?; + + prop_assert_eq!(state_clone, state); + + Ok::<(), TestCaseError>(()) + })?; + } +} diff --git a/consensus/src/tests/context/rx_vms.rs b/consensus/src/tests/context/rx_vms.rs index f18a9b5..41c6279 100644 --- a/consensus/src/tests/context/rx_vms.rs +++ b/consensus/src/tests/context/rx_vms.rs @@ -3,15 +3,13 @@ use std::collections::VecDeque; use proptest::prelude::*; use tokio::runtime::Builder; +use cuprate_consensus_context::rx_vms::{get_last_rx_seed_heights, RandomXVmCache}; use cuprate_consensus_rules::{ blocks::{is_randomx_seed_height, randomx_seed_height}, HardFork, }; -use crate::{ - context::rx_vms::{get_last_rx_seed_heights, RandomXVMCache}, - tests::mock_db::*, -}; +use crate::tests::mock_db::*; #[test] fn rx_heights_consistent() { @@ -39,10 +37,11 @@ fn rx_heights_consistent() { } #[tokio::test] +#[expect(unused_qualifications, reason = "false positive in tokio macro")] async fn rx_vm_created_on_hf_12() { let db = DummyDatabaseBuilder::default().finish(Some(10)); - let mut cache = RandomXVMCache::init_from_chain_height(10, &HardFork::V11, db) + let mut cache = RandomXVmCache::init_from_chain_height(10, &HardFork::V11, db) .await .unwrap(); @@ -67,7 +66,7 @@ proptest! { let rt = Builder::new_multi_thread().enable_all().build().unwrap(); rt.block_on(async move { - let cache = RandomXVMCache::init_from_chain_height(10, &hf, db).await.unwrap(); + let cache = RandomXVmCache::init_from_chain_height(10, &hf, db).await.unwrap(); assert!(cache.seeds.len() == cache.vms.len() || hf < HardFork::V12); }); } diff --git a/consensus/src/tests/context/weight.rs b/consensus/src/tests/context/weight.rs index 902d446..dab3979 100644 --- a/consensus/src/tests/context/weight.rs +++ b/consensus/src/tests/context/weight.rs @@ -1,13 +1,15 @@ use crate::{ - context::{ - weight::{calculate_block_long_term_weight, BlockWeightsCache}, - BlockWeightsCacheConfig, - }, tests::{context::data::BW_2850000_3050000, mock_db::*}, HardFork, }; +use cuprate_consensus_context::{ + weight::{calculate_block_long_term_weight, BlockWeightsCache}, + BlockWeightsCacheConfig, +}; +use cuprate_types::Chain; -pub const TEST_WEIGHT_CONFIG: BlockWeightsCacheConfig = BlockWeightsCacheConfig::new(100, 5000); +pub(crate) const TEST_WEIGHT_CONFIG: BlockWeightsCacheConfig = + BlockWeightsCacheConfig::new(100, 5000); #[tokio::test] async fn blocks_out_of_window_not_counted() -> Result<(), tower::BoxError> { @@ -21,6 +23,7 @@ async fn blocks_out_of_window_not_counted() -> Result<(), tower::BoxError> { 5000, TEST_WEIGHT_CONFIG, db_builder.finish(None), + Chain::Main, ) .await?; assert_eq!(weight_cache.median_long_term_weight(), 2500); @@ -37,6 +40,74 @@ async fn blocks_out_of_window_not_counted() -> Result<(), tower::BoxError> { Ok(()) } +#[tokio::test] +async fn pop_blocks_greater_than_window() -> Result<(), tower::BoxError> { + let mut db_builder = DummyDatabaseBuilder::default(); + for weight in 1..=5000 { + let block = DummyBlockExtendedHeader::default().with_weight_into(weight, weight); + db_builder.add_block(block); + } + + let database = db_builder.finish(None); + + let mut weight_cache = BlockWeightsCache::init_from_chain_height( + 5000, + TEST_WEIGHT_CONFIG, + database.clone(), + Chain::Main, + ) + .await?; + + let old_cache = weight_cache.clone(); + + weight_cache.new_block(5000, 0, 0); + weight_cache.new_block(5001, 0, 0); + weight_cache.new_block(5002, 0, 0); + + weight_cache + .pop_blocks_main_chain(3, database) + .await + .unwrap(); + + assert_eq!(weight_cache, old_cache); + + Ok(()) +} + +#[tokio::test] +async fn pop_blocks_less_than_window() -> Result<(), tower::BoxError> { + let mut db_builder = DummyDatabaseBuilder::default(); + for weight in 1..=500 { + let block = DummyBlockExtendedHeader::default().with_weight_into(weight, weight); + db_builder.add_block(block); + } + + let database = db_builder.finish(None); + + let mut weight_cache = BlockWeightsCache::init_from_chain_height( + 500, + TEST_WEIGHT_CONFIG, + database.clone(), + Chain::Main, + ) + .await?; + + let old_cache = weight_cache.clone(); + + weight_cache.new_block(500, 0, 0); + weight_cache.new_block(501, 0, 0); + weight_cache.new_block(502, 0, 0); + + weight_cache + .pop_blocks_main_chain(3, database) + .await + .unwrap(); + + assert_eq!(weight_cache, old_cache); + + Ok(()) +} + #[tokio::test] async fn weight_cache_calculates_correct_median() -> Result<(), tower::BoxError> { let mut db_builder = DummyDatabaseBuilder::default(); @@ -44,19 +115,23 @@ async fn weight_cache_calculates_correct_median() -> Result<(), tower::BoxError> let block = DummyBlockExtendedHeader::default().with_weight_into(0, 0); db_builder.add_block(block); - let mut weight_cache = - BlockWeightsCache::init_from_chain_height(1, TEST_WEIGHT_CONFIG, db_builder.finish(None)) - .await?; + let mut weight_cache = BlockWeightsCache::init_from_chain_height( + 1, + TEST_WEIGHT_CONFIG, + db_builder.finish(None), + Chain::Main, + ) + .await?; for height in 1..=100 { - weight_cache.new_block(height as u64, height, height); + weight_cache.new_block(height, height, height); assert_eq!(weight_cache.median_short_term_weight(), height / 2); assert_eq!(weight_cache.median_long_term_weight(), height / 2); } for height in 101..=5000 { - weight_cache.new_block(height as u64, height, height); + weight_cache.new_block(height, height, height); assert_eq!(weight_cache.median_long_term_weight(), height / 2); } @@ -76,18 +151,19 @@ async fn calc_bw_ltw_2850000_3050000() { 2950000, TEST_WEIGHT_CONFIG, db_builder.finish(Some(2950000)), + Chain::Main, ) .await .unwrap(); for (i, (weight, ltw)) in BW_2850000_3050000.iter().skip(100_000).enumerate() { let calc_ltw = calculate_block_long_term_weight( - &HardFork::V16, + HardFork::V16, *weight, weight_cache.median_long_term_weight(), ); assert_eq!(calc_ltw, *ltw); - weight_cache.new_block((2950000 + i) as u64, *weight, *ltw); + weight_cache.new_block(2950000 + i, *weight, *ltw); } } diff --git a/consensus/src/tests/mock_db.rs b/consensus/src/tests/mock_db.rs index d1c6255..bf005d2 100644 --- a/consensus/src/tests/mock_db.rs +++ b/consensus/src/tests/mock_db.rs @@ -16,7 +16,7 @@ use proptest_derive::Arbitrary; use tower::{BoxError, Service}; use cuprate_types::{ - blockchain::{BCReadRequest, BCResponse}, + blockchain::{BlockchainReadRequest, BlockchainResponse}, ExtendedBlockHeader, }; @@ -60,9 +60,9 @@ pub struct DummyBlockExtendedHeader { impl From for ExtendedBlockHeader { fn from(value: DummyBlockExtendedHeader) -> Self { - ExtendedBlockHeader { - version: value.version.unwrap_or(HardFork::V1) as u8, - vote: value.vote.unwrap_or(HardFork::V1) as u8, + Self { + version: value.version.unwrap_or(HardFork::V1), + vote: value.vote.unwrap_or(HardFork::V1).as_u8(), timestamp: value.timestamp.unwrap_or_default(), cumulative_difficulty: value.cumulative_difficulty.unwrap_or_default(), block_weight: value.block_weight.unwrap_or_default(), @@ -72,31 +72,23 @@ impl From for ExtendedBlockHeader { } impl DummyBlockExtendedHeader { - pub fn with_weight_into( - mut self, - weight: usize, - long_term_weight: usize, - ) -> DummyBlockExtendedHeader { + pub const fn with_weight_into(mut self, weight: usize, long_term_weight: usize) -> Self { self.block_weight = Some(weight); self.long_term_weight = Some(long_term_weight); self } - pub fn with_hard_fork_info( - mut self, - version: HardFork, - vote: HardFork, - ) -> DummyBlockExtendedHeader { + pub const fn with_hard_fork_info(mut self, version: HardFork, vote: HardFork) -> Self { self.vote = Some(vote); self.version = Some(version); self } - pub fn with_difficulty_info( + pub const fn with_difficulty_info( mut self, timestamp: u64, cumulative_difficulty: u128, - ) -> DummyBlockExtendedHeader { + ) -> Self { self.timestamp = Some(timestamp); self.cumulative_difficulty = Some(cumulative_difficulty); self @@ -104,16 +96,16 @@ impl DummyBlockExtendedHeader { } #[derive(Debug, Default)] -pub struct DummyDatabaseBuilder { +pub(crate) struct DummyDatabaseBuilder { blocks: Vec, } impl DummyDatabaseBuilder { - pub fn add_block(&mut self, block: DummyBlockExtendedHeader) { + pub(crate) fn add_block(&mut self, block: DummyBlockExtendedHeader) { self.blocks.push(block); } - pub fn finish(self, dummy_height: Option) -> DummyDatabase { + pub(crate) fn finish(self, dummy_height: Option) -> DummyDatabase { DummyDatabase { blocks: Arc::new(self.blocks.into()), dummy_height, @@ -122,13 +114,20 @@ impl DummyDatabaseBuilder { } #[derive(Clone, Debug)] -pub struct DummyDatabase { +pub(crate) struct DummyDatabase { blocks: Arc>>, dummy_height: Option, } -impl Service for DummyDatabase { - type Response = BCResponse; +impl DummyDatabase { + #[expect(clippy::needless_pass_by_ref_mut)] + pub(crate) fn add_block(&mut self, block: DummyBlockExtendedHeader) { + self.blocks.write().unwrap().push(block); + } +} + +impl Service for DummyDatabase { + type Response = BlockchainResponse; type Error = BoxError; type Future = Pin> + Send + 'static>>; @@ -137,21 +136,21 @@ impl Service for DummyDatabase { Poll::Ready(Ok(())) } - fn call(&mut self, req: BCReadRequest) -> Self::Future { - let blocks = self.blocks.clone(); + fn call(&mut self, req: BlockchainReadRequest) -> Self::Future { + let blocks = Arc::clone(&self.blocks); let dummy_height = self.dummy_height; async move { Ok(match req { - BCReadRequest::BlockExtendedHeader(id) => { - let mut id = usize::try_from(id).unwrap(); + BlockchainReadRequest::BlockExtendedHeader(id) => { + let mut id = id; if let Some(dummy_height) = dummy_height { let block_len = blocks.read().unwrap().len(); id -= dummy_height - block_len; } - BCResponse::BlockExtendedHeader( + BlockchainResponse::BlockExtendedHeader( blocks .read() .unwrap() @@ -161,14 +160,14 @@ impl Service for DummyDatabase { .ok_or("block not in database!")?, ) } - BCReadRequest::BlockHash(id) => { + BlockchainReadRequest::BlockHash(id, _) => { let mut hash = [0; 32]; hash[0..8].copy_from_slice(&id.to_le_bytes()); - BCResponse::BlockHash(hash) + BlockchainResponse::BlockHash(hash) } - BCReadRequest::BlockExtendedHeaderInRange(range) => { - let mut end = usize::try_from(range.end).unwrap(); - let mut start = usize::try_from(range.start).unwrap(); + BlockchainReadRequest::BlockExtendedHeaderInRange(range, _) => { + let mut end = range.end; + let mut start = range.start; if let Some(dummy_height) = dummy_height { let block_len = blocks.read().unwrap().len(); @@ -177,7 +176,7 @@ impl Service for DummyDatabase { start -= dummy_height - block_len; } - BCResponse::BlockExtendedHeaderInRange( + BlockchainResponse::BlockExtendedHeaderInRange( blocks .read() .unwrap() @@ -189,18 +188,15 @@ impl Service for DummyDatabase { .collect(), ) } - BCReadRequest::ChainHeight => { - let height: u64 = dummy_height - .unwrap_or(blocks.read().unwrap().len()) - .try_into() - .unwrap(); + BlockchainReadRequest::ChainHeight => { + let height = dummy_height.unwrap_or(blocks.read().unwrap().len()); let mut top_hash = [0; 32]; top_hash[0..8].copy_from_slice(&height.to_le_bytes()); - BCResponse::ChainHeight(height, top_hash) + BlockchainResponse::ChainHeight(height, top_hash) } - BCReadRequest::GeneratedCoins => BCResponse::GeneratedCoins(0), + BlockchainReadRequest::GeneratedCoins(_) => BlockchainResponse::GeneratedCoins(0), _ => unimplemented!("the context svc should not need these requests!"), }) } diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs index 417eb48..f29c852 100644 --- a/consensus/src/transactions.rs +++ b/consensus/src/transactions.rs @@ -5,17 +5,13 @@ use std::{ collections::HashSet, future::Future, - ops::Deref, pin::Pin, - sync::{Arc, Mutex as StdMutex}, + sync::Arc, task::{Context, Poll}, }; use futures::FutureExt; -use monero_serai::{ - ringct::RctType, - transaction::{Input, Timelock, Transaction}, -}; +use monero_serai::transaction::{Input, Timelock, Transaction}; use rayon::prelude::*; use tower::{Service, ServiceExt}; use tracing::instrument; @@ -25,10 +21,13 @@ use cuprate_consensus_rules::{ check_decoy_info, check_transaction_contextual, check_transaction_semantic, output_unlocked, TransactionError, }, - ConsensusError, HardFork, TxVersion, + ConsensusError, HardFork, }; use cuprate_helper::asynch::rayon_spawn_async; -use cuprate_types::blockchain::{BCReadRequest, BCResponse}; +use cuprate_types::{ + blockchain::{BlockchainReadRequest, BlockchainResponse}, + CachedVerificationState, TransactionVerificationData, TxVersion, +}; use crate::{ batch_verifier::MultiThreadedBatchVerifier, @@ -37,6 +36,9 @@ use crate::{ }; pub mod contextual_data; +mod free; + +pub use free::new_tx_verification_data; /// A struct representing the type of validation that needs to be completed for this transaction. #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -47,84 +49,6 @@ enum VerificationNeeded { Contextual, } -/// Represents if a transaction has been fully validated and under what conditions -/// the transaction is valid in the future. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum CachedVerificationState { - /// The transaction has not been validated. - NotVerified, - /// The transaction is valid* if the block represented by this hash is in the blockchain and the [`HardFork`] - /// is the same. - /// - /// *V1 transactions require checks on their ring-length even if this hash is in the blockchain. - ValidAtHashAndHF([u8; 32], HardFork), - /// The transaction is valid* if the block represented by this hash is in the blockchain _and_ this - /// given time lock is unlocked. The time lock here will represent the youngest used time based lock - /// (If the transaction uses any time based time locks). This is because time locks are not monotonic - /// so unlocked outputs could become re-locked. - /// - /// *V1 transactions require checks on their ring-length even if this hash is in the blockchain. - ValidAtHashAndHFWithTimeBasedLock([u8; 32], HardFork, Timelock), -} - -impl CachedVerificationState { - /// Returns the block hash this is valid for if in state [`CachedVerificationState::ValidAtHashAndHF`] or [`CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock`]. - fn verified_at_block_hash(&self) -> Option<[u8; 32]> { - match self { - CachedVerificationState::NotVerified => None, - CachedVerificationState::ValidAtHashAndHF(hash, _) - | CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock(hash, _, _) => Some(*hash), - } - } -} - -/// Data needed to verify a transaction. -#[derive(Debug)] -pub struct TransactionVerificationData { - /// The transaction we are verifying - pub tx: Transaction, - /// The [`TxVersion`] of this tx. - pub version: TxVersion, - /// The serialised transaction. - pub tx_blob: Vec, - /// The weight of the transaction. - pub tx_weight: usize, - /// The fee this transaction has paid. - pub fee: u64, - /// The hash of this transaction. - pub tx_hash: [u8; 32], - /// The verification state of this transaction. - pub cached_verification_state: StdMutex, -} - -impl TransactionVerificationData { - /// Creates a new [`TransactionVerificationData`] from the given [`Transaction`]. - pub fn new(tx: Transaction) -> Result { - let tx_hash = tx.hash(); - let tx_blob = tx.serialize(); - - // the tx weight is only different from the blobs length for bp(+) txs. - let tx_weight = match tx.rct_signatures.rct_type() { - RctType::Bulletproofs - | RctType::BulletproofsCompactAmount - | RctType::Clsag - | RctType::BulletproofsPlus => tx.weight(), - _ => tx_blob.len(), - }; - - Ok(TransactionVerificationData { - tx_hash, - tx_blob, - tx_weight, - fee: tx.rct_signatures.base.fee, - cached_verification_state: StdMutex::new(CachedVerificationState::NotVerified), - version: TxVersion::from_raw(tx.prefix.version) - .ok_or(TransactionError::TransactionVersionInvalid)?, - tx, - }) - } -} - /// A request to verify a transaction. pub enum VerifyTxRequest { /// Verifies a batch of prepared txs. @@ -133,7 +57,7 @@ pub enum VerifyTxRequest { // TODO: Can we use references to remove the Vec? wont play nicely with Service though txs: Vec>, /// The current chain height. - current_chain_height: u64, + current_chain_height: usize, /// The top block hash. top_hash: [u8; 32], /// The value for time to use to check time locked outputs. @@ -147,7 +71,7 @@ pub enum VerifyTxRequest { /// The transactions to verify. txs: Vec, /// The current chain height. - current_chain_height: u64, + current_chain_height: usize, /// The top block hash. top_hash: [u8; 32], /// The value for time to use to check time locked outputs. @@ -177,8 +101,8 @@ where D::Future: Send + 'static, { /// Creates a new [`TxVerifierService`]. - pub fn new(database: D) -> TxVerifierService { - TxVerifierService { database } + pub const fn new(database: D) -> Self { + Self { database } } } @@ -246,7 +170,7 @@ where async fn prep_and_verify_transactions( database: D, txs: Vec, - current_chain_height: u64, + current_chain_height: usize, top_hash: [u8; 32], time_for_time_lock: u64, hf: HardFork, @@ -259,7 +183,7 @@ where tracing::debug!(parent: &span, "prepping transactions for verification."); let txs = rayon_spawn_async(|| { txs.into_par_iter() - .map(|tx| TransactionVerificationData::new(tx).map(Arc::new)) + .map(|tx| new_tx_verification_data(tx).map(Arc::new)) .collect::, _>>() }) .await?; @@ -281,7 +205,7 @@ where async fn verify_prepped_transactions( mut database: D, txs: &[Arc], - current_chain_height: u64, + current_chain_height: usize, top_hash: [u8; 32], time_for_time_lock: u64, hf: HardFork, @@ -296,7 +220,7 @@ where let mut spent_kis = HashSet::with_capacity(txs.len()); txs.iter().try_for_each(|tx| { - tx.tx.prefix.inputs.iter().try_for_each(|input| { + tx.tx.prefix().inputs.iter().try_for_each(|input| { if let Input::ToKey { key_image, .. } = input { if !spent_kis.insert(key_image.compress().0) { tracing::debug!("Duplicate key image found in batch."); @@ -308,10 +232,10 @@ where }) })?; - let BCResponse::KeyImagesSpent(kis_spent) = database + let BlockchainResponse::KeyImagesSpent(kis_spent) = database .ready() .await? - .call(BCReadRequest::KeyImagesSpent(spent_kis)) + .call(BlockchainReadRequest::KeyImagesSpent(spent_kis)) .await? else { panic!("Database sent incorrect response!"); @@ -319,7 +243,7 @@ where if kis_spent { tracing::debug!("One or more key images in batch already spent."); - Err(ConsensusError::Transaction(TransactionError::KeyImageSpent))?; + return Err(ConsensusError::Transaction(TransactionError::KeyImageSpent).into()); } let mut verified_at_block_hashes = txs @@ -340,10 +264,12 @@ where if !verified_at_block_hashes.is_empty() { tracing::trace!("Filtering block hashes not in the main chain."); - let BCResponse::FilterUnknownHashes(known_hashes) = database + let BlockchainResponse::FilterUnknownHashes(known_hashes) = database .ready() .await? - .call(BCReadRequest::FilterUnknownHashes(verified_at_block_hashes)) + .call(BlockchainReadRequest::FilterUnknownHashes( + verified_at_block_hashes, + )) .await? else { panic!("Database returned wrong response!"); @@ -354,8 +280,8 @@ where let (txs_needing_full_verification, txs_needing_partial_verification) = transactions_needing_verification( txs, - verified_at_block_hashes, - &hf, + &verified_at_block_hashes, + hf, current_chain_height, time_for_time_lock, )?; @@ -375,12 +301,15 @@ where Ok(VerifyTxResponse::Ok) } -#[allow(clippy::type_complexity)] // I don't think the return is too complex +#[expect( + clippy::type_complexity, + reason = "I don't think the return is too complex" +)] fn transactions_needing_verification( txs: &[Arc], - hashes_in_main_chain: HashSet<[u8; 32]>, - current_hf: &HardFork, - current_chain_height: u64, + hashes_in_main_chain: &HashSet<[u8; 32]>, + current_hf: HardFork, + current_chain_height: usize, time_for_time_lock: u64, ) -> Result< ( @@ -394,46 +323,52 @@ fn transactions_needing_verification( // txs needing partial _contextual_ validation, not semantic. let mut partial_validation_transactions = Vec::new(); - for tx in txs.iter() { + for tx in txs { let guard = tx.cached_verification_state.lock().unwrap(); - match guard.deref() { + match &*guard { CachedVerificationState::NotVerified => { drop(guard); full_validation_transactions - .push((tx.clone(), VerificationNeeded::SemanticAndContextual)); + .push((Arc::clone(tx), VerificationNeeded::SemanticAndContextual)); continue; } - CachedVerificationState::ValidAtHashAndHF(hash, hf) => { - if current_hf != hf { + CachedVerificationState::ValidAtHashAndHF { block_hash, hf } => { + if current_hf != *hf { drop(guard); full_validation_transactions - .push((tx.clone(), VerificationNeeded::SemanticAndContextual)); + .push((Arc::clone(tx), VerificationNeeded::SemanticAndContextual)); continue; } - if !hashes_in_main_chain.contains(hash) { + if !hashes_in_main_chain.contains(block_hash) { drop(guard); - full_validation_transactions.push((tx.clone(), VerificationNeeded::Contextual)); + full_validation_transactions + .push((Arc::clone(tx), VerificationNeeded::Contextual)); continue; } } - CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock(hash, hf, lock) => { - if current_hf != hf { + CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock { + block_hash, + hf, + time_lock, + } => { + if current_hf != *hf { drop(guard); full_validation_transactions - .push((tx.clone(), VerificationNeeded::SemanticAndContextual)); + .push((Arc::clone(tx), VerificationNeeded::SemanticAndContextual)); continue; } - if !hashes_in_main_chain.contains(hash) { + if !hashes_in_main_chain.contains(block_hash) { drop(guard); - full_validation_transactions.push((tx.clone(), VerificationNeeded::Contextual)); + full_validation_transactions + .push((Arc::clone(tx), VerificationNeeded::Contextual)); continue; } // If the time lock is still locked then the transaction is invalid. - if !output_unlocked(lock, current_chain_height, time_for_time_lock, hf) { + if !output_unlocked(time_lock, current_chain_height, time_for_time_lock, *hf) { return Err(ConsensusError::Transaction( TransactionError::OneOrMoreRingMembersLocked, )); @@ -443,7 +378,7 @@ fn transactions_needing_verification( if tx.version == TxVersion::RingSignatures { drop(guard); - partial_validation_transactions.push(tx.clone()); + partial_validation_transactions.push(Arc::clone(tx)); continue; } } @@ -462,16 +397,21 @@ async fn verify_transactions_decoy_info( where D: Database + Clone + Sync + Send + 'static, { + // Decoy info is not validated for V1 txs. + if hf == HardFork::V1 || txs.is_empty() { + return Ok(()); + } + batch_get_decoy_info(&txs, hf, database) .await? - .try_for_each(|decoy_info| decoy_info.and_then(|di| Ok(check_decoy_info(&di, &hf)?)))?; + .try_for_each(|decoy_info| decoy_info.and_then(|di| Ok(check_decoy_info(&di, hf)?)))?; Ok(()) } async fn verify_transactions( txs: Vec<(Arc, VerificationNeeded)>, - current_chain_height: u64, + current_chain_height: usize, top_hash: [u8; 32], current_time_lock_timestamp: u64, hf: HardFork, @@ -481,7 +421,7 @@ where D: Database + Clone + Sync + Send + 'static, { let txs_ring_member_info = - batch_get_ring_member_info(txs.iter().map(|(tx, _)| tx), &hf, database).await?; + batch_get_ring_member_info(txs.iter().map(|(tx, _)| tx), hf, database).await?; rayon_spawn_async(move || { let batch_verifier = MultiThreadedBatchVerifier::new(rayon::current_num_threads()); @@ -496,10 +436,10 @@ where tx.tx_blob.len(), tx.tx_weight, &tx.tx_hash, - &hf, + hf, &batch_verifier, )?; - // make sure monero-serai calculated the same fee. + // make sure we calculated the right fee. assert_eq!(fee, tx.fee); } @@ -509,7 +449,7 @@ where ring, current_chain_height, current_time_lock_timestamp, - &hf, + hf, )?; Ok::<_, ConsensusError>(()) @@ -522,10 +462,15 @@ where txs.iter() .zip(txs_ring_member_info) .for_each(|((tx, _), ring)| { - if ring.time_locked_outs.is_empty() { - *tx.cached_verification_state.lock().unwrap() = - CachedVerificationState::ValidAtHashAndHF(top_hash, hf); + *tx.cached_verification_state.lock().unwrap() = if ring.time_locked_outs.is_empty() + { + // no outputs with time-locks used. + CachedVerificationState::ValidAtHashAndHF { + block_hash: top_hash, + hf, + } } else { + // an output with a time-lock was used, check if it was time-based. let youngest_timebased_lock = ring .time_locked_outs .iter() @@ -535,16 +480,20 @@ where }) .min(); - *tx.cached_verification_state.lock().unwrap() = - if let Some(time) = youngest_timebased_lock { - CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock( - top_hash, - hf, - Timelock::Time(time), - ) - } else { - CachedVerificationState::ValidAtHashAndHF(top_hash, hf) - }; + if let Some(time) = youngest_timebased_lock { + // time-based lock used. + CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock { + block_hash: top_hash, + hf, + time_lock: Timelock::Time(time), + } + } else { + // no time-based locked output was used. + CachedVerificationState::ValidAtHashAndHF { + block_hash: top_hash, + hf, + } + } } }); diff --git a/consensus/src/transactions/contextual_data.rs b/consensus/src/transactions/contextual_data.rs index 95e5262..66c53b3 100644 --- a/consensus/src/transactions/contextual_data.rs +++ b/consensus/src/transactions/contextual_data.rs @@ -27,7 +27,7 @@ use cuprate_consensus_rules::{ ConsensusError, HardFork, TxVersion, }; use cuprate_types::{ - blockchain::{BCReadRequest, BCResponse}, + blockchain::{BlockchainReadRequest, BlockchainResponse}, OutputOnChain, }; @@ -57,7 +57,7 @@ fn get_ring_members_for_inputs( }) .collect::>()?) } - _ => Err(TransactionError::IncorrectInputType), + Input::Gen(_) => Err(TransactionError::IncorrectInputType), }) .collect::>() } @@ -143,29 +143,29 @@ fn new_rings( /// them. pub async fn batch_get_ring_member_info( txs_verification_data: impl Iterator> + Clone, - hf: &HardFork, + hf: HardFork, mut database: D, ) -> Result, ExtendedConsensusError> { let mut output_ids = HashMap::new(); for tx_v_data in txs_verification_data.clone() { - insert_ring_member_ids(&tx_v_data.tx.prefix.inputs, &mut output_ids) + insert_ring_member_ids(&tx_v_data.tx.prefix().inputs, &mut output_ids) .map_err(ConsensusError::Transaction)?; } - let BCResponse::Outputs(outputs) = database + let BlockchainResponse::Outputs(outputs) = database .ready() .await? - .call(BCReadRequest::Outputs(output_ids)) + .call(BlockchainReadRequest::Outputs(output_ids)) .await? else { panic!("Database sent incorrect response!") }; - let BCResponse::NumberOutputsWithAmount(outputs_with_amount) = database + let BlockchainResponse::NumberOutputsWithAmount(outputs_with_amount) = database .ready() .await? - .call(BCReadRequest::NumberOutputsWithAmount( + .call(BlockchainReadRequest::NumberOutputsWithAmount( outputs.keys().copied().collect(), )) .await? @@ -179,18 +179,18 @@ pub async fn batch_get_ring_member_info( let ring_members_for_tx = get_ring_members_for_inputs( |amt, idx| outputs.get(&amt)?.get(&idx).copied(), - &tx_v_data.tx.prefix.inputs, + &tx_v_data.tx.prefix().inputs, ) .map_err(ConsensusError::Transaction)?; - let decoy_info = if hf != &HardFork::V1 { + let decoy_info = if hf == HardFork::V1 { + None + } else { // this data is only needed after hard-fork 1. Some( - DecoyInfo::new(&tx_v_data.tx.prefix.inputs, numb_outputs, hf) + DecoyInfo::new(&tx_v_data.tx.prefix().inputs, numb_outputs, hf) .map_err(ConsensusError::Transaction)?, ) - } else { - None }; new_ring_member_info(ring_members_for_tx, decoy_info, tx_v_data.version) @@ -222,9 +222,9 @@ pub async fn batch_get_decoy_info<'a, D: Database + Clone + Send + 'static>( let unique_input_amounts = txs_verification_data .iter() .flat_map(|tx_info| { - tx_info.tx.prefix.inputs.iter().map(|input| match input { + tx_info.tx.prefix().inputs.iter().map(|input| match input { Input::ToKey { amount, .. } => amount.unwrap_or(0), - _ => 0, + Input::Gen(_) => 0, }) }) .collect::>(); @@ -234,10 +234,10 @@ pub async fn batch_get_decoy_info<'a, D: Database + Clone + Send + 'static>( unique_input_amounts.len() ); - let BCResponse::NumberOutputsWithAmount(outputs_with_amount) = database + let BlockchainResponse::NumberOutputsWithAmount(outputs_with_amount) = database .ready() .await? - .call(BCReadRequest::NumberOutputsWithAmount( + .call(BlockchainReadRequest::NumberOutputsWithAmount( unique_input_amounts.into_iter().collect(), )) .await? @@ -247,9 +247,9 @@ pub async fn batch_get_decoy_info<'a, D: Database + Clone + Send + 'static>( Ok(txs_verification_data.iter().map(move |tx_v_data| { DecoyInfo::new( - &tx_v_data.tx.prefix.inputs, + &tx_v_data.tx.prefix().inputs, |amt| outputs_with_amount.get(&amt).copied().unwrap_or(0), - &hf, + hf, ) .map_err(ConsensusError::Transaction) })) diff --git a/consensus/src/transactions/free.rs b/consensus/src/transactions/free.rs new file mode 100644 index 0000000..3613f29 --- /dev/null +++ b/consensus/src/transactions/free.rs @@ -0,0 +1,96 @@ +use std::sync::Mutex as StdMutex; + +use monero_serai::{ + ringct::{bulletproofs::Bulletproof, RctType}, + transaction::{Input, Transaction}, +}; + +use cuprate_consensus_rules::{transactions::TransactionError, ConsensusError}; +use cuprate_types::{CachedVerificationState, TransactionVerificationData, TxVersion}; + +/// Creates a new [`TransactionVerificationData`] from a [`Transaction`]. +/// +/// # Errors +/// +/// This function will return [`Err`] if the transaction is malformed, although returning [`Ok`] does +/// not necessarily mean the tx is correctly formed. +pub fn new_tx_verification_data( + tx: Transaction, +) -> Result { + let tx_hash = tx.hash(); + let tx_blob = tx.serialize(); + + let tx_weight = tx_weight(&tx, &tx_blob); + + let fee = tx_fee(&tx)?; + + Ok(TransactionVerificationData { + tx_hash, + version: TxVersion::from_raw(tx.version()) + .ok_or(TransactionError::TransactionVersionInvalid)?, + tx_blob, + tx_weight, + fee, + cached_verification_state: StdMutex::new(CachedVerificationState::NotVerified), + tx, + }) +} + +/// Calculates the weight of a [`Transaction`]. +/// +/// This is more efficient that [`Transaction::weight`] if you already have the transaction blob. +pub(crate) fn tx_weight(tx: &Transaction, tx_blob: &[u8]) -> usize { + // the tx weight is only different from the blobs length for bp(+) txs. + + match &tx { + Transaction::V1 { .. } | Transaction::V2 { proofs: None, .. } => tx_blob.len(), + Transaction::V2 { + proofs: Some(proofs), + .. + } => match proofs.rct_type() { + RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => tx_blob.len(), + RctType::MlsagBulletproofs + | RctType::MlsagBulletproofsCompactAmount + | RctType::ClsagBulletproof => { + tx_blob.len() + + Bulletproof::calculate_bp_clawback(false, tx.prefix().outputs.len()).0 + } + RctType::ClsagBulletproofPlus => { + tx_blob.len() + + Bulletproof::calculate_bp_clawback(true, tx.prefix().outputs.len()).0 + } + }, + } +} + +/// Calculates the fee of the [`Transaction`]. +pub(crate) fn tx_fee(tx: &Transaction) -> Result { + let mut fee = 0_u64; + + match &tx { + Transaction::V1 { prefix, .. } => { + for input in &prefix.inputs { + if let Input::ToKey { amount, .. } = input { + fee = fee + .checked_add(amount.unwrap_or(0)) + .ok_or(TransactionError::InputsOverflow)?; + } + } + + for output in &prefix.outputs { + fee = fee + .checked_sub(output.amount.unwrap_or(0)) + .ok_or(TransactionError::OutputsTooHigh)?; + } + } + Transaction::V2 { proofs, .. } => { + fee = proofs + .as_ref() + .ok_or(TransactionError::TransactionVersionInvalid)? + .base + .fee; + } + }; + + Ok(fee) +} diff --git a/consensus/tests/verify_correct_txs.rs b/consensus/tests/verify_correct_txs.rs index b71b52d..4d6c179 100644 --- a/consensus/tests/verify_correct_txs.rs +++ b/consensus/tests/verify_correct_txs.rs @@ -1,3 +1,6 @@ +#![expect(unused_crate_dependencies, reason = "external test module")] +#![expect(clippy::allow_attributes, reason = "usage inside macro")] + use std::{ collections::{BTreeMap, HashMap}, future::ready, @@ -12,7 +15,7 @@ use cuprate_consensus::{ TxVerifierService, VerifyTxRequest, VerifyTxResponse, __private::Database, }; use cuprate_types::{ - blockchain::{BCReadRequest, BCResponse}, + blockchain::{BlockchainReadRequest, BlockchainResponse}, OutputOnChain, }; @@ -23,13 +26,13 @@ use cuprate_test_utils::data::TX_E2D393; fn dummy_database(outputs: BTreeMap) -> impl Database + Clone { let outputs = Arc::new(outputs); - service_fn(move |req: BCReadRequest| { + service_fn(move |req: BlockchainReadRequest| { ready(Ok(match req { - BCReadRequest::NumberOutputsWithAmount(_) => { - BCResponse::NumberOutputsWithAmount(HashMap::new()) + BlockchainReadRequest::NumberOutputsWithAmount(_) => { + BlockchainResponse::NumberOutputsWithAmount(HashMap::new()) } - BCReadRequest::Outputs(outs) => { - let idxs = outs.get(&0).unwrap(); + BlockchainReadRequest::Outputs(outs) => { + let idxs = &outs[&0]; let mut ret = HashMap::new(); @@ -40,9 +43,9 @@ fn dummy_database(outputs: BTreeMap) -> impl Database + Clon .collect::>(), ); - BCResponse::Outputs(ret) + BlockchainResponse::Outputs(ret) } - BCReadRequest::KeyImagesSpent(_) => BCResponse::KeyImagesSpent(false), + BlockchainReadRequest::KeyImagesSpent(_) => BlockchainResponse::KeyImagesSpent(false), _ => panic!("Database request not needed for this test"), })) }) diff --git a/constants/Cargo.toml b/constants/Cargo.toml new file mode 100644 index 0000000..5ce3732 --- /dev/null +++ b/constants/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "cuprate-constants" +version = "0.1.0" +edition = "2021" +description = "Constant/static data used throughout Cuprate" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/constants" +keywords = ["cuprate", "constants"] + +[features] +default = [] +block = [] +build = [] +rpc = [] + +[dependencies] + +[dev-dependencies] + +[lints] +workspace = true diff --git a/constants/README.md b/constants/README.md new file mode 100644 index 0000000..b045447 --- /dev/null +++ b/constants/README.md @@ -0,0 +1,3 @@ +# cuprate-constants +This crate contains general constants that are not specific to any particular +part of the codebase yet are used in multiple places such as the maximum block height. diff --git a/helper/build.rs b/constants/build.rs similarity index 93% rename from helper/build.rs rename to constants/build.rs index 709db42..a680714 100644 --- a/helper/build.rs +++ b/constants/build.rs @@ -1,9 +1,7 @@ fn main() { - #[cfg(feature = "constants")] set_commit_env(); } -#[cfg(feature = "constants")] /// This sets the git `COMMIT` environment variable. fn set_commit_env() { const PATH: &str = "../.git/refs/heads/"; diff --git a/constants/src/block.rs b/constants/src/block.rs new file mode 100644 index 0000000..9ddaff6 --- /dev/null +++ b/constants/src/block.rs @@ -0,0 +1,11 @@ +//! Block related. + +use crate::macros::monero_definition_link; + +/// The maximum block height possible. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/cryptonote_config.h", 40)] +pub const MAX_BLOCK_HEIGHT: u64 = 500_000_000; + +/// [`MAX_BLOCK_HEIGHT`] as a [`usize`]. +#[expect(clippy::cast_possible_truncation, reason = "will not be truncated")] +pub const MAX_BLOCK_HEIGHT_USIZE: usize = MAX_BLOCK_HEIGHT as usize; diff --git a/constants/src/build.rs b/constants/src/build.rs new file mode 100644 index 0000000..12236ad --- /dev/null +++ b/constants/src/build.rs @@ -0,0 +1,22 @@ +//! Build related metadata. + +/// The current commit hash of the root Cuprate repository. +/// +/// # Case & length +/// It is guaranteed that `COMMIT` will be: +/// - Lowercase ASCII +/// - 40 characters long (no newline) +/// +/// ```rust +/// # use cuprate_constants::build::*; +/// assert!(COMMIT.is_ascii()); +/// assert_eq!(COMMIT.as_bytes().len(), 40); +/// assert_eq!(COMMIT.to_lowercase(), COMMIT); +/// ``` +pub const COMMIT: &str = core::env!("COMMIT"); // Set in `constants/build.rs`. + +/// `true` if debug build, else `false`. +pub const DEBUG: bool = cfg!(debug_assertions); + +/// `true` if release build, else `false`. +pub const RELEASE: bool = !DEBUG; diff --git a/constants/src/lib.rs b/constants/src/lib.rs new file mode 100644 index 0000000..f1b29fb --- /dev/null +++ b/constants/src/lib.rs @@ -0,0 +1,12 @@ +#![doc = include_str!("../README.md")] +#![deny(missing_docs, reason = "all constants should document what they are")] +#![no_std] // This can be removed if we eventually need `std`. + +mod macros; + +#[cfg(feature = "block")] +pub mod block; +#[cfg(feature = "build")] +pub mod build; +#[cfg(feature = "rpc")] +pub mod rpc; diff --git a/constants/src/macros.rs b/constants/src/macros.rs new file mode 100644 index 0000000..f41ae7b --- /dev/null +++ b/constants/src/macros.rs @@ -0,0 +1,35 @@ +/// Output a string link to `monerod` source code. +#[allow( + clippy::allow_attributes, + unused_macros, + reason = "used in feature gated modules" +)] +macro_rules! monero_definition_link { + ( + $commit:ident, // Git commit hash + $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h` + $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0` + ) => { + concat!( + "", + "[Original definition](https://github.com/monero-project/monero/blob/", + stringify!($commit), + "/src/", + $file_path, + "#L", + stringify!($start), + $( + "-L", + stringify!($end), + )? + ")." + ) + }; +} + +#[allow( + clippy::allow_attributes, + unused_imports, + reason = "used in feature gated modules" +)] +pub(crate) use monero_definition_link; diff --git a/constants/src/rpc.rs b/constants/src/rpc.rs new file mode 100644 index 0000000..1130eb7 --- /dev/null +++ b/constants/src/rpc.rs @@ -0,0 +1,101 @@ +//! RPC related. + +use core::time::Duration; + +use crate::macros::monero_definition_link; + +/// Maximum requestable block header range. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/rpc/core_rpc_server.cpp", 74)] +/// +/// This is the maximum amount of blocks that can be requested +/// per invocation of `get_block_headers` if the RPC server is +/// in restricted mode. +/// +/// Used at: +/// - +pub const RESTRICTED_BLOCK_HEADER_RANGE: u64 = 1000; + +/// Maximum requestable transaction count. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/rpc/core_rpc_server.cpp", 75)] +/// +/// This is the maximum amount of transactions that can be requested +/// per invocation of `get_transactions` and `get_indexes` if the +/// RPC server is in restricted mode. +/// +/// Used at: +/// - +/// - +pub const RESTRICTED_TRANSACTIONS_COUNT: usize = 100; + +/// Maximum amount of requestable key image checks. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/rpc/core_rpc_server.cpp", 76)] +/// +/// This is the maximum amount of key images that can be requested +/// to be checked per `/is_key_image_spent` call if the RPC server +/// is in restricted mode. +/// +/// Used at: +/// - +/// - +pub const RESTRICTED_SPENT_KEY_IMAGES_COUNT: usize = 5000; + +/// Maximum amount of requestable blocks. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/rpc/core_rpc_server.cpp", 77)] +/// +/// This is the maximum amount of blocks that can be +/// requested if the RPC server is in restricted mode. +/// +/// Used at: +/// - +/// - +pub const RESTRICTED_BLOCK_COUNT: usize = 1000; + +/// Maximum amount of fake outputs. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/rpc/core_rpc_server.cpp", 67)] +/// +/// This is the maximum amount of outputs that can be +/// requested if the RPC server is in restricted mode. +/// +/// Used at: +/// - +/// - +pub const MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT: usize = 5000; + +/// Maximum output histrogram cutoff. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/rpc/core_rpc_server.cpp", 69)] +/// +/// This is the maximum cutoff duration allowed in `get_output_histogram` (3 days). +/// +/// ```rust +/// # use cuprate_constants::rpc::*; +/// assert_eq!(OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION.as_secs(), 86_400 * 3); +/// ``` +/// +/// Used at: +/// +pub const OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION: Duration = Duration::from_secs(86400 * 3); + +/// Maximum amount of requestable blocks in `/get_blocks.bin`. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/cryptonote_config.h", 128)] +pub const GET_BLOCKS_BIN_MAX_BLOCK_COUNT: u64 = 1000; + +/// Maximum amount of requestable transactions in `/get_blocks.bin`. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/cryptonote_config.h", 129)] +pub const GET_BLOCKS_BIN_MAX_TX_COUNT: u64 = 20_000; + +/// Max message content length in the RPC server. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/cryptonote_config.h", 130)] +/// +/// This is the maximum amount of bytes an HTTP request +/// body can be before the RPC server rejects it (1 megabyte). +pub const MAX_RPC_CONTENT_LENGTH: u64 = 1_048_576; + +/// Amount of fails before blocking a remote RPC server. +#[doc = monero_definition_link!(a1dc85c5373a30f14aaf7dcfdd95f5a7375d3623, "/src/cryptonote_config.h", 159)] +/// +/// This is the amount of times an RPC will attempt to +/// connect to another remote IP before blocking it. +/// +/// RPC servers connect to nodes when they themselves +/// lack the data to fulfill the response. +pub const RPC_IP_FAILS_BEFORE_BLOCK: u64 = 3; diff --git a/cryptonight/Cargo.toml b/cryptonight/Cargo.toml index e270114..f455f7d 100644 --- a/cryptonight/Cargo.toml +++ b/cryptonight/Cargo.toml @@ -4,14 +4,21 @@ version = "0.1.0" edition = "2021" description = "A wrapper around Monero's CryptoNight hash function." license = "MIT" -authors = ["Boog900", "The Monero Project"] +authors = ["dimalinux", "Boog900", "The Monero Project"] repository = "https://github.com/Cuprate/cuprate/tree/main/cryptonight" [dependencies] -thiserror = "1" - -[build-dependencies] -cc = "1" +thiserror = { workspace = true } +sha3 = "0.10.8" +groestl = "0.10.1" +skein = "0.1.0" +jh = "0.1.0" +keccak = "0.1.5" +digest = "0.10.7" +seq-macro = "0.3.5" [dev-dependencies] -hex = "0.4" +hex = { workspace = true, features = ["std"] } + +[lints] +workspace = true diff --git a/cryptonight/build.rs b/cryptonight/build.rs deleted file mode 100644 index 465236e..0000000 --- a/cryptonight/build.rs +++ /dev/null @@ -1,52 +0,0 @@ -extern crate cc; - -use std::env; - -use cc::Build; - -fn main() { - let mut cfg = Build::new(); - cfg.include("c") - .file("c/aesb.c") - .file("c/blake256.c") - .file("c/groestl.c") - .file("c/hash-extra-blake.c") - .file("c/hash-extra-groestl.c") - .file("c/hash-extra-jh.c") - .file("c/hash-extra-skein.c") - .file("c/hash.c") - .file("c/jh.c") - .file("c/keccak.c") - .file("c/oaes_lib.c") - .file("c/skein.c") - .file("c/memwipe.c") - .file("c/slow-hash.c") - .file("c/CryptonightR_JIT.c") - .flag_if_supported("-fexceptions") - // c/oaes_lib.c: In function ‘oaes_get_seed’: - // c/oaes_lib.c:515:9: warning: ‘ftime’ is deprecated: Use gettimeofday or clock_gettime instead [-Wdeprecated-declarations] - // 515 | ftime (&timer); - // | ^~~~~ - // In file included from c/oaes_lib.c:45: - // /usr/include/sys/timeb.h:29:12: note: declared here - // 29 | extern int ftime (struct timeb *__timebuf) - // | ^~~~~ - // This flag doesn't work on MSVC and breaks CI. - .flag_if_supported("-Wno-deprecated-declarations") - // `#include ` isn't found without this in macOS CI. - // - .flag_if_supported("-I/opt/homebrew/include"); - - // Optimization flags are automatically added. - // https://docs.rs/cc/latest/cc/struct.Build.html#method.opt_level - - let target = env::var("TARGET").unwrap(); - if target.contains("x86_64") { - // FIXME: what are the equivalent flags for MSVC? - cfg.file("c/CryptonightR_template.S") - .flag_if_supported("-maes") - .flag_if_supported("-msse2"); - } - - cfg.compile("cryptonight") -} diff --git a/cryptonight/c/CryptonightR_JIT.c b/cryptonight/c/CryptonightR_JIT.c deleted file mode 100644 index ffe7820..0000000 --- a/cryptonight/c/CryptonightR_JIT.c +++ /dev/null @@ -1,123 +0,0 @@ -#include -#include -#include -#include -#include -#include -#if !(defined(_MSC_VER) || defined(__MINGW32__)) -#include -#endif - -#include "int-util.h" -#include "hash-ops.h" -#include "variant4_random_math.h" -#include "CryptonightR_JIT.h" -#include "CryptonightR_template.h" - -static const uint8_t prologue[] = { -#if defined __i386 || defined __x86_64__ - 0x4C, 0x8B, 0xD7, // mov r10, rdi - 0x53, // push rbx - 0x55, // push rbp - 0x41, 0x57, // push r15 - 0x4C, 0x8B, 0xDC, // mov r11, rsp - 0x41, 0x8B, 0x1A, // mov ebx, DWORD PTR [r10] - 0x41, 0x8B, 0x72, 0x04, // mov esi, DWORD PTR [r10+4] - 0x41, 0x8B, 0x7A, 0x08, // mov edi, DWORD PTR [r10+8] - 0x41, 0x8B, 0x6A, 0x0C, // mov ebp, DWORD PTR [r10+12] - 0x41, 0x8B, 0x62, 0x10, // mov esp, DWORD PTR [r10+16] - 0x45, 0x8B, 0x7A, 0x14, // mov r15d, DWORD PTR [r10+20] - 0x41, 0x8B, 0x42, 0x18, // mov eax, DWORD PTR [r10+24] - 0x41, 0x8B, 0x52, 0x1C, // mov edx, DWORD PTR [r10+28] - 0x45, 0x8B, 0x4A, 0x20, // mov r9d, DWORD PTR [r10+32] -#endif -}; - -static const uint8_t epilogue[] = { -#if defined __i386 || defined __x86_64__ - 0x49, 0x8B, 0xE3, // mov rsp, r11 - 0x41, 0x89, 0x1A, // mov DWORD PTR [r10], ebx - 0x41, 0x89, 0x72, 0x04, // mov DWORD PTR [r10+4], esi - 0x41, 0x89, 0x7A, 0x08, // mov DWORD PTR [r10+8], edi - 0x41, 0x89, 0x6A, 0x0C, // mov DWORD PTR [r10+12], ebp - 0x41, 0x5F, // pop r15 - 0x5D, // pop rbp - 0x5B, // pop rbx - 0xC3, // ret -#endif -}; - -#define APPEND_CODE(src, size) \ - do { \ - if (JIT_code + (size) > JIT_code_end) \ - return -1; \ - memcpy(JIT_code, (src), (size)); \ - JIT_code += (size); \ - } while (0) - -int v4_generate_JIT_code(const struct V4_Instruction* code, v4_random_math_JIT_func buf, const size_t buf_size) -{ -#if defined __i386 || defined __x86_64__ - uint8_t* JIT_code = (uint8_t*) buf; - const uint8_t* JIT_code_end = JIT_code + buf_size; - -#if !(defined(_MSC_VER) || defined(__MINGW32__)) - if (mprotect((void*)buf, buf_size, PROT_READ | PROT_WRITE)) - return -1; -#endif - - APPEND_CODE(prologue, sizeof(prologue)); - - uint32_t prev_rot_src = 0xFFFFFFFFU; - - for (int i = 0;; ++i) - { - const struct V4_Instruction inst = code[i]; - if (inst.opcode == RET) - break; - - const uint8_t opcode = (inst.opcode == MUL) ? inst.opcode : (inst.opcode + 2); - - const uint32_t a = inst.dst_index; - const uint32_t b = inst.src_index; - const uint8_t c = opcode | (inst.dst_index << V4_OPCODE_BITS) | (((inst.src_index == 8) ? inst.dst_index : inst.src_index) << (V4_OPCODE_BITS + V4_DST_INDEX_BITS)); - - switch (inst.opcode) - { - case ROR: - case ROL: - if (b != prev_rot_src) - { - prev_rot_src = b; - const uint8_t* p1 = (const uint8_t*) instructions_mov[c]; - const uint8_t* p2 = (const uint8_t*) instructions_mov[c + 1]; - APPEND_CODE(p1, p2 - p1); - } - break; - } - - if (a == prev_rot_src) - prev_rot_src = 0xFFFFFFFFU; - - const uint8_t* p1 = (const uint8_t*) instructions[c]; - const uint8_t* p2 = (const uint8_t*) instructions[c + 1]; - APPEND_CODE(p1, p2 - p1); - - if (inst.opcode == ADD) - *(uint32_t*)(JIT_code - 4) = inst.C; - } - - APPEND_CODE(epilogue, sizeof(epilogue)); - -#if !(defined(_MSC_VER) || defined(__MINGW32__)) - if (mprotect((void*)buf, buf_size, PROT_READ | PROT_EXEC)) - return -1; -#endif - - __builtin___clear_cache((char*)buf, (char*)JIT_code); - - return 0; -#else - return -1; -#endif -} diff --git a/cryptonight/c/CryptonightR_JIT.h b/cryptonight/c/CryptonightR_JIT.h deleted file mode 100644 index cb32c3a..0000000 --- a/cryptonight/c/CryptonightR_JIT.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CRYPTONIGHTR_JIT_H -#define CRYPTONIGHTR_JIT_H - -// Minimalistic JIT code generator for random math sequence in CryptonightR -// -// Usage: -// - Allocate writable and executable memory -// - Call v4_generate_JIT_code with "buf" pointed to memory allocated on previous step -// - Call the generated code instead of "v4_random_math(code, r)", omit the "code" parameter - -typedef void (*v4_random_math_JIT_func)(uint32_t* r) -#if defined __i386 || defined __x86_64__ -__attribute__((sysv_abi)) -#endif -; - -// Given the random math sequence, generates machine code (x86-64) for it -// Returns 0 if code was generated successfully -// Returns -1 if provided buffer was too small -int v4_generate_JIT_code(const struct V4_Instruction* code, v4_random_math_JIT_func buf, const size_t buf_size); - -#endif // CRYPTONIGHTR_JIT_H diff --git a/cryptonight/c/CryptonightR_template.S b/cryptonight/c/CryptonightR_template.S deleted file mode 100644 index 068de22..0000000 --- a/cryptonight/c/CryptonightR_template.S +++ /dev/null @@ -1,1590 +0,0 @@ -#ifdef __APPLE__ -# define ALIGN(x) .align 6 -#else -# define ALIGN(x) .align 64 -#endif -.intel_syntax noprefix -#ifdef __APPLE__ -# define FN_PREFIX(fn) _ ## fn -.text -#else -# define FN_PREFIX(fn) fn -.section .text -#endif - -#define PUBLIC .global - -PUBLIC FN_PREFIX(CryptonightR_instruction0) -PUBLIC FN_PREFIX(CryptonightR_instruction1) -PUBLIC FN_PREFIX(CryptonightR_instruction2) -PUBLIC FN_PREFIX(CryptonightR_instruction3) -PUBLIC FN_PREFIX(CryptonightR_instruction4) -PUBLIC FN_PREFIX(CryptonightR_instruction5) -PUBLIC FN_PREFIX(CryptonightR_instruction6) -PUBLIC FN_PREFIX(CryptonightR_instruction7) -PUBLIC FN_PREFIX(CryptonightR_instruction8) -PUBLIC FN_PREFIX(CryptonightR_instruction9) -PUBLIC FN_PREFIX(CryptonightR_instruction10) -PUBLIC FN_PREFIX(CryptonightR_instruction11) -PUBLIC FN_PREFIX(CryptonightR_instruction12) -PUBLIC FN_PREFIX(CryptonightR_instruction13) -PUBLIC FN_PREFIX(CryptonightR_instruction14) -PUBLIC FN_PREFIX(CryptonightR_instruction15) -PUBLIC FN_PREFIX(CryptonightR_instruction16) -PUBLIC FN_PREFIX(CryptonightR_instruction17) -PUBLIC FN_PREFIX(CryptonightR_instruction18) -PUBLIC FN_PREFIX(CryptonightR_instruction19) -PUBLIC FN_PREFIX(CryptonightR_instruction20) -PUBLIC FN_PREFIX(CryptonightR_instruction21) -PUBLIC FN_PREFIX(CryptonightR_instruction22) -PUBLIC FN_PREFIX(CryptonightR_instruction23) -PUBLIC FN_PREFIX(CryptonightR_instruction24) -PUBLIC FN_PREFIX(CryptonightR_instruction25) -PUBLIC FN_PREFIX(CryptonightR_instruction26) -PUBLIC FN_PREFIX(CryptonightR_instruction27) -PUBLIC FN_PREFIX(CryptonightR_instruction28) -PUBLIC FN_PREFIX(CryptonightR_instruction29) -PUBLIC FN_PREFIX(CryptonightR_instruction30) -PUBLIC FN_PREFIX(CryptonightR_instruction31) -PUBLIC FN_PREFIX(CryptonightR_instruction32) -PUBLIC FN_PREFIX(CryptonightR_instruction33) -PUBLIC FN_PREFIX(CryptonightR_instruction34) -PUBLIC FN_PREFIX(CryptonightR_instruction35) -PUBLIC FN_PREFIX(CryptonightR_instruction36) -PUBLIC FN_PREFIX(CryptonightR_instruction37) -PUBLIC FN_PREFIX(CryptonightR_instruction38) -PUBLIC FN_PREFIX(CryptonightR_instruction39) -PUBLIC FN_PREFIX(CryptonightR_instruction40) -PUBLIC FN_PREFIX(CryptonightR_instruction41) -PUBLIC FN_PREFIX(CryptonightR_instruction42) -PUBLIC FN_PREFIX(CryptonightR_instruction43) -PUBLIC FN_PREFIX(CryptonightR_instruction44) -PUBLIC FN_PREFIX(CryptonightR_instruction45) -PUBLIC FN_PREFIX(CryptonightR_instruction46) -PUBLIC FN_PREFIX(CryptonightR_instruction47) -PUBLIC FN_PREFIX(CryptonightR_instruction48) -PUBLIC FN_PREFIX(CryptonightR_instruction49) -PUBLIC FN_PREFIX(CryptonightR_instruction50) -PUBLIC FN_PREFIX(CryptonightR_instruction51) -PUBLIC FN_PREFIX(CryptonightR_instruction52) -PUBLIC FN_PREFIX(CryptonightR_instruction53) -PUBLIC FN_PREFIX(CryptonightR_instruction54) -PUBLIC FN_PREFIX(CryptonightR_instruction55) -PUBLIC FN_PREFIX(CryptonightR_instruction56) -PUBLIC FN_PREFIX(CryptonightR_instruction57) -PUBLIC FN_PREFIX(CryptonightR_instruction58) -PUBLIC FN_PREFIX(CryptonightR_instruction59) -PUBLIC FN_PREFIX(CryptonightR_instruction60) -PUBLIC FN_PREFIX(CryptonightR_instruction61) -PUBLIC FN_PREFIX(CryptonightR_instruction62) -PUBLIC FN_PREFIX(CryptonightR_instruction63) -PUBLIC FN_PREFIX(CryptonightR_instruction64) -PUBLIC FN_PREFIX(CryptonightR_instruction65) -PUBLIC FN_PREFIX(CryptonightR_instruction66) -PUBLIC FN_PREFIX(CryptonightR_instruction67) -PUBLIC FN_PREFIX(CryptonightR_instruction68) -PUBLIC FN_PREFIX(CryptonightR_instruction69) -PUBLIC FN_PREFIX(CryptonightR_instruction70) -PUBLIC FN_PREFIX(CryptonightR_instruction71) -PUBLIC FN_PREFIX(CryptonightR_instruction72) -PUBLIC FN_PREFIX(CryptonightR_instruction73) -PUBLIC FN_PREFIX(CryptonightR_instruction74) -PUBLIC FN_PREFIX(CryptonightR_instruction75) -PUBLIC FN_PREFIX(CryptonightR_instruction76) -PUBLIC FN_PREFIX(CryptonightR_instruction77) -PUBLIC FN_PREFIX(CryptonightR_instruction78) -PUBLIC FN_PREFIX(CryptonightR_instruction79) -PUBLIC FN_PREFIX(CryptonightR_instruction80) -PUBLIC FN_PREFIX(CryptonightR_instruction81) -PUBLIC FN_PREFIX(CryptonightR_instruction82) -PUBLIC FN_PREFIX(CryptonightR_instruction83) -PUBLIC FN_PREFIX(CryptonightR_instruction84) -PUBLIC FN_PREFIX(CryptonightR_instruction85) -PUBLIC FN_PREFIX(CryptonightR_instruction86) -PUBLIC FN_PREFIX(CryptonightR_instruction87) -PUBLIC FN_PREFIX(CryptonightR_instruction88) -PUBLIC FN_PREFIX(CryptonightR_instruction89) -PUBLIC FN_PREFIX(CryptonightR_instruction90) -PUBLIC FN_PREFIX(CryptonightR_instruction91) -PUBLIC FN_PREFIX(CryptonightR_instruction92) -PUBLIC FN_PREFIX(CryptonightR_instruction93) -PUBLIC FN_PREFIX(CryptonightR_instruction94) -PUBLIC FN_PREFIX(CryptonightR_instruction95) -PUBLIC FN_PREFIX(CryptonightR_instruction96) -PUBLIC FN_PREFIX(CryptonightR_instruction97) -PUBLIC FN_PREFIX(CryptonightR_instruction98) -PUBLIC FN_PREFIX(CryptonightR_instruction99) -PUBLIC FN_PREFIX(CryptonightR_instruction100) -PUBLIC FN_PREFIX(CryptonightR_instruction101) -PUBLIC FN_PREFIX(CryptonightR_instruction102) -PUBLIC FN_PREFIX(CryptonightR_instruction103) -PUBLIC FN_PREFIX(CryptonightR_instruction104) -PUBLIC FN_PREFIX(CryptonightR_instruction105) -PUBLIC FN_PREFIX(CryptonightR_instruction106) -PUBLIC FN_PREFIX(CryptonightR_instruction107) -PUBLIC FN_PREFIX(CryptonightR_instruction108) -PUBLIC FN_PREFIX(CryptonightR_instruction109) -PUBLIC FN_PREFIX(CryptonightR_instruction110) -PUBLIC FN_PREFIX(CryptonightR_instruction111) -PUBLIC FN_PREFIX(CryptonightR_instruction112) -PUBLIC FN_PREFIX(CryptonightR_instruction113) -PUBLIC FN_PREFIX(CryptonightR_instruction114) -PUBLIC FN_PREFIX(CryptonightR_instruction115) -PUBLIC FN_PREFIX(CryptonightR_instruction116) -PUBLIC FN_PREFIX(CryptonightR_instruction117) -PUBLIC FN_PREFIX(CryptonightR_instruction118) -PUBLIC FN_PREFIX(CryptonightR_instruction119) -PUBLIC FN_PREFIX(CryptonightR_instruction120) -PUBLIC FN_PREFIX(CryptonightR_instruction121) -PUBLIC FN_PREFIX(CryptonightR_instruction122) -PUBLIC FN_PREFIX(CryptonightR_instruction123) -PUBLIC FN_PREFIX(CryptonightR_instruction124) -PUBLIC FN_PREFIX(CryptonightR_instruction125) -PUBLIC FN_PREFIX(CryptonightR_instruction126) -PUBLIC FN_PREFIX(CryptonightR_instruction127) -PUBLIC FN_PREFIX(CryptonightR_instruction128) -PUBLIC FN_PREFIX(CryptonightR_instruction129) -PUBLIC FN_PREFIX(CryptonightR_instruction130) -PUBLIC FN_PREFIX(CryptonightR_instruction131) -PUBLIC FN_PREFIX(CryptonightR_instruction132) -PUBLIC FN_PREFIX(CryptonightR_instruction133) -PUBLIC FN_PREFIX(CryptonightR_instruction134) -PUBLIC FN_PREFIX(CryptonightR_instruction135) -PUBLIC FN_PREFIX(CryptonightR_instruction136) -PUBLIC FN_PREFIX(CryptonightR_instruction137) -PUBLIC FN_PREFIX(CryptonightR_instruction138) -PUBLIC FN_PREFIX(CryptonightR_instruction139) -PUBLIC FN_PREFIX(CryptonightR_instruction140) -PUBLIC FN_PREFIX(CryptonightR_instruction141) -PUBLIC FN_PREFIX(CryptonightR_instruction142) -PUBLIC FN_PREFIX(CryptonightR_instruction143) -PUBLIC FN_PREFIX(CryptonightR_instruction144) -PUBLIC FN_PREFIX(CryptonightR_instruction145) -PUBLIC FN_PREFIX(CryptonightR_instruction146) -PUBLIC FN_PREFIX(CryptonightR_instruction147) -PUBLIC FN_PREFIX(CryptonightR_instruction148) -PUBLIC FN_PREFIX(CryptonightR_instruction149) -PUBLIC FN_PREFIX(CryptonightR_instruction150) -PUBLIC FN_PREFIX(CryptonightR_instruction151) -PUBLIC FN_PREFIX(CryptonightR_instruction152) -PUBLIC FN_PREFIX(CryptonightR_instruction153) -PUBLIC FN_PREFIX(CryptonightR_instruction154) -PUBLIC FN_PREFIX(CryptonightR_instruction155) -PUBLIC FN_PREFIX(CryptonightR_instruction156) -PUBLIC FN_PREFIX(CryptonightR_instruction157) -PUBLIC FN_PREFIX(CryptonightR_instruction158) -PUBLIC FN_PREFIX(CryptonightR_instruction159) -PUBLIC FN_PREFIX(CryptonightR_instruction160) -PUBLIC FN_PREFIX(CryptonightR_instruction161) -PUBLIC FN_PREFIX(CryptonightR_instruction162) -PUBLIC FN_PREFIX(CryptonightR_instruction163) -PUBLIC FN_PREFIX(CryptonightR_instruction164) -PUBLIC FN_PREFIX(CryptonightR_instruction165) -PUBLIC FN_PREFIX(CryptonightR_instruction166) -PUBLIC FN_PREFIX(CryptonightR_instruction167) -PUBLIC FN_PREFIX(CryptonightR_instruction168) -PUBLIC FN_PREFIX(CryptonightR_instruction169) -PUBLIC FN_PREFIX(CryptonightR_instruction170) -PUBLIC FN_PREFIX(CryptonightR_instruction171) -PUBLIC FN_PREFIX(CryptonightR_instruction172) -PUBLIC FN_PREFIX(CryptonightR_instruction173) -PUBLIC FN_PREFIX(CryptonightR_instruction174) -PUBLIC FN_PREFIX(CryptonightR_instruction175) -PUBLIC FN_PREFIX(CryptonightR_instruction176) -PUBLIC FN_PREFIX(CryptonightR_instruction177) -PUBLIC FN_PREFIX(CryptonightR_instruction178) -PUBLIC FN_PREFIX(CryptonightR_instruction179) -PUBLIC FN_PREFIX(CryptonightR_instruction180) -PUBLIC FN_PREFIX(CryptonightR_instruction181) -PUBLIC FN_PREFIX(CryptonightR_instruction182) -PUBLIC FN_PREFIX(CryptonightR_instruction183) -PUBLIC FN_PREFIX(CryptonightR_instruction184) -PUBLIC FN_PREFIX(CryptonightR_instruction185) -PUBLIC FN_PREFIX(CryptonightR_instruction186) -PUBLIC FN_PREFIX(CryptonightR_instruction187) -PUBLIC FN_PREFIX(CryptonightR_instruction188) -PUBLIC FN_PREFIX(CryptonightR_instruction189) -PUBLIC FN_PREFIX(CryptonightR_instruction190) -PUBLIC FN_PREFIX(CryptonightR_instruction191) -PUBLIC FN_PREFIX(CryptonightR_instruction192) -PUBLIC FN_PREFIX(CryptonightR_instruction193) -PUBLIC FN_PREFIX(CryptonightR_instruction194) -PUBLIC FN_PREFIX(CryptonightR_instruction195) -PUBLIC FN_PREFIX(CryptonightR_instruction196) -PUBLIC FN_PREFIX(CryptonightR_instruction197) -PUBLIC FN_PREFIX(CryptonightR_instruction198) -PUBLIC FN_PREFIX(CryptonightR_instruction199) -PUBLIC FN_PREFIX(CryptonightR_instruction200) -PUBLIC FN_PREFIX(CryptonightR_instruction201) -PUBLIC FN_PREFIX(CryptonightR_instruction202) -PUBLIC FN_PREFIX(CryptonightR_instruction203) -PUBLIC FN_PREFIX(CryptonightR_instruction204) -PUBLIC FN_PREFIX(CryptonightR_instruction205) -PUBLIC FN_PREFIX(CryptonightR_instruction206) -PUBLIC FN_PREFIX(CryptonightR_instruction207) -PUBLIC FN_PREFIX(CryptonightR_instruction208) -PUBLIC FN_PREFIX(CryptonightR_instruction209) -PUBLIC FN_PREFIX(CryptonightR_instruction210) -PUBLIC FN_PREFIX(CryptonightR_instruction211) -PUBLIC FN_PREFIX(CryptonightR_instruction212) -PUBLIC FN_PREFIX(CryptonightR_instruction213) -PUBLIC FN_PREFIX(CryptonightR_instruction214) -PUBLIC FN_PREFIX(CryptonightR_instruction215) -PUBLIC FN_PREFIX(CryptonightR_instruction216) -PUBLIC FN_PREFIX(CryptonightR_instruction217) -PUBLIC FN_PREFIX(CryptonightR_instruction218) -PUBLIC FN_PREFIX(CryptonightR_instruction219) -PUBLIC FN_PREFIX(CryptonightR_instruction220) -PUBLIC FN_PREFIX(CryptonightR_instruction221) -PUBLIC FN_PREFIX(CryptonightR_instruction222) -PUBLIC FN_PREFIX(CryptonightR_instruction223) -PUBLIC FN_PREFIX(CryptonightR_instruction224) -PUBLIC FN_PREFIX(CryptonightR_instruction225) -PUBLIC FN_PREFIX(CryptonightR_instruction226) -PUBLIC FN_PREFIX(CryptonightR_instruction227) -PUBLIC FN_PREFIX(CryptonightR_instruction228) -PUBLIC FN_PREFIX(CryptonightR_instruction229) -PUBLIC FN_PREFIX(CryptonightR_instruction230) -PUBLIC FN_PREFIX(CryptonightR_instruction231) -PUBLIC FN_PREFIX(CryptonightR_instruction232) -PUBLIC FN_PREFIX(CryptonightR_instruction233) -PUBLIC FN_PREFIX(CryptonightR_instruction234) -PUBLIC FN_PREFIX(CryptonightR_instruction235) -PUBLIC FN_PREFIX(CryptonightR_instruction236) -PUBLIC FN_PREFIX(CryptonightR_instruction237) -PUBLIC FN_PREFIX(CryptonightR_instruction238) -PUBLIC FN_PREFIX(CryptonightR_instruction239) -PUBLIC FN_PREFIX(CryptonightR_instruction240) -PUBLIC FN_PREFIX(CryptonightR_instruction241) -PUBLIC FN_PREFIX(CryptonightR_instruction242) -PUBLIC FN_PREFIX(CryptonightR_instruction243) -PUBLIC FN_PREFIX(CryptonightR_instruction244) -PUBLIC FN_PREFIX(CryptonightR_instruction245) -PUBLIC FN_PREFIX(CryptonightR_instruction246) -PUBLIC FN_PREFIX(CryptonightR_instruction247) -PUBLIC FN_PREFIX(CryptonightR_instruction248) -PUBLIC FN_PREFIX(CryptonightR_instruction249) -PUBLIC FN_PREFIX(CryptonightR_instruction250) -PUBLIC FN_PREFIX(CryptonightR_instruction251) -PUBLIC FN_PREFIX(CryptonightR_instruction252) -PUBLIC FN_PREFIX(CryptonightR_instruction253) -PUBLIC FN_PREFIX(CryptonightR_instruction254) -PUBLIC FN_PREFIX(CryptonightR_instruction255) -PUBLIC FN_PREFIX(CryptonightR_instruction256) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov0) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov1) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov2) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov3) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov4) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov5) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov6) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov7) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov8) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov9) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov10) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov11) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov12) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov13) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov14) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov15) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov16) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov17) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov18) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov19) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov20) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov21) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov22) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov23) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov24) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov25) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov26) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov27) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov28) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov29) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov30) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov31) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov32) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov33) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov34) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov35) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov36) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov37) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov38) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov39) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov40) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov41) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov42) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov43) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov44) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov45) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov46) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov47) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov48) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov49) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov50) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov51) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov52) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov53) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov54) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov55) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov56) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov57) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov58) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov59) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov60) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov61) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov62) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov63) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov64) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov65) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov66) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov67) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov68) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov69) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov70) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov71) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov72) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov73) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov74) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov75) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov76) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov77) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov78) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov79) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov80) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov81) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov82) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov83) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov84) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov85) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov86) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov87) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov88) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov89) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov90) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov91) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov92) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov93) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov94) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov95) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov96) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov97) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov98) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov99) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov100) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov101) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov102) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov103) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov104) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov105) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov106) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov107) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov108) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov109) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov110) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov111) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov112) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov113) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov114) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov115) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov116) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov117) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov118) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov119) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov120) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov121) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov122) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov123) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov124) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov125) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov126) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov127) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov128) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov129) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov130) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov131) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov132) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov133) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov134) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov135) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov136) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov137) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov138) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov139) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov140) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov141) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov142) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov143) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov144) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov145) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov146) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov147) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov148) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov149) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov150) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov151) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov152) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov153) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov154) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov155) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov156) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov157) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov158) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov159) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov160) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov161) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov162) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov163) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov164) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov165) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov166) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov167) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov168) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov169) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov170) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov171) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov172) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov173) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov174) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov175) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov176) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov177) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov178) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov179) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov180) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov181) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov182) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov183) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov184) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov185) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov186) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov187) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov188) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov189) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov190) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov191) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov192) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov193) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov194) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov195) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov196) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov197) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov198) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov199) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov200) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov201) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov202) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov203) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov204) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov205) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov206) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov207) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov208) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov209) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov210) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov211) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov212) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov213) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov214) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov215) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov216) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov217) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov218) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov219) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov220) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov221) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov222) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov223) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov224) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov225) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov226) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov227) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov228) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov229) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov230) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov231) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov232) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov233) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov234) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov235) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov236) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov237) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov238) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov239) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov240) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov241) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov242) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov243) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov244) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov245) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov246) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov247) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov248) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov249) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov250) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov251) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov252) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov253) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov254) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov255) -PUBLIC FN_PREFIX(CryptonightR_instruction_mov256) - -FN_PREFIX(CryptonightR_instruction0): - imul ebx, ebx -FN_PREFIX(CryptonightR_instruction1): - imul ebx, ebx -FN_PREFIX(CryptonightR_instruction2): - imul ebx, ebx -FN_PREFIX(CryptonightR_instruction3): - add ebx, r9d - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction4): - sub ebx, r9d -FN_PREFIX(CryptonightR_instruction5): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction6): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction7): - xor ebx, r9d -FN_PREFIX(CryptonightR_instruction8): - imul esi, ebx -FN_PREFIX(CryptonightR_instruction9): - imul esi, ebx -FN_PREFIX(CryptonightR_instruction10): - imul esi, ebx -FN_PREFIX(CryptonightR_instruction11): - add esi, ebx - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction12): - sub esi, ebx -FN_PREFIX(CryptonightR_instruction13): - ror esi, cl -FN_PREFIX(CryptonightR_instruction14): - rol esi, cl -FN_PREFIX(CryptonightR_instruction15): - xor esi, ebx -FN_PREFIX(CryptonightR_instruction16): - imul edi, ebx -FN_PREFIX(CryptonightR_instruction17): - imul edi, ebx -FN_PREFIX(CryptonightR_instruction18): - imul edi, ebx -FN_PREFIX(CryptonightR_instruction19): - add edi, ebx - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction20): - sub edi, ebx -FN_PREFIX(CryptonightR_instruction21): - ror edi, cl -FN_PREFIX(CryptonightR_instruction22): - rol edi, cl -FN_PREFIX(CryptonightR_instruction23): - xor edi, ebx -FN_PREFIX(CryptonightR_instruction24): - imul ebp, ebx -FN_PREFIX(CryptonightR_instruction25): - imul ebp, ebx -FN_PREFIX(CryptonightR_instruction26): - imul ebp, ebx -FN_PREFIX(CryptonightR_instruction27): - add ebp, ebx - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction28): - sub ebp, ebx -FN_PREFIX(CryptonightR_instruction29): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction30): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction31): - xor ebp, ebx -FN_PREFIX(CryptonightR_instruction32): - imul ebx, esi -FN_PREFIX(CryptonightR_instruction33): - imul ebx, esi -FN_PREFIX(CryptonightR_instruction34): - imul ebx, esi -FN_PREFIX(CryptonightR_instruction35): - add ebx, esi - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction36): - sub ebx, esi -FN_PREFIX(CryptonightR_instruction37): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction38): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction39): - xor ebx, esi -FN_PREFIX(CryptonightR_instruction40): - imul esi, esi -FN_PREFIX(CryptonightR_instruction41): - imul esi, esi -FN_PREFIX(CryptonightR_instruction42): - imul esi, esi -FN_PREFIX(CryptonightR_instruction43): - add esi, r9d - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction44): - sub esi, r9d -FN_PREFIX(CryptonightR_instruction45): - ror esi, cl -FN_PREFIX(CryptonightR_instruction46): - rol esi, cl -FN_PREFIX(CryptonightR_instruction47): - xor esi, r9d -FN_PREFIX(CryptonightR_instruction48): - imul edi, esi -FN_PREFIX(CryptonightR_instruction49): - imul edi, esi -FN_PREFIX(CryptonightR_instruction50): - imul edi, esi -FN_PREFIX(CryptonightR_instruction51): - add edi, esi - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction52): - sub edi, esi -FN_PREFIX(CryptonightR_instruction53): - ror edi, cl -FN_PREFIX(CryptonightR_instruction54): - rol edi, cl -FN_PREFIX(CryptonightR_instruction55): - xor edi, esi -FN_PREFIX(CryptonightR_instruction56): - imul ebp, esi -FN_PREFIX(CryptonightR_instruction57): - imul ebp, esi -FN_PREFIX(CryptonightR_instruction58): - imul ebp, esi -FN_PREFIX(CryptonightR_instruction59): - add ebp, esi - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction60): - sub ebp, esi -FN_PREFIX(CryptonightR_instruction61): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction62): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction63): - xor ebp, esi -FN_PREFIX(CryptonightR_instruction64): - imul ebx, edi -FN_PREFIX(CryptonightR_instruction65): - imul ebx, edi -FN_PREFIX(CryptonightR_instruction66): - imul ebx, edi -FN_PREFIX(CryptonightR_instruction67): - add ebx, edi - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction68): - sub ebx, edi -FN_PREFIX(CryptonightR_instruction69): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction70): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction71): - xor ebx, edi -FN_PREFIX(CryptonightR_instruction72): - imul esi, edi -FN_PREFIX(CryptonightR_instruction73): - imul esi, edi -FN_PREFIX(CryptonightR_instruction74): - imul esi, edi -FN_PREFIX(CryptonightR_instruction75): - add esi, edi - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction76): - sub esi, edi -FN_PREFIX(CryptonightR_instruction77): - ror esi, cl -FN_PREFIX(CryptonightR_instruction78): - rol esi, cl -FN_PREFIX(CryptonightR_instruction79): - xor esi, edi -FN_PREFIX(CryptonightR_instruction80): - imul edi, edi -FN_PREFIX(CryptonightR_instruction81): - imul edi, edi -FN_PREFIX(CryptonightR_instruction82): - imul edi, edi -FN_PREFIX(CryptonightR_instruction83): - add edi, r9d - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction84): - sub edi, r9d -FN_PREFIX(CryptonightR_instruction85): - ror edi, cl -FN_PREFIX(CryptonightR_instruction86): - rol edi, cl -FN_PREFIX(CryptonightR_instruction87): - xor edi, r9d -FN_PREFIX(CryptonightR_instruction88): - imul ebp, edi -FN_PREFIX(CryptonightR_instruction89): - imul ebp, edi -FN_PREFIX(CryptonightR_instruction90): - imul ebp, edi -FN_PREFIX(CryptonightR_instruction91): - add ebp, edi - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction92): - sub ebp, edi -FN_PREFIX(CryptonightR_instruction93): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction94): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction95): - xor ebp, edi -FN_PREFIX(CryptonightR_instruction96): - imul ebx, ebp -FN_PREFIX(CryptonightR_instruction97): - imul ebx, ebp -FN_PREFIX(CryptonightR_instruction98): - imul ebx, ebp -FN_PREFIX(CryptonightR_instruction99): - add ebx, ebp - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction100): - sub ebx, ebp -FN_PREFIX(CryptonightR_instruction101): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction102): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction103): - xor ebx, ebp -FN_PREFIX(CryptonightR_instruction104): - imul esi, ebp -FN_PREFIX(CryptonightR_instruction105): - imul esi, ebp -FN_PREFIX(CryptonightR_instruction106): - imul esi, ebp -FN_PREFIX(CryptonightR_instruction107): - add esi, ebp - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction108): - sub esi, ebp -FN_PREFIX(CryptonightR_instruction109): - ror esi, cl -FN_PREFIX(CryptonightR_instruction110): - rol esi, cl -FN_PREFIX(CryptonightR_instruction111): - xor esi, ebp -FN_PREFIX(CryptonightR_instruction112): - imul edi, ebp -FN_PREFIX(CryptonightR_instruction113): - imul edi, ebp -FN_PREFIX(CryptonightR_instruction114): - imul edi, ebp -FN_PREFIX(CryptonightR_instruction115): - add edi, ebp - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction116): - sub edi, ebp -FN_PREFIX(CryptonightR_instruction117): - ror edi, cl -FN_PREFIX(CryptonightR_instruction118): - rol edi, cl -FN_PREFIX(CryptonightR_instruction119): - xor edi, ebp -FN_PREFIX(CryptonightR_instruction120): - imul ebp, ebp -FN_PREFIX(CryptonightR_instruction121): - imul ebp, ebp -FN_PREFIX(CryptonightR_instruction122): - imul ebp, ebp -FN_PREFIX(CryptonightR_instruction123): - add ebp, r9d - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction124): - sub ebp, r9d -FN_PREFIX(CryptonightR_instruction125): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction126): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction127): - xor ebp, r9d -FN_PREFIX(CryptonightR_instruction128): - imul ebx, esp -FN_PREFIX(CryptonightR_instruction129): - imul ebx, esp -FN_PREFIX(CryptonightR_instruction130): - imul ebx, esp -FN_PREFIX(CryptonightR_instruction131): - add ebx, esp - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction132): - sub ebx, esp -FN_PREFIX(CryptonightR_instruction133): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction134): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction135): - xor ebx, esp -FN_PREFIX(CryptonightR_instruction136): - imul esi, esp -FN_PREFIX(CryptonightR_instruction137): - imul esi, esp -FN_PREFIX(CryptonightR_instruction138): - imul esi, esp -FN_PREFIX(CryptonightR_instruction139): - add esi, esp - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction140): - sub esi, esp -FN_PREFIX(CryptonightR_instruction141): - ror esi, cl -FN_PREFIX(CryptonightR_instruction142): - rol esi, cl -FN_PREFIX(CryptonightR_instruction143): - xor esi, esp -FN_PREFIX(CryptonightR_instruction144): - imul edi, esp -FN_PREFIX(CryptonightR_instruction145): - imul edi, esp -FN_PREFIX(CryptonightR_instruction146): - imul edi, esp -FN_PREFIX(CryptonightR_instruction147): - add edi, esp - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction148): - sub edi, esp -FN_PREFIX(CryptonightR_instruction149): - ror edi, cl -FN_PREFIX(CryptonightR_instruction150): - rol edi, cl -FN_PREFIX(CryptonightR_instruction151): - xor edi, esp -FN_PREFIX(CryptonightR_instruction152): - imul ebp, esp -FN_PREFIX(CryptonightR_instruction153): - imul ebp, esp -FN_PREFIX(CryptonightR_instruction154): - imul ebp, esp -FN_PREFIX(CryptonightR_instruction155): - add ebp, esp - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction156): - sub ebp, esp -FN_PREFIX(CryptonightR_instruction157): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction158): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction159): - xor ebp, esp -FN_PREFIX(CryptonightR_instruction160): - imul ebx, r15d -FN_PREFIX(CryptonightR_instruction161): - imul ebx, r15d -FN_PREFIX(CryptonightR_instruction162): - imul ebx, r15d -FN_PREFIX(CryptonightR_instruction163): - add ebx, r15d - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction164): - sub ebx, r15d -FN_PREFIX(CryptonightR_instruction165): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction166): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction167): - xor ebx, r15d -FN_PREFIX(CryptonightR_instruction168): - imul esi, r15d -FN_PREFIX(CryptonightR_instruction169): - imul esi, r15d -FN_PREFIX(CryptonightR_instruction170): - imul esi, r15d -FN_PREFIX(CryptonightR_instruction171): - add esi, r15d - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction172): - sub esi, r15d -FN_PREFIX(CryptonightR_instruction173): - ror esi, cl -FN_PREFIX(CryptonightR_instruction174): - rol esi, cl -FN_PREFIX(CryptonightR_instruction175): - xor esi, r15d -FN_PREFIX(CryptonightR_instruction176): - imul edi, r15d -FN_PREFIX(CryptonightR_instruction177): - imul edi, r15d -FN_PREFIX(CryptonightR_instruction178): - imul edi, r15d -FN_PREFIX(CryptonightR_instruction179): - add edi, r15d - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction180): - sub edi, r15d -FN_PREFIX(CryptonightR_instruction181): - ror edi, cl -FN_PREFIX(CryptonightR_instruction182): - rol edi, cl -FN_PREFIX(CryptonightR_instruction183): - xor edi, r15d -FN_PREFIX(CryptonightR_instruction184): - imul ebp, r15d -FN_PREFIX(CryptonightR_instruction185): - imul ebp, r15d -FN_PREFIX(CryptonightR_instruction186): - imul ebp, r15d -FN_PREFIX(CryptonightR_instruction187): - add ebp, r15d - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction188): - sub ebp, r15d -FN_PREFIX(CryptonightR_instruction189): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction190): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction191): - xor ebp, r15d -FN_PREFIX(CryptonightR_instruction192): - imul ebx, eax -FN_PREFIX(CryptonightR_instruction193): - imul ebx, eax -FN_PREFIX(CryptonightR_instruction194): - imul ebx, eax -FN_PREFIX(CryptonightR_instruction195): - add ebx, eax - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction196): - sub ebx, eax -FN_PREFIX(CryptonightR_instruction197): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction198): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction199): - xor ebx, eax -FN_PREFIX(CryptonightR_instruction200): - imul esi, eax -FN_PREFIX(CryptonightR_instruction201): - imul esi, eax -FN_PREFIX(CryptonightR_instruction202): - imul esi, eax -FN_PREFIX(CryptonightR_instruction203): - add esi, eax - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction204): - sub esi, eax -FN_PREFIX(CryptonightR_instruction205): - ror esi, cl -FN_PREFIX(CryptonightR_instruction206): - rol esi, cl -FN_PREFIX(CryptonightR_instruction207): - xor esi, eax -FN_PREFIX(CryptonightR_instruction208): - imul edi, eax -FN_PREFIX(CryptonightR_instruction209): - imul edi, eax -FN_PREFIX(CryptonightR_instruction210): - imul edi, eax -FN_PREFIX(CryptonightR_instruction211): - add edi, eax - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction212): - sub edi, eax -FN_PREFIX(CryptonightR_instruction213): - ror edi, cl -FN_PREFIX(CryptonightR_instruction214): - rol edi, cl -FN_PREFIX(CryptonightR_instruction215): - xor edi, eax -FN_PREFIX(CryptonightR_instruction216): - imul ebp, eax -FN_PREFIX(CryptonightR_instruction217): - imul ebp, eax -FN_PREFIX(CryptonightR_instruction218): - imul ebp, eax -FN_PREFIX(CryptonightR_instruction219): - add ebp, eax - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction220): - sub ebp, eax -FN_PREFIX(CryptonightR_instruction221): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction222): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction223): - xor ebp, eax -FN_PREFIX(CryptonightR_instruction224): - imul ebx, edx -FN_PREFIX(CryptonightR_instruction225): - imul ebx, edx -FN_PREFIX(CryptonightR_instruction226): - imul ebx, edx -FN_PREFIX(CryptonightR_instruction227): - add ebx, edx - add ebx, 2147483647 -FN_PREFIX(CryptonightR_instruction228): - sub ebx, edx -FN_PREFIX(CryptonightR_instruction229): - ror ebx, cl -FN_PREFIX(CryptonightR_instruction230): - rol ebx, cl -FN_PREFIX(CryptonightR_instruction231): - xor ebx, edx -FN_PREFIX(CryptonightR_instruction232): - imul esi, edx -FN_PREFIX(CryptonightR_instruction233): - imul esi, edx -FN_PREFIX(CryptonightR_instruction234): - imul esi, edx -FN_PREFIX(CryptonightR_instruction235): - add esi, edx - add esi, 2147483647 -FN_PREFIX(CryptonightR_instruction236): - sub esi, edx -FN_PREFIX(CryptonightR_instruction237): - ror esi, cl -FN_PREFIX(CryptonightR_instruction238): - rol esi, cl -FN_PREFIX(CryptonightR_instruction239): - xor esi, edx -FN_PREFIX(CryptonightR_instruction240): - imul edi, edx -FN_PREFIX(CryptonightR_instruction241): - imul edi, edx -FN_PREFIX(CryptonightR_instruction242): - imul edi, edx -FN_PREFIX(CryptonightR_instruction243): - add edi, edx - add edi, 2147483647 -FN_PREFIX(CryptonightR_instruction244): - sub edi, edx -FN_PREFIX(CryptonightR_instruction245): - ror edi, cl -FN_PREFIX(CryptonightR_instruction246): - rol edi, cl -FN_PREFIX(CryptonightR_instruction247): - xor edi, edx -FN_PREFIX(CryptonightR_instruction248): - imul ebp, edx -FN_PREFIX(CryptonightR_instruction249): - imul ebp, edx -FN_PREFIX(CryptonightR_instruction250): - imul ebp, edx -FN_PREFIX(CryptonightR_instruction251): - add ebp, edx - add ebp, 2147483647 -FN_PREFIX(CryptonightR_instruction252): - sub ebp, edx -FN_PREFIX(CryptonightR_instruction253): - ror ebp, cl -FN_PREFIX(CryptonightR_instruction254): - rol ebp, cl -FN_PREFIX(CryptonightR_instruction255): - xor ebp, edx -FN_PREFIX(CryptonightR_instruction256): - imul ebx, ebx -FN_PREFIX(CryptonightR_instruction_mov0): - -FN_PREFIX(CryptonightR_instruction_mov1): - -FN_PREFIX(CryptonightR_instruction_mov2): - -FN_PREFIX(CryptonightR_instruction_mov3): - -FN_PREFIX(CryptonightR_instruction_mov4): - -FN_PREFIX(CryptonightR_instruction_mov5): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov6): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov7): - -FN_PREFIX(CryptonightR_instruction_mov8): - -FN_PREFIX(CryptonightR_instruction_mov9): - -FN_PREFIX(CryptonightR_instruction_mov10): - -FN_PREFIX(CryptonightR_instruction_mov11): - -FN_PREFIX(CryptonightR_instruction_mov12): - -FN_PREFIX(CryptonightR_instruction_mov13): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov14): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov15): - -FN_PREFIX(CryptonightR_instruction_mov16): - -FN_PREFIX(CryptonightR_instruction_mov17): - -FN_PREFIX(CryptonightR_instruction_mov18): - -FN_PREFIX(CryptonightR_instruction_mov19): - -FN_PREFIX(CryptonightR_instruction_mov20): - -FN_PREFIX(CryptonightR_instruction_mov21): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov22): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov23): - -FN_PREFIX(CryptonightR_instruction_mov24): - -FN_PREFIX(CryptonightR_instruction_mov25): - -FN_PREFIX(CryptonightR_instruction_mov26): - -FN_PREFIX(CryptonightR_instruction_mov27): - -FN_PREFIX(CryptonightR_instruction_mov28): - -FN_PREFIX(CryptonightR_instruction_mov29): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov30): - mov ecx, ebx -FN_PREFIX(CryptonightR_instruction_mov31): - -FN_PREFIX(CryptonightR_instruction_mov32): - -FN_PREFIX(CryptonightR_instruction_mov33): - -FN_PREFIX(CryptonightR_instruction_mov34): - -FN_PREFIX(CryptonightR_instruction_mov35): - -FN_PREFIX(CryptonightR_instruction_mov36): - -FN_PREFIX(CryptonightR_instruction_mov37): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov38): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov39): - -FN_PREFIX(CryptonightR_instruction_mov40): - -FN_PREFIX(CryptonightR_instruction_mov41): - -FN_PREFIX(CryptonightR_instruction_mov42): - -FN_PREFIX(CryptonightR_instruction_mov43): - -FN_PREFIX(CryptonightR_instruction_mov44): - -FN_PREFIX(CryptonightR_instruction_mov45): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov46): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov47): - -FN_PREFIX(CryptonightR_instruction_mov48): - -FN_PREFIX(CryptonightR_instruction_mov49): - -FN_PREFIX(CryptonightR_instruction_mov50): - -FN_PREFIX(CryptonightR_instruction_mov51): - -FN_PREFIX(CryptonightR_instruction_mov52): - -FN_PREFIX(CryptonightR_instruction_mov53): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov54): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov55): - -FN_PREFIX(CryptonightR_instruction_mov56): - -FN_PREFIX(CryptonightR_instruction_mov57): - -FN_PREFIX(CryptonightR_instruction_mov58): - -FN_PREFIX(CryptonightR_instruction_mov59): - -FN_PREFIX(CryptonightR_instruction_mov60): - -FN_PREFIX(CryptonightR_instruction_mov61): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov62): - mov ecx, esi -FN_PREFIX(CryptonightR_instruction_mov63): - -FN_PREFIX(CryptonightR_instruction_mov64): - -FN_PREFIX(CryptonightR_instruction_mov65): - -FN_PREFIX(CryptonightR_instruction_mov66): - -FN_PREFIX(CryptonightR_instruction_mov67): - -FN_PREFIX(CryptonightR_instruction_mov68): - -FN_PREFIX(CryptonightR_instruction_mov69): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov70): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov71): - -FN_PREFIX(CryptonightR_instruction_mov72): - -FN_PREFIX(CryptonightR_instruction_mov73): - -FN_PREFIX(CryptonightR_instruction_mov74): - -FN_PREFIX(CryptonightR_instruction_mov75): - -FN_PREFIX(CryptonightR_instruction_mov76): - -FN_PREFIX(CryptonightR_instruction_mov77): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov78): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov79): - -FN_PREFIX(CryptonightR_instruction_mov80): - -FN_PREFIX(CryptonightR_instruction_mov81): - -FN_PREFIX(CryptonightR_instruction_mov82): - -FN_PREFIX(CryptonightR_instruction_mov83): - -FN_PREFIX(CryptonightR_instruction_mov84): - -FN_PREFIX(CryptonightR_instruction_mov85): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov86): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov87): - -FN_PREFIX(CryptonightR_instruction_mov88): - -FN_PREFIX(CryptonightR_instruction_mov89): - -FN_PREFIX(CryptonightR_instruction_mov90): - -FN_PREFIX(CryptonightR_instruction_mov91): - -FN_PREFIX(CryptonightR_instruction_mov92): - -FN_PREFIX(CryptonightR_instruction_mov93): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov94): - mov ecx, edi -FN_PREFIX(CryptonightR_instruction_mov95): - -FN_PREFIX(CryptonightR_instruction_mov96): - -FN_PREFIX(CryptonightR_instruction_mov97): - -FN_PREFIX(CryptonightR_instruction_mov98): - -FN_PREFIX(CryptonightR_instruction_mov99): - -FN_PREFIX(CryptonightR_instruction_mov100): - -FN_PREFIX(CryptonightR_instruction_mov101): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov102): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov103): - -FN_PREFIX(CryptonightR_instruction_mov104): - -FN_PREFIX(CryptonightR_instruction_mov105): - -FN_PREFIX(CryptonightR_instruction_mov106): - -FN_PREFIX(CryptonightR_instruction_mov107): - -FN_PREFIX(CryptonightR_instruction_mov108): - -FN_PREFIX(CryptonightR_instruction_mov109): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov110): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov111): - -FN_PREFIX(CryptonightR_instruction_mov112): - -FN_PREFIX(CryptonightR_instruction_mov113): - -FN_PREFIX(CryptonightR_instruction_mov114): - -FN_PREFIX(CryptonightR_instruction_mov115): - -FN_PREFIX(CryptonightR_instruction_mov116): - -FN_PREFIX(CryptonightR_instruction_mov117): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov118): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov119): - -FN_PREFIX(CryptonightR_instruction_mov120): - -FN_PREFIX(CryptonightR_instruction_mov121): - -FN_PREFIX(CryptonightR_instruction_mov122): - -FN_PREFIX(CryptonightR_instruction_mov123): - -FN_PREFIX(CryptonightR_instruction_mov124): - -FN_PREFIX(CryptonightR_instruction_mov125): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov126): - mov ecx, ebp -FN_PREFIX(CryptonightR_instruction_mov127): - -FN_PREFIX(CryptonightR_instruction_mov128): - -FN_PREFIX(CryptonightR_instruction_mov129): - -FN_PREFIX(CryptonightR_instruction_mov130): - -FN_PREFIX(CryptonightR_instruction_mov131): - -FN_PREFIX(CryptonightR_instruction_mov132): - -FN_PREFIX(CryptonightR_instruction_mov133): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov134): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov135): - -FN_PREFIX(CryptonightR_instruction_mov136): - -FN_PREFIX(CryptonightR_instruction_mov137): - -FN_PREFIX(CryptonightR_instruction_mov138): - -FN_PREFIX(CryptonightR_instruction_mov139): - -FN_PREFIX(CryptonightR_instruction_mov140): - -FN_PREFIX(CryptonightR_instruction_mov141): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov142): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov143): - -FN_PREFIX(CryptonightR_instruction_mov144): - -FN_PREFIX(CryptonightR_instruction_mov145): - -FN_PREFIX(CryptonightR_instruction_mov146): - -FN_PREFIX(CryptonightR_instruction_mov147): - -FN_PREFIX(CryptonightR_instruction_mov148): - -FN_PREFIX(CryptonightR_instruction_mov149): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov150): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov151): - -FN_PREFIX(CryptonightR_instruction_mov152): - -FN_PREFIX(CryptonightR_instruction_mov153): - -FN_PREFIX(CryptonightR_instruction_mov154): - -FN_PREFIX(CryptonightR_instruction_mov155): - -FN_PREFIX(CryptonightR_instruction_mov156): - -FN_PREFIX(CryptonightR_instruction_mov157): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov158): - mov ecx, esp -FN_PREFIX(CryptonightR_instruction_mov159): - -FN_PREFIX(CryptonightR_instruction_mov160): - -FN_PREFIX(CryptonightR_instruction_mov161): - -FN_PREFIX(CryptonightR_instruction_mov162): - -FN_PREFIX(CryptonightR_instruction_mov163): - -FN_PREFIX(CryptonightR_instruction_mov164): - -FN_PREFIX(CryptonightR_instruction_mov165): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov166): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov167): - -FN_PREFIX(CryptonightR_instruction_mov168): - -FN_PREFIX(CryptonightR_instruction_mov169): - -FN_PREFIX(CryptonightR_instruction_mov170): - -FN_PREFIX(CryptonightR_instruction_mov171): - -FN_PREFIX(CryptonightR_instruction_mov172): - -FN_PREFIX(CryptonightR_instruction_mov173): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov174): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov175): - -FN_PREFIX(CryptonightR_instruction_mov176): - -FN_PREFIX(CryptonightR_instruction_mov177): - -FN_PREFIX(CryptonightR_instruction_mov178): - -FN_PREFIX(CryptonightR_instruction_mov179): - -FN_PREFIX(CryptonightR_instruction_mov180): - -FN_PREFIX(CryptonightR_instruction_mov181): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov182): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov183): - -FN_PREFIX(CryptonightR_instruction_mov184): - -FN_PREFIX(CryptonightR_instruction_mov185): - -FN_PREFIX(CryptonightR_instruction_mov186): - -FN_PREFIX(CryptonightR_instruction_mov187): - -FN_PREFIX(CryptonightR_instruction_mov188): - -FN_PREFIX(CryptonightR_instruction_mov189): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov190): - mov ecx, r15d -FN_PREFIX(CryptonightR_instruction_mov191): - -FN_PREFIX(CryptonightR_instruction_mov192): - -FN_PREFIX(CryptonightR_instruction_mov193): - -FN_PREFIX(CryptonightR_instruction_mov194): - -FN_PREFIX(CryptonightR_instruction_mov195): - -FN_PREFIX(CryptonightR_instruction_mov196): - -FN_PREFIX(CryptonightR_instruction_mov197): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov198): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov199): - -FN_PREFIX(CryptonightR_instruction_mov200): - -FN_PREFIX(CryptonightR_instruction_mov201): - -FN_PREFIX(CryptonightR_instruction_mov202): - -FN_PREFIX(CryptonightR_instruction_mov203): - -FN_PREFIX(CryptonightR_instruction_mov204): - -FN_PREFIX(CryptonightR_instruction_mov205): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov206): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov207): - -FN_PREFIX(CryptonightR_instruction_mov208): - -FN_PREFIX(CryptonightR_instruction_mov209): - -FN_PREFIX(CryptonightR_instruction_mov210): - -FN_PREFIX(CryptonightR_instruction_mov211): - -FN_PREFIX(CryptonightR_instruction_mov212): - -FN_PREFIX(CryptonightR_instruction_mov213): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov214): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov215): - -FN_PREFIX(CryptonightR_instruction_mov216): - -FN_PREFIX(CryptonightR_instruction_mov217): - -FN_PREFIX(CryptonightR_instruction_mov218): - -FN_PREFIX(CryptonightR_instruction_mov219): - -FN_PREFIX(CryptonightR_instruction_mov220): - -FN_PREFIX(CryptonightR_instruction_mov221): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov222): - mov ecx, eax -FN_PREFIX(CryptonightR_instruction_mov223): - -FN_PREFIX(CryptonightR_instruction_mov224): - -FN_PREFIX(CryptonightR_instruction_mov225): - -FN_PREFIX(CryptonightR_instruction_mov226): - -FN_PREFIX(CryptonightR_instruction_mov227): - -FN_PREFIX(CryptonightR_instruction_mov228): - -FN_PREFIX(CryptonightR_instruction_mov229): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov230): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov231): - -FN_PREFIX(CryptonightR_instruction_mov232): - -FN_PREFIX(CryptonightR_instruction_mov233): - -FN_PREFIX(CryptonightR_instruction_mov234): - -FN_PREFIX(CryptonightR_instruction_mov235): - -FN_PREFIX(CryptonightR_instruction_mov236): - -FN_PREFIX(CryptonightR_instruction_mov237): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov238): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov239): - -FN_PREFIX(CryptonightR_instruction_mov240): - -FN_PREFIX(CryptonightR_instruction_mov241): - -FN_PREFIX(CryptonightR_instruction_mov242): - -FN_PREFIX(CryptonightR_instruction_mov243): - -FN_PREFIX(CryptonightR_instruction_mov244): - -FN_PREFIX(CryptonightR_instruction_mov245): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov246): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov247): - -FN_PREFIX(CryptonightR_instruction_mov248): - -FN_PREFIX(CryptonightR_instruction_mov249): - -FN_PREFIX(CryptonightR_instruction_mov250): - -FN_PREFIX(CryptonightR_instruction_mov251): - -FN_PREFIX(CryptonightR_instruction_mov252): - -FN_PREFIX(CryptonightR_instruction_mov253): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov254): - mov ecx, edx -FN_PREFIX(CryptonightR_instruction_mov255): - -FN_PREFIX(CryptonightR_instruction_mov256): diff --git a/cryptonight/c/CryptonightR_template.h b/cryptonight/c/CryptonightR_template.h deleted file mode 100644 index dab1bd7..0000000 --- a/cryptonight/c/CryptonightR_template.h +++ /dev/null @@ -1,1043 +0,0 @@ -#ifndef CRYPTONIGHTR_TEMPLATE_H -#define CRYPTONIGHTR_TEMPLATE_H - -#if defined __i386 || defined __x86_64__ - -void CryptonightR_instruction0(void); -void CryptonightR_instruction1(void); -void CryptonightR_instruction2(void); -void CryptonightR_instruction3(void); -void CryptonightR_instruction4(void); -void CryptonightR_instruction5(void); -void CryptonightR_instruction6(void); -void CryptonightR_instruction7(void); -void CryptonightR_instruction8(void); -void CryptonightR_instruction9(void); -void CryptonightR_instruction10(void); -void CryptonightR_instruction11(void); -void CryptonightR_instruction12(void); -void CryptonightR_instruction13(void); -void CryptonightR_instruction14(void); -void CryptonightR_instruction15(void); -void CryptonightR_instruction16(void); -void CryptonightR_instruction17(void); -void CryptonightR_instruction18(void); -void CryptonightR_instruction19(void); -void CryptonightR_instruction20(void); -void CryptonightR_instruction21(void); -void CryptonightR_instruction22(void); -void CryptonightR_instruction23(void); -void CryptonightR_instruction24(void); -void CryptonightR_instruction25(void); -void CryptonightR_instruction26(void); -void CryptonightR_instruction27(void); -void CryptonightR_instruction28(void); -void CryptonightR_instruction29(void); -void CryptonightR_instruction30(void); -void CryptonightR_instruction31(void); -void CryptonightR_instruction32(void); -void CryptonightR_instruction33(void); -void CryptonightR_instruction34(void); -void CryptonightR_instruction35(void); -void CryptonightR_instruction36(void); -void CryptonightR_instruction37(void); -void CryptonightR_instruction38(void); -void CryptonightR_instruction39(void); -void CryptonightR_instruction40(void); -void CryptonightR_instruction41(void); -void CryptonightR_instruction42(void); -void CryptonightR_instruction43(void); -void CryptonightR_instruction44(void); -void CryptonightR_instruction45(void); -void CryptonightR_instruction46(void); -void CryptonightR_instruction47(void); -void CryptonightR_instruction48(void); -void CryptonightR_instruction49(void); -void CryptonightR_instruction50(void); -void CryptonightR_instruction51(void); -void CryptonightR_instruction52(void); -void CryptonightR_instruction53(void); -void CryptonightR_instruction54(void); -void CryptonightR_instruction55(void); -void CryptonightR_instruction56(void); -void CryptonightR_instruction57(void); -void CryptonightR_instruction58(void); -void CryptonightR_instruction59(void); -void CryptonightR_instruction60(void); -void CryptonightR_instruction61(void); -void CryptonightR_instruction62(void); -void CryptonightR_instruction63(void); -void CryptonightR_instruction64(void); -void CryptonightR_instruction65(void); -void CryptonightR_instruction66(void); -void CryptonightR_instruction67(void); -void CryptonightR_instruction68(void); -void CryptonightR_instruction69(void); -void CryptonightR_instruction70(void); -void CryptonightR_instruction71(void); -void CryptonightR_instruction72(void); -void CryptonightR_instruction73(void); -void CryptonightR_instruction74(void); -void CryptonightR_instruction75(void); -void CryptonightR_instruction76(void); -void CryptonightR_instruction77(void); -void CryptonightR_instruction78(void); -void CryptonightR_instruction79(void); -void CryptonightR_instruction80(void); -void CryptonightR_instruction81(void); -void CryptonightR_instruction82(void); -void CryptonightR_instruction83(void); -void CryptonightR_instruction84(void); -void CryptonightR_instruction85(void); -void CryptonightR_instruction86(void); -void CryptonightR_instruction87(void); -void CryptonightR_instruction88(void); -void CryptonightR_instruction89(void); -void CryptonightR_instruction90(void); -void CryptonightR_instruction91(void); -void CryptonightR_instruction92(void); -void CryptonightR_instruction93(void); -void CryptonightR_instruction94(void); -void CryptonightR_instruction95(void); -void CryptonightR_instruction96(void); -void CryptonightR_instruction97(void); -void CryptonightR_instruction98(void); -void CryptonightR_instruction99(void); -void CryptonightR_instruction100(void); -void CryptonightR_instruction101(void); -void CryptonightR_instruction102(void); -void CryptonightR_instruction103(void); -void CryptonightR_instruction104(void); -void CryptonightR_instruction105(void); -void CryptonightR_instruction106(void); -void CryptonightR_instruction107(void); -void CryptonightR_instruction108(void); -void CryptonightR_instruction109(void); -void CryptonightR_instruction110(void); -void CryptonightR_instruction111(void); -void CryptonightR_instruction112(void); -void CryptonightR_instruction113(void); -void CryptonightR_instruction114(void); -void CryptonightR_instruction115(void); -void CryptonightR_instruction116(void); -void CryptonightR_instruction117(void); -void CryptonightR_instruction118(void); -void CryptonightR_instruction119(void); -void CryptonightR_instruction120(void); -void CryptonightR_instruction121(void); -void CryptonightR_instruction122(void); -void CryptonightR_instruction123(void); -void CryptonightR_instruction124(void); -void CryptonightR_instruction125(void); -void CryptonightR_instruction126(void); -void CryptonightR_instruction127(void); -void CryptonightR_instruction128(void); -void CryptonightR_instruction129(void); -void CryptonightR_instruction130(void); -void CryptonightR_instruction131(void); -void CryptonightR_instruction132(void); -void CryptonightR_instruction133(void); -void CryptonightR_instruction134(void); -void CryptonightR_instruction135(void); -void CryptonightR_instruction136(void); -void CryptonightR_instruction137(void); -void CryptonightR_instruction138(void); -void CryptonightR_instruction139(void); -void CryptonightR_instruction140(void); -void CryptonightR_instruction141(void); -void CryptonightR_instruction142(void); -void CryptonightR_instruction143(void); -void CryptonightR_instruction144(void); -void CryptonightR_instruction145(void); -void CryptonightR_instruction146(void); -void CryptonightR_instruction147(void); -void CryptonightR_instruction148(void); -void CryptonightR_instruction149(void); -void CryptonightR_instruction150(void); -void CryptonightR_instruction151(void); -void CryptonightR_instruction152(void); -void CryptonightR_instruction153(void); -void CryptonightR_instruction154(void); -void CryptonightR_instruction155(void); -void CryptonightR_instruction156(void); -void CryptonightR_instruction157(void); -void CryptonightR_instruction158(void); -void CryptonightR_instruction159(void); -void CryptonightR_instruction160(void); -void CryptonightR_instruction161(void); -void CryptonightR_instruction162(void); -void CryptonightR_instruction163(void); -void CryptonightR_instruction164(void); -void CryptonightR_instruction165(void); -void CryptonightR_instruction166(void); -void CryptonightR_instruction167(void); -void CryptonightR_instruction168(void); -void CryptonightR_instruction169(void); -void CryptonightR_instruction170(void); -void CryptonightR_instruction171(void); -void CryptonightR_instruction172(void); -void CryptonightR_instruction173(void); -void CryptonightR_instruction174(void); -void CryptonightR_instruction175(void); -void CryptonightR_instruction176(void); -void CryptonightR_instruction177(void); -void CryptonightR_instruction178(void); -void CryptonightR_instruction179(void); -void CryptonightR_instruction180(void); -void CryptonightR_instruction181(void); -void CryptonightR_instruction182(void); -void CryptonightR_instruction183(void); -void CryptonightR_instruction184(void); -void CryptonightR_instruction185(void); -void CryptonightR_instruction186(void); -void CryptonightR_instruction187(void); -void CryptonightR_instruction188(void); -void CryptonightR_instruction189(void); -void CryptonightR_instruction190(void); -void CryptonightR_instruction191(void); -void CryptonightR_instruction192(void); -void CryptonightR_instruction193(void); -void CryptonightR_instruction194(void); -void CryptonightR_instruction195(void); -void CryptonightR_instruction196(void); -void CryptonightR_instruction197(void); -void CryptonightR_instruction198(void); -void CryptonightR_instruction199(void); -void CryptonightR_instruction200(void); -void CryptonightR_instruction201(void); -void CryptonightR_instruction202(void); -void CryptonightR_instruction203(void); -void CryptonightR_instruction204(void); -void CryptonightR_instruction205(void); -void CryptonightR_instruction206(void); -void CryptonightR_instruction207(void); -void CryptonightR_instruction208(void); -void CryptonightR_instruction209(void); -void CryptonightR_instruction210(void); -void CryptonightR_instruction211(void); -void CryptonightR_instruction212(void); -void CryptonightR_instruction213(void); -void CryptonightR_instruction214(void); -void CryptonightR_instruction215(void); -void CryptonightR_instruction216(void); -void CryptonightR_instruction217(void); -void CryptonightR_instruction218(void); -void CryptonightR_instruction219(void); -void CryptonightR_instruction220(void); -void CryptonightR_instruction221(void); -void CryptonightR_instruction222(void); -void CryptonightR_instruction223(void); -void CryptonightR_instruction224(void); -void CryptonightR_instruction225(void); -void CryptonightR_instruction226(void); -void CryptonightR_instruction227(void); -void CryptonightR_instruction228(void); -void CryptonightR_instruction229(void); -void CryptonightR_instruction230(void); -void CryptonightR_instruction231(void); -void CryptonightR_instruction232(void); -void CryptonightR_instruction233(void); -void CryptonightR_instruction234(void); -void CryptonightR_instruction235(void); -void CryptonightR_instruction236(void); -void CryptonightR_instruction237(void); -void CryptonightR_instruction238(void); -void CryptonightR_instruction239(void); -void CryptonightR_instruction240(void); -void CryptonightR_instruction241(void); -void CryptonightR_instruction242(void); -void CryptonightR_instruction243(void); -void CryptonightR_instruction244(void); -void CryptonightR_instruction245(void); -void CryptonightR_instruction246(void); -void CryptonightR_instruction247(void); -void CryptonightR_instruction248(void); -void CryptonightR_instruction249(void); -void CryptonightR_instruction250(void); -void CryptonightR_instruction251(void); -void CryptonightR_instruction252(void); -void CryptonightR_instruction253(void); -void CryptonightR_instruction254(void); -void CryptonightR_instruction255(void); -void CryptonightR_instruction256(void); -void CryptonightR_instruction_mov0(void); -void CryptonightR_instruction_mov1(void); -void CryptonightR_instruction_mov2(void); -void CryptonightR_instruction_mov3(void); -void CryptonightR_instruction_mov4(void); -void CryptonightR_instruction_mov5(void); -void CryptonightR_instruction_mov6(void); -void CryptonightR_instruction_mov7(void); -void CryptonightR_instruction_mov8(void); -void CryptonightR_instruction_mov9(void); -void CryptonightR_instruction_mov10(void); -void CryptonightR_instruction_mov11(void); -void CryptonightR_instruction_mov12(void); -void CryptonightR_instruction_mov13(void); -void CryptonightR_instruction_mov14(void); -void CryptonightR_instruction_mov15(void); -void CryptonightR_instruction_mov16(void); -void CryptonightR_instruction_mov17(void); -void CryptonightR_instruction_mov18(void); -void CryptonightR_instruction_mov19(void); -void CryptonightR_instruction_mov20(void); -void CryptonightR_instruction_mov21(void); -void CryptonightR_instruction_mov22(void); -void CryptonightR_instruction_mov23(void); -void CryptonightR_instruction_mov24(void); -void CryptonightR_instruction_mov25(void); -void CryptonightR_instruction_mov26(void); -void CryptonightR_instruction_mov27(void); -void CryptonightR_instruction_mov28(void); -void CryptonightR_instruction_mov29(void); -void CryptonightR_instruction_mov30(void); -void CryptonightR_instruction_mov31(void); -void CryptonightR_instruction_mov32(void); -void CryptonightR_instruction_mov33(void); -void CryptonightR_instruction_mov34(void); -void CryptonightR_instruction_mov35(void); -void CryptonightR_instruction_mov36(void); -void CryptonightR_instruction_mov37(void); -void CryptonightR_instruction_mov38(void); -void CryptonightR_instruction_mov39(void); -void CryptonightR_instruction_mov40(void); -void CryptonightR_instruction_mov41(void); -void CryptonightR_instruction_mov42(void); -void CryptonightR_instruction_mov43(void); -void CryptonightR_instruction_mov44(void); -void CryptonightR_instruction_mov45(void); -void CryptonightR_instruction_mov46(void); -void CryptonightR_instruction_mov47(void); -void CryptonightR_instruction_mov48(void); -void CryptonightR_instruction_mov49(void); -void CryptonightR_instruction_mov50(void); -void CryptonightR_instruction_mov51(void); -void CryptonightR_instruction_mov52(void); -void CryptonightR_instruction_mov53(void); -void CryptonightR_instruction_mov54(void); -void CryptonightR_instruction_mov55(void); -void CryptonightR_instruction_mov56(void); -void CryptonightR_instruction_mov57(void); -void CryptonightR_instruction_mov58(void); -void CryptonightR_instruction_mov59(void); -void CryptonightR_instruction_mov60(void); -void CryptonightR_instruction_mov61(void); -void CryptonightR_instruction_mov62(void); -void CryptonightR_instruction_mov63(void); -void CryptonightR_instruction_mov64(void); -void CryptonightR_instruction_mov65(void); -void CryptonightR_instruction_mov66(void); -void CryptonightR_instruction_mov67(void); -void CryptonightR_instruction_mov68(void); -void CryptonightR_instruction_mov69(void); -void CryptonightR_instruction_mov70(void); -void CryptonightR_instruction_mov71(void); -void CryptonightR_instruction_mov72(void); -void CryptonightR_instruction_mov73(void); -void CryptonightR_instruction_mov74(void); -void CryptonightR_instruction_mov75(void); -void CryptonightR_instruction_mov76(void); -void CryptonightR_instruction_mov77(void); -void CryptonightR_instruction_mov78(void); -void CryptonightR_instruction_mov79(void); -void CryptonightR_instruction_mov80(void); -void CryptonightR_instruction_mov81(void); -void CryptonightR_instruction_mov82(void); -void CryptonightR_instruction_mov83(void); -void CryptonightR_instruction_mov84(void); -void CryptonightR_instruction_mov85(void); -void CryptonightR_instruction_mov86(void); -void CryptonightR_instruction_mov87(void); -void CryptonightR_instruction_mov88(void); -void CryptonightR_instruction_mov89(void); -void CryptonightR_instruction_mov90(void); -void CryptonightR_instruction_mov91(void); -void CryptonightR_instruction_mov92(void); -void CryptonightR_instruction_mov93(void); -void CryptonightR_instruction_mov94(void); -void CryptonightR_instruction_mov95(void); -void CryptonightR_instruction_mov96(void); -void CryptonightR_instruction_mov97(void); -void CryptonightR_instruction_mov98(void); -void CryptonightR_instruction_mov99(void); -void CryptonightR_instruction_mov100(void); -void CryptonightR_instruction_mov101(void); -void CryptonightR_instruction_mov102(void); -void CryptonightR_instruction_mov103(void); -void CryptonightR_instruction_mov104(void); -void CryptonightR_instruction_mov105(void); -void CryptonightR_instruction_mov106(void); -void CryptonightR_instruction_mov107(void); -void CryptonightR_instruction_mov108(void); -void CryptonightR_instruction_mov109(void); -void CryptonightR_instruction_mov110(void); -void CryptonightR_instruction_mov111(void); -void CryptonightR_instruction_mov112(void); -void CryptonightR_instruction_mov113(void); -void CryptonightR_instruction_mov114(void); -void CryptonightR_instruction_mov115(void); -void CryptonightR_instruction_mov116(void); -void CryptonightR_instruction_mov117(void); -void CryptonightR_instruction_mov118(void); -void CryptonightR_instruction_mov119(void); -void CryptonightR_instruction_mov120(void); -void CryptonightR_instruction_mov121(void); -void CryptonightR_instruction_mov122(void); -void CryptonightR_instruction_mov123(void); -void CryptonightR_instruction_mov124(void); -void CryptonightR_instruction_mov125(void); -void CryptonightR_instruction_mov126(void); -void CryptonightR_instruction_mov127(void); -void CryptonightR_instruction_mov128(void); -void CryptonightR_instruction_mov129(void); -void CryptonightR_instruction_mov130(void); -void CryptonightR_instruction_mov131(void); -void CryptonightR_instruction_mov132(void); -void CryptonightR_instruction_mov133(void); -void CryptonightR_instruction_mov134(void); -void CryptonightR_instruction_mov135(void); -void CryptonightR_instruction_mov136(void); -void CryptonightR_instruction_mov137(void); -void CryptonightR_instruction_mov138(void); -void CryptonightR_instruction_mov139(void); -void CryptonightR_instruction_mov140(void); -void CryptonightR_instruction_mov141(void); -void CryptonightR_instruction_mov142(void); -void CryptonightR_instruction_mov143(void); -void CryptonightR_instruction_mov144(void); -void CryptonightR_instruction_mov145(void); -void CryptonightR_instruction_mov146(void); -void CryptonightR_instruction_mov147(void); -void CryptonightR_instruction_mov148(void); -void CryptonightR_instruction_mov149(void); -void CryptonightR_instruction_mov150(void); -void CryptonightR_instruction_mov151(void); -void CryptonightR_instruction_mov152(void); -void CryptonightR_instruction_mov153(void); -void CryptonightR_instruction_mov154(void); -void CryptonightR_instruction_mov155(void); -void CryptonightR_instruction_mov156(void); -void CryptonightR_instruction_mov157(void); -void CryptonightR_instruction_mov158(void); -void CryptonightR_instruction_mov159(void); -void CryptonightR_instruction_mov160(void); -void CryptonightR_instruction_mov161(void); -void CryptonightR_instruction_mov162(void); -void CryptonightR_instruction_mov163(void); -void CryptonightR_instruction_mov164(void); -void CryptonightR_instruction_mov165(void); -void CryptonightR_instruction_mov166(void); -void CryptonightR_instruction_mov167(void); -void CryptonightR_instruction_mov168(void); -void CryptonightR_instruction_mov169(void); -void CryptonightR_instruction_mov170(void); -void CryptonightR_instruction_mov171(void); -void CryptonightR_instruction_mov172(void); -void CryptonightR_instruction_mov173(void); -void CryptonightR_instruction_mov174(void); -void CryptonightR_instruction_mov175(void); -void CryptonightR_instruction_mov176(void); -void CryptonightR_instruction_mov177(void); -void CryptonightR_instruction_mov178(void); -void CryptonightR_instruction_mov179(void); -void CryptonightR_instruction_mov180(void); -void CryptonightR_instruction_mov181(void); -void CryptonightR_instruction_mov182(void); -void CryptonightR_instruction_mov183(void); -void CryptonightR_instruction_mov184(void); -void CryptonightR_instruction_mov185(void); -void CryptonightR_instruction_mov186(void); -void CryptonightR_instruction_mov187(void); -void CryptonightR_instruction_mov188(void); -void CryptonightR_instruction_mov189(void); -void CryptonightR_instruction_mov190(void); -void CryptonightR_instruction_mov191(void); -void CryptonightR_instruction_mov192(void); -void CryptonightR_instruction_mov193(void); -void CryptonightR_instruction_mov194(void); -void CryptonightR_instruction_mov195(void); -void CryptonightR_instruction_mov196(void); -void CryptonightR_instruction_mov197(void); -void CryptonightR_instruction_mov198(void); -void CryptonightR_instruction_mov199(void); -void CryptonightR_instruction_mov200(void); -void CryptonightR_instruction_mov201(void); -void CryptonightR_instruction_mov202(void); -void CryptonightR_instruction_mov203(void); -void CryptonightR_instruction_mov204(void); -void CryptonightR_instruction_mov205(void); -void CryptonightR_instruction_mov206(void); -void CryptonightR_instruction_mov207(void); -void CryptonightR_instruction_mov208(void); -void CryptonightR_instruction_mov209(void); -void CryptonightR_instruction_mov210(void); -void CryptonightR_instruction_mov211(void); -void CryptonightR_instruction_mov212(void); -void CryptonightR_instruction_mov213(void); -void CryptonightR_instruction_mov214(void); -void CryptonightR_instruction_mov215(void); -void CryptonightR_instruction_mov216(void); -void CryptonightR_instruction_mov217(void); -void CryptonightR_instruction_mov218(void); -void CryptonightR_instruction_mov219(void); -void CryptonightR_instruction_mov220(void); -void CryptonightR_instruction_mov221(void); -void CryptonightR_instruction_mov222(void); -void CryptonightR_instruction_mov223(void); -void CryptonightR_instruction_mov224(void); -void CryptonightR_instruction_mov225(void); -void CryptonightR_instruction_mov226(void); -void CryptonightR_instruction_mov227(void); -void CryptonightR_instruction_mov228(void); -void CryptonightR_instruction_mov229(void); -void CryptonightR_instruction_mov230(void); -void CryptonightR_instruction_mov231(void); -void CryptonightR_instruction_mov232(void); -void CryptonightR_instruction_mov233(void); -void CryptonightR_instruction_mov234(void); -void CryptonightR_instruction_mov235(void); -void CryptonightR_instruction_mov236(void); -void CryptonightR_instruction_mov237(void); -void CryptonightR_instruction_mov238(void); -void CryptonightR_instruction_mov239(void); -void CryptonightR_instruction_mov240(void); -void CryptonightR_instruction_mov241(void); -void CryptonightR_instruction_mov242(void); -void CryptonightR_instruction_mov243(void); -void CryptonightR_instruction_mov244(void); -void CryptonightR_instruction_mov245(void); -void CryptonightR_instruction_mov246(void); -void CryptonightR_instruction_mov247(void); -void CryptonightR_instruction_mov248(void); -void CryptonightR_instruction_mov249(void); -void CryptonightR_instruction_mov250(void); -void CryptonightR_instruction_mov251(void); -void CryptonightR_instruction_mov252(void); -void CryptonightR_instruction_mov253(void); -void CryptonightR_instruction_mov254(void); -void CryptonightR_instruction_mov255(void); -void CryptonightR_instruction_mov256(void); - -const void* instructions[257] = { - CryptonightR_instruction0, - CryptonightR_instruction1, - CryptonightR_instruction2, - CryptonightR_instruction3, - CryptonightR_instruction4, - CryptonightR_instruction5, - CryptonightR_instruction6, - CryptonightR_instruction7, - CryptonightR_instruction8, - CryptonightR_instruction9, - CryptonightR_instruction10, - CryptonightR_instruction11, - CryptonightR_instruction12, - CryptonightR_instruction13, - CryptonightR_instruction14, - CryptonightR_instruction15, - CryptonightR_instruction16, - CryptonightR_instruction17, - CryptonightR_instruction18, - CryptonightR_instruction19, - CryptonightR_instruction20, - CryptonightR_instruction21, - CryptonightR_instruction22, - CryptonightR_instruction23, - CryptonightR_instruction24, - CryptonightR_instruction25, - CryptonightR_instruction26, - CryptonightR_instruction27, - CryptonightR_instruction28, - CryptonightR_instruction29, - CryptonightR_instruction30, - CryptonightR_instruction31, - CryptonightR_instruction32, - CryptonightR_instruction33, - CryptonightR_instruction34, - CryptonightR_instruction35, - CryptonightR_instruction36, - CryptonightR_instruction37, - CryptonightR_instruction38, - CryptonightR_instruction39, - CryptonightR_instruction40, - CryptonightR_instruction41, - CryptonightR_instruction42, - CryptonightR_instruction43, - CryptonightR_instruction44, - CryptonightR_instruction45, - CryptonightR_instruction46, - CryptonightR_instruction47, - CryptonightR_instruction48, - CryptonightR_instruction49, - CryptonightR_instruction50, - CryptonightR_instruction51, - CryptonightR_instruction52, - CryptonightR_instruction53, - CryptonightR_instruction54, - CryptonightR_instruction55, - CryptonightR_instruction56, - CryptonightR_instruction57, - CryptonightR_instruction58, - CryptonightR_instruction59, - CryptonightR_instruction60, - CryptonightR_instruction61, - CryptonightR_instruction62, - CryptonightR_instruction63, - CryptonightR_instruction64, - CryptonightR_instruction65, - CryptonightR_instruction66, - CryptonightR_instruction67, - CryptonightR_instruction68, - CryptonightR_instruction69, - CryptonightR_instruction70, - CryptonightR_instruction71, - CryptonightR_instruction72, - CryptonightR_instruction73, - CryptonightR_instruction74, - CryptonightR_instruction75, - CryptonightR_instruction76, - CryptonightR_instruction77, - CryptonightR_instruction78, - CryptonightR_instruction79, - CryptonightR_instruction80, - CryptonightR_instruction81, - CryptonightR_instruction82, - CryptonightR_instruction83, - CryptonightR_instruction84, - CryptonightR_instruction85, - CryptonightR_instruction86, - CryptonightR_instruction87, - CryptonightR_instruction88, - CryptonightR_instruction89, - CryptonightR_instruction90, - CryptonightR_instruction91, - CryptonightR_instruction92, - CryptonightR_instruction93, - CryptonightR_instruction94, - CryptonightR_instruction95, - CryptonightR_instruction96, - CryptonightR_instruction97, - CryptonightR_instruction98, - CryptonightR_instruction99, - CryptonightR_instruction100, - CryptonightR_instruction101, - CryptonightR_instruction102, - CryptonightR_instruction103, - CryptonightR_instruction104, - CryptonightR_instruction105, - CryptonightR_instruction106, - CryptonightR_instruction107, - CryptonightR_instruction108, - CryptonightR_instruction109, - CryptonightR_instruction110, - CryptonightR_instruction111, - CryptonightR_instruction112, - CryptonightR_instruction113, - CryptonightR_instruction114, - CryptonightR_instruction115, - CryptonightR_instruction116, - CryptonightR_instruction117, - CryptonightR_instruction118, - CryptonightR_instruction119, - CryptonightR_instruction120, - CryptonightR_instruction121, - CryptonightR_instruction122, - CryptonightR_instruction123, - CryptonightR_instruction124, - CryptonightR_instruction125, - CryptonightR_instruction126, - CryptonightR_instruction127, - CryptonightR_instruction128, - CryptonightR_instruction129, - CryptonightR_instruction130, - CryptonightR_instruction131, - CryptonightR_instruction132, - CryptonightR_instruction133, - CryptonightR_instruction134, - CryptonightR_instruction135, - CryptonightR_instruction136, - CryptonightR_instruction137, - CryptonightR_instruction138, - CryptonightR_instruction139, - CryptonightR_instruction140, - CryptonightR_instruction141, - CryptonightR_instruction142, - CryptonightR_instruction143, - CryptonightR_instruction144, - CryptonightR_instruction145, - CryptonightR_instruction146, - CryptonightR_instruction147, - CryptonightR_instruction148, - CryptonightR_instruction149, - CryptonightR_instruction150, - CryptonightR_instruction151, - CryptonightR_instruction152, - CryptonightR_instruction153, - CryptonightR_instruction154, - CryptonightR_instruction155, - CryptonightR_instruction156, - CryptonightR_instruction157, - CryptonightR_instruction158, - CryptonightR_instruction159, - CryptonightR_instruction160, - CryptonightR_instruction161, - CryptonightR_instruction162, - CryptonightR_instruction163, - CryptonightR_instruction164, - CryptonightR_instruction165, - CryptonightR_instruction166, - CryptonightR_instruction167, - CryptonightR_instruction168, - CryptonightR_instruction169, - CryptonightR_instruction170, - CryptonightR_instruction171, - CryptonightR_instruction172, - CryptonightR_instruction173, - CryptonightR_instruction174, - CryptonightR_instruction175, - CryptonightR_instruction176, - CryptonightR_instruction177, - CryptonightR_instruction178, - CryptonightR_instruction179, - CryptonightR_instruction180, - CryptonightR_instruction181, - CryptonightR_instruction182, - CryptonightR_instruction183, - CryptonightR_instruction184, - CryptonightR_instruction185, - CryptonightR_instruction186, - CryptonightR_instruction187, - CryptonightR_instruction188, - CryptonightR_instruction189, - CryptonightR_instruction190, - CryptonightR_instruction191, - CryptonightR_instruction192, - CryptonightR_instruction193, - CryptonightR_instruction194, - CryptonightR_instruction195, - CryptonightR_instruction196, - CryptonightR_instruction197, - CryptonightR_instruction198, - CryptonightR_instruction199, - CryptonightR_instruction200, - CryptonightR_instruction201, - CryptonightR_instruction202, - CryptonightR_instruction203, - CryptonightR_instruction204, - CryptonightR_instruction205, - CryptonightR_instruction206, - CryptonightR_instruction207, - CryptonightR_instruction208, - CryptonightR_instruction209, - CryptonightR_instruction210, - CryptonightR_instruction211, - CryptonightR_instruction212, - CryptonightR_instruction213, - CryptonightR_instruction214, - CryptonightR_instruction215, - CryptonightR_instruction216, - CryptonightR_instruction217, - CryptonightR_instruction218, - CryptonightR_instruction219, - CryptonightR_instruction220, - CryptonightR_instruction221, - CryptonightR_instruction222, - CryptonightR_instruction223, - CryptonightR_instruction224, - CryptonightR_instruction225, - CryptonightR_instruction226, - CryptonightR_instruction227, - CryptonightR_instruction228, - CryptonightR_instruction229, - CryptonightR_instruction230, - CryptonightR_instruction231, - CryptonightR_instruction232, - CryptonightR_instruction233, - CryptonightR_instruction234, - CryptonightR_instruction235, - CryptonightR_instruction236, - CryptonightR_instruction237, - CryptonightR_instruction238, - CryptonightR_instruction239, - CryptonightR_instruction240, - CryptonightR_instruction241, - CryptonightR_instruction242, - CryptonightR_instruction243, - CryptonightR_instruction244, - CryptonightR_instruction245, - CryptonightR_instruction246, - CryptonightR_instruction247, - CryptonightR_instruction248, - CryptonightR_instruction249, - CryptonightR_instruction250, - CryptonightR_instruction251, - CryptonightR_instruction252, - CryptonightR_instruction253, - CryptonightR_instruction254, - CryptonightR_instruction255, - CryptonightR_instruction256, -}; - -const void* instructions_mov[257] = { - CryptonightR_instruction_mov0, - CryptonightR_instruction_mov1, - CryptonightR_instruction_mov2, - CryptonightR_instruction_mov3, - CryptonightR_instruction_mov4, - CryptonightR_instruction_mov5, - CryptonightR_instruction_mov6, - CryptonightR_instruction_mov7, - CryptonightR_instruction_mov8, - CryptonightR_instruction_mov9, - CryptonightR_instruction_mov10, - CryptonightR_instruction_mov11, - CryptonightR_instruction_mov12, - CryptonightR_instruction_mov13, - CryptonightR_instruction_mov14, - CryptonightR_instruction_mov15, - CryptonightR_instruction_mov16, - CryptonightR_instruction_mov17, - CryptonightR_instruction_mov18, - CryptonightR_instruction_mov19, - CryptonightR_instruction_mov20, - CryptonightR_instruction_mov21, - CryptonightR_instruction_mov22, - CryptonightR_instruction_mov23, - CryptonightR_instruction_mov24, - CryptonightR_instruction_mov25, - CryptonightR_instruction_mov26, - CryptonightR_instruction_mov27, - CryptonightR_instruction_mov28, - CryptonightR_instruction_mov29, - CryptonightR_instruction_mov30, - CryptonightR_instruction_mov31, - CryptonightR_instruction_mov32, - CryptonightR_instruction_mov33, - CryptonightR_instruction_mov34, - CryptonightR_instruction_mov35, - CryptonightR_instruction_mov36, - CryptonightR_instruction_mov37, - CryptonightR_instruction_mov38, - CryptonightR_instruction_mov39, - CryptonightR_instruction_mov40, - CryptonightR_instruction_mov41, - CryptonightR_instruction_mov42, - CryptonightR_instruction_mov43, - CryptonightR_instruction_mov44, - CryptonightR_instruction_mov45, - CryptonightR_instruction_mov46, - CryptonightR_instruction_mov47, - CryptonightR_instruction_mov48, - CryptonightR_instruction_mov49, - CryptonightR_instruction_mov50, - CryptonightR_instruction_mov51, - CryptonightR_instruction_mov52, - CryptonightR_instruction_mov53, - CryptonightR_instruction_mov54, - CryptonightR_instruction_mov55, - CryptonightR_instruction_mov56, - CryptonightR_instruction_mov57, - CryptonightR_instruction_mov58, - CryptonightR_instruction_mov59, - CryptonightR_instruction_mov60, - CryptonightR_instruction_mov61, - CryptonightR_instruction_mov62, - CryptonightR_instruction_mov63, - CryptonightR_instruction_mov64, - CryptonightR_instruction_mov65, - CryptonightR_instruction_mov66, - CryptonightR_instruction_mov67, - CryptonightR_instruction_mov68, - CryptonightR_instruction_mov69, - CryptonightR_instruction_mov70, - CryptonightR_instruction_mov71, - CryptonightR_instruction_mov72, - CryptonightR_instruction_mov73, - CryptonightR_instruction_mov74, - CryptonightR_instruction_mov75, - CryptonightR_instruction_mov76, - CryptonightR_instruction_mov77, - CryptonightR_instruction_mov78, - CryptonightR_instruction_mov79, - CryptonightR_instruction_mov80, - CryptonightR_instruction_mov81, - CryptonightR_instruction_mov82, - CryptonightR_instruction_mov83, - CryptonightR_instruction_mov84, - CryptonightR_instruction_mov85, - CryptonightR_instruction_mov86, - CryptonightR_instruction_mov87, - CryptonightR_instruction_mov88, - CryptonightR_instruction_mov89, - CryptonightR_instruction_mov90, - CryptonightR_instruction_mov91, - CryptonightR_instruction_mov92, - CryptonightR_instruction_mov93, - CryptonightR_instruction_mov94, - CryptonightR_instruction_mov95, - CryptonightR_instruction_mov96, - CryptonightR_instruction_mov97, - CryptonightR_instruction_mov98, - CryptonightR_instruction_mov99, - CryptonightR_instruction_mov100, - CryptonightR_instruction_mov101, - CryptonightR_instruction_mov102, - CryptonightR_instruction_mov103, - CryptonightR_instruction_mov104, - CryptonightR_instruction_mov105, - CryptonightR_instruction_mov106, - CryptonightR_instruction_mov107, - CryptonightR_instruction_mov108, - CryptonightR_instruction_mov109, - CryptonightR_instruction_mov110, - CryptonightR_instruction_mov111, - CryptonightR_instruction_mov112, - CryptonightR_instruction_mov113, - CryptonightR_instruction_mov114, - CryptonightR_instruction_mov115, - CryptonightR_instruction_mov116, - CryptonightR_instruction_mov117, - CryptonightR_instruction_mov118, - CryptonightR_instruction_mov119, - CryptonightR_instruction_mov120, - CryptonightR_instruction_mov121, - CryptonightR_instruction_mov122, - CryptonightR_instruction_mov123, - CryptonightR_instruction_mov124, - CryptonightR_instruction_mov125, - CryptonightR_instruction_mov126, - CryptonightR_instruction_mov127, - CryptonightR_instruction_mov128, - CryptonightR_instruction_mov129, - CryptonightR_instruction_mov130, - CryptonightR_instruction_mov131, - CryptonightR_instruction_mov132, - CryptonightR_instruction_mov133, - CryptonightR_instruction_mov134, - CryptonightR_instruction_mov135, - CryptonightR_instruction_mov136, - CryptonightR_instruction_mov137, - CryptonightR_instruction_mov138, - CryptonightR_instruction_mov139, - CryptonightR_instruction_mov140, - CryptonightR_instruction_mov141, - CryptonightR_instruction_mov142, - CryptonightR_instruction_mov143, - CryptonightR_instruction_mov144, - CryptonightR_instruction_mov145, - CryptonightR_instruction_mov146, - CryptonightR_instruction_mov147, - CryptonightR_instruction_mov148, - CryptonightR_instruction_mov149, - CryptonightR_instruction_mov150, - CryptonightR_instruction_mov151, - CryptonightR_instruction_mov152, - CryptonightR_instruction_mov153, - CryptonightR_instruction_mov154, - CryptonightR_instruction_mov155, - CryptonightR_instruction_mov156, - CryptonightR_instruction_mov157, - CryptonightR_instruction_mov158, - CryptonightR_instruction_mov159, - CryptonightR_instruction_mov160, - CryptonightR_instruction_mov161, - CryptonightR_instruction_mov162, - CryptonightR_instruction_mov163, - CryptonightR_instruction_mov164, - CryptonightR_instruction_mov165, - CryptonightR_instruction_mov166, - CryptonightR_instruction_mov167, - CryptonightR_instruction_mov168, - CryptonightR_instruction_mov169, - CryptonightR_instruction_mov170, - CryptonightR_instruction_mov171, - CryptonightR_instruction_mov172, - CryptonightR_instruction_mov173, - CryptonightR_instruction_mov174, - CryptonightR_instruction_mov175, - CryptonightR_instruction_mov176, - CryptonightR_instruction_mov177, - CryptonightR_instruction_mov178, - CryptonightR_instruction_mov179, - CryptonightR_instruction_mov180, - CryptonightR_instruction_mov181, - CryptonightR_instruction_mov182, - CryptonightR_instruction_mov183, - CryptonightR_instruction_mov184, - CryptonightR_instruction_mov185, - CryptonightR_instruction_mov186, - CryptonightR_instruction_mov187, - CryptonightR_instruction_mov188, - CryptonightR_instruction_mov189, - CryptonightR_instruction_mov190, - CryptonightR_instruction_mov191, - CryptonightR_instruction_mov192, - CryptonightR_instruction_mov193, - CryptonightR_instruction_mov194, - CryptonightR_instruction_mov195, - CryptonightR_instruction_mov196, - CryptonightR_instruction_mov197, - CryptonightR_instruction_mov198, - CryptonightR_instruction_mov199, - CryptonightR_instruction_mov200, - CryptonightR_instruction_mov201, - CryptonightR_instruction_mov202, - CryptonightR_instruction_mov203, - CryptonightR_instruction_mov204, - CryptonightR_instruction_mov205, - CryptonightR_instruction_mov206, - CryptonightR_instruction_mov207, - CryptonightR_instruction_mov208, - CryptonightR_instruction_mov209, - CryptonightR_instruction_mov210, - CryptonightR_instruction_mov211, - CryptonightR_instruction_mov212, - CryptonightR_instruction_mov213, - CryptonightR_instruction_mov214, - CryptonightR_instruction_mov215, - CryptonightR_instruction_mov216, - CryptonightR_instruction_mov217, - CryptonightR_instruction_mov218, - CryptonightR_instruction_mov219, - CryptonightR_instruction_mov220, - CryptonightR_instruction_mov221, - CryptonightR_instruction_mov222, - CryptonightR_instruction_mov223, - CryptonightR_instruction_mov224, - CryptonightR_instruction_mov225, - CryptonightR_instruction_mov226, - CryptonightR_instruction_mov227, - CryptonightR_instruction_mov228, - CryptonightR_instruction_mov229, - CryptonightR_instruction_mov230, - CryptonightR_instruction_mov231, - CryptonightR_instruction_mov232, - CryptonightR_instruction_mov233, - CryptonightR_instruction_mov234, - CryptonightR_instruction_mov235, - CryptonightR_instruction_mov236, - CryptonightR_instruction_mov237, - CryptonightR_instruction_mov238, - CryptonightR_instruction_mov239, - CryptonightR_instruction_mov240, - CryptonightR_instruction_mov241, - CryptonightR_instruction_mov242, - CryptonightR_instruction_mov243, - CryptonightR_instruction_mov244, - CryptonightR_instruction_mov245, - CryptonightR_instruction_mov246, - CryptonightR_instruction_mov247, - CryptonightR_instruction_mov248, - CryptonightR_instruction_mov249, - CryptonightR_instruction_mov250, - CryptonightR_instruction_mov251, - CryptonightR_instruction_mov252, - CryptonightR_instruction_mov253, - CryptonightR_instruction_mov254, - CryptonightR_instruction_mov255, - CryptonightR_instruction_mov256, -}; - -#endif - -#endif // CRYPTONIGHTR_TEMPLATE_H diff --git a/cryptonight/c/aesb.c b/cryptonight/c/aesb.c deleted file mode 100644 index 6d4905a..0000000 --- a/cryptonight/c/aesb.c +++ /dev/null @@ -1,186 +0,0 @@ -/* ---------------------------------------------------------------------------- -Copyright (c) 1998-2013, Brian Gladman, Worcester, UK. All rights reserved. - -The redistribution and use of this software (with or without changes) -is allowed without the payment of fees or royalties provided that: - - source code distributions include the above copyright notice, this - list of conditions and the following disclaimer; - - binary distributions include the above copyright notice, this list - of conditions and the following disclaimer in their documentation. - -This software is provided 'as is' with no explicit or implied warranties -in respect of its operation, including, but not limited to, correctness -and fitness for purpose. ---------------------------------------------------------------------------- -Issue Date: 20/12/2007 -*/ - -#include -#include "int-util.h" - -#if defined(__cplusplus) -extern "C" -{ -#endif - -#define TABLE_ALIGN 32 -#define WPOLY 0x011b -#define N_COLS 4 -#define AES_BLOCK_SIZE 16 -#define RC_LENGTH (5 * (AES_BLOCK_SIZE / 4 - 2)) - -#if defined(_MSC_VER) -#define LOCAL_ALIGN __declspec(align(TABLE_ALIGN)) -#elif defined(__GNUC__) -#define LOCAL_ALIGN __attribute__ ((aligned(16))) -#else -#define LOCAL_ALIGN -#endif - -#define rf1(r,c) (r) -#define word_in(x,c) (*((uint32_t*)(x)+(c))) -#define word_out(x,c,v) (*((uint32_t*)(x)+(c)) = (v)) - -#define s(x,c) x[c] -#define si(y,x,c) (s(y,c) = word_in(x, c)) -#define so(y,x,c) word_out(y, c, s(x,c)) -#define state_in(y,x) si(y,x,0); si(y,x,1); si(y,x,2); si(y,x,3) -#define state_out(y,x) so(y,x,0); so(y,x,1); so(y,x,2); so(y,x,3) -#define round(rm,y,x,k) rm(y,x,k,0); rm(y,x,k,1); rm(y,x,k,2); rm(y,x,k,3) -#define to_byte(x) ((x) & 0xff) -#define bval(x,n) to_byte(SWAP32LE(x) >> (8 * (n))) - -#define fwd_var(x,r,c)\ - ( r == 0 ? ( c == 0 ? s(x,0) : c == 1 ? s(x,1) : c == 2 ? s(x,2) : s(x,3))\ - : r == 1 ? ( c == 0 ? s(x,1) : c == 1 ? s(x,2) : c == 2 ? s(x,3) : s(x,0))\ - : r == 2 ? ( c == 0 ? s(x,2) : c == 1 ? s(x,3) : c == 2 ? s(x,0) : s(x,1))\ - : ( c == 0 ? s(x,3) : c == 1 ? s(x,0) : c == 2 ? s(x,1) : s(x,2))) - -#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ SWAP32LE(four_tables(x,t_use(f,n),fwd_var,rf1,c))) - -#define sb_data(w) {\ - w(0x63), w(0x7c), w(0x77), w(0x7b), w(0xf2), w(0x6b), w(0x6f), w(0xc5),\ - w(0x30), w(0x01), w(0x67), w(0x2b), w(0xfe), w(0xd7), w(0xab), w(0x76),\ - w(0xca), w(0x82), w(0xc9), w(0x7d), w(0xfa), w(0x59), w(0x47), w(0xf0),\ - w(0xad), w(0xd4), w(0xa2), w(0xaf), w(0x9c), w(0xa4), w(0x72), w(0xc0),\ - w(0xb7), w(0xfd), w(0x93), w(0x26), w(0x36), w(0x3f), w(0xf7), w(0xcc),\ - w(0x34), w(0xa5), w(0xe5), w(0xf1), w(0x71), w(0xd8), w(0x31), w(0x15),\ - w(0x04), w(0xc7), w(0x23), w(0xc3), w(0x18), w(0x96), w(0x05), w(0x9a),\ - w(0x07), w(0x12), w(0x80), w(0xe2), w(0xeb), w(0x27), w(0xb2), w(0x75),\ - w(0x09), w(0x83), w(0x2c), w(0x1a), w(0x1b), w(0x6e), w(0x5a), w(0xa0),\ - w(0x52), w(0x3b), w(0xd6), w(0xb3), w(0x29), w(0xe3), w(0x2f), w(0x84),\ - w(0x53), w(0xd1), w(0x00), w(0xed), w(0x20), w(0xfc), w(0xb1), w(0x5b),\ - w(0x6a), w(0xcb), w(0xbe), w(0x39), w(0x4a), w(0x4c), w(0x58), w(0xcf),\ - w(0xd0), w(0xef), w(0xaa), w(0xfb), w(0x43), w(0x4d), w(0x33), w(0x85),\ - w(0x45), w(0xf9), w(0x02), w(0x7f), w(0x50), w(0x3c), w(0x9f), w(0xa8),\ - w(0x51), w(0xa3), w(0x40), w(0x8f), w(0x92), w(0x9d), w(0x38), w(0xf5),\ - w(0xbc), w(0xb6), w(0xda), w(0x21), w(0x10), w(0xff), w(0xf3), w(0xd2),\ - w(0xcd), w(0x0c), w(0x13), w(0xec), w(0x5f), w(0x97), w(0x44), w(0x17),\ - w(0xc4), w(0xa7), w(0x7e), w(0x3d), w(0x64), w(0x5d), w(0x19), w(0x73),\ - w(0x60), w(0x81), w(0x4f), w(0xdc), w(0x22), w(0x2a), w(0x90), w(0x88),\ - w(0x46), w(0xee), w(0xb8), w(0x14), w(0xde), w(0x5e), w(0x0b), w(0xdb),\ - w(0xe0), w(0x32), w(0x3a), w(0x0a), w(0x49), w(0x06), w(0x24), w(0x5c),\ - w(0xc2), w(0xd3), w(0xac), w(0x62), w(0x91), w(0x95), w(0xe4), w(0x79),\ - w(0xe7), w(0xc8), w(0x37), w(0x6d), w(0x8d), w(0xd5), w(0x4e), w(0xa9),\ - w(0x6c), w(0x56), w(0xf4), w(0xea), w(0x65), w(0x7a), w(0xae), w(0x08),\ - w(0xba), w(0x78), w(0x25), w(0x2e), w(0x1c), w(0xa6), w(0xb4), w(0xc6),\ - w(0xe8), w(0xdd), w(0x74), w(0x1f), w(0x4b), w(0xbd), w(0x8b), w(0x8a),\ - w(0x70), w(0x3e), w(0xb5), w(0x66), w(0x48), w(0x03), w(0xf6), w(0x0e),\ - w(0x61), w(0x35), w(0x57), w(0xb9), w(0x86), w(0xc1), w(0x1d), w(0x9e),\ - w(0xe1), w(0xf8), w(0x98), w(0x11), w(0x69), w(0xd9), w(0x8e), w(0x94),\ - w(0x9b), w(0x1e), w(0x87), w(0xe9), w(0xce), w(0x55), w(0x28), w(0xdf),\ - w(0x8c), w(0xa1), w(0x89), w(0x0d), w(0xbf), w(0xe6), w(0x42), w(0x68),\ - w(0x41), w(0x99), w(0x2d), w(0x0f), w(0xb0), w(0x54), w(0xbb), w(0x16) } - -#define rc_data(w) {\ - w(0x01), w(0x02), w(0x04), w(0x08), w(0x10),w(0x20), w(0x40), w(0x80),\ - w(0x1b), w(0x36) } - -#define bytes2word(b0, b1, b2, b3) (((uint32_t)(b3) << 24) | \ - ((uint32_t)(b2) << 16) | ((uint32_t)(b1) << 8) | (b0)) - -#define h0(x) (x) -#define w0(p) bytes2word(p, 0, 0, 0) -#define w1(p) bytes2word(0, p, 0, 0) -#define w2(p) bytes2word(0, 0, p, 0) -#define w3(p) bytes2word(0, 0, 0, p) - -#define u0(p) bytes2word(f2(p), p, p, f3(p)) -#define u1(p) bytes2word(f3(p), f2(p), p, p) -#define u2(p) bytes2word(p, f3(p), f2(p), p) -#define u3(p) bytes2word(p, p, f3(p), f2(p)) - -#define v0(p) bytes2word(fe(p), f9(p), fd(p), fb(p)) -#define v1(p) bytes2word(fb(p), fe(p), f9(p), fd(p)) -#define v2(p) bytes2word(fd(p), fb(p), fe(p), f9(p)) -#define v3(p) bytes2word(f9(p), fd(p), fb(p), fe(p)) - -#define f2(x) ((x<<1) ^ (((x>>7) & 1) * WPOLY)) -#define f4(x) ((x<<2) ^ (((x>>6) & 1) * WPOLY) ^ (((x>>6) & 2) * WPOLY)) -#define f8(x) ((x<<3) ^ (((x>>5) & 1) * WPOLY) ^ (((x>>5) & 2) * WPOLY) ^ (((x>>5) & 4) * WPOLY)) -#define f3(x) (f2(x) ^ x) -#define f9(x) (f8(x) ^ x) -#define fb(x) (f8(x) ^ f2(x) ^ x) -#define fd(x) (f8(x) ^ f4(x) ^ x) -#define fe(x) (f8(x) ^ f4(x) ^ f2(x)) - -#define t_dec(m,n) t_##m##n -#define t_set(m,n) t_##m##n -#define t_use(m,n) t_##m##n - -#define d_4(t,n,b,e,f,g,h) LOCAL_ALIGN const t n[4][256] = { b(e), b(f), b(g), b(h) } - -#define four_tables(x,tab,vf,rf,c) \ - (tab[0][bval(vf(x,0,c),rf(0,c))] \ - ^ tab[1][bval(vf(x,1,c),rf(1,c))] \ - ^ tab[2][bval(vf(x,2,c),rf(2,c))] \ - ^ tab[3][bval(vf(x,3,c),rf(3,c))]) - -d_4(uint32_t, t_dec(f,n), sb_data, u0, u1, u2, u3); - -#if !defined(STATIC) -#define STATIC -#endif - -#if !defined(INLINE) -#define INLINE -#endif - -STATIC INLINE void aesb_single_round(const uint8_t *in, uint8_t *out, uint8_t *expandedKey) -{ - uint32_t b0[4], b1[4]; - const uint32_t *kp = (uint32_t *) expandedKey; - state_in(b0, in); - - round(fwd_rnd, b1, b0, kp); - - state_out(out, b1); -} - -STATIC INLINE void aesb_pseudo_round(const uint8_t *in, uint8_t *out, uint8_t *expandedKey) -{ - uint32_t b0[4], b1[4]; - const uint32_t *kp = (uint32_t *) expandedKey; - state_in(b0, in); - - round(fwd_rnd, b1, b0, kp); - round(fwd_rnd, b0, b1, kp + 1 * N_COLS); - round(fwd_rnd, b1, b0, kp + 2 * N_COLS); - round(fwd_rnd, b0, b1, kp + 3 * N_COLS); - round(fwd_rnd, b1, b0, kp + 4 * N_COLS); - round(fwd_rnd, b0, b1, kp + 5 * N_COLS); - round(fwd_rnd, b1, b0, kp + 6 * N_COLS); - round(fwd_rnd, b0, b1, kp + 7 * N_COLS); - round(fwd_rnd, b1, b0, kp + 8 * N_COLS); - round(fwd_rnd, b0, b1, kp + 9 * N_COLS); - - state_out(out, b0); -} - - -#if defined(__cplusplus) -} -#endif diff --git a/cryptonight/c/blake256.c b/cryptonight/c/blake256.c deleted file mode 100644 index e39c348..0000000 --- a/cryptonight/c/blake256.c +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -/* - * The blake256_* and blake224_* functions are largely copied from - * blake256_light.c and blake224_light.c from the BLAKE website: - * - * https://131002.net/blake/ - * - * The hmac_* functions implement HMAC-BLAKE-256 and HMAC-BLAKE-224. - * HMAC is specified by RFC 2104. - */ - -#include -#include -#include -#include "memwipe.h" -#include "blake256.h" - -#define U8TO32(p) \ - (((uint32_t)((p)[0]) << 24) | ((uint32_t)((p)[1]) << 16) | \ - ((uint32_t)((p)[2]) << 8) | ((uint32_t)((p)[3]) )) -#define U32TO8(p, v) \ - (p)[0] = (uint8_t)((v) >> 24); (p)[1] = (uint8_t)((v) >> 16); \ - (p)[2] = (uint8_t)((v) >> 8); (p)[3] = (uint8_t)((v) ); - -const uint8_t sigma[][16] = { - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15}, - {14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3}, - {11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4}, - { 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8}, - { 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13}, - { 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9}, - {12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11}, - {13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10}, - { 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5}, - {10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0}, - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15}, - {14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3}, - {11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4}, - { 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8} -}; - -const uint32_t cst[16] = { - 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, - 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, - 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, - 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917 -}; - -static const uint8_t padding[] = { - 0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -}; - - -void blake256_compress(state *S, const uint8_t *block) { - uint32_t v[16], m[16], i; - -#define ROT(x,n) (((x)<<(32-n))|((x)>>(n))) -#define G(a,b,c,d,e) \ - v[a] += (m[sigma[i][e]] ^ cst[sigma[i][e+1]]) + v[b]; \ - v[d] = ROT(v[d] ^ v[a],16); \ - v[c] += v[d]; \ - v[b] = ROT(v[b] ^ v[c],12); \ - v[a] += (m[sigma[i][e+1]] ^ cst[sigma[i][e]])+v[b]; \ - v[d] = ROT(v[d] ^ v[a], 8); \ - v[c] += v[d]; \ - v[b] = ROT(v[b] ^ v[c], 7); - - for (i = 0; i < 16; ++i) m[i] = U8TO32(block + i * 4); - for (i = 0; i < 8; ++i) v[i] = S->h[i]; - v[ 8] = S->s[0] ^ 0x243F6A88; - v[ 9] = S->s[1] ^ 0x85A308D3; - v[10] = S->s[2] ^ 0x13198A2E; - v[11] = S->s[3] ^ 0x03707344; - v[12] = 0xA4093822; - v[13] = 0x299F31D0; - v[14] = 0x082EFA98; - v[15] = 0xEC4E6C89; - - if (S->nullt == 0) { - v[12] ^= S->t[0]; - v[13] ^= S->t[0]; - v[14] ^= S->t[1]; - v[15] ^= S->t[1]; - } - - for (i = 0; i < 14; ++i) { - G(0, 4, 8, 12, 0); - G(1, 5, 9, 13, 2); - G(2, 6, 10, 14, 4); - G(3, 7, 11, 15, 6); - G(3, 4, 9, 14, 14); - G(2, 7, 8, 13, 12); - G(0, 5, 10, 15, 8); - G(1, 6, 11, 12, 10); - } - - for (i = 0; i < 16; ++i) S->h[i % 8] ^= v[i]; - for (i = 0; i < 8; ++i) S->h[i] ^= S->s[i % 4]; -} - -void blake256_init(state *S) { - S->h[0] = 0x6A09E667; - S->h[1] = 0xBB67AE85; - S->h[2] = 0x3C6EF372; - S->h[3] = 0xA54FF53A; - S->h[4] = 0x510E527F; - S->h[5] = 0x9B05688C; - S->h[6] = 0x1F83D9AB; - S->h[7] = 0x5BE0CD19; - S->t[0] = S->t[1] = S->buflen = S->nullt = 0; - S->s[0] = S->s[1] = S->s[2] = S->s[3] = 0; -} - -void blake224_init(state *S) { - S->h[0] = 0xC1059ED8; - S->h[1] = 0x367CD507; - S->h[2] = 0x3070DD17; - S->h[3] = 0xF70E5939; - S->h[4] = 0xFFC00B31; - S->h[5] = 0x68581511; - S->h[6] = 0x64F98FA7; - S->h[7] = 0xBEFA4FA4; - S->t[0] = S->t[1] = S->buflen = S->nullt = 0; - S->s[0] = S->s[1] = S->s[2] = S->s[3] = 0; -} - -// datalen = number of bits -void blake256_update(state *S, const uint8_t *data, uint64_t datalen) { - int left = S->buflen >> 3; - int fill = 64 - left; - - if (left && (((datalen >> 3)) >= (unsigned) fill)) { - memcpy((void *) (S->buf + left), (void *) data, fill); - S->t[0] += 512; - if (S->t[0] == 0) S->t[1]++; - blake256_compress(S, S->buf); - data += fill; - datalen -= (fill << 3); - left = 0; - } - - while (datalen >= 512) { - S->t[0] += 512; - if (S->t[0] == 0) S->t[1]++; - blake256_compress(S, data); - data += 64; - datalen -= 512; - } - - if (datalen > 0) { - memcpy((void *) (S->buf + left), (void *) data, datalen >> 3); - S->buflen = (left << 3) + datalen; - } else { - S->buflen = 0; - } -} - -// datalen = number of bits -void blake224_update(state *S, const uint8_t *data, uint64_t datalen) { - blake256_update(S, data, datalen); -} - -void blake256_final_h(state *S, uint8_t *digest, uint8_t pa, uint8_t pb) { - uint8_t msglen[8]; - uint32_t lo = S->t[0] + S->buflen, hi = S->t[1]; - if (lo < (unsigned) S->buflen) hi++; - U32TO8(msglen + 0, hi); - U32TO8(msglen + 4, lo); - - if (S->buflen == 440) { /* one padding byte */ - S->t[0] -= 8; - blake256_update(S, &pa, 8); - } else { - if (S->buflen < 440) { /* enough space to fill the block */ - if (S->buflen == 0) S->nullt = 1; - S->t[0] -= 440 - S->buflen; - blake256_update(S, padding, 440 - S->buflen); - } else { /* need 2 compressions */ - S->t[0] -= 512 - S->buflen; - blake256_update(S, padding, 512 - S->buflen); - S->t[0] -= 440; - blake256_update(S, padding + 1, 440); - S->nullt = 1; - } - blake256_update(S, &pb, 8); - S->t[0] -= 8; - } - S->t[0] -= 64; - blake256_update(S, msglen, 64); - - U32TO8(digest + 0, S->h[0]); - U32TO8(digest + 4, S->h[1]); - U32TO8(digest + 8, S->h[2]); - U32TO8(digest + 12, S->h[3]); - U32TO8(digest + 16, S->h[4]); - U32TO8(digest + 20, S->h[5]); - U32TO8(digest + 24, S->h[6]); - U32TO8(digest + 28, S->h[7]); -} - -void blake256_final(state *S, uint8_t *digest) { - blake256_final_h(S, digest, 0x81, 0x01); -} - -void blake224_final(state *S, uint8_t *digest) { - blake256_final_h(S, digest, 0x80, 0x00); -} - -// inlen = number of bytes -void blake256_hash(uint8_t *out, const uint8_t *in, uint64_t inlen) { - state S; - blake256_init(&S); - blake256_update(&S, in, inlen * 8); - blake256_final(&S, out); -} - -// inlen = number of bytes -void blake224_hash(uint8_t *out, const uint8_t *in, uint64_t inlen) { - state S; - blake224_init(&S); - blake224_update(&S, in, inlen * 8); - blake224_final(&S, out); -} - -// keylen = number of bytes -void hmac_blake256_init(hmac_state *S, const uint8_t *_key, uint64_t keylen) { - const uint8_t *key = _key; - uint8_t keyhash[32]; - uint8_t pad[64]; - uint64_t i; - - if (keylen > 64) { - blake256_hash(keyhash, key, keylen); - key = keyhash; - keylen = 32; - } - - blake256_init(&S->inner); - memset(pad, 0x36, 64); - for (i = 0; i < keylen; ++i) { - pad[i] ^= key[i]; - } - blake256_update(&S->inner, pad, 512); - - blake256_init(&S->outer); - memset(pad, 0x5c, 64); - for (i = 0; i < keylen; ++i) { - pad[i] ^= key[i]; - } - blake256_update(&S->outer, pad, 512); - - memwipe(keyhash, sizeof(keyhash)); -} - -// keylen = number of bytes -void hmac_blake224_init(hmac_state *S, const uint8_t *_key, uint64_t keylen) { - const uint8_t *key = _key; - uint8_t keyhash[32]; - uint8_t pad[64]; - uint64_t i; - - if (keylen > 64) { - blake256_hash(keyhash, key, keylen); - key = keyhash; - keylen = 28; - } - - blake224_init(&S->inner); - memset(pad, 0x36, 64); - for (i = 0; i < keylen; ++i) { - pad[i] ^= key[i]; - } - blake224_update(&S->inner, pad, 512); - - blake224_init(&S->outer); - memset(pad, 0x5c, 64); - for (i = 0; i < keylen; ++i) { - pad[i] ^= key[i]; - } - blake224_update(&S->outer, pad, 512); - - memwipe(keyhash, sizeof(keyhash)); -} - -// datalen = number of bits -void hmac_blake256_update(hmac_state *S, const uint8_t *data, uint64_t datalen) { - // update the inner state - blake256_update(&S->inner, data, datalen); -} - -// datalen = number of bits -void hmac_blake224_update(hmac_state *S, const uint8_t *data, uint64_t datalen) { - // update the inner state - blake224_update(&S->inner, data, datalen); -} - -void hmac_blake256_final(hmac_state *S, uint8_t *digest) { - uint8_t ihash[32]; - blake256_final(&S->inner, ihash); - blake256_update(&S->outer, ihash, 256); - blake256_final(&S->outer, digest); - memwipe(ihash, sizeof(ihash)); -} - -void hmac_blake224_final(hmac_state *S, uint8_t *digest) { - uint8_t ihash[32]; - blake224_final(&S->inner, ihash); - blake224_update(&S->outer, ihash, 224); - blake224_final(&S->outer, digest); - memwipe(ihash, sizeof(ihash)); -} - -// keylen = number of bytes; inlen = number of bytes -void hmac_blake256_hash(uint8_t *out, const uint8_t *key, uint64_t keylen, const uint8_t *in, uint64_t inlen) { - hmac_state S; - hmac_blake256_init(&S, key, keylen); - hmac_blake256_update(&S, in, inlen * 8); - hmac_blake256_final(&S, out); -} - -// keylen = number of bytes; inlen = number of bytes -void hmac_blake224_hash(uint8_t *out, const uint8_t *key, uint64_t keylen, const uint8_t *in, uint64_t inlen) { - hmac_state S; - hmac_blake224_init(&S, key, keylen); - hmac_blake224_update(&S, in, inlen * 8); - hmac_blake224_final(&S, out); -} diff --git a/cryptonight/c/blake256.h b/cryptonight/c/blake256.h deleted file mode 100644 index 5bde2cb..0000000 --- a/cryptonight/c/blake256.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#ifndef _BLAKE256_H_ -#define _BLAKE256_H_ - -#include - -typedef struct { - uint32_t h[8], s[4], t[2]; - int buflen, nullt; - uint8_t buf[64]; -} state; - -typedef struct { - state inner; - state outer; -} hmac_state; - -void blake256_init(state *); -void blake224_init(state *); - -void blake256_update(state *, const uint8_t *, uint64_t); -void blake224_update(state *, const uint8_t *, uint64_t); - -void blake256_final(state *, uint8_t *); -void blake224_final(state *, uint8_t *); - -void blake256_hash(uint8_t *, const uint8_t *, uint64_t); -void blake224_hash(uint8_t *, const uint8_t *, uint64_t); - -/* HMAC functions: */ - -void hmac_blake256_init(hmac_state *, const uint8_t *, uint64_t); -void hmac_blake224_init(hmac_state *, const uint8_t *, uint64_t); - -void hmac_blake256_update(hmac_state *, const uint8_t *, uint64_t); -void hmac_blake224_update(hmac_state *, const uint8_t *, uint64_t); - -void hmac_blake256_final(hmac_state *, uint8_t *); -void hmac_blake224_final(hmac_state *, uint8_t *); - -void hmac_blake256_hash(uint8_t *, const uint8_t *, uint64_t, const uint8_t *, uint64_t); -void hmac_blake224_hash(uint8_t *, const uint8_t *, uint64_t, const uint8_t *, uint64_t); - -#endif /* _BLAKE256_H_ */ diff --git a/cryptonight/c/crypto-ops-data.c b/cryptonight/c/crypto-ops-data.c deleted file mode 100644 index e31cd6b..0000000 --- a/cryptonight/c/crypto-ops-data.c +++ /dev/null @@ -1,879 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include - -#include "crypto-ops.h" - -/* sqrt(x) is such an integer y that 0 <= y <= p - 1, y % 2 = 0, and y^2 = x (mod p). */ -/* d = -121665 / 121666 */ -const fe fe_d = {-10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116}; /* d */ -const fe fe_sqrtm1 = {-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482}; /* sqrt(-1) */ -const fe fe_d2 = {-21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199}; /* 2 * d */ - -/* base[i][j] = (j+1)*256^i*B */ -const ge_precomp ge_base[32][8] = { - { - {{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, - {-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, - {-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546}}, - {{-12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303}, - {-21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081}, - {26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697}}, - {{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, - {16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, - {30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357}}, - {{-17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540}, - {23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397}, - {7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325}}, - {{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, - {4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, - {19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942}}, - {{-15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777}, - {-8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737}, - {-18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652}}, - {{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, - {-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, - {28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300}}, - {{14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726}, - {-7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955}, - {27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425}} - }, { - {{-13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171}, - {27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510}, - {17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660}}, - {{-10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639}, - {29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963}, - {5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950}}, - {{-27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568}, - {12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335}, - {25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628}}, - {{-26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007}, - {-2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772}, - {-22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653}}, - {{2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567}, - {13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686}, - {21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372}}, - {{-13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887}, - {-23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954}, - {-29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953}}, - {{24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833}, - {-16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532}, - {-22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876}}, - {{2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268}, - {33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214}, - {1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038}} - }, { - {{6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800}, - {4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645}, - {-4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664}}, - {{1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933}, - {-25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182}, - {-17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222}}, - {{-18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991}, - {20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880}, - {9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092}}, - {{-16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295}, - {19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788}, - {8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553}}, - {{-15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026}, - {11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347}, - {-18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033}}, - {{-23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395}, - {-27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278}, - {1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890}}, - {{32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995}, - {-30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596}, - {-11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891}}, - {{31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060}, - {11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608}, - {-20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606}} - }, { - {{7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389}, - {-19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016}, - {-11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341}}, - {{-22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505}, - {14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553}, - {-28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655}}, - {{15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220}, - {12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631}, - {-4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099}}, - {{26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556}, - {14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749}, - {236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930}}, - {{1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391}, - {5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253}, - {20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066}}, - {{24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958}, - {-11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082}, - {-28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383}}, - {{-30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521}, - {-11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807}, - {23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948}}, - {{9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134}, - {-32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455}, - {27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629}} - }, { - {{-8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069}, - {-32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746}, - {24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919}}, - {{11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837}, - {8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906}, - {-28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771}}, - {{-25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817}, - {10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098}, - {10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409}}, - {{-12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504}, - {-26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727}, - {28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420}}, - {{-32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003}, - {-1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605}, - {-30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384}}, - {{-26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701}, - {-23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683}, - {29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708}}, - {{-3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563}, - {-19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260}, - {-5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387}}, - {{-19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672}, - {23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686}, - {-24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665}} - }, { - {{11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182}, - {-31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277}, - {14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628}}, - {{-4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474}, - {-26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539}, - {-25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822}}, - {{-10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970}, - {19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756}, - {-24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508}}, - {{-26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683}, - {-10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655}, - {-20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158}}, - {{-4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125}, - {-15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839}, - {-20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664}}, - {{27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294}, - {-18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899}, - {-11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070}}, - {{3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294}, - {-15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949}, - {-21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083}}, - {{31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420}, - {-5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940}, - {29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396}} - }, { - {{-12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567}, - {20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127}, - {-16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294}}, - {{-12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887}, - {22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964}, - {16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195}}, - {{9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244}, - {24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999}, - {-1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762}}, - {{-18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274}, - {-33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236}, - {-16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605}}, - {{-13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761}, - {-22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884}, - {-6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482}}, - {{-24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638}, - {-11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490}, - {-32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170}}, - {{5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736}, - {10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124}, - {-17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392}}, - {{8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029}, - {6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048}, - {28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958}} - }, { - {{24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593}, - {26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071}, - {-11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692}}, - {{11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687}, - {-160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441}, - {-20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001}}, - {{-938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460}, - {-19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007}, - {-21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762}}, - {{15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005}, - {-9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674}, - {4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035}}, - {{7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590}, - {-2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957}, - {-30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812}}, - {{33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740}, - {-18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122}, - {-27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158}}, - {{8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885}, - {26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140}, - {19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857}}, - {{801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155}, - {19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260}, - {19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483}} - }, { - {{-3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677}, - {32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815}, - {22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751}}, - {{-16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203}, - {-11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208}, - {1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230}}, - {{16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850}, - {-21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389}, - {-9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968}}, - {{-11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689}, - {14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880}, - {5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304}}, - {{30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632}, - {-3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412}, - {20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566}}, - {{-20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038}, - {-26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232}, - {-1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943}}, - {{17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856}, - {23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738}, - {15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971}}, - {{-27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718}, - {-13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697}, - {-11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883}} - }, { - {{5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912}, - {-26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358}, - {3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849}}, - {{29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307}, - {-14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977}, - {-6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335}}, - {{-29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644}, - {-22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616}, - {-27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735}}, - {{-21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099}, - {29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341}, - {-936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336}}, - {{-23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646}, - {31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425}, - {-17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388}}, - {{-31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743}, - {-16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822}, - {-8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462}}, - {{18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985}, - {9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702}, - {-22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797}}, - {{21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293}, - {27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100}, - {19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688}} - }, { - {{12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186}, - {2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610}, - {-2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707}}, - {{7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220}, - {915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025}, - {32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044}}, - {{32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992}, - {-4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027}, - {21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197}}, - {{8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901}, - {31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952}, - {19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878}}, - {{-28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390}, - {32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730}, - {2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730}}, - {{-19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180}, - {-30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272}, - {-15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715}}, - {{-22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970}, - {-31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772}, - {-17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865}}, - {{15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750}, - {20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373}, - {32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348}} - }, { - {{9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144}, - {-22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195}, - {5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086}}, - {{-13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684}, - {-8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518}, - {-2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233}}, - {{-5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793}, - {-2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794}, - {580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435}}, - {{23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921}, - {13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518}, - {2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563}}, - {{14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278}, - {-27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024}, - {4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030}}, - {{10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783}, - {27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717}, - {6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844}}, - {{14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333}, - {16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048}, - {22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760}}, - {{-4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760}, - {-15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757}, - {-2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112}} - }, { - {{-19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468}, - {3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184}, - {10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289}}, - {{15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066}, - {24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882}, - {13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226}}, - {{16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101}, - {29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279}, - {-6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811}}, - {{27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709}, - {20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714}, - {-2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121}}, - {{9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464}, - {12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847}, - {13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400}}, - {{4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414}, - {-15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158}, - {17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045}}, - {{-461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415}, - {-5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459}, - {-31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079}}, - {{21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412}, - {-20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743}, - {-14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836}} - }, { - {{12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022}, - {18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429}, - {-6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065}}, - {{30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861}, - {10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000}, - {-33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101}}, - {{32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815}, - {29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642}, - {10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966}}, - {{25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574}, - {-21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742}, - {-18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689}}, - {{12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020}, - {-10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772}, - {3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982}}, - {{-14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953}, - {-16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218}, - {-17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265}}, - {{29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073}, - {-3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325}, - {-11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798}}, - {{-4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870}, - {-7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863}, - {-13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927}} - }, { - {{-2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267}, - {-9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663}, - {22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862}}, - {{-25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673}, - {15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943}, - {15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020}}, - {{-4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238}, - {11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064}, - {14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795}}, - {{15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052}, - {-10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904}, - {29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531}}, - {{-13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979}, - {-5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841}, - {10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431}}, - {{10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324}, - {-31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940}, - {10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320}}, - {{-15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184}, - {14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114}, - {30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878}}, - {{12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784}, - {-2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091}, - {-16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585}} - }, { - {{-8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208}, - {10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864}, - {17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661}}, - {{7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233}, - {26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212}, - {-12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525}}, - {{-24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068}, - {9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397}, - {-8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988}}, - {{5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889}, - {32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038}, - {14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697}}, - {{20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875}, - {-25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905}, - {-25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656}}, - {{11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818}, - {27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714}, - {10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203}}, - {{20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931}, - {-30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024}, - {-23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084}}, - {{-1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204}, - {20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817}, - {27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667}} - }, { - {{11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504}, - {-12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768}, - {-19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255}}, - {{6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790}, - {1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438}, - {-22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333}}, - {{17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971}, - {31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905}, - {29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409}}, - {{12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409}, - {6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499}, - {-8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363}}, - {{28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664}, - {-11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324}, - {-21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940}}, - {{13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990}, - {-17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914}, - {-25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290}}, - {{24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257}, - {-6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433}, - {-16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236}}, - {{-12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045}, - {11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093}, - {-1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347}} - }, { - {{-28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191}, - {-15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507}, - {-12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906}}, - {{3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018}, - {-16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109}, - {-23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926}}, - {{-24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528}, - {8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625}, - {-32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286}}, - {{2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033}, - {27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866}, - {21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896}}, - {{30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075}, - {26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347}, - {-22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437}}, - {{-5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165}, - {-18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588}, - {-32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193}}, - {{-19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017}, - {-28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883}, - {21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961}}, - {{8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043}, - {29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663}, - {-20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362}} - }, { - {{-33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860}, - {2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466}, - {-24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063}}, - {{-26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997}, - {-1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295}, - {-13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369}}, - {{9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385}, - {18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109}, - {2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906}}, - {{4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424}, - {-19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185}, - {7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962}}, - {{-7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325}, - {10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593}, - {696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404}}, - {{-11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644}, - {17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801}, - {26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804}}, - {{-31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884}, - {-586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577}, - {-9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849}}, - {{32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473}, - {-8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644}, - {-2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319}} - }, { - {{-11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599}, - {-9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768}, - {-27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084}}, - {{-27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328}, - {-15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369}, - {20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920}}, - {{12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815}, - {-32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025}, - {-21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397}}, - {{-20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448}, - {6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981}, - {30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165}}, - {{32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501}, - {17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073}, - {-1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861}}, - {{14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845}, - {-1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211}, - {18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870}}, - {{10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096}, - {33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803}, - {-32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168}}, - {{30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965}, - {-14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505}, - {18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598}} - }, { - {{5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782}, - {5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900}, - {-31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479}}, - {{-12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208}, - {8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232}, - {17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719}}, - {{16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271}, - {-4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326}, - {-8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132}}, - {{14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300}, - {8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570}, - {15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670}}, - {{-2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994}, - {-12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913}, - {31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317}}, - {{-25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730}, - {842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096}, - {-4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078}}, - {{-15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411}, - {-19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905}, - {-9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654}}, - {{-28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870}, - {-23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498}, - {12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579}} - }, { - {{14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677}, - {10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647}, - {-2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743}}, - {{-25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468}, - {21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375}, - {-25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155}}, - {{6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725}, - {-12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612}, - {-10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943}}, - {{-30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944}, - {30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928}, - {9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406}}, - {{22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139}, - {-8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963}, - {-31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693}}, - {{1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734}, - {-448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680}, - {-24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410}}, - {{-9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931}, - {-16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654}, - {22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710}}, - {{29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180}, - {-26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684}, - {-10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895}} - }, { - {{22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501}, - {-11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413}, - {6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880}}, - {{-8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874}, - {22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962}, - {-7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899}}, - {{21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152}, - {9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063}, - {7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080}}, - {{-9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146}, - {-17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183}, - {-19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133}}, - {{-32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421}, - {-3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622}, - {-4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197}}, - {{2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663}, - {31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753}, - {4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755}}, - {{-9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862}, - {-26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118}, - {26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171}}, - {{15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380}, - {16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824}, - {28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270}} - }, { - {{-817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438}, - {-31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584}, - {-594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562}}, - {{30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471}, - {18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610}, - {19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269}}, - {{-30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650}, - {14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369}, - {19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461}}, - {{30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462}, - {-5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793}, - {-2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218}}, - {{-24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226}, - {18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019}, - {-15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037}}, - {{31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171}, - {-17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132}, - {-28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841}}, - {{21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181}, - {-33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210}, - {-1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040}}, - {{3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935}, - {24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105}, - {-28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814}} - }, { - {{793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852}, - {5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581}, - {-4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646}}, - {{10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844}, - {10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025}, - {27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453}}, - {{-23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068}, - {4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192}, - {-17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921}}, - {{-9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259}, - {-12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426}, - {-5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072}}, - {{-17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305}, - {13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832}, - {28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943}}, - {{-16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011}, - {24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447}, - {17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494}}, - {{-28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245}, - {-20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859}, - {28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915}}, - {{16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707}, - {10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848}, - {-11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224}} - }, { - {{-25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391}, - {15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215}, - {-23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101}}, - {{23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713}, - {21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849}, - {-7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930}}, - {{-29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940}, - {-21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031}, - {-17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404}}, - {{-25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243}, - {-23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116}, - {-24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525}}, - {{-23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509}, - {-10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883}, - {15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865}}, - {{-3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660}, - {4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273}, - {-28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138}}, - {{-25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560}, - {-10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135}, - {2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941}}, - {{-4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739}, - {18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756}, - {-30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819}} - }, { - {{-6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347}, - {-27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028}, - {21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075}}, - {{16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799}, - {-2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609}, - {-25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817}}, - {{-23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989}, - {-30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523}, - {4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278}}, - {{31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045}, - {19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377}, - {24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480}}, - {{17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016}, - {510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426}, - {18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525}}, - {{13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396}, - {9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080}, - {12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892}}, - {{15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275}, - {11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074}, - {20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140}}, - {{-16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717}, - {-1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101}, - {24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127}} - }, { - {{-12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632}, - {-26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415}, - {-31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160}}, - {{31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876}, - {22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625}, - {-15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478}}, - {{27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164}, - {26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595}, - {-7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248}}, - {{-16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858}, - {15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193}, - {8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184}}, - {{-18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942}, - {-1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635}, - {21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948}}, - {{11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935}, - {-25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415}, - {-15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416}}, - {{-7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018}, - {4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778}, - {366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659}}, - {{-24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385}, - {18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503}, - {476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329}} - }, { - {{20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056}, - {-13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838}, - {24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948}}, - {{-3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691}, - {-15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118}, - {-23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517}}, - {{-20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269}, - {-6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904}, - {-23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589}}, - {{-28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193}, - {-7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910}, - {-30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930}}, - {{-7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667}, - {25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481}, - {-9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876}}, - {{22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640}, - {-8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278}, - {-21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112}}, - {{26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272}, - {17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012}, - {-10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221}}, - {{30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046}, - {13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345}, - {-19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310}} - }, { - {{19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937}, - {31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636}, - {-9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008}}, - {{-2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429}, - {-15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576}, - {31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066}}, - {{-9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490}, - {-12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104}, - {33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053}}, - {{31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275}, - {-20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511}, - {22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095}}, - {{-28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439}, - {23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939}, - {-23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424}}, - {{2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310}, - {3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608}, - {-32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079}}, - {{-23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101}, - {21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418}, - {18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576}}, - {{30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356}, - {9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996}, - {-26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099}} - }, { - {{-26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728}, - {-13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658}, - {-10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242}}, - {{-21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001}, - {-4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766}, - {18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373}}, - {{26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458}, - {-17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628}, - {-13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657}}, - {{-23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062}, - {25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616}, - {31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014}}, - {{24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383}, - {-25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814}, - {-20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718}}, - {{30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417}, - {2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222}, - {33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444}}, - {{-20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597}, - {23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970}, - {1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799}}, - {{-5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647}, - {13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511}, - {-29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032}} - }, { - {{9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834}, - {-23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461}, - {29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062}}, - {{-25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516}, - {-20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547}, - {-24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240}}, - {{-17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038}, - {-33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741}, - {16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103}}, - {{-19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747}, - {-1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323}, - {31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016}}, - {{-14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373}, - {15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228}, - {-2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141}}, - {{16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399}, - {11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831}, - {-185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376}}, - {{-32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313}, - {-18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958}, - {-6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577}}, - {{-22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743}, - {29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684}, - {-20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476}} - } -}; - -const ge_precomp ge_Bi[8] = { - {{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, - {-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, - {-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546}}, {{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, - {16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, - {30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357}}, {{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, - {4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, - {19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942}}, {{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, - {-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, - {28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300}}, {{-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877}, - {-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951}, - {4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784}}, {{-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436}, - {25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918}, - {23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877}}, {{-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800}, - {-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305}, - {-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300}}, {{-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876}, - {-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619}, - {-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683}} -}; - -/* A = 2 * (1 - d) / (1 + d) = 486662 */ -const fe fe_ma2 = {-12721188, -3529, 0, 0, 0, 0, 0, 0, 0, 0}; /* -A^2 */ -const fe fe_ma = {-486662, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* -A */ -const fe fe_fffb1 = {-31702527, -2466483, -26106795, -12203692, -12169197, -321052, 14850977, -10296299, -16929438, -407568}; /* sqrt(-2 * A * (A + 2)) */ -const fe fe_fffb2 = {8166131, -6741800, -17040804, 3154616, 21461005, 1466302, -30876704, -6368709, 10503587, -13363080}; /* sqrt(2 * A * (A + 2)) */ -const fe fe_fffb3 = {-13620103, 14639558, 4532995, 7679154, 16815101, -15883539, -22863840, -14813421, 13716513, -6477756}; /* sqrt(-sqrt(-1) * A * (A + 2)) */ -const fe fe_fffb4 = {-21786234, -12173074, 21573800, 4524538, -4645904, 16204591, 8012863, -8444712, 3212926, 6885324}; /* sqrt(sqrt(-1) * A * (A + 2)) */ -const ge_p3 ge_p3_identity = { {0}, {1, 0}, {1, 0}, {0} }; -const ge_p3 ge_p3_H = { - {7329926, -15101362, 31411471, 7614783, 27996851, -3197071, -11157635, -6878293, 466949, -7986503}, - {5858699, 5096796, 21321203, -7536921, -5553480, -11439507, -5627669, 15045946, 19977121, 5275251}, - {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {23443568, -5110398, -8776029, -4345135, 6889568, -14710814, 7474843, 3279062, 14550766, -7453428} -}; diff --git a/cryptonight/c/generic-ops.h b/cryptonight/c/generic-ops.h deleted file mode 100644 index da19157..0000000 --- a/cryptonight/c/generic-ops.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include -#include -#include -#include - -#define CRYPTO_MAKE_COMPARABLE(type) \ -namespace crypto { \ - inline bool operator==(const type &_v1, const type &_v2) { \ - return !memcmp(&_v1, &_v2, sizeof(_v1)); \ - } \ - inline bool operator!=(const type &_v1, const type &_v2) { \ - return !operator==(_v1, _v2); \ - } \ -} - -#define CRYPTO_MAKE_COMPARABLE_CONSTANT_TIME(type) \ -namespace crypto { \ - inline bool operator==(const type &_v1, const type &_v2) { \ - static_assert(sizeof(_v1) == 32, "constant time comparison is only implenmted for 32 bytes"); \ - return crypto_verify_32((const unsigned char*)&_v1, (const unsigned char*)&_v2) == 0; \ - } \ - inline bool operator!=(const type &_v1, const type &_v2) { \ - return !operator==(_v1, _v2); \ - } \ -} - -#define CRYPTO_DEFINE_HASH_FUNCTIONS(type) \ -namespace crypto { \ - static_assert(sizeof(std::size_t) <= sizeof(type), "Size of " #type " must be at least that of size_t"); \ - inline std::size_t hash_value(const type &_v) { \ - return reinterpret_cast(_v); \ - } \ -} \ -namespace std { \ - template<> \ - struct hash { \ - std::size_t operator()(const crypto::type &_v) const { \ - return reinterpret_cast(_v); \ - } \ - }; \ -} - -#define CRYPTO_MAKE_HASHABLE(type) \ -CRYPTO_MAKE_COMPARABLE(type) \ -CRYPTO_DEFINE_HASH_FUNCTIONS(type) - -#define CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(type) \ -CRYPTO_MAKE_COMPARABLE_CONSTANT_TIME(type) \ -CRYPTO_DEFINE_HASH_FUNCTIONS(type) - diff --git a/cryptonight/c/groestl.c b/cryptonight/c/groestl.c deleted file mode 100644 index d5e2989..0000000 --- a/cryptonight/c/groestl.c +++ /dev/null @@ -1,367 +0,0 @@ -/* hash.c April 2012 - * Groestl ANSI C code optimised for 32-bit machines - * Author: Thomas Krinninger - * - * This work is based on the implementation of - * Soeren S. Thomsen and Krystian Matusiewicz - * - * - */ - -#include -#include "groestl.h" -#include "groestl_tables.h" - -#define P_TYPE 0 -#define Q_TYPE 1 - -const uint8_t shift_Values[2][8] = {{0,1,2,3,4,5,6,7},{1,3,5,7,0,2,4,6}}; - -const uint8_t indices_cyclic[15] = {0,1,2,3,4,5,6,7,0,1,2,3,4,5,6}; - - -#if BYTE_ORDER == LITTLE_ENDIAN -#define ROTATE_COLUMN_DOWN(v1, v2, amount_bytes, temp_var) {temp_var = (v1<<(8*amount_bytes))|(v2>>(8*(4-amount_bytes))); \ - v2 = (v2<<(8*amount_bytes))|(v1>>(8*(4-amount_bytes))); \ - v1 = temp_var;} -#else -#define ROTATE_COLUMN_DOWN(v1, v2, amount_bytes, temp_var) {temp_var = (v1>>(8*amount_bytes))|(v2<<(8*(4-amount_bytes))); \ - v2 = (v2>>(8*amount_bytes))|(v1<<(8*(4-amount_bytes))); \ - v1 = temp_var;} -#endif - - -#define COLUMN(x,y,i,c0,c1,c2,c3,c4,c5,c6,c7,tv1,tv2,tu,tl,t) \ - tu = T[2*(uint32_t)x[4*c0+0]]; \ - tl = T[2*(uint32_t)x[4*c0+0]+1]; \ - tv1 = T[2*(uint32_t)x[4*c1+1]]; \ - tv2 = T[2*(uint32_t)x[4*c1+1]+1]; \ - ROTATE_COLUMN_DOWN(tv1,tv2,1,t) \ - tu ^= tv1; \ - tl ^= tv2; \ - tv1 = T[2*(uint32_t)x[4*c2+2]]; \ - tv2 = T[2*(uint32_t)x[4*c2+2]+1]; \ - ROTATE_COLUMN_DOWN(tv1,tv2,2,t) \ - tu ^= tv1; \ - tl ^= tv2; \ - tv1 = T[2*(uint32_t)x[4*c3+3]]; \ - tv2 = T[2*(uint32_t)x[4*c3+3]+1]; \ - ROTATE_COLUMN_DOWN(tv1,tv2,3,t) \ - tu ^= tv1; \ - tl ^= tv2; \ - tl ^= T[2*(uint32_t)x[4*c4+0]]; \ - tu ^= T[2*(uint32_t)x[4*c4+0]+1]; \ - tv1 = T[2*(uint32_t)x[4*c5+1]]; \ - tv2 = T[2*(uint32_t)x[4*c5+1]+1]; \ - ROTATE_COLUMN_DOWN(tv1,tv2,1,t) \ - tl ^= tv1; \ - tu ^= tv2; \ - tv1 = T[2*(uint32_t)x[4*c6+2]]; \ - tv2 = T[2*(uint32_t)x[4*c6+2]+1]; \ - ROTATE_COLUMN_DOWN(tv1,tv2,2,t) \ - tl ^= tv1; \ - tu ^= tv2; \ - tv1 = T[2*(uint32_t)x[4*c7+3]]; \ - tv2 = T[2*(uint32_t)x[4*c7+3]+1]; \ - ROTATE_COLUMN_DOWN(tv1,tv2,3,t) \ - tl ^= tv1; \ - tu ^= tv2; \ - y[i] = tu; \ - y[i+1] = tl; - - -/* compute one round of P (short variants) */ -static void RND512P(uint8_t *x, uint32_t *y, uint32_t r) { - uint32_t temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp; - uint32_t* x32 = (uint32_t*)x; - x32[ 0] ^= SWAP32LE(0x00000000)^r; - x32[ 2] ^= SWAP32LE(0x00000010)^r; - x32[ 4] ^= SWAP32LE(0x00000020)^r; - x32[ 6] ^= SWAP32LE(0x00000030)^r; - x32[ 8] ^= SWAP32LE(0x00000040)^r; - x32[10] ^= SWAP32LE(0x00000050)^r; - x32[12] ^= SWAP32LE(0x00000060)^r; - x32[14] ^= SWAP32LE(0x00000070)^r; - COLUMN(x,y, 0, 0, 2, 4, 6, 9, 11, 13, 15, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 2, 2, 4, 6, 8, 11, 13, 15, 1, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 4, 4, 6, 8, 10, 13, 15, 1, 3, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 6, 6, 8, 10, 12, 15, 1, 3, 5, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 8, 8, 10, 12, 14, 1, 3, 5, 7, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y,10, 10, 12, 14, 0, 3, 5, 7, 9, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y,12, 12, 14, 0, 2, 5, 7, 9, 11, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y,14, 14, 0, 2, 4, 7, 9, 11, 13, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); -} - -/* compute one round of Q (short variants) */ -static void RND512Q(uint8_t *x, uint32_t *y, uint32_t r) { - uint32_t temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp; - uint32_t* x32 = (uint32_t*)x; - x32[ 0] = ~x32[ 0]; - x32[ 1] ^= SWAP32LE(0xffffffff)^r; - x32[ 2] = ~x32[ 2]; - x32[ 3] ^= SWAP32LE(0xefffffff)^r; - x32[ 4] = ~x32[ 4]; - x32[ 5] ^= SWAP32LE(0xdfffffff)^r; - x32[ 6] = ~x32[ 6]; - x32[ 7] ^= SWAP32LE(0xcfffffff)^r; - x32[ 8] = ~x32[ 8]; - x32[ 9] ^= SWAP32LE(0xbfffffff)^r; - x32[10] = ~x32[10]; - x32[11] ^= SWAP32LE(0xafffffff)^r; - x32[12] = ~x32[12]; - x32[13] ^= SWAP32LE(0x9fffffff)^r; - x32[14] = ~x32[14]; - x32[15] ^= SWAP32LE(0x8fffffff)^r; - - COLUMN(x,y, 0, 2, 6, 10, 14, 1, 5, 9, 13, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 2, 4, 8, 12, 0, 3, 7, 11, 15, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 4, 6, 10, 14, 2, 5, 9, 13, 1, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 6, 8, 12, 0, 4, 7, 11, 15, 3, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y, 8, 10, 14, 2, 6, 9, 13, 1, 5, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y,10, 12, 0, 4, 8, 11, 15, 3, 7, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y,12, 14, 2, 6, 10, 13, 1, 5, 9, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); - COLUMN(x,y,14, 0, 4, 8, 12, 15, 3, 7, 11, temp_v1, temp_v2, temp_upper_value, temp_lower_value, temp); -} - -/* compute compression function (short variants) */ -static void F512(uint32_t *h, const uint32_t *m) { - int i; - uint32_t Ptmp[2*COLS512]; - uint32_t Qtmp[2*COLS512]; - uint32_t y[2*COLS512]; - uint32_t z[2*COLS512]; - - for (i = 0; i < 2*COLS512; i++) { - z[i] = m[i]; - Ptmp[i] = h[i]^m[i]; - } - - /* compute Q(m) */ - RND512Q((uint8_t*)z, y, SWAP32LE(0x00000000)); - RND512Q((uint8_t*)y, z, SWAP32LE(0x01000000)); - RND512Q((uint8_t*)z, y, SWAP32LE(0x02000000)); - RND512Q((uint8_t*)y, z, SWAP32LE(0x03000000)); - RND512Q((uint8_t*)z, y, SWAP32LE(0x04000000)); - RND512Q((uint8_t*)y, z, SWAP32LE(0x05000000)); - RND512Q((uint8_t*)z, y, SWAP32LE(0x06000000)); - RND512Q((uint8_t*)y, z, SWAP32LE(0x07000000)); - RND512Q((uint8_t*)z, y, SWAP32LE(0x08000000)); - RND512Q((uint8_t*)y, Qtmp, SWAP32LE(0x09000000)); - - /* compute P(h+m) */ - RND512P((uint8_t*)Ptmp, y, SWAP32LE(0x00000000)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000001)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000002)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000003)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000004)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000005)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000006)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000007)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000008)); - RND512P((uint8_t*)y, Ptmp, SWAP32LE(0x00000009)); - - /* compute P(h+m) + Q(m) + h */ - for (i = 0; i < 2*COLS512; i++) { - h[i] ^= Ptmp[i]^Qtmp[i]; - } -} - - -/* digest up to msglen bytes of input (full blocks only) */ -static void Transform(hashState *ctx, - const uint8_t *input, - int msglen) { - - /* digest message, one block at a time */ - for (; msglen >= SIZE512; - msglen -= SIZE512, input += SIZE512) { - F512(ctx->chaining,(uint32_t*)input); - - /* increment block counter */ - ctx->block_counter1++; - if (ctx->block_counter1 == 0) ctx->block_counter2++; - } -} - -/* given state h, do h <- P(h)+h */ -static void OutputTransformation(hashState *ctx) { - int j; - uint32_t temp[2*COLS512]; - uint32_t y[2*COLS512]; - uint32_t z[2*COLS512]; - - - - for (j = 0; j < 2*COLS512; j++) { - temp[j] = ctx->chaining[j]; - } - RND512P((uint8_t*)temp, y, SWAP32LE(0x00000000)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000001)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000002)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000003)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000004)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000005)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000006)); - RND512P((uint8_t*)y, z, SWAP32LE(0x00000007)); - RND512P((uint8_t*)z, y, SWAP32LE(0x00000008)); - RND512P((uint8_t*)y, temp, SWAP32LE(0x00000009)); - for (j = 0; j < 2*COLS512; j++) { - ctx->chaining[j] ^= temp[j]; - } -} - -/* initialise context */ -static void Init(hashState* ctx) { - /* allocate memory for state and data buffer */ - - for(size_t i = 0; i < (SIZE512/sizeof(uint32_t)); i++) - { - ctx->chaining[i] = 0; - } - - /* set initial value */ - ctx->chaining[2*COLS512-1] = SWAP32LE(u32BIG((uint32_t)HASH_BIT_LEN)); - - /* set other variables */ - ctx->buf_ptr = 0; - ctx->block_counter1 = 0; - ctx->block_counter2 = 0; - ctx->bits_in_last_byte = 0; -} - -/* update state with databitlen bits of input */ -static void Update(hashState* ctx, - const BitSequence* input, - DataLength databitlen) { - int index = 0; - int msglen = (int)(databitlen/8); - int rem = (int)(databitlen%8); - - /* if the buffer contains data that has not yet been digested, first - add data to buffer until full */ - if (ctx->buf_ptr) { - while (ctx->buf_ptr < SIZE512 && index < msglen) { - ctx->buffer[(int)ctx->buf_ptr++] = input[index++]; - } - if (ctx->buf_ptr < SIZE512) { - /* buffer still not full, return */ - if (rem) { - ctx->bits_in_last_byte = rem; - ctx->buffer[(int)ctx->buf_ptr++] = input[index]; - } - return; - } - - /* digest buffer */ - ctx->buf_ptr = 0; - Transform(ctx, ctx->buffer, SIZE512); - } - - /* digest bulk of message */ - Transform(ctx, input+index, msglen-index); - index += ((msglen-index)/SIZE512)*SIZE512; - - /* store remaining data in buffer */ - while (index < msglen) { - ctx->buffer[(int)ctx->buf_ptr++] = input[index++]; - } - - /* if non-integral number of bytes have been supplied, store - remaining bits in last byte, together with information about - number of bits */ - if (rem) { - ctx->bits_in_last_byte = rem; - ctx->buffer[(int)ctx->buf_ptr++] = input[index]; - } -} - -#define BILB ctx->bits_in_last_byte - -/* finalise: process remaining data (including padding), perform - output transformation, and write hash result to 'output' */ -static void Final(hashState* ctx, - BitSequence* output) { - int i, j = 0, hashbytelen = HASH_BIT_LEN/8; - uint8_t *s = (BitSequence*)ctx->chaining; - - /* pad with '1'-bit and first few '0'-bits */ - if (BILB) { - ctx->buffer[(int)ctx->buf_ptr-1] &= ((1<buffer[(int)ctx->buf_ptr-1] ^= 0x1<<(7-BILB); - BILB = 0; - } - else ctx->buffer[(int)ctx->buf_ptr++] = 0x80; - - /* pad with '0'-bits */ - if (ctx->buf_ptr > SIZE512-LENGTHFIELDLEN) { - /* padding requires two blocks */ - while (ctx->buf_ptr < SIZE512) { - ctx->buffer[(int)ctx->buf_ptr++] = 0; - } - /* digest first padding block */ - Transform(ctx, ctx->buffer, SIZE512); - ctx->buf_ptr = 0; - } - while (ctx->buf_ptr < SIZE512-LENGTHFIELDLEN) { - ctx->buffer[(int)ctx->buf_ptr++] = 0; - } - - /* length padding */ - ctx->block_counter1++; - if (ctx->block_counter1 == 0) ctx->block_counter2++; - ctx->buf_ptr = SIZE512; - - while (ctx->buf_ptr > SIZE512-(int)sizeof(uint32_t)) { - ctx->buffer[(int)--ctx->buf_ptr] = (uint8_t)ctx->block_counter1; - ctx->block_counter1 >>= 8; - } - while (ctx->buf_ptr > SIZE512-LENGTHFIELDLEN) { - ctx->buffer[(int)--ctx->buf_ptr] = (uint8_t)ctx->block_counter2; - ctx->block_counter2 >>= 8; - } - /* digest final padding block */ - Transform(ctx, ctx->buffer, SIZE512); - /* perform output transformation */ - OutputTransformation(ctx); - - /* store hash result in output */ - for (i = SIZE512-hashbytelen; i < SIZE512; i++,j++) { - output[j] = s[i]; - } - - /* zeroise relevant variables and deallocate memory */ - for (i = 0; i < COLS512; i++) { - ctx->chaining[i] = 0; - } - for (i = 0; i < SIZE512; i++) { - ctx->buffer[i] = 0; - } -} - -/* hash bit sequence */ -void groestl(const BitSequence* data, - DataLength databitlen, - BitSequence* hashval) { - - hashState context; - - /* initialise */ - Init(&context); - - - /* process message */ - Update(&context, data, databitlen); - - /* finalise */ - Final(&context, hashval); -} -/* -static int crypto_hash(unsigned char *out, - const unsigned char *in, - unsigned long long len) -{ - groestl(in, 8*len, out); - return 0; -} - -*/ diff --git a/cryptonight/c/groestl.h b/cryptonight/c/groestl.h deleted file mode 100644 index 6fb0a26..0000000 --- a/cryptonight/c/groestl.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef __hash_h -#define __hash_h -/* -#include "crypto_uint8.h" -#include "crypto_uint32.h" -#include "crypto_uint64.h" -#include "crypto_hash.h" - -typedef crypto_uint8 uint8_t; -typedef crypto_uint32 uint32_t; -typedef crypto_uint64 uint64_t; -*/ -#include - -/* some sizes (number of bytes) */ -#define ROWS 8 -#define LENGTHFIELDLEN ROWS -#define COLS512 8 - -#define SIZE512 (ROWS*COLS512) - -#define ROUNDS512 10 -#define HASH_BIT_LEN 256 - -#define ROTL32(v, n) ((((v)<<(n))|((v)>>(32-(n))))&li_32(ffffffff)) - - -#define li_32(h) 0x##h##u -#define EXT_BYTE(var,n) ((uint8_t)((uint32_t)(var) >> (8*n))) -#define u32BIG(a) \ - ((ROTL32(a,8) & li_32(00FF00FF)) | \ - (ROTL32(a,24) & li_32(FF00FF00))) - - -/* NIST API begin */ -typedef unsigned char BitSequence; -typedef unsigned long long DataLength; -typedef struct { - uint32_t chaining[SIZE512/sizeof(uint32_t)]; /* actual state */ - uint32_t block_counter1, - block_counter2; /* message block counter(s) */ - BitSequence buffer[SIZE512]; /* data buffer */ - int buf_ptr; /* data buffer pointer */ - int bits_in_last_byte; /* no. of message bits in last byte of - data buffer */ -} hashState; - -/*void Init(hashState*); -void Update(hashState*, const BitSequence*, DataLength); -void Final(hashState*, BitSequence*); */ -void groestl(const BitSequence*, DataLength, BitSequence*); -/* NIST API end */ - -/* -int crypto_hash(unsigned char *out, - const unsigned char *in, - unsigned long long len); -*/ - -#endif /* __hash_h */ diff --git a/cryptonight/c/groestl_tables.h b/cryptonight/c/groestl_tables.h deleted file mode 100644 index c553b98..0000000 --- a/cryptonight/c/groestl_tables.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef __tables_h -#define __tables_h - -#include "int-util.h" - - -#if BYTE_ORDER == LITTLE_ENDIAN -const uint32_t T[512] = {0xa5f432c6, 0xc6a597f4, 0x84976ff8, 0xf884eb97, 0x99b05eee, 0xee99c7b0, 0x8d8c7af6, 0xf68df78c, 0xd17e8ff, 0xff0de517, 0xbddc0ad6, 0xd6bdb7dc, 0xb1c816de, 0xdeb1a7c8, 0x54fc6d91, 0x915439fc -, 0x50f09060, 0x6050c0f0, 0x3050702, 0x2030405, 0xa9e02ece, 0xcea987e0, 0x7d87d156, 0x567dac87, 0x192bcce7, 0xe719d52b, 0x62a613b5, 0xb56271a6, 0xe6317c4d, 0x4de69a31, 0x9ab559ec, 0xec9ac3b5 -, 0x45cf408f, 0x8f4505cf, 0x9dbca31f, 0x1f9d3ebc, 0x40c04989, 0x894009c0, 0x879268fa, 0xfa87ef92, 0x153fd0ef, 0xef15c53f, 0xeb2694b2, 0xb2eb7f26, 0xc940ce8e, 0x8ec90740, 0xb1de6fb, 0xfb0bed1d -, 0xec2f6e41, 0x41ec822f, 0x67a91ab3, 0xb3677da9, 0xfd1c435f, 0x5ffdbe1c, 0xea256045, 0x45ea8a25, 0xbfdaf923, 0x23bf46da, 0xf7025153, 0x53f7a602, 0x96a145e4, 0xe496d3a1, 0x5bed769b, 0x9b5b2ded -, 0xc25d2875, 0x75c2ea5d, 0x1c24c5e1, 0xe11cd924, 0xaee9d43d, 0x3dae7ae9, 0x6abef24c, 0x4c6a98be, 0x5aee826c, 0x6c5ad8ee, 0x41c3bd7e, 0x7e41fcc3, 0x206f3f5, 0xf502f106, 0x4fd15283, 0x834f1dd1 -, 0x5ce48c68, 0x685cd0e4, 0xf4075651, 0x51f4a207, 0x345c8dd1, 0xd134b95c, 0x818e1f9, 0xf908e918, 0x93ae4ce2, 0xe293dfae, 0x73953eab, 0xab734d95, 0x53f59762, 0x6253c4f5, 0x3f416b2a, 0x2a3f5441 -, 0xc141c08, 0x80c1014, 0x52f66395, 0x955231f6, 0x65afe946, 0x46658caf, 0x5ee27f9d, 0x9d5e21e2, 0x28784830, 0x30286078, 0xa1f8cf37, 0x37a16ef8, 0xf111b0a, 0xa0f1411, 0xb5c4eb2f, 0x2fb55ec4 -, 0x91b150e, 0xe091c1b, 0x365a7e24, 0x2436485a, 0x9bb6ad1b, 0x1b9b36b6, 0x3d4798df, 0xdf3da547, 0x266aa7cd, 0xcd26816a, 0x69bbf54e, 0x4e699cbb, 0xcd4c337f, 0x7fcdfe4c, 0x9fba50ea, 0xea9fcfba -, 0x1b2d3f12, 0x121b242d, 0x9eb9a41d, 0x1d9e3ab9, 0x749cc458, 0x5874b09c, 0x2e724634, 0x342e6872, 0x2d774136, 0x362d6c77, 0xb2cd11dc, 0xdcb2a3cd, 0xee299db4, 0xb4ee7329, 0xfb164d5b, 0x5bfbb616 -, 0xf601a5a4, 0xa4f65301, 0x4dd7a176, 0x764decd7, 0x61a314b7, 0xb76175a3, 0xce49347d, 0x7dcefa49, 0x7b8ddf52, 0x527ba48d, 0x3e429fdd, 0xdd3ea142, 0x7193cd5e, 0x5e71bc93, 0x97a2b113, 0x139726a2 -, 0xf504a2a6, 0xa6f55704, 0x68b801b9, 0xb96869b8, 0x0, 0x0, 0x2c74b5c1, 0xc12c9974, 0x60a0e040, 0x406080a0, 0x1f21c2e3, 0xe31fdd21, 0xc8433a79, 0x79c8f243, 0xed2c9ab6, 0xb6ed772c -, 0xbed90dd4, 0xd4beb3d9, 0x46ca478d, 0x8d4601ca, 0xd9701767, 0x67d9ce70, 0x4bddaf72, 0x724be4dd, 0xde79ed94, 0x94de3379, 0xd467ff98, 0x98d42b67, 0xe82393b0, 0xb0e87b23, 0x4ade5b85, 0x854a11de -, 0x6bbd06bb, 0xbb6b6dbd, 0x2a7ebbc5, 0xc52a917e, 0xe5347b4f, 0x4fe59e34, 0x163ad7ed, 0xed16c13a, 0xc554d286, 0x86c51754, 0xd762f89a, 0x9ad72f62, 0x55ff9966, 0x6655ccff, 0x94a7b611, 0x119422a7 -, 0xcf4ac08a, 0x8acf0f4a, 0x1030d9e9, 0xe910c930, 0x60a0e04, 0x406080a, 0x819866fe, 0xfe81e798, 0xf00baba0, 0xa0f05b0b, 0x44ccb478, 0x7844f0cc, 0xbad5f025, 0x25ba4ad5, 0xe33e754b, 0x4be3963e -, 0xf30eaca2, 0xa2f35f0e, 0xfe19445d, 0x5dfeba19, 0xc05bdb80, 0x80c01b5b, 0x8a858005, 0x58a0a85, 0xadecd33f, 0x3fad7eec, 0xbcdffe21, 0x21bc42df, 0x48d8a870, 0x7048e0d8, 0x40cfdf1, 0xf104f90c -, 0xdf7a1963, 0x63dfc67a, 0xc1582f77, 0x77c1ee58, 0x759f30af, 0xaf75459f, 0x63a5e742, 0x426384a5, 0x30507020, 0x20304050, 0x1a2ecbe5, 0xe51ad12e, 0xe12effd, 0xfd0ee112, 0x6db708bf, 0xbf6d65b7 -, 0x4cd45581, 0x814c19d4, 0x143c2418, 0x1814303c, 0x355f7926, 0x26354c5f, 0x2f71b2c3, 0xc32f9d71, 0xe13886be, 0xbee16738, 0xa2fdc835, 0x35a26afd, 0xcc4fc788, 0x88cc0b4f, 0x394b652e, 0x2e395c4b -, 0x57f96a93, 0x93573df9, 0xf20d5855, 0x55f2aa0d, 0x829d61fc, 0xfc82e39d, 0x47c9b37a, 0x7a47f4c9, 0xacef27c8, 0xc8ac8bef, 0xe73288ba, 0xbae76f32, 0x2b7d4f32, 0x322b647d, 0x95a442e6, 0xe695d7a4 -, 0xa0fb3bc0, 0xc0a09bfb, 0x98b3aa19, 0x199832b3, 0xd168f69e, 0x9ed12768, 0x7f8122a3, 0xa37f5d81, 0x66aaee44, 0x446688aa, 0x7e82d654, 0x547ea882, 0xabe6dd3b, 0x3bab76e6, 0x839e950b, 0xb83169e -, 0xca45c98c, 0x8cca0345, 0x297bbcc7, 0xc729957b, 0xd36e056b, 0x6bd3d66e, 0x3c446c28, 0x283c5044, 0x798b2ca7, 0xa779558b, 0xe23d81bc, 0xbce2633d, 0x1d273116, 0x161d2c27, 0x769a37ad, 0xad76419a -, 0x3b4d96db, 0xdb3bad4d, 0x56fa9e64, 0x6456c8fa, 0x4ed2a674, 0x744ee8d2, 0x1e223614, 0x141e2822, 0xdb76e492, 0x92db3f76, 0xa1e120c, 0xc0a181e, 0x6cb4fc48, 0x486c90b4, 0xe4378fb8, 0xb8e46b37 -, 0x5de7789f, 0x9f5d25e7, 0x6eb20fbd, 0xbd6e61b2, 0xef2a6943, 0x43ef862a, 0xa6f135c4, 0xc4a693f1, 0xa8e3da39, 0x39a872e3, 0xa4f7c631, 0x31a462f7, 0x37598ad3, 0xd337bd59, 0x8b8674f2, 0xf28bff86 -, 0x325683d5, 0xd532b156, 0x43c54e8b, 0x8b430dc5, 0x59eb856e, 0x6e59dceb, 0xb7c218da, 0xdab7afc2, 0x8c8f8e01, 0x18c028f, 0x64ac1db1, 0xb16479ac, 0xd26df19c, 0x9cd2236d, 0xe03b7249, 0x49e0923b -, 0xb4c71fd8, 0xd8b4abc7, 0xfa15b9ac, 0xacfa4315, 0x709faf3, 0xf307fd09, 0x256fa0cf, 0xcf25856f, 0xafea20ca, 0xcaaf8fea, 0x8e897df4, 0xf48ef389, 0xe9206747, 0x47e98e20, 0x18283810, 0x10182028 -, 0xd5640b6f, 0x6fd5de64, 0x888373f0, 0xf088fb83, 0x6fb1fb4a, 0x4a6f94b1, 0x7296ca5c, 0x5c72b896, 0x246c5438, 0x3824706c, 0xf1085f57, 0x57f1ae08, 0xc7522173, 0x73c7e652, 0x51f36497, 0x975135f3 -, 0x2365aecb, 0xcb238d65, 0x7c8425a1, 0xa17c5984, 0x9cbf57e8, 0xe89ccbbf, 0x21635d3e, 0x3e217c63, 0xdd7cea96, 0x96dd377c, 0xdc7f1e61, 0x61dcc27f, 0x86919c0d, 0xd861a91, 0x85949b0f, 0xf851e94 -, 0x90ab4be0, 0xe090dbab, 0x42c6ba7c, 0x7c42f8c6, 0xc4572671, 0x71c4e257, 0xaae529cc, 0xccaa83e5, 0xd873e390, 0x90d83b73, 0x50f0906, 0x6050c0f, 0x103f4f7, 0xf701f503, 0x12362a1c, 0x1c123836 -, 0xa3fe3cc2, 0xc2a39ffe, 0x5fe18b6a, 0x6a5fd4e1, 0xf910beae, 0xaef94710, 0xd06b0269, 0x69d0d26b, 0x91a8bf17, 0x17912ea8, 0x58e87199, 0x995829e8, 0x2769533a, 0x3a277469, 0xb9d0f727, 0x27b94ed0 -, 0x384891d9, 0xd938a948, 0x1335deeb, 0xeb13cd35, 0xb3cee52b, 0x2bb356ce, 0x33557722, 0x22334455, 0xbbd604d2, 0xd2bbbfd6, 0x709039a9, 0xa9704990, 0x89808707, 0x7890e80, 0xa7f2c133, 0x33a766f2 -, 0xb6c1ec2d, 0x2db65ac1, 0x22665a3c, 0x3c227866, 0x92adb815, 0x15922aad, 0x2060a9c9, 0xc9208960, 0x49db5c87, 0x874915db, 0xff1ab0aa, 0xaaff4f1a, 0x7888d850, 0x5078a088, 0x7a8e2ba5, 0xa57a518e -, 0x8f8a8903, 0x38f068a, 0xf8134a59, 0x59f8b213, 0x809b9209, 0x980129b, 0x1739231a, 0x1a173439, 0xda751065, 0x65daca75, 0x315384d7, 0xd731b553, 0xc651d584, 0x84c61351, 0xb8d303d0, 0xd0b8bbd3 -, 0xc35edc82, 0x82c31f5e, 0xb0cbe229, 0x29b052cb, 0x7799c35a, 0x5a77b499, 0x11332d1e, 0x1e113c33, 0xcb463d7b, 0x7bcbf646, 0xfc1fb7a8, 0xa8fc4b1f, 0xd6610c6d, 0x6dd6da61, 0x3a4e622c, 0x2c3a584e}; -#else -const uint32_t T[512] = {0xc632f4a5, 0xf497a5c6, 0xf86f9784, 0x97eb84f8, 0xee5eb099, 0xb0c799ee, 0xf67a8c8d, 0x8cf78df6, 0xffe8170d, 0x17e50dff, 0xd60adcbd, 0xdcb7bdd6, 0xde16c8b1, 0xc8a7b1de, 0x916dfc54, 0xfc395491 -, 0x6090f050, 0xf0c05060, 0x02070503, 0x05040302, 0xce2ee0a9, 0xe087a9ce, 0x56d1877d, 0x87ac7d56, 0xe7cc2b19, 0x2bd519e7, 0xb513a662, 0xa67162b5, 0x4d7c31e6, 0x319ae64d, 0xec59b59a, 0xb5c39aec -, 0x8f40cf45, 0xcf05458f, 0x1fa3bc9d, 0xbc3e9d1f, 0x8949c040, 0xc0094089, 0xfa689287, 0x92ef87fa, 0xefd03f15, 0x3fc515ef, 0xb29426eb, 0x267febb2, 0x8ece40c9, 0x4007c98e, 0xfbe61d0b, 0x1ded0bfb -, 0x416e2fec, 0x2f82ec41, 0xb31aa967, 0xa97d67b3, 0x5f431cfd, 0x1cbefd5f, 0x456025ea, 0x258aea45, 0x23f9dabf, 0xda46bf23, 0x535102f7, 0x02a6f753, 0xe445a196, 0xa1d396e4, 0x9b76ed5b, 0xed2d5b9b -, 0x75285dc2, 0x5deac275, 0xe1c5241c, 0x24d91ce1, 0x3dd4e9ae, 0xe97aae3d, 0x4cf2be6a, 0xbe986a4c, 0x6c82ee5a, 0xeed85a6c, 0x7ebdc341, 0xc3fc417e, 0xf5f30602, 0x06f102f5, 0x8352d14f, 0xd11d4f83 -, 0x688ce45c, 0xe4d05c68, 0x515607f4, 0x07a2f451, 0xd18d5c34, 0x5cb934d1, 0xf9e11808, 0x18e908f9, 0xe24cae93, 0xaedf93e2, 0xab3e9573, 0x954d73ab, 0x6297f553, 0xf5c45362, 0x2a6b413f, 0x41543f2a -, 0x081c140c, 0x14100c08, 0x9563f652, 0xf6315295, 0x46e9af65, 0xaf8c6546, 0x9d7fe25e, 0xe2215e9d, 0x30487828, 0x78602830, 0x37cff8a1, 0xf86ea137, 0x0a1b110f, 0x11140f0a, 0x2febc4b5, 0xc45eb52f -, 0x0e151b09, 0x1b1c090e, 0x247e5a36, 0x5a483624, 0x1badb69b, 0xb6369b1b, 0xdf98473d, 0x47a53ddf, 0xcda76a26, 0x6a8126cd, 0x4ef5bb69, 0xbb9c694e, 0x7f334ccd, 0x4cfecd7f, 0xea50ba9f, 0xbacf9fea -, 0x123f2d1b, 0x2d241b12, 0x1da4b99e, 0xb93a9e1d, 0x58c49c74, 0x9cb07458, 0x3446722e, 0x72682e34, 0x3641772d, 0x776c2d36, 0xdc11cdb2, 0xcda3b2dc, 0xb49d29ee, 0x2973eeb4, 0x5b4d16fb, 0x16b6fb5b -, 0xa4a501f6, 0x0153f6a4, 0x76a1d74d, 0xd7ec4d76, 0xb714a361, 0xa37561b7, 0x7d3449ce, 0x49face7d, 0x52df8d7b, 0x8da47b52, 0xdd9f423e, 0x42a13edd, 0x5ecd9371, 0x93bc715e, 0x13b1a297, 0xa2269713 -, 0xa6a204f5, 0x0457f5a6, 0xb901b868, 0xb86968b9, 0x00000000, 0x00000000, 0xc1b5742c, 0x74992cc1, 0x40e0a060, 0xa0806040, 0xe3c2211f, 0x21dd1fe3, 0x793a43c8, 0x43f2c879, 0xb69a2ced, 0x2c77edb6 -, 0xd40dd9be, 0xd9b3bed4, 0x8d47ca46, 0xca01468d, 0x671770d9, 0x70ced967, 0x72afdd4b, 0xdde44b72, 0x94ed79de, 0x7933de94, 0x98ff67d4, 0x672bd498, 0xb09323e8, 0x237be8b0, 0x855bde4a, 0xde114a85 -, 0xbb06bd6b, 0xbd6d6bbb, 0xc5bb7e2a, 0x7e912ac5, 0x4f7b34e5, 0x349ee54f, 0xedd73a16, 0x3ac116ed, 0x86d254c5, 0x5417c586, 0x9af862d7, 0x622fd79a, 0x6699ff55, 0xffcc5566, 0x11b6a794, 0xa7229411 -, 0x8ac04acf, 0x4a0fcf8a, 0xe9d93010, 0x30c910e9, 0x040e0a06, 0x0a080604, 0xfe669881, 0x98e781fe, 0xa0ab0bf0, 0x0b5bf0a0, 0x78b4cc44, 0xccf04478, 0x25f0d5ba, 0xd54aba25, 0x4b753ee3, 0x3e96e34b -, 0xa2ac0ef3, 0x0e5ff3a2, 0x5d4419fe, 0x19bafe5d, 0x80db5bc0, 0x5b1bc080, 0x0580858a, 0x850a8a05, 0x3fd3ecad, 0xec7ead3f, 0x21fedfbc, 0xdf42bc21, 0x70a8d848, 0xd8e04870, 0xf1fd0c04, 0x0cf904f1 -, 0x63197adf, 0x7ac6df63, 0x772f58c1, 0x58eec177, 0xaf309f75, 0x9f4575af, 0x42e7a563, 0xa5846342, 0x20705030, 0x50403020, 0xe5cb2e1a, 0x2ed11ae5, 0xfdef120e, 0x12e10efd, 0xbf08b76d, 0xb7656dbf -, 0x8155d44c, 0xd4194c81, 0x18243c14, 0x3c301418, 0x26795f35, 0x5f4c3526, 0xc3b2712f, 0x719d2fc3, 0xbe8638e1, 0x3867e1be, 0x35c8fda2, 0xfd6aa235, 0x88c74fcc, 0x4f0bcc88, 0x2e654b39, 0x4b5c392e -, 0x936af957, 0xf93d5793, 0x55580df2, 0x0daaf255, 0xfc619d82, 0x9de382fc, 0x7ab3c947, 0xc9f4477a, 0xc827efac, 0xef8bacc8, 0xba8832e7, 0x326fe7ba, 0x324f7d2b, 0x7d642b32, 0xe642a495, 0xa4d795e6 -, 0xc03bfba0, 0xfb9ba0c0, 0x19aab398, 0xb3329819, 0x9ef668d1, 0x6827d19e, 0xa322817f, 0x815d7fa3, 0x44eeaa66, 0xaa886644, 0x54d6827e, 0x82a87e54, 0x3bdde6ab, 0xe676ab3b, 0x0b959e83, 0x9e16830b -, 0x8cc945ca, 0x4503ca8c, 0xc7bc7b29, 0x7b9529c7, 0x6b056ed3, 0x6ed6d36b, 0x286c443c, 0x44503c28, 0xa72c8b79, 0x8b5579a7, 0xbc813de2, 0x3d63e2bc, 0x1631271d, 0x272c1d16, 0xad379a76, 0x9a4176ad -, 0xdb964d3b, 0x4dad3bdb, 0x649efa56, 0xfac85664, 0x74a6d24e, 0xd2e84e74, 0x1436221e, 0x22281e14, 0x92e476db, 0x763fdb92, 0x0c121e0a, 0x1e180a0c, 0x48fcb46c, 0xb4906c48, 0xb88f37e4, 0x376be4b8 -, 0x9f78e75d, 0xe7255d9f, 0xbd0fb26e, 0xb2616ebd, 0x43692aef, 0x2a86ef43, 0xc435f1a6, 0xf193a6c4, 0x39dae3a8, 0xe372a839, 0x31c6f7a4, 0xf762a431, 0xd38a5937, 0x59bd37d3, 0xf274868b, 0x86ff8bf2 -, 0xd5835632, 0x56b132d5, 0x8b4ec543, 0xc50d438b, 0x6e85eb59, 0xebdc596e, 0xda18c2b7, 0xc2afb7da, 0x018e8f8c, 0x8f028c01, 0xb11dac64, 0xac7964b1, 0x9cf16dd2, 0x6d23d29c, 0x49723be0, 0x3b92e049 -, 0xd81fc7b4, 0xc7abb4d8, 0xacb915fa, 0x1543faac, 0xf3fa0907, 0x09fd07f3, 0xcfa06f25, 0x6f8525cf, 0xca20eaaf, 0xea8fafca, 0xf47d898e, 0x89f38ef4, 0x476720e9, 0x208ee947, 0x10382818, 0x28201810 -, 0x6f0b64d5, 0x64ded56f, 0xf0738388, 0x83fb88f0, 0x4afbb16f, 0xb1946f4a, 0x5cca9672, 0x96b8725c, 0x38546c24, 0x6c702438, 0x575f08f1, 0x08aef157, 0x732152c7, 0x52e6c773, 0x9764f351, 0xf3355197 -, 0xcbae6523, 0x658d23cb, 0xa125847c, 0x84597ca1, 0xe857bf9c, 0xbfcb9ce8, 0x3e5d6321, 0x637c213e, 0x96ea7cdd, 0x7c37dd96, 0x611e7fdc, 0x7fc2dc61, 0x0d9c9186, 0x911a860d, 0x0f9b9485, 0x941e850f -, 0xe04bab90, 0xabdb90e0, 0x7cbac642, 0xc6f8427c, 0x712657c4, 0x57e2c471, 0xcc29e5aa, 0xe583aacc, 0x90e373d8, 0x733bd890, 0x06090f05, 0x0f0c0506, 0xf7f40301, 0x03f501f7, 0x1c2a3612, 0x3638121c -, 0xc23cfea3, 0xfe9fa3c2, 0x6a8be15f, 0xe1d45f6a, 0xaebe10f9, 0x1047f9ae, 0x69026bd0, 0x6bd2d069, 0x17bfa891, 0xa82e9117, 0x9971e858, 0xe8295899, 0x3a536927, 0x6974273a, 0x27f7d0b9, 0xd04eb927 -, 0xd9914838, 0x48a938d9, 0xebde3513, 0x35cd13eb, 0x2be5ceb3, 0xce56b32b, 0x22775533, 0x55443322, 0xd204d6bb, 0xd6bfbbd2, 0xa9399070, 0x904970a9, 0x07878089, 0x800e8907, 0x33c1f2a7, 0xf266a733 -, 0x2decc1b6, 0xc15ab62d, 0x3c5a6622, 0x6678223c, 0x15b8ad92, 0xad2a9215, 0xc9a96020, 0x608920c9, 0x875cdb49, 0xdb154987, 0xaab01aff, 0x1a4fffaa, 0x50d88878, 0x88a07850, 0xa52b8e7a, 0x8e517aa5 -, 0x03898a8f, 0x8a068f03, 0x594a13f8, 0x13b2f859, 0x09929b80, 0x9b128009, 0x1a233917, 0x3934171a, 0x651075da, 0x75cada65, 0xd7845331, 0x53b531d7, 0x84d551c6, 0x5113c684, 0xd003d3b8, 0xd3bbb8d0 -, 0x82dc5ec3, 0x5e1fc382, 0x29e2cbb0, 0xcb52b029, 0x5ac39977, 0x99b4775a, 0x1e2d3311, 0x333c111e, 0x7b3d46cb, 0x46f6cb7b, 0xa8b71ffc, 0x1f4bfca8, 0x6d0c61d6, 0x61dad66d, 0x2c624e3a, 0x4e583a2c}; -#endif - -#endif /* __tables_h */ diff --git a/cryptonight/c/hash-extra-blake.c b/cryptonight/c/hash-extra-blake.c deleted file mode 100644 index f3e91a0..0000000 --- a/cryptonight/c/hash-extra-blake.c +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include - -#include "blake256.h" - -void hash_extra_blake(const void *data, size_t length, char *hash) { - blake256_hash((uint8_t*)hash, data, length); -} diff --git a/cryptonight/c/hash-extra-groestl.c b/cryptonight/c/hash-extra-groestl.c deleted file mode 100644 index 7d8e84e..0000000 --- a/cryptonight/c/hash-extra-groestl.c +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include - -#include "groestl.h" - -void hash_extra_groestl(const void *data, size_t length, char *hash) { - groestl(data, length * 8, (uint8_t*)hash); -} diff --git a/cryptonight/c/hash-extra-jh.c b/cryptonight/c/hash-extra-jh.c deleted file mode 100644 index f2180a5..0000000 --- a/cryptonight/c/hash-extra-jh.c +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include -#include -#include - -#include "jh.h" -#include "hash-ops.h" - -#define JH_HASH_BITLEN HASH_SIZE * 8 - -void hash_extra_jh(const void *data, size_t length, char *hash) { - // No need to check for failure b/c jh_hash only fails for invalid hash size - jh_hash(JH_HASH_BITLEN, data, 8 * length, (uint8_t*)hash); -} diff --git a/cryptonight/c/hash-extra-skein.c b/cryptonight/c/hash-extra-skein.c deleted file mode 100644 index 07bfdcc..0000000 --- a/cryptonight/c/hash-extra-skein.c +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include - -#include "hash-ops.h" -#include "skein.h" - -#define SKEIN_HASH_BITLEN HASH_SIZE * 8 - -void hash_extra_skein(const void *data, size_t length, char *hash) { - // No need to check for failure b/c skein_hash only fails for invalid hash size - skein_hash(SKEIN_HASH_BITLEN, data, 8 * length, (uint8_t*)hash); -} diff --git a/cryptonight/c/hash-ops.h b/cryptonight/c/hash-ops.h deleted file mode 100644 index ed50662..0000000 --- a/cryptonight/c/hash-ops.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#if !defined(__cplusplus) - -#include -#include -#include -#include - -#include "int-util.h" -#include "warnings.h" - -static inline void *padd(void *p, size_t i) { - return (char *) p + i; -} - -static inline const void *cpadd(const void *p, size_t i) { - return (const char *) p + i; -} - -PUSH_WARNINGS -DISABLE_VS_WARNINGS(4267) -static_assert(sizeof(size_t) == 4 || sizeof(size_t) == 8, "size_t must be 4 or 8 bytes long"); -static inline void place_length(uint8_t *buffer, size_t bufsize, size_t length) { - if (sizeof(size_t) == 4) { - *(uint32_t *) padd(buffer, bufsize - 4) = swap32be(length); - } else { - *(uint64_t *) padd(buffer, bufsize - 8) = swap64be(length); - } -} -POP_WARNINGS - -#pragma pack(push, 1) -union hash_state { - uint8_t b[200]; - uint64_t w[25]; -}; -#pragma pack(pop) -static_assert(sizeof(union hash_state) == 200, "Invalid structure size"); - -void hash_permutation(union hash_state *state); -void hash_process(union hash_state *state, const uint8_t *buf, size_t count); - -#endif - -enum { - HASH_SIZE = 32, - HASH_DATA_AREA = 136 -}; - -void cn_fast_hash(const void *data, size_t length, char *hash); -void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height); - -void hash_extra_blake(const void *data, size_t length, char *hash); -void hash_extra_groestl(const void *data, size_t length, char *hash); -void hash_extra_jh(const void *data, size_t length, char *hash); -void hash_extra_skein(const void *data, size_t length, char *hash); diff --git a/cryptonight/c/hash.c b/cryptonight/c/hash.c deleted file mode 100644 index a34a07d..0000000 --- a/cryptonight/c/hash.c +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include -#include - -#include "hash-ops.h" -#include "keccak.h" - -void hash_permutation(union hash_state *state) { -#if BYTE_ORDER == LITTLE_ENDIAN - keccakf((uint64_t*)state, 24); -#else - uint64_t le_state[25]; - memcpy_swap64le(le_state, state, 25); - keccakf(le_state, 24); - memcpy_swap64le(state, le_state, 25); -#endif -} - -void hash_process(union hash_state *state, const uint8_t *buf, size_t count) { - keccak1600(buf, count, (uint8_t*)state); -} - -void cn_fast_hash(const void *data, size_t length, char *hash) { - union hash_state state; - hash_process(&state, data, length); - memcpy(hash, &state, HASH_SIZE); -} \ No newline at end of file diff --git a/cryptonight/c/hash.h b/cryptonight/c/hash.h deleted file mode 100644 index 7654dee..0000000 --- a/cryptonight/c/hash.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include -#include - -#include "common/pod-class.h" -#include "generic-ops.h" -#include "hex.h" -#include "span.h" - -namespace crypto { - - extern "C" { -#include "hash-ops.h" - } - -#pragma pack(push, 1) - POD_CLASS hash { - char data[HASH_SIZE]; - }; -#pragma pack(pop) - - static_assert(sizeof(hash) == HASH_SIZE, "Invalid structure size"); - - /* - Cryptonight hash functions - */ - - inline void cn_slow_hash(const void *data, std::size_t length, hash &hash, int variant = 0, uint64_t height = 0) { - cn_slow_hash(data, length, reinterpret_cast(&hash), variant, 0/*prehashed*/, height); - } - - constexpr static crypto::hash null_hash = {}; -} - -CRYPTO_MAKE_HASHABLE(hash) diff --git a/cryptonight/c/int-util.h b/cryptonight/c/int-util.h deleted file mode 100644 index d3fbd84..0000000 --- a/cryptonight/c/int-util.h +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include -#include -#include -#include - -#ifndef _MSC_VER -#include -#endif - -#if defined(__ANDROID__) -#include -#endif - -#if defined(__sun) && defined(__SVR4) -#include -#endif - -#if defined(_MSC_VER) -#include - -static inline uint32_t rol32(uint32_t x, int r) { - static_assert(sizeof(uint32_t) == sizeof(unsigned int), "this code assumes 32-bit integers"); - return _rotl(x, r); -} - -static inline uint64_t rol64(uint64_t x, int r) { - return _rotl64(x, r); -} - -#else - -static inline uint32_t rol32(uint32_t x, int r) { - return (x << (r & 31)) | (x >> (-r & 31)); -} - -static inline uint64_t rol64(uint64_t x, int r) { - return (x << (r & 63)) | (x >> (-r & 63)); -} - -#endif - -static inline uint64_t hi_dword(uint64_t val) { - return val >> 32; -} - -static inline uint64_t lo_dword(uint64_t val) { - return val & 0xFFFFFFFF; -} - -static inline uint64_t mul128(uint64_t multiplier, uint64_t multiplicand, uint64_t* product_hi) { - // multiplier = ab = a * 2^32 + b - // multiplicand = cd = c * 2^32 + d - // ab * cd = a * c * 2^64 + (a * d + b * c) * 2^32 + b * d - uint64_t a = hi_dword(multiplier); - uint64_t b = lo_dword(multiplier); - uint64_t c = hi_dword(multiplicand); - uint64_t d = lo_dword(multiplicand); - - uint64_t ac = a * c; - uint64_t ad = a * d; - uint64_t bc = b * c; - uint64_t bd = b * d; - - uint64_t adbc = ad + bc; - uint64_t adbc_carry = adbc < ad ? 1 : 0; - - // multiplier * multiplicand = product_hi * 2^64 + product_lo - uint64_t product_lo = bd + (adbc << 32); - uint64_t product_lo_carry = product_lo < bd ? 1 : 0; - *product_hi = ac + (adbc >> 32) + (adbc_carry << 32) + product_lo_carry; - assert(ac <= *product_hi); - - return product_lo; -} - -static inline uint64_t div_with_reminder(uint64_t dividend, uint32_t divisor, uint32_t* remainder) { - dividend |= ((uint64_t)*remainder) << 32; - *remainder = dividend % divisor; - return dividend / divisor; -} - -// Long division with 2^32 base -static inline uint32_t div128_32(uint64_t dividend_hi, uint64_t dividend_lo, uint32_t divisor, uint64_t* quotient_hi, uint64_t* quotient_lo) { - uint64_t dividend_dwords[4]; - uint32_t remainder = 0; - - dividend_dwords[3] = hi_dword(dividend_hi); - dividend_dwords[2] = lo_dword(dividend_hi); - dividend_dwords[1] = hi_dword(dividend_lo); - dividend_dwords[0] = lo_dword(dividend_lo); - - *quotient_hi = div_with_reminder(dividend_dwords[3], divisor, &remainder) << 32; - *quotient_hi |= div_with_reminder(dividend_dwords[2], divisor, &remainder); - *quotient_lo = div_with_reminder(dividend_dwords[1], divisor, &remainder) << 32; - *quotient_lo |= div_with_reminder(dividend_dwords[0], divisor, &remainder); - - return remainder; -} - -// Long divisor with 2^64 base -void div128_64(uint64_t dividend_hi, uint64_t dividend_lo, uint64_t divisor, uint64_t* quotient_hi, uint64_t *quotient_lo, uint64_t *remainder_hi, uint64_t *remainder_lo); - -static inline void add64clamp(uint64_t *value, uint64_t add) -{ - static const uint64_t maxval = (uint64_t)-1; - if (*value > maxval - add) - *value = maxval; - else - *value += add; -} - -static inline void sub64clamp(uint64_t *value, uint64_t sub) -{ - if (*value < sub) - *value = 0; - else - *value -= sub; -} - -#define IDENT16(x) ((uint16_t) (x)) -#define IDENT32(x) ((uint32_t) (x)) -#define IDENT64(x) ((uint64_t) (x)) - -#define SWAP16(x) ((((uint16_t) (x) & 0x00ff) << 8) | \ - (((uint16_t) (x) & 0xff00) >> 8)) -#define SWAP32(x) ((((uint32_t) (x) & 0x000000ff) << 24) | \ - (((uint32_t) (x) & 0x0000ff00) << 8) | \ - (((uint32_t) (x) & 0x00ff0000) >> 8) | \ - (((uint32_t) (x) & 0xff000000) >> 24)) -#define SWAP64(x) ((((uint64_t) (x) & 0x00000000000000ff) << 56) | \ - (((uint64_t) (x) & 0x000000000000ff00) << 40) | \ - (((uint64_t) (x) & 0x0000000000ff0000) << 24) | \ - (((uint64_t) (x) & 0x00000000ff000000) << 8) | \ - (((uint64_t) (x) & 0x000000ff00000000) >> 8) | \ - (((uint64_t) (x) & 0x0000ff0000000000) >> 24) | \ - (((uint64_t) (x) & 0x00ff000000000000) >> 40) | \ - (((uint64_t) (x) & 0xff00000000000000) >> 56)) - -static inline uint16_t ident16(uint16_t x) { return x; } -static inline uint32_t ident32(uint32_t x) { return x; } -static inline uint64_t ident64(uint64_t x) { return x; } - -#ifndef __OpenBSD__ -# if defined(__ANDROID__) && defined(__swap16) && !defined(swap16) -# define swap16 __swap16 -# elif !defined(swap16) -static inline uint16_t swap16(uint16_t x) { - return ((x & 0x00ff) << 8) | ((x & 0xff00) >> 8); -} -# endif -# if defined(__ANDROID__) && defined(__swap32) && !defined(swap32) -# define swap32 __swap32 -# elif !defined(swap32) -static inline uint32_t swap32(uint32_t x) { - x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >> 8); - return (x << 16) | (x >> 16); -} -# endif -# if defined(__ANDROID__) && defined(__swap64) && !defined(swap64) -# define swap64 __swap64 -# elif !defined(swap64) -static inline uint64_t swap64(uint64_t x) { - x = ((x & 0x00ff00ff00ff00ff) << 8) | ((x & 0xff00ff00ff00ff00) >> 8); - x = ((x & 0x0000ffff0000ffff) << 16) | ((x & 0xffff0000ffff0000) >> 16); - return (x << 32) | (x >> 32); -} -# endif -#endif /* __OpenBSD__ */ - -#if defined(__GNUC__) -#define UNUSED __attribute__((unused)) -#else -#define UNUSED -#endif -static inline void mem_inplace_ident(void *mem UNUSED, size_t n UNUSED) { } -#undef UNUSED - -static inline void mem_inplace_swap16(void *mem, size_t n) { - size_t i; - for (i = 0; i < n; i++) { - ((uint16_t *) mem)[i] = swap16(((const uint16_t *) mem)[i]); - } -} -static inline void mem_inplace_swap32(void *mem, size_t n) { - size_t i; - for (i = 0; i < n; i++) { - ((uint32_t *) mem)[i] = swap32(((const uint32_t *) mem)[i]); - } -} -static inline void mem_inplace_swap64(void *mem, size_t n) { - size_t i; - for (i = 0; i < n; i++) { - ((uint64_t *) mem)[i] = swap64(((const uint64_t *) mem)[i]); - } -} - -static inline void memcpy_ident16(void *dst, const void *src, size_t n) { - memcpy(dst, src, 2 * n); -} -static inline void memcpy_ident32(void *dst, const void *src, size_t n) { - memcpy(dst, src, 4 * n); -} -static inline void memcpy_ident64(void *dst, const void *src, size_t n) { - memcpy(dst, src, 8 * n); -} - -static inline void memcpy_swap16(void *dst, const void *src, size_t n) { - size_t i; - for (i = 0; i < n; i++) { - ((uint16_t *) dst)[i] = swap16(((const uint16_t *) src)[i]); - } -} -static inline void memcpy_swap32(void *dst, const void *src, size_t n) { - size_t i; - for (i = 0; i < n; i++) { - ((uint32_t *) dst)[i] = swap32(((const uint32_t *) src)[i]); - } -} -static inline void memcpy_swap64(void *dst, const void *src, size_t n) { - size_t i; - for (i = 0; i < n; i++) { - ((uint64_t *) dst)[i] = swap64(((const uint64_t *) src)[i]); - } -} - -#ifdef _MSC_VER -# define LITTLE_ENDIAN 1234 -# define BIG_ENDIAN 4321 -# define BYTE_ORDER LITTLE_ENDIAN -#endif - -#if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN) || !defined(BIG_ENDIAN) -static_assert(false, "BYTE_ORDER is undefined. Perhaps, GNU extensions are not enabled"); -#endif - -#if BYTE_ORDER == LITTLE_ENDIAN -#define SWAP16LE IDENT16 -#define SWAP16BE SWAP16 -#define swap16le ident16 -#define swap16be swap16 -#define mem_inplace_swap16le mem_inplace_ident -#define mem_inplace_swap16be mem_inplace_swap16 -#define memcpy_swap16le memcpy_ident16 -#define memcpy_swap16be memcpy_swap16 -#define SWAP32LE IDENT32 -#define SWAP32BE SWAP32 -#define swap32le ident32 -#define swap32be swap32 -#define mem_inplace_swap32le mem_inplace_ident -#define mem_inplace_swap32be mem_inplace_swap32 -#define memcpy_swap32le memcpy_ident32 -#define memcpy_swap32be memcpy_swap32 -#define SWAP64LE IDENT64 -#define SWAP64BE SWAP64 -#define swap64le ident64 -#define swap64be swap64 -#define mem_inplace_swap64le mem_inplace_ident -#define mem_inplace_swap64be mem_inplace_swap64 -#define memcpy_swap64le memcpy_ident64 -#define memcpy_swap64be memcpy_swap64 -#endif - -#if BYTE_ORDER == BIG_ENDIAN -#define SWAP16BE IDENT16 -#define SWAP16LE SWAP16 -#define swap16be ident16 -#define swap16le swap16 -#define mem_inplace_swap16be mem_inplace_ident -#define mem_inplace_swap16le mem_inplace_swap16 -#define memcpy_swap16be memcpy_ident16 -#define memcpy_swap16le memcpy_swap16 -#define SWAP32BE IDENT32 -#define SWAP32LE SWAP32 -#define swap32be ident32 -#define swap32le swap32 -#define mem_inplace_swap32be mem_inplace_ident -#define mem_inplace_swap32le mem_inplace_swap32 -#define memcpy_swap32be memcpy_ident32 -#define memcpy_swap32le memcpy_swap32 -#define SWAP64BE IDENT64 -#define SWAP64LE SWAP64 -#define swap64be ident64 -#define swap64le swap64 -#define mem_inplace_swap64be mem_inplace_ident -#define mem_inplace_swap64le mem_inplace_swap64 -#define memcpy_swap64be memcpy_ident64 -#define memcpy_swap64le memcpy_swap64 -#endif diff --git a/cryptonight/c/jh.c b/cryptonight/c/jh.c deleted file mode 100644 index e23ea09..0000000 --- a/cryptonight/c/jh.c +++ /dev/null @@ -1,369 +0,0 @@ -/*This program gives the 64-bit optimized bitslice implementation of JH using ANSI C - - -------------------------------- - Performance - - Microprocessor: Intel CORE 2 processor (Core 2 Duo Mobile T6600 2.2GHz) - Operating System: 64-bit Ubuntu 10.04 (Linux kernel 2.6.32-22-generic) - Speed for long message: - 1) 45.8 cycles/byte compiler: Intel C++ Compiler 11.1 compilation option: icc -O2 - 2) 56.8 cycles/byte compiler: gcc 4.4.3 compilation option: gcc -O3 - - -------------------------------- - Last Modified: January 16, 2011 -*/ - -#include "jh.h" - -#include -#include - -/*typedef unsigned long long uint64;*/ -typedef uint64_t uint64; - -/*define data alignment for different C compilers*/ -#if defined(__GNUC__) - #define DATA_ALIGN16(x) x __attribute__ ((aligned(16))) -#else - #define DATA_ALIGN16(x) __declspec(align(16)) x -#endif - - -typedef struct { - int hashbitlen; /*the message digest size*/ - unsigned long long databitlen; /*the message size in bits*/ - unsigned long long datasize_in_buffer; /*the size of the message remained in buffer; assumed to be multiple of 8bits except for the last partial block at the end of the message*/ - DATA_ALIGN16(uint64 x[8][2]); /*the 1024-bit state, ( x[i][0] || x[i][1] ) is the ith row of the state in the pseudocode*/ - unsigned char buffer[64]; /*the 512-bit message block to be hashed;*/ -} hashState; - - -/*The initial hash value H(0)*/ -const unsigned char JH224_H0[128]={0x2d,0xfe,0xdd,0x62,0xf9,0x9a,0x98,0xac,0xae,0x7c,0xac,0xd6,0x19,0xd6,0x34,0xe7,0xa4,0x83,0x10,0x5,0xbc,0x30,0x12,0x16,0xb8,0x60,0x38,0xc6,0xc9,0x66,0x14,0x94,0x66,0xd9,0x89,0x9f,0x25,0x80,0x70,0x6f,0xce,0x9e,0xa3,0x1b,0x1d,0x9b,0x1a,0xdc,0x11,0xe8,0x32,0x5f,0x7b,0x36,0x6e,0x10,0xf9,0x94,0x85,0x7f,0x2,0xfa,0x6,0xc1,0x1b,0x4f,0x1b,0x5c,0xd8,0xc8,0x40,0xb3,0x97,0xf6,0xa1,0x7f,0x6e,0x73,0x80,0x99,0xdc,0xdf,0x93,0xa5,0xad,0xea,0xa3,0xd3,0xa4,0x31,0xe8,0xde,0xc9,0x53,0x9a,0x68,0x22,0xb4,0xa9,0x8a,0xec,0x86,0xa1,0xe4,0xd5,0x74,0xac,0x95,0x9c,0xe5,0x6c,0xf0,0x15,0x96,0xd,0xea,0xb5,0xab,0x2b,0xbf,0x96,0x11,0xdc,0xf0,0xdd,0x64,0xea,0x6e}; -const unsigned char JH256_H0[128]={0xeb,0x98,0xa3,0x41,0x2c,0x20,0xd3,0xeb,0x92,0xcd,0xbe,0x7b,0x9c,0xb2,0x45,0xc1,0x1c,0x93,0x51,0x91,0x60,0xd4,0xc7,0xfa,0x26,0x0,0x82,0xd6,0x7e,0x50,0x8a,0x3,0xa4,0x23,0x9e,0x26,0x77,0x26,0xb9,0x45,0xe0,0xfb,0x1a,0x48,0xd4,0x1a,0x94,0x77,0xcd,0xb5,0xab,0x26,0x2,0x6b,0x17,0x7a,0x56,0xf0,0x24,0x42,0xf,0xff,0x2f,0xa8,0x71,0xa3,0x96,0x89,0x7f,0x2e,0x4d,0x75,0x1d,0x14,0x49,0x8,0xf7,0x7d,0xe2,0x62,0x27,0x76,0x95,0xf7,0x76,0x24,0x8f,0x94,0x87,0xd5,0xb6,0x57,0x47,0x80,0x29,0x6c,0x5c,0x5e,0x27,0x2d,0xac,0x8e,0xd,0x6c,0x51,0x84,0x50,0xc6,0x57,0x5,0x7a,0xf,0x7b,0xe4,0xd3,0x67,0x70,0x24,0x12,0xea,0x89,0xe3,0xab,0x13,0xd3,0x1c,0xd7,0x69}; -const unsigned char JH384_H0[128]={0x48,0x1e,0x3b,0xc6,0xd8,0x13,0x39,0x8a,0x6d,0x3b,0x5e,0x89,0x4a,0xde,0x87,0x9b,0x63,0xfa,0xea,0x68,0xd4,0x80,0xad,0x2e,0x33,0x2c,0xcb,0x21,0x48,0xf,0x82,0x67,0x98,0xae,0xc8,0x4d,0x90,0x82,0xb9,0x28,0xd4,0x55,0xea,0x30,0x41,0x11,0x42,0x49,0x36,0xf5,0x55,0xb2,0x92,0x48,0x47,0xec,0xc7,0x25,0xa,0x93,0xba,0xf4,0x3c,0xe1,0x56,0x9b,0x7f,0x8a,0x27,0xdb,0x45,0x4c,0x9e,0xfc,0xbd,0x49,0x63,0x97,0xaf,0xe,0x58,0x9f,0xc2,0x7d,0x26,0xaa,0x80,0xcd,0x80,0xc0,0x8b,0x8c,0x9d,0xeb,0x2e,0xda,0x8a,0x79,0x81,0xe8,0xf8,0xd5,0x37,0x3a,0xf4,0x39,0x67,0xad,0xdd,0xd1,0x7a,0x71,0xa9,0xb4,0xd3,0xbd,0xa4,0x75,0xd3,0x94,0x97,0x6c,0x3f,0xba,0x98,0x42,0x73,0x7f}; -const unsigned char JH512_H0[128]={0x6f,0xd1,0x4b,0x96,0x3e,0x0,0xaa,0x17,0x63,0x6a,0x2e,0x5,0x7a,0x15,0xd5,0x43,0x8a,0x22,0x5e,0x8d,0xc,0x97,0xef,0xb,0xe9,0x34,0x12,0x59,0xf2,0xb3,0xc3,0x61,0x89,0x1d,0xa0,0xc1,0x53,0x6f,0x80,0x1e,0x2a,0xa9,0x5,0x6b,0xea,0x2b,0x6d,0x80,0x58,0x8e,0xcc,0xdb,0x20,0x75,0xba,0xa6,0xa9,0xf,0x3a,0x76,0xba,0xf8,0x3b,0xf7,0x1,0x69,0xe6,0x5,0x41,0xe3,0x4a,0x69,0x46,0xb5,0x8a,0x8e,0x2e,0x6f,0xe6,0x5a,0x10,0x47,0xa7,0xd0,0xc1,0x84,0x3c,0x24,0x3b,0x6e,0x71,0xb1,0x2d,0x5a,0xc1,0x99,0xcf,0x57,0xf6,0xec,0x9d,0xb1,0xf8,0x56,0xa7,0x6,0x88,0x7c,0x57,0x16,0xb1,0x56,0xe3,0xc2,0xfc,0xdf,0xe6,0x85,0x17,0xfb,0x54,0x5a,0x46,0x78,0xcc,0x8c,0xdd,0x4b}; - -/*42 round constants, each round constant is 32-byte (256-bit)*/ -const unsigned char E8_bitslice_roundconstant[42][32]={ -{0x72,0xd5,0xde,0xa2,0xdf,0x15,0xf8,0x67,0x7b,0x84,0x15,0xa,0xb7,0x23,0x15,0x57,0x81,0xab,0xd6,0x90,0x4d,0x5a,0x87,0xf6,0x4e,0x9f,0x4f,0xc5,0xc3,0xd1,0x2b,0x40}, -{0xea,0x98,0x3a,0xe0,0x5c,0x45,0xfa,0x9c,0x3,0xc5,0xd2,0x99,0x66,0xb2,0x99,0x9a,0x66,0x2,0x96,0xb4,0xf2,0xbb,0x53,0x8a,0xb5,0x56,0x14,0x1a,0x88,0xdb,0xa2,0x31}, -{0x3,0xa3,0x5a,0x5c,0x9a,0x19,0xe,0xdb,0x40,0x3f,0xb2,0xa,0x87,0xc1,0x44,0x10,0x1c,0x5,0x19,0x80,0x84,0x9e,0x95,0x1d,0x6f,0x33,0xeb,0xad,0x5e,0xe7,0xcd,0xdc}, -{0x10,0xba,0x13,0x92,0x2,0xbf,0x6b,0x41,0xdc,0x78,0x65,0x15,0xf7,0xbb,0x27,0xd0,0xa,0x2c,0x81,0x39,0x37,0xaa,0x78,0x50,0x3f,0x1a,0xbf,0xd2,0x41,0x0,0x91,0xd3}, -{0x42,0x2d,0x5a,0xd,0xf6,0xcc,0x7e,0x90,0xdd,0x62,0x9f,0x9c,0x92,0xc0,0x97,0xce,0x18,0x5c,0xa7,0xb,0xc7,0x2b,0x44,0xac,0xd1,0xdf,0x65,0xd6,0x63,0xc6,0xfc,0x23}, -{0x97,0x6e,0x6c,0x3,0x9e,0xe0,0xb8,0x1a,0x21,0x5,0x45,0x7e,0x44,0x6c,0xec,0xa8,0xee,0xf1,0x3,0xbb,0x5d,0x8e,0x61,0xfa,0xfd,0x96,0x97,0xb2,0x94,0x83,0x81,0x97}, -{0x4a,0x8e,0x85,0x37,0xdb,0x3,0x30,0x2f,0x2a,0x67,0x8d,0x2d,0xfb,0x9f,0x6a,0x95,0x8a,0xfe,0x73,0x81,0xf8,0xb8,0x69,0x6c,0x8a,0xc7,0x72,0x46,0xc0,0x7f,0x42,0x14}, -{0xc5,0xf4,0x15,0x8f,0xbd,0xc7,0x5e,0xc4,0x75,0x44,0x6f,0xa7,0x8f,0x11,0xbb,0x80,0x52,0xde,0x75,0xb7,0xae,0xe4,0x88,0xbc,0x82,0xb8,0x0,0x1e,0x98,0xa6,0xa3,0xf4}, -{0x8e,0xf4,0x8f,0x33,0xa9,0xa3,0x63,0x15,0xaa,0x5f,0x56,0x24,0xd5,0xb7,0xf9,0x89,0xb6,0xf1,0xed,0x20,0x7c,0x5a,0xe0,0xfd,0x36,0xca,0xe9,0x5a,0x6,0x42,0x2c,0x36}, -{0xce,0x29,0x35,0x43,0x4e,0xfe,0x98,0x3d,0x53,0x3a,0xf9,0x74,0x73,0x9a,0x4b,0xa7,0xd0,0xf5,0x1f,0x59,0x6f,0x4e,0x81,0x86,0xe,0x9d,0xad,0x81,0xaf,0xd8,0x5a,0x9f}, -{0xa7,0x5,0x6,0x67,0xee,0x34,0x62,0x6a,0x8b,0xb,0x28,0xbe,0x6e,0xb9,0x17,0x27,0x47,0x74,0x7,0x26,0xc6,0x80,0x10,0x3f,0xe0,0xa0,0x7e,0x6f,0xc6,0x7e,0x48,0x7b}, -{0xd,0x55,0xa,0xa5,0x4a,0xf8,0xa4,0xc0,0x91,0xe3,0xe7,0x9f,0x97,0x8e,0xf1,0x9e,0x86,0x76,0x72,0x81,0x50,0x60,0x8d,0xd4,0x7e,0x9e,0x5a,0x41,0xf3,0xe5,0xb0,0x62}, -{0xfc,0x9f,0x1f,0xec,0x40,0x54,0x20,0x7a,0xe3,0xe4,0x1a,0x0,0xce,0xf4,0xc9,0x84,0x4f,0xd7,0x94,0xf5,0x9d,0xfa,0x95,0xd8,0x55,0x2e,0x7e,0x11,0x24,0xc3,0x54,0xa5}, -{0x5b,0xdf,0x72,0x28,0xbd,0xfe,0x6e,0x28,0x78,0xf5,0x7f,0xe2,0xf,0xa5,0xc4,0xb2,0x5,0x89,0x7c,0xef,0xee,0x49,0xd3,0x2e,0x44,0x7e,0x93,0x85,0xeb,0x28,0x59,0x7f}, -{0x70,0x5f,0x69,0x37,0xb3,0x24,0x31,0x4a,0x5e,0x86,0x28,0xf1,0x1d,0xd6,0xe4,0x65,0xc7,0x1b,0x77,0x4,0x51,0xb9,0x20,0xe7,0x74,0xfe,0x43,0xe8,0x23,0xd4,0x87,0x8a}, -{0x7d,0x29,0xe8,0xa3,0x92,0x76,0x94,0xf2,0xdd,0xcb,0x7a,0x9,0x9b,0x30,0xd9,0xc1,0x1d,0x1b,0x30,0xfb,0x5b,0xdc,0x1b,0xe0,0xda,0x24,0x49,0x4f,0xf2,0x9c,0x82,0xbf}, -{0xa4,0xe7,0xba,0x31,0xb4,0x70,0xbf,0xff,0xd,0x32,0x44,0x5,0xde,0xf8,0xbc,0x48,0x3b,0xae,0xfc,0x32,0x53,0xbb,0xd3,0x39,0x45,0x9f,0xc3,0xc1,0xe0,0x29,0x8b,0xa0}, -{0xe5,0xc9,0x5,0xfd,0xf7,0xae,0x9,0xf,0x94,0x70,0x34,0x12,0x42,0x90,0xf1,0x34,0xa2,0x71,0xb7,0x1,0xe3,0x44,0xed,0x95,0xe9,0x3b,0x8e,0x36,0x4f,0x2f,0x98,0x4a}, -{0x88,0x40,0x1d,0x63,0xa0,0x6c,0xf6,0x15,0x47,0xc1,0x44,0x4b,0x87,0x52,0xaf,0xff,0x7e,0xbb,0x4a,0xf1,0xe2,0xa,0xc6,0x30,0x46,0x70,0xb6,0xc5,0xcc,0x6e,0x8c,0xe6}, -{0xa4,0xd5,0xa4,0x56,0xbd,0x4f,0xca,0x0,0xda,0x9d,0x84,0x4b,0xc8,0x3e,0x18,0xae,0x73,0x57,0xce,0x45,0x30,0x64,0xd1,0xad,0xe8,0xa6,0xce,0x68,0x14,0x5c,0x25,0x67}, -{0xa3,0xda,0x8c,0xf2,0xcb,0xe,0xe1,0x16,0x33,0xe9,0x6,0x58,0x9a,0x94,0x99,0x9a,0x1f,0x60,0xb2,0x20,0xc2,0x6f,0x84,0x7b,0xd1,0xce,0xac,0x7f,0xa0,0xd1,0x85,0x18}, -{0x32,0x59,0x5b,0xa1,0x8d,0xdd,0x19,0xd3,0x50,0x9a,0x1c,0xc0,0xaa,0xa5,0xb4,0x46,0x9f,0x3d,0x63,0x67,0xe4,0x4,0x6b,0xba,0xf6,0xca,0x19,0xab,0xb,0x56,0xee,0x7e}, -{0x1f,0xb1,0x79,0xea,0xa9,0x28,0x21,0x74,0xe9,0xbd,0xf7,0x35,0x3b,0x36,0x51,0xee,0x1d,0x57,0xac,0x5a,0x75,0x50,0xd3,0x76,0x3a,0x46,0xc2,0xfe,0xa3,0x7d,0x70,0x1}, -{0xf7,0x35,0xc1,0xaf,0x98,0xa4,0xd8,0x42,0x78,0xed,0xec,0x20,0x9e,0x6b,0x67,0x79,0x41,0x83,0x63,0x15,0xea,0x3a,0xdb,0xa8,0xfa,0xc3,0x3b,0x4d,0x32,0x83,0x2c,0x83}, -{0xa7,0x40,0x3b,0x1f,0x1c,0x27,0x47,0xf3,0x59,0x40,0xf0,0x34,0xb7,0x2d,0x76,0x9a,0xe7,0x3e,0x4e,0x6c,0xd2,0x21,0x4f,0xfd,0xb8,0xfd,0x8d,0x39,0xdc,0x57,0x59,0xef}, -{0x8d,0x9b,0xc,0x49,0x2b,0x49,0xeb,0xda,0x5b,0xa2,0xd7,0x49,0x68,0xf3,0x70,0xd,0x7d,0x3b,0xae,0xd0,0x7a,0x8d,0x55,0x84,0xf5,0xa5,0xe9,0xf0,0xe4,0xf8,0x8e,0x65}, -{0xa0,0xb8,0xa2,0xf4,0x36,0x10,0x3b,0x53,0xc,0xa8,0x7,0x9e,0x75,0x3e,0xec,0x5a,0x91,0x68,0x94,0x92,0x56,0xe8,0x88,0x4f,0x5b,0xb0,0x5c,0x55,0xf8,0xba,0xbc,0x4c}, -{0xe3,0xbb,0x3b,0x99,0xf3,0x87,0x94,0x7b,0x75,0xda,0xf4,0xd6,0x72,0x6b,0x1c,0x5d,0x64,0xae,0xac,0x28,0xdc,0x34,0xb3,0x6d,0x6c,0x34,0xa5,0x50,0xb8,0x28,0xdb,0x71}, -{0xf8,0x61,0xe2,0xf2,0x10,0x8d,0x51,0x2a,0xe3,0xdb,0x64,0x33,0x59,0xdd,0x75,0xfc,0x1c,0xac,0xbc,0xf1,0x43,0xce,0x3f,0xa2,0x67,0xbb,0xd1,0x3c,0x2,0xe8,0x43,0xb0}, -{0x33,0xa,0x5b,0xca,0x88,0x29,0xa1,0x75,0x7f,0x34,0x19,0x4d,0xb4,0x16,0x53,0x5c,0x92,0x3b,0x94,0xc3,0xe,0x79,0x4d,0x1e,0x79,0x74,0x75,0xd7,0xb6,0xee,0xaf,0x3f}, -{0xea,0xa8,0xd4,0xf7,0xbe,0x1a,0x39,0x21,0x5c,0xf4,0x7e,0x9,0x4c,0x23,0x27,0x51,0x26,0xa3,0x24,0x53,0xba,0x32,0x3c,0xd2,0x44,0xa3,0x17,0x4a,0x6d,0xa6,0xd5,0xad}, -{0xb5,0x1d,0x3e,0xa6,0xaf,0xf2,0xc9,0x8,0x83,0x59,0x3d,0x98,0x91,0x6b,0x3c,0x56,0x4c,0xf8,0x7c,0xa1,0x72,0x86,0x60,0x4d,0x46,0xe2,0x3e,0xcc,0x8,0x6e,0xc7,0xf6}, -{0x2f,0x98,0x33,0xb3,0xb1,0xbc,0x76,0x5e,0x2b,0xd6,0x66,0xa5,0xef,0xc4,0xe6,0x2a,0x6,0xf4,0xb6,0xe8,0xbe,0xc1,0xd4,0x36,0x74,0xee,0x82,0x15,0xbc,0xef,0x21,0x63}, -{0xfd,0xc1,0x4e,0xd,0xf4,0x53,0xc9,0x69,0xa7,0x7d,0x5a,0xc4,0x6,0x58,0x58,0x26,0x7e,0xc1,0x14,0x16,0x6,0xe0,0xfa,0x16,0x7e,0x90,0xaf,0x3d,0x28,0x63,0x9d,0x3f}, -{0xd2,0xc9,0xf2,0xe3,0x0,0x9b,0xd2,0xc,0x5f,0xaa,0xce,0x30,0xb7,0xd4,0xc,0x30,0x74,0x2a,0x51,0x16,0xf2,0xe0,0x32,0x98,0xd,0xeb,0x30,0xd8,0xe3,0xce,0xf8,0x9a}, -{0x4b,0xc5,0x9e,0x7b,0xb5,0xf1,0x79,0x92,0xff,0x51,0xe6,0x6e,0x4,0x86,0x68,0xd3,0x9b,0x23,0x4d,0x57,0xe6,0x96,0x67,0x31,0xcc,0xe6,0xa6,0xf3,0x17,0xa,0x75,0x5}, -{0xb1,0x76,0x81,0xd9,0x13,0x32,0x6c,0xce,0x3c,0x17,0x52,0x84,0xf8,0x5,0xa2,0x62,0xf4,0x2b,0xcb,0xb3,0x78,0x47,0x15,0x47,0xff,0x46,0x54,0x82,0x23,0x93,0x6a,0x48}, -{0x38,0xdf,0x58,0x7,0x4e,0x5e,0x65,0x65,0xf2,0xfc,0x7c,0x89,0xfc,0x86,0x50,0x8e,0x31,0x70,0x2e,0x44,0xd0,0xb,0xca,0x86,0xf0,0x40,0x9,0xa2,0x30,0x78,0x47,0x4e}, -{0x65,0xa0,0xee,0x39,0xd1,0xf7,0x38,0x83,0xf7,0x5e,0xe9,0x37,0xe4,0x2c,0x3a,0xbd,0x21,0x97,0xb2,0x26,0x1,0x13,0xf8,0x6f,0xa3,0x44,0xed,0xd1,0xef,0x9f,0xde,0xe7}, -{0x8b,0xa0,0xdf,0x15,0x76,0x25,0x92,0xd9,0x3c,0x85,0xf7,0xf6,0x12,0xdc,0x42,0xbe,0xd8,0xa7,0xec,0x7c,0xab,0x27,0xb0,0x7e,0x53,0x8d,0x7d,0xda,0xaa,0x3e,0xa8,0xde}, -{0xaa,0x25,0xce,0x93,0xbd,0x2,0x69,0xd8,0x5a,0xf6,0x43,0xfd,0x1a,0x73,0x8,0xf9,0xc0,0x5f,0xef,0xda,0x17,0x4a,0x19,0xa5,0x97,0x4d,0x66,0x33,0x4c,0xfd,0x21,0x6a}, -{0x35,0xb4,0x98,0x31,0xdb,0x41,0x15,0x70,0xea,0x1e,0xf,0xbb,0xed,0xcd,0x54,0x9b,0x9a,0xd0,0x63,0xa1,0x51,0x97,0x40,0x72,0xf6,0x75,0x9d,0xbf,0x91,0x47,0x6f,0xe2}}; - - -static void E8(hashState *state); /*The bijective function E8, in bitslice form*/ -static void F8(hashState *state); /*The compression function F8 */ - -/*The API functions*/ -static HashReturn Init(hashState *state, int hashbitlen); -static HashReturn Update(hashState *state, const BitSequence *data, DataLength databitlen); -static HashReturn Final(hashState *state, BitSequence *hashval); -HashReturn jh_hash(int hashbitlen, const BitSequence *data,DataLength databitlen, BitSequence *hashval); - -/*swapping bit 2i with bit 2i+1 of 64-bit x*/ -#define SWAP1(x) (x) = ((((x) & 0x5555555555555555ULL) << 1) | (((x) & 0xaaaaaaaaaaaaaaaaULL) >> 1)); -/*swapping bits 4i||4i+1 with bits 4i+2||4i+3 of 64-bit x*/ -#define SWAP2(x) (x) = ((((x) & 0x3333333333333333ULL) << 2) | (((x) & 0xccccccccccccccccULL) >> 2)); -/*swapping bits 8i||8i+1||8i+2||8i+3 with bits 8i+4||8i+5||8i+6||8i+7 of 64-bit x*/ -#define SWAP4(x) (x) = ((((x) & 0x0f0f0f0f0f0f0f0fULL) << 4) | (((x) & 0xf0f0f0f0f0f0f0f0ULL) >> 4)); -/*swapping bits 16i||16i+1||......||16i+7 with bits 16i+8||16i+9||......||16i+15 of 64-bit x*/ -#define SWAP8(x) (x) = ((((x) & 0x00ff00ff00ff00ffULL) << 8) | (((x) & 0xff00ff00ff00ff00ULL) >> 8)); -/*swapping bits 32i||32i+1||......||32i+15 with bits 32i+16||32i+17||......||32i+31 of 64-bit x*/ -#define SWAP16(x) (x) = ((((x) & 0x0000ffff0000ffffULL) << 16) | (((x) & 0xffff0000ffff0000ULL) >> 16)); -/*swapping bits 64i||64i+1||......||64i+31 with bits 64i+32||64i+33||......||64i+63 of 64-bit x*/ -#define SWAP32(x) (x) = (((x) << 32) | ((x) >> 32)); - -/*The MDS transform*/ -#define L(m0,m1,m2,m3,m4,m5,m6,m7) \ - (m4) ^= (m1); \ - (m5) ^= (m2); \ - (m6) ^= (m0) ^ (m3); \ - (m7) ^= (m0); \ - (m0) ^= (m5); \ - (m1) ^= (m6); \ - (m2) ^= (m4) ^ (m7); \ - (m3) ^= (m4); - -/*Two Sboxes are computed in parallel, each Sbox implements S0 and S1, selected by a constant bit*/ -/*The reason to compute two Sboxes in parallel is to try to fully utilize the parallel processing power*/ -#define SS(m0,m1,m2,m3,m4,m5,m6,m7,cc0,cc1) \ - m3 = ~(m3); \ - m7 = ~(m7); \ - m0 ^= ((~(m2)) & (cc0)); \ - m4 ^= ((~(m6)) & (cc1)); \ - temp0 = (cc0) ^ ((m0) & (m1));\ - temp1 = (cc1) ^ ((m4) & (m5));\ - m0 ^= ((m2) & (m3)); \ - m4 ^= ((m6) & (m7)); \ - m3 ^= ((~(m1)) & (m2)); \ - m7 ^= ((~(m5)) & (m6)); \ - m1 ^= ((m0) & (m2)); \ - m5 ^= ((m4) & (m6)); \ - m2 ^= ((m0) & (~(m3))); \ - m6 ^= ((m4) & (~(m7))); \ - m0 ^= ((m1) | (m3)); \ - m4 ^= ((m5) | (m7)); \ - m3 ^= ((m1) & (m2)); \ - m7 ^= ((m5) & (m6)); \ - m1 ^= (temp0 & (m0)); \ - m5 ^= (temp1 & (m4)); \ - m2 ^= temp0; \ - m6 ^= temp1; - -/*The bijective function E8, in bitslice form*/ -static void E8(hashState *state) -{ - uint64 i,roundnumber,temp0,temp1; - - for (roundnumber = 0; roundnumber < 42; roundnumber = roundnumber+7) { - /*round 7*roundnumber+0: Sbox, MDS and Swapping layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+0])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+0])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - SWAP1(state->x[1][i]); SWAP1(state->x[3][i]); SWAP1(state->x[5][i]); SWAP1(state->x[7][i]); - } - - /*round 7*roundnumber+1: Sbox, MDS and Swapping layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+1])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+1])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - SWAP2(state->x[1][i]); SWAP2(state->x[3][i]); SWAP2(state->x[5][i]); SWAP2(state->x[7][i]); - } - - /*round 7*roundnumber+2: Sbox, MDS and Swapping layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+2])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+2])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - SWAP4(state->x[1][i]); SWAP4(state->x[3][i]); SWAP4(state->x[5][i]); SWAP4(state->x[7][i]); - } - - /*round 7*roundnumber+3: Sbox, MDS and Swapping layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+3])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+3])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - SWAP8(state->x[1][i]); SWAP8(state->x[3][i]); SWAP8(state->x[5][i]); SWAP8(state->x[7][i]); - } - - /*round 7*roundnumber+4: Sbox, MDS and Swapping layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+4])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+4])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - SWAP16(state->x[1][i]); SWAP16(state->x[3][i]); SWAP16(state->x[5][i]); SWAP16(state->x[7][i]); - } - - /*round 7*roundnumber+5: Sbox, MDS and Swapping layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+5])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+5])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - SWAP32(state->x[1][i]); SWAP32(state->x[3][i]); SWAP32(state->x[5][i]); SWAP32(state->x[7][i]); - } - - /*round 7*roundnumber+6: Sbox and MDS layers*/ - for (i = 0; i < 2; i++) { - SS(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i],((uint64*)E8_bitslice_roundconstant[roundnumber+6])[i],((uint64*)E8_bitslice_roundconstant[roundnumber+6])[i+2] ); - L(state->x[0][i],state->x[2][i],state->x[4][i],state->x[6][i],state->x[1][i],state->x[3][i],state->x[5][i],state->x[7][i]); - } - /*round 7*roundnumber+6: swapping layer*/ - for (i = 1; i < 8; i = i+2) { - temp0 = state->x[i][0]; state->x[i][0] = state->x[i][1]; state->x[i][1] = temp0; - } - } - -} - -/*The compression function F8 */ -static void F8(hashState *state) -{ - uint64_t* x = (uint64_t*)state->x; - const uint64_t* buf = (uint64*)state->buffer; - - /*xor the 512-bit message with the fist half of the 1024-bit hash state*/ - for (int i = 0; i < 8; ++i) x[i] ^= buf[i]; - - /*the bijective function E8 */ - E8(state); - - /*xor the 512-bit message with the second half of the 1024-bit hash state*/ - for (int i = 0; i < 8; ++i) x[i + 8] ^= buf[i]; -} - -/*before hashing a message, initialize the hash state as H0 */ -static HashReturn Init(hashState *state, int hashbitlen) -{ - state->databitlen = 0; - state->datasize_in_buffer = 0; - - /*initialize the initial hash value of JH*/ - state->hashbitlen = hashbitlen; - - /*load the intital hash value into state*/ - switch (hashbitlen) - { - case 224: memcpy(state->x,JH224_H0,128); break; - case 256: memcpy(state->x,JH256_H0,128); break; - case 384: memcpy(state->x,JH384_H0,128); break; - default: - case 512: memcpy(state->x,JH512_H0,128); break; - } - - return(SUCCESS); -} - - -/*hash each 512-bit message block, except the last partial block*/ -static HashReturn Update(hashState *state, const BitSequence *data, DataLength databitlen) -{ - DataLength index; /*the starting address of the data to be compressed*/ - - state->databitlen += databitlen; - index = 0; - - /*if there is remaining data in the buffer, fill it to a full message block first*/ - /*we assume that the size of the data in the buffer is the multiple of 8 bits if it is not at the end of a message*/ - - /*There is data in the buffer, but the incoming data is insufficient for a full block*/ - if ( (state->datasize_in_buffer > 0 ) && (( state->datasize_in_buffer + databitlen) < 512) ) { - if ( (databitlen & 7) == 0 ) { - memcpy(state->buffer + (state->datasize_in_buffer >> 3), data, 64-(state->datasize_in_buffer >> 3)) ; - } - else memcpy(state->buffer + (state->datasize_in_buffer >> 3), data, 64-(state->datasize_in_buffer >> 3)+1) ; - state->datasize_in_buffer += databitlen; - databitlen = 0; - } - - /*There is data in the buffer, and the incoming data is sufficient for a full block*/ - if ( (state->datasize_in_buffer > 0 ) && (( state->datasize_in_buffer + databitlen) >= 512) ) { - memcpy( state->buffer + (state->datasize_in_buffer >> 3), data, 64-(state->datasize_in_buffer >> 3) ) ; - index = 64-(state->datasize_in_buffer >> 3); - databitlen = databitlen - (512 - state->datasize_in_buffer); - F8(state); - state->datasize_in_buffer = 0; - } - - /*hash the remaining full message blocks*/ - for ( ; databitlen >= 512; index = index+64, databitlen = databitlen - 512) { - memcpy(state->buffer, data+index, 64); - F8(state); - } - - /*store the partial block into buffer, assume that -- if part of the last byte is not part of the message, then that part consists of 0 bits*/ - if ( databitlen > 0) { - if ((databitlen & 7) == 0) - memcpy(state->buffer, data+index, (databitlen & 0x1ff) >> 3); - else - memcpy(state->buffer, data+index, ((databitlen & 0x1ff) >> 3)+1); - state->datasize_in_buffer = databitlen; - } - - return(SUCCESS); -} - -/*pad the message, process the padded block(s), truncate the hash value H to obtain the message digest*/ -static HashReturn Final(hashState *state, BitSequence *hashval) -{ - unsigned int i; - - if ( (state->databitlen & 0x1ff) == 0 ) { - /*pad the message when databitlen is multiple of 512 bits, then process the padded block*/ - memset(state->buffer, 0, 64); - state->buffer[0] = 0x80; - state->buffer[63] = state->databitlen & 0xff; - state->buffer[62] = (state->databitlen >> 8) & 0xff; - state->buffer[61] = (state->databitlen >> 16) & 0xff; - state->buffer[60] = (state->databitlen >> 24) & 0xff; - state->buffer[59] = (state->databitlen >> 32) & 0xff; - state->buffer[58] = (state->databitlen >> 40) & 0xff; - state->buffer[57] = (state->databitlen >> 48) & 0xff; - state->buffer[56] = (state->databitlen >> 56) & 0xff; - F8(state); - } - else { - /*set the rest of the bytes in the buffer to 0*/ - if ( (state->datasize_in_buffer & 7) == 0) - for (i = (state->databitlen & 0x1ff) >> 3; i < 64; i++) state->buffer[i] = 0; - else - for (i = ((state->databitlen & 0x1ff) >> 3)+1; i < 64; i++) state->buffer[i] = 0; - - /*pad and process the partial block when databitlen is not multiple of 512 bits, then hash the padded blocks*/ - state->buffer[((state->databitlen & 0x1ff) >> 3)] |= 1 << (7- (state->databitlen & 7)); - - F8(state); - memset(state->buffer, 0, 64); - state->buffer[63] = state->databitlen & 0xff; - state->buffer[62] = (state->databitlen >> 8) & 0xff; - state->buffer[61] = (state->databitlen >> 16) & 0xff; - state->buffer[60] = (state->databitlen >> 24) & 0xff; - state->buffer[59] = (state->databitlen >> 32) & 0xff; - state->buffer[58] = (state->databitlen >> 40) & 0xff; - state->buffer[57] = (state->databitlen >> 48) & 0xff; - state->buffer[56] = (state->databitlen >> 56) & 0xff; - F8(state); - } - - /*truncating the final hash value to generate the message digest*/ - switch(state->hashbitlen) { - case 224: memcpy(hashval,(unsigned char*)state->x+64+36,28); break; - case 256: memcpy(hashval,(unsigned char*)state->x+64+32,32); break; - case 384: memcpy(hashval,(unsigned char*)state->x+64+16,48); break; - case 512: memcpy(hashval,(unsigned char*)state->x+64,64); break; - } - - return(SUCCESS); -} - -/* hash a message, - three inputs: message digest size in bits (hashbitlen); message (data); message length in bits (databitlen) - one output: message digest (hashval) -*/ -HashReturn jh_hash(int hashbitlen, const BitSequence *data,DataLength databitlen, BitSequence *hashval) -{ - hashState state; - - if ( hashbitlen == 224 || hashbitlen == 256 || hashbitlen == 384 || hashbitlen == 512 ) { - Init(&state, hashbitlen); - Update(&state, data, databitlen); - Final(&state, hashval); - return SUCCESS; - } - else - return(BAD_HASHLEN); -} diff --git a/cryptonight/c/jh.h b/cryptonight/c/jh.h deleted file mode 100644 index 627a9ff..0000000 --- a/cryptonight/c/jh.h +++ /dev/null @@ -1,21 +0,0 @@ -/*This program gives the 64-bit optimized bitslice implementation of JH using ANSI C - - -------------------------------- - Performance - - Microprocessor: Intel CORE 2 processor (Core 2 Duo Mobile T6600 2.2GHz) - Operating System: 64-bit Ubuntu 10.04 (Linux kernel 2.6.32-22-generic) - Speed for long message: - 1) 45.8 cycles/byte compiler: Intel C++ Compiler 11.1 compilation option: icc -O2 - 2) 56.8 cycles/byte compiler: gcc 4.4.3 compilation option: gcc -O3 - - -------------------------------- - Last Modified: January 16, 2011 -*/ -#pragma once - -typedef unsigned char BitSequence; -typedef unsigned long long DataLength; -typedef enum {SUCCESS = 0, FAIL = 1, BAD_HASHLEN = 2} HashReturn; - -HashReturn jh_hash(int hashbitlen, const BitSequence *data, DataLength databitlen, BitSequence *hashval); diff --git a/cryptonight/c/keccak.c b/cryptonight/c/keccak.c deleted file mode 100644 index 6616d35..0000000 --- a/cryptonight/c/keccak.c +++ /dev/null @@ -1,239 +0,0 @@ -// keccak.c -// 19-Nov-11 Markku-Juhani O. Saarinen -// A baseline Keccak (3rd round) implementation. - -#include -#include -#include -#include "int-util.h" -#include "hash-ops.h" -#include "keccak.h" - -static void local_abort(const char *msg) -{ - fprintf(stderr, "%s\n", msg); -#ifdef NDEBUG - _exit(1); -#else - abort(); -#endif -} - -const uint64_t keccakf_rndc[24] = -{ - 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, - 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, - 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, - 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, - 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, - 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, - 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 -}; - -// update the state with given number of rounds - -void keccakf(uint64_t st[25], int rounds) -{ - int round; - uint64_t t, bc[5]; - - for (round = 0; round < rounds; ++round) { - // Theta - bc[0] = st[0] ^ st[5] ^ st[10] ^ st[15] ^ st[20]; - bc[1] = st[1] ^ st[6] ^ st[11] ^ st[16] ^ st[21]; - bc[2] = st[2] ^ st[7] ^ st[12] ^ st[17] ^ st[22]; - bc[3] = st[3] ^ st[8] ^ st[13] ^ st[18] ^ st[23]; - bc[4] = st[4] ^ st[9] ^ st[14] ^ st[19] ^ st[24]; - -#define THETA(i) { \ - t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1); \ - st[i ] ^= t; \ - st[i + 5] ^= t; \ - st[i + 10] ^= t; \ - st[i + 15] ^= t; \ - st[i + 20] ^= t; \ - } - - THETA(0); - THETA(1); - THETA(2); - THETA(3); - THETA(4); - - // Rho Pi - t = st[1]; - st[ 1] = ROTL64(st[ 6], 44); - st[ 6] = ROTL64(st[ 9], 20); - st[ 9] = ROTL64(st[22], 61); - st[22] = ROTL64(st[14], 39); - st[14] = ROTL64(st[20], 18); - st[20] = ROTL64(st[ 2], 62); - st[ 2] = ROTL64(st[12], 43); - st[12] = ROTL64(st[13], 25); - st[13] = ROTL64(st[19], 8); - st[19] = ROTL64(st[23], 56); - st[23] = ROTL64(st[15], 41); - st[15] = ROTL64(st[ 4], 27); - st[ 4] = ROTL64(st[24], 14); - st[24] = ROTL64(st[21], 2); - st[21] = ROTL64(st[ 8], 55); - st[ 8] = ROTL64(st[16], 45); - st[16] = ROTL64(st[ 5], 36); - st[ 5] = ROTL64(st[ 3], 28); - st[ 3] = ROTL64(st[18], 21); - st[18] = ROTL64(st[17], 15); - st[17] = ROTL64(st[11], 10); - st[11] = ROTL64(st[ 7], 6); - st[ 7] = ROTL64(st[10], 3); - st[10] = ROTL64(t, 1); - - // Chi -#define CHI(j) { \ - const uint64_t st0 = st[j ]; \ - const uint64_t st1 = st[j + 1]; \ - const uint64_t st2 = st[j + 2]; \ - const uint64_t st3 = st[j + 3]; \ - const uint64_t st4 = st[j + 4]; \ - st[j ] ^= ~st1 & st2; \ - st[j + 1] ^= ~st2 & st3; \ - st[j + 2] ^= ~st3 & st4; \ - st[j + 3] ^= ~st4 & st0; \ - st[j + 4] ^= ~st0 & st1; \ - } - - CHI( 0); - CHI( 5); - CHI(10); - CHI(15); - CHI(20); - - // Iota - st[0] ^= keccakf_rndc[round]; - } -} - -// compute a keccak hash (md) of given byte length from "in" -typedef uint64_t state_t[25]; - -void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) -{ - state_t st; - uint8_t temp[144]; - size_t i, rsiz, rsizw; - - static_assert(HASH_DATA_AREA <= sizeof(temp), "Bad keccak preconditions"); - if (mdlen <= 0 || (mdlen >= 100 && sizeof(st) != (size_t)mdlen)) - { - local_abort("Bad keccak use"); - } - - rsiz = sizeof(state_t) == mdlen ? HASH_DATA_AREA : 200 - 2 * mdlen; - rsizw = rsiz / 8; - - memset(st, 0, sizeof(st)); - - for ( ; inlen >= rsiz; inlen -= rsiz, in += rsiz) { - for (i = 0; i < rsizw; i++) { - uint64_t ina; - memcpy(&ina, in + i * 8, 8); - st[i] ^= swap64le(ina); - } - keccakf(st, KECCAK_ROUNDS); - } - - // last block and padding - if (inlen + 1 >= sizeof(temp) || inlen > rsiz || rsiz - inlen + inlen + 1 >= sizeof(temp) || rsiz == 0 || rsiz - 1 >= sizeof(temp) || rsizw * 8 > sizeof(temp)) - { - local_abort("Bad keccak use"); - } - - if (inlen > 0) - memcpy(temp, in, inlen); - temp[inlen++] = 1; - memset(temp + inlen, 0, rsiz - inlen); - temp[rsiz - 1] |= 0x80; - - for (i = 0; i < rsizw; i++) - st[i] ^= swap64le(((uint64_t *) temp)[i]); - - keccakf(st, KECCAK_ROUNDS); - - if (((size_t)mdlen % sizeof(uint64_t)) != 0) - { - local_abort("Bad keccak use"); - } - memcpy_swap64le(md, st, mdlen/sizeof(uint64_t)); -} - -void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) -{ - keccak(in, inlen, md, sizeof(state_t)); -} - -#define KECCAK_FINALIZED 0x80000000 -#define KECCAK_BLOCKLEN 136 -#define KECCAK_WORDS 17 -#define KECCAK_DIGESTSIZE 32 -#define KECCAK_PROCESS_BLOCK(st, block) { \ - for (int i_ = 0; i_ < KECCAK_WORDS; i_++){ \ - ((st))[i_] ^= swap64le(((block))[i_]); \ - }; \ - keccakf(st, KECCAK_ROUNDS); } - - -void keccak_init(KECCAK_CTX * ctx){ - memset(ctx, 0, sizeof(KECCAK_CTX)); -} - -void keccak_update(KECCAK_CTX * ctx, const uint8_t *in, size_t inlen){ - if (ctx->rest & KECCAK_FINALIZED) { - local_abort("Bad keccak use"); - } - - const size_t idx = ctx->rest; - ctx->rest = (ctx->rest + inlen) % KECCAK_BLOCKLEN; - - // fill partial block - if (idx) { - size_t left = KECCAK_BLOCKLEN - idx; - memcpy((char*)ctx->message + idx, in, (inlen < left ? inlen : left)); - if (inlen < left) return; - - KECCAK_PROCESS_BLOCK(ctx->hash, ctx->message); - - in += left; - inlen -= left; - } - - while (inlen >= KECCAK_BLOCKLEN) { - memcpy(ctx->message, in, KECCAK_BLOCKLEN); - - KECCAK_PROCESS_BLOCK(ctx->hash, ctx->message); - in += KECCAK_BLOCKLEN; - inlen -= KECCAK_BLOCKLEN; - } - if (inlen) { - memcpy(ctx->message, in, inlen); - } -} - -void keccak_finish(KECCAK_CTX * ctx, uint8_t *md){ - if (!(ctx->rest & KECCAK_FINALIZED)) - { - // clear the rest of the data queue - memset((char*)ctx->message + ctx->rest, 0, KECCAK_BLOCKLEN - ctx->rest); - ((char*)ctx->message)[ctx->rest] |= 0x01; - ((char*)ctx->message)[KECCAK_BLOCKLEN - 1] |= 0x80; - - // process final block - KECCAK_PROCESS_BLOCK(ctx->hash, ctx->message); - ctx->rest = KECCAK_FINALIZED; // mark context as finalized - } - - static_assert(KECCAK_BLOCKLEN > KECCAK_DIGESTSIZE, ""); - static_assert(KECCAK_DIGESTSIZE % sizeof(uint64_t) == 0, ""); - if (md) { - memcpy_swap64le(md, ctx->hash, KECCAK_DIGESTSIZE / sizeof(uint64_t)); - } -} diff --git a/cryptonight/c/keccak.h b/cryptonight/c/keccak.h deleted file mode 100644 index 9123c7a..0000000 --- a/cryptonight/c/keccak.h +++ /dev/null @@ -1,40 +0,0 @@ -// keccak.h -// 19-Nov-11 Markku-Juhani O. Saarinen - -#ifndef KECCAK_H -#define KECCAK_H - -#include -#include - -#ifndef KECCAK_ROUNDS -#define KECCAK_ROUNDS 24 -#endif - -#ifndef ROTL64 -#define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y)))) -#endif - -// SHA3 Algorithm context. -typedef struct KECCAK_CTX -{ - // 1600 bits algorithm hashing state - uint64_t hash[25]; - // 1088-bit buffer for leftovers, block size = 136 B for 256-bit keccak - uint64_t message[17]; - // count of bytes in the message[] buffer - size_t rest; -} KECCAK_CTX; - -// compute a keccak hash (md) of given byte length from "in" -void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); - -// update the state -void keccakf(uint64_t st[25], int norounds); - -void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md); - -void keccak_init(KECCAK_CTX * ctx); -void keccak_update(KECCAK_CTX * ctx, const uint8_t *in, size_t inlen); -void keccak_finish(KECCAK_CTX * ctx, uint8_t *md); -#endif diff --git a/cryptonight/c/memwipe.c b/cryptonight/c/memwipe.c deleted file mode 100644 index 27dfb28..0000000 --- a/cryptonight/c/memwipe.c +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2017-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file Copyright (c) 2009-2015 The Bitcoin Core developers - -#define __STDC_WANT_LIB_EXT1__ 1 -#include -#include -#include -#include -#ifdef HAVE_EXPLICIT_BZERO -#include -#endif -#include "memwipe.h" - -#if defined(_MSC_VER) -#define SCARECROW \ - __asm; -#else -#define SCARECROW \ - __asm__ __volatile__("" : : "r"(ptr) : "memory"); -#endif - -#ifdef HAVE_MEMSET_S - -void *memwipe(void *ptr, size_t n) -{ - if (n > 0 && memset_s(ptr, n, 0, n)) - { -#ifdef NDEBUG - fprintf(stderr, "Error: memset_s failed\n"); - _exit(1); -#else - abort(); -#endif - } - SCARECROW // might as well... - return ptr; -} - -#elif defined HAVE_EXPLICIT_BZERO - -void *memwipe(void *ptr, size_t n) -{ - if (n > 0) - explicit_bzero(ptr, n); - SCARECROW - return ptr; -} - -#else - -/* The memory_cleanse implementation is taken from Bitcoin */ - -/* Compilers have a bad habit of removing "superfluous" memset calls that - * are trying to zero memory. For example, when memset()ing a buffer and - * then free()ing it, the compiler might decide that the memset is - * unobservable and thus can be removed. - * - * Previously we used OpenSSL which tried to stop this by a) implementing - * memset in assembly on x86 and b) putting the function in its own file - * for other platforms. - * - * This change removes those tricks in favour of using asm directives to - * scare the compiler away. As best as our compiler folks can tell, this is - * sufficient and will continue to be so. - * - * Adam Langley - * Commit: ad1907fe73334d6c696c8539646c21b11178f20f - * BoringSSL (LICENSE: ISC) - */ -static void memory_cleanse(void *ptr, size_t len) -{ - memset(ptr, 0, len); - - /* As best as we can tell, this is sufficient to break any optimisations that - might try to eliminate "superfluous" memsets. If there's an easy way to - detect memset_s, it would be better to use that. */ - SCARECROW -} - -void *memwipe(void *ptr, size_t n) -{ - if (n > 0) - memory_cleanse(ptr, n); - SCARECROW - return ptr; -} - -#endif diff --git a/cryptonight/c/memwipe.h b/cryptonight/c/memwipe.h deleted file mode 100644 index a27e0bb..0000000 --- a/cryptonight/c/memwipe.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2017-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#ifdef __cplusplus -#include -#include - -extern "C" { -#endif - -void *memwipe(void *src, size_t n); - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus -namespace tools { - - /// Scrubs data in the contained type upon destruction. - /// - /// Primarily useful for making sure that private keys don't stick around in - /// memory after the objects that held them have gone out of scope. - template - struct scrubbed : public T { - using type = T; - - ~scrubbed() { - scrub(); - } - - /// Destroy the contents of the contained type. - void scrub() { - static_assert(std::is_pod::value, - "T cannot be auto-scrubbed. T must be POD."); - static_assert(std::is_trivially_destructible::value, - "T cannot be auto-scrubbed. T must be trivially destructable."); - memwipe(this, sizeof(T)); - } - }; - - template - T& unwrap(scrubbed& src) { return src; } - - template - const T& unwrap(scrubbed const& src) { return src; } - - template - using scrubbed_arr = scrubbed>; -} // namespace tools - -#endif // __cplusplus diff --git a/cryptonight/c/oaes_lib.c b/cryptonight/c/oaes_lib.c deleted file mode 100644 index 6c9099e..0000000 --- a/cryptonight/c/oaes_lib.c +++ /dev/null @@ -1,1507 +0,0 @@ -/* - * --------------------------------------------------------------------------- - * OpenAES License - * --------------------------------------------------------------------------- - * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * --------------------------------------------------------------------------- - */ -#include -#include -#include -#include -#include - -// OS X, FreeBSD, OpenBSD and NetBSD don't need malloc.h -#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) \ - && !defined(__DragonFly__) && !defined(__NetBSD__) - #include -#endif - -// ANDROID, FreeBSD, OpenBSD and NetBSD also don't need timeb.h -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__) \ - && !defined(__NetBSD__) - #include -#else - #include -#endif - -#ifdef WIN32 -#include -#else -#include -#include -#endif - -#ifdef _MSC_VER -#define GETPID() _getpid() -#else -#define GETPID() getpid() -#endif - -//#include "oaes_config.h" -#include "oaes_lib.h" - -#ifdef OAES_HAVE_ISAAC -#include "rand.h" -#endif // OAES_HAVE_ISAAC - -#define OAES_RKEY_LEN 4 -#define OAES_COL_LEN 4 -#define OAES_ROUND_BASE 7 - -// the block is padded -#define OAES_FLAG_PAD 0x01 - -#ifndef min -# define min(a,b) (((a)<(b)) ? (a) : (b)) -#endif /* min */ - -// "OAES<8-bit header version><8-bit type><16-bit options><8-bit flags><56-bit reserved>" -static uint8_t oaes_header[OAES_BLOCK_SIZE] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ 0x4f, 0x41, 0x45, 0x53, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; -static uint8_t oaes_gf_8[] = { - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; - -static uint8_t oaes_sub_byte_value[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76 }, - /*1*/ { 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0 }, - /*2*/ { 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15 }, - /*3*/ { 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75 }, - /*4*/ { 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84 }, - /*5*/ { 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf }, - /*6*/ { 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8 }, - /*7*/ { 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2 }, - /*8*/ { 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73 }, - /*9*/ { 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb }, - /*a*/ { 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79 }, - /*b*/ { 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08 }, - /*c*/ { 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a }, - /*d*/ { 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e }, - /*e*/ { 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf }, - /*f*/ { 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }, -}; - -static uint8_t oaes_inv_sub_byte_value[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb }, - /*1*/ { 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb }, - /*2*/ { 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e }, - /*3*/ { 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 }, - /*4*/ { 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92 }, - /*5*/ { 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 }, - /*6*/ { 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06 }, - /*7*/ { 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b }, - /*8*/ { 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73 }, - /*9*/ { 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e }, - /*a*/ { 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b }, - /*b*/ { 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4 }, - /*c*/ { 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f }, - /*d*/ { 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef }, - /*e*/ { 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 }, - /*f*/ { 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }, -}; - -static uint8_t oaes_gf_mul_2[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, - /*1*/ { 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e }, - /*2*/ { 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e }, - /*3*/ { 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e }, - /*4*/ { 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e }, - /*5*/ { 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe }, - /*6*/ { 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde }, - /*7*/ { 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe }, - /*8*/ { 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05 }, - /*9*/ { 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25 }, - /*a*/ { 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45 }, - /*b*/ { 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65 }, - /*c*/ { 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85 }, - /*d*/ { 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5 }, - /*e*/ { 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5 }, - /*f*/ { 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5 }, -}; - -static uint8_t oaes_gf_mul_3[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11 }, - /*1*/ { 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21 }, - /*2*/ { 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71 }, - /*3*/ { 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41 }, - /*4*/ { 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1 }, - /*5*/ { 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1 }, - /*6*/ { 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1 }, - /*7*/ { 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81 }, - /*8*/ { 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a }, - /*9*/ { 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba }, - /*a*/ { 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea }, - /*b*/ { 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda }, - /*c*/ { 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a }, - /*d*/ { 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a }, - /*e*/ { 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a }, - /*f*/ { 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a }, -}; - -static uint8_t oaes_gf_mul_9[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77 }, - /*1*/ { 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7 }, - /*2*/ { 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c }, - /*3*/ { 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc }, - /*4*/ { 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01 }, - /*5*/ { 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91 }, - /*6*/ { 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a }, - /*7*/ { 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa }, - /*8*/ { 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b }, - /*9*/ { 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b }, - /*a*/ { 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0 }, - /*b*/ { 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30 }, - /*c*/ { 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed }, - /*d*/ { 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d }, - /*e*/ { 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6 }, - /*f*/ { 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46 }, -}; - -static uint8_t oaes_gf_mul_b[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69 }, - /*1*/ { 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9 }, - /*2*/ { 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12 }, - /*3*/ { 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2 }, - /*4*/ { 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f }, - /*5*/ { 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f }, - /*6*/ { 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4 }, - /*7*/ { 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54 }, - /*8*/ { 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e }, - /*9*/ { 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e }, - /*a*/ { 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5 }, - /*b*/ { 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55 }, - /*c*/ { 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68 }, - /*d*/ { 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8 }, - /*e*/ { 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13 }, - /*f*/ { 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3 }, -}; - -static uint8_t oaes_gf_mul_d[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b }, - /*1*/ { 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b }, - /*2*/ { 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0 }, - /*3*/ { 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20 }, - /*4*/ { 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26 }, - /*5*/ { 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6 }, - /*6*/ { 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d }, - /*7*/ { 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d }, - /*8*/ { 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91 }, - /*9*/ { 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41 }, - /*a*/ { 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a }, - /*b*/ { 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa }, - /*c*/ { 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc }, - /*d*/ { 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c }, - /*e*/ { 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47 }, - /*f*/ { 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97 }, -}; - -static uint8_t oaes_gf_mul_e[16][16] = { - // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, - /*0*/ { 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a }, - /*1*/ { 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba }, - /*2*/ { 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81 }, - /*3*/ { 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61 }, - /*4*/ { 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7 }, - /*5*/ { 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17 }, - /*6*/ { 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c }, - /*7*/ { 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc }, - /*8*/ { 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b }, - /*9*/ { 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb }, - /*a*/ { 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0 }, - /*b*/ { 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20 }, - /*c*/ { 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6 }, - /*d*/ { 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56 }, - /*e*/ { 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d }, - /*f*/ { 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d }, -}; - -static OAES_RET oaes_sub_byte( uint8_t * byte ) -{ - size_t _x, _y; - - if( NULL == byte ) - return OAES_RET_ARG1; - - _x = _y = *byte; - _x &= 0x0f; - _y &= 0xf0; - _y >>= 4; - *byte = oaes_sub_byte_value[_y][_x]; - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_inv_sub_byte( uint8_t * byte ) -{ - size_t _x, _y; - - if( NULL == byte ) - return OAES_RET_ARG1; - - _x = _y = *byte; - _x &= 0x0f; - _y &= 0xf0; - _y >>= 4; - *byte = oaes_inv_sub_byte_value[_y][_x]; - - return OAES_RET_SUCCESS; -} -/* -static OAES_RET oaes_word_rot_right( uint8_t word[OAES_COL_LEN] ) -{ - uint8_t _temp[OAES_COL_LEN]; - - if( NULL == word ) - return OAES_RET_ARG1; - - memcpy( _temp + 1, word, OAES_COL_LEN - 1 ); - _temp[0] = word[OAES_COL_LEN - 1]; - memcpy( word, _temp, OAES_COL_LEN ); - - return OAES_RET_SUCCESS; -} -*/ -static OAES_RET oaes_word_rot_left( uint8_t word[OAES_COL_LEN] ) -{ - uint8_t _temp[OAES_COL_LEN]; - - if( NULL == word ) - return OAES_RET_ARG1; - - memcpy( _temp, word + 1, OAES_COL_LEN - 1 ); - _temp[OAES_COL_LEN - 1] = word[0]; - memcpy( word, _temp, OAES_COL_LEN ); - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_shift_rows( uint8_t block[OAES_BLOCK_SIZE] ) -{ - uint8_t _temp[OAES_BLOCK_SIZE]; - - if( NULL == block ) - return OAES_RET_ARG1; - - _temp[0x00] = block[0x00]; - _temp[0x01] = block[0x05]; - _temp[0x02] = block[0x0a]; - _temp[0x03] = block[0x0f]; - _temp[0x04] = block[0x04]; - _temp[0x05] = block[0x09]; - _temp[0x06] = block[0x0e]; - _temp[0x07] = block[0x03]; - _temp[0x08] = block[0x08]; - _temp[0x09] = block[0x0d]; - _temp[0x0a] = block[0x02]; - _temp[0x0b] = block[0x07]; - _temp[0x0c] = block[0x0c]; - _temp[0x0d] = block[0x01]; - _temp[0x0e] = block[0x06]; - _temp[0x0f] = block[0x0b]; - memcpy( block, _temp, OAES_BLOCK_SIZE ); - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_inv_shift_rows( uint8_t block[OAES_BLOCK_SIZE] ) -{ - uint8_t _temp[OAES_BLOCK_SIZE]; - - if( NULL == block ) - return OAES_RET_ARG1; - - _temp[0x00] = block[0x00]; - _temp[0x01] = block[0x0d]; - _temp[0x02] = block[0x0a]; - _temp[0x03] = block[0x07]; - _temp[0x04] = block[0x04]; - _temp[0x05] = block[0x01]; - _temp[0x06] = block[0x0e]; - _temp[0x07] = block[0x0b]; - _temp[0x08] = block[0x08]; - _temp[0x09] = block[0x05]; - _temp[0x0a] = block[0x02]; - _temp[0x0b] = block[0x0f]; - _temp[0x0c] = block[0x0c]; - _temp[0x0d] = block[0x09]; - _temp[0x0e] = block[0x06]; - _temp[0x0f] = block[0x03]; - memcpy( block, _temp, OAES_BLOCK_SIZE ); - - return OAES_RET_SUCCESS; -} - -static uint8_t oaes_gf_mul(uint8_t left, uint8_t right) -{ - size_t _x, _y; - - _x = _y = left; - _x &= 0x0f; - _y &= 0xf0; - _y >>= 4; - - switch( right ) - { - case 0x02: - return oaes_gf_mul_2[_y][_x]; - break; - case 0x03: - return oaes_gf_mul_3[_y][_x]; - break; - case 0x09: - return oaes_gf_mul_9[_y][_x]; - break; - case 0x0b: - return oaes_gf_mul_b[_y][_x]; - break; - case 0x0d: - return oaes_gf_mul_d[_y][_x]; - break; - case 0x0e: - return oaes_gf_mul_e[_y][_x]; - break; - default: - return left; - break; - } -} - -static OAES_RET oaes_mix_cols( uint8_t word[OAES_COL_LEN] ) -{ - uint8_t _temp[OAES_COL_LEN]; - - if( NULL == word ) - return OAES_RET_ARG1; - - _temp[0] = oaes_gf_mul(word[0], 0x02) ^ oaes_gf_mul( word[1], 0x03 ) ^ - word[2] ^ word[3]; - _temp[1] = word[0] ^ oaes_gf_mul( word[1], 0x02 ) ^ - oaes_gf_mul( word[2], 0x03 ) ^ word[3]; - _temp[2] = word[0] ^ word[1] ^ - oaes_gf_mul( word[2], 0x02 ) ^ oaes_gf_mul( word[3], 0x03 ); - _temp[3] = oaes_gf_mul( word[0], 0x03 ) ^ word[1] ^ - word[2] ^ oaes_gf_mul( word[3], 0x02 ); - memcpy( word, _temp, OAES_COL_LEN ); - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_inv_mix_cols( uint8_t word[OAES_COL_LEN] ) -{ - uint8_t _temp[OAES_COL_LEN]; - - if( NULL == word ) - return OAES_RET_ARG1; - - _temp[0] = oaes_gf_mul( word[0], 0x0e ) ^ oaes_gf_mul( word[1], 0x0b ) ^ - oaes_gf_mul( word[2], 0x0d ) ^ oaes_gf_mul( word[3], 0x09 ); - _temp[1] = oaes_gf_mul( word[0], 0x09 ) ^ oaes_gf_mul( word[1], 0x0e ) ^ - oaes_gf_mul( word[2], 0x0b ) ^ oaes_gf_mul( word[3], 0x0d ); - _temp[2] = oaes_gf_mul( word[0], 0x0d ) ^ oaes_gf_mul( word[1], 0x09 ) ^ - oaes_gf_mul( word[2], 0x0e ) ^ oaes_gf_mul( word[3], 0x0b ); - _temp[3] = oaes_gf_mul( word[0], 0x0b ) ^ oaes_gf_mul( word[1], 0x0d ) ^ - oaes_gf_mul( word[2], 0x09 ) ^ oaes_gf_mul( word[3], 0x0e ); - memcpy( word, _temp, OAES_COL_LEN ); - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_sprintf( - char * buf, size_t * buf_len, const uint8_t * data, size_t data_len ) -{ - size_t _i, _buf_len_in; - char _temp[4]; - - if( NULL == buf_len ) - return OAES_RET_ARG2; - - _buf_len_in = *buf_len; - *buf_len = data_len * 3 + data_len / OAES_BLOCK_SIZE + 1; - - if( NULL == buf ) - return OAES_RET_SUCCESS; - - if( *buf_len > _buf_len_in ) - return OAES_RET_BUF; - - if( NULL == data ) - return OAES_RET_ARG3; - - strcpy( buf, "" ); - - for( _i = 0; _i < data_len; _i++ ) - { - sprintf( _temp, "%02x ", data[_i] ); - strcat( buf, _temp ); - if( _i && 0 == ( _i + 1 ) % OAES_BLOCK_SIZE ) - strcat( buf, "\n" ); - } - - return OAES_RET_SUCCESS; -} - -#ifdef OAES_HAVE_ISAAC -static void oaes_get_seed( char buf[RANDSIZ + 1] ) -{ - #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) - struct timeb timer; - struct tm *gmTimer; - char * _test = NULL; - - ftime (&timer); - gmTimer = gmtime( &timer.time ); - _test = (char *) calloc( sizeof( char ), timer.millitm ); - sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d", - gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday, - gmTimer->tm_hour, gmTimer->tm_min, gmTimer->tm_sec, timer.millitm, - _test + timer.millitm, GETPID() ); - #else - struct timeval timer; - struct tm *gmTimer; - char * _test = NULL; - - gettimeofday(&timer, NULL); - gmTimer = gmtime( &timer.tv_sec ); - _test = (char *) calloc( sizeof( char ), timer.tv_usec/1000 ); - sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d", - gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday, - gmTimer->tm_hour, gmTimer->tm_min, gmTimer->tm_sec, timer.tv_usec/1000, - _test + timer.tv_usec/1000, GETPID() ); - #endif - - if( _test ) - free( _test ); -} -#else -static uint32_t oaes_get_seed(void) -{ - #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__) && !defined(__NetBSD__) - struct timeb timer; - struct tm *gmTimer; - char * _test = NULL; - uint32_t _ret = 0; - - ftime (&timer); - gmTimer = gmtime( &timer.time ); - _test = (char *) calloc( sizeof( char ), timer.millitm ); - _ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday + - gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.millitm + - (uintptr_t) ( _test + timer.millitm ) + GETPID(); - #else - struct timeval timer; - struct tm *gmTimer; - char * _test = NULL; - uint32_t _ret = 0; - - gettimeofday(&timer, NULL); - gmTimer = gmtime( &timer.tv_sec ); - _test = (char *) calloc( sizeof( char ), timer.tv_usec/1000 ); - _ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday + - gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.tv_usec/1000 + - (uintptr_t) ( _test + timer.tv_usec/1000 ) + GETPID(); - #endif - - if( _test ) - free( _test ); - - return _ret; -} -#endif // OAES_HAVE_ISAAC - -static OAES_RET oaes_key_destroy( oaes_key ** key ) -{ - if( NULL == *key ) - return OAES_RET_SUCCESS; - - if( (*key)->data ) - { - free( (*key)->data ); - (*key)->data = NULL; - } - - if( (*key)->exp_data ) - { - free( (*key)->exp_data ); - (*key)->exp_data = NULL; - } - - (*key)->data_len = 0; - (*key)->exp_data_len = 0; - (*key)->num_keys = 0; - (*key)->key_base = 0; - free( *key ); - *key = NULL; - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_key_expand( OAES_CTX * ctx ) -{ - size_t _i, _j; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - - _ctx->key->key_base = _ctx->key->data_len / OAES_RKEY_LEN; - _ctx->key->num_keys = _ctx->key->key_base + OAES_ROUND_BASE; - - _ctx->key->exp_data_len = _ctx->key->num_keys * OAES_RKEY_LEN * OAES_COL_LEN; - _ctx->key->exp_data = (uint8_t *) - calloc( _ctx->key->exp_data_len, sizeof( uint8_t )); - - if( NULL == _ctx->key->exp_data ) - return OAES_RET_MEM; - - // the first _ctx->key->data_len are a direct copy - memcpy( _ctx->key->exp_data, _ctx->key->data, _ctx->key->data_len ); - - // apply ExpandKey algorithm for remainder - for( _i = _ctx->key->key_base; _i < _ctx->key->num_keys * OAES_RKEY_LEN; _i++ ) - { - uint8_t _temp[OAES_COL_LEN]; - - memcpy( _temp, - _ctx->key->exp_data + ( _i - 1 ) * OAES_RKEY_LEN, OAES_COL_LEN ); - - // transform key column - if( 0 == _i % _ctx->key->key_base ) - { - oaes_word_rot_left( _temp ); - - for( _j = 0; _j < OAES_COL_LEN; _j++ ) - oaes_sub_byte( _temp + _j ); - - _temp[0] = _temp[0] ^ oaes_gf_8[ _i / _ctx->key->key_base - 1 ]; - } - else if( _ctx->key->key_base > 6 && 4 == _i % _ctx->key->key_base ) - { - for( _j = 0; _j < OAES_COL_LEN; _j++ ) - oaes_sub_byte( _temp + _j ); - } - - for( _j = 0; _j < OAES_COL_LEN; _j++ ) - { - _ctx->key->exp_data[ _i * OAES_RKEY_LEN + _j ] = - _ctx->key->exp_data[ ( _i - _ctx->key->key_base ) * - OAES_RKEY_LEN + _j ] ^ _temp[_j]; - } - } - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_key_gen( OAES_CTX * ctx, size_t key_size ) -{ - size_t _i; - oaes_key * _key = NULL; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - OAES_RET _rc = OAES_RET_SUCCESS; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - _key = (oaes_key *) calloc( sizeof( oaes_key ), 1 ); - - if( NULL == _key ) - return OAES_RET_MEM; - - if( _ctx->key ) - oaes_key_destroy( &(_ctx->key) ); - - _key->data_len = key_size; - _key->data = (uint8_t *) calloc( key_size, sizeof( uint8_t )); - - if( NULL == _key->data ) - { - free( _key ); - return OAES_RET_MEM; - } - - for( _i = 0; _i < key_size; _i++ ) -#ifdef OAES_HAVE_ISAAC - _key->data[_i] = (uint8_t) rand( _ctx->rctx ); -#else - _key->data[_i] = (uint8_t) rand(); -#endif // OAES_HAVE_ISAAC - - _ctx->key = _key; - _rc = _rc || oaes_key_expand( ctx ); - - if( _rc != OAES_RET_SUCCESS ) - { - oaes_key_destroy( &(_ctx->key) ); - return _rc; - } - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_key_gen_128( OAES_CTX * ctx ) -{ - return oaes_key_gen( ctx, 16 ); -} - -OAES_RET oaes_key_gen_192( OAES_CTX * ctx ) -{ - return oaes_key_gen( ctx, 24 ); -} - -OAES_RET oaes_key_gen_256( OAES_CTX * ctx ) -{ - return oaes_key_gen( ctx, 32 ); -} - -OAES_RET oaes_key_export( OAES_CTX * ctx, - uint8_t * data, size_t * data_len ) -{ - size_t _data_len_in; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - - if( NULL == data_len ) - return OAES_RET_ARG3; - - _data_len_in = *data_len; - // data + header - *data_len = _ctx->key->data_len + OAES_BLOCK_SIZE; - - if( NULL == data ) - return OAES_RET_SUCCESS; - - if( _data_len_in < *data_len ) - return OAES_RET_BUF; - - // header - memcpy( data, oaes_header, OAES_BLOCK_SIZE ); - data[5] = 0x01; - data[7] = _ctx->key->data_len; - memcpy( data + OAES_BLOCK_SIZE, _ctx->key->data, _ctx->key->data_len ); - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_key_export_data( OAES_CTX * ctx, - uint8_t * data, size_t * data_len ) -{ - size_t _data_len_in; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - - if( NULL == data_len ) - return OAES_RET_ARG3; - - _data_len_in = *data_len; - *data_len = _ctx->key->data_len; - - if( NULL == data ) - return OAES_RET_SUCCESS; - - if( _data_len_in < *data_len ) - return OAES_RET_BUF; - - memcpy( data, _ctx->key->data, *data_len ); - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_key_import( OAES_CTX * ctx, - const uint8_t * data, size_t data_len ) -{ - oaes_ctx * _ctx = (oaes_ctx *) ctx; - OAES_RET _rc = OAES_RET_SUCCESS; - int _key_length; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == data ) - return OAES_RET_ARG2; - - switch( data_len ) - { - case 16 + OAES_BLOCK_SIZE: - case 24 + OAES_BLOCK_SIZE: - case 32 + OAES_BLOCK_SIZE: - break; - default: - return OAES_RET_ARG3; - } - - // header - if( 0 != memcmp( data, oaes_header, 4 ) ) - return OAES_RET_HEADER; - - // header version - switch( data[4] ) - { - case 0x01: - break; - default: - return OAES_RET_HEADER; - } - - // header type - switch( data[5] ) - { - case 0x01: - break; - default: - return OAES_RET_HEADER; - } - - // options - _key_length = data[7]; - switch( _key_length ) - { - case 16: - case 24: - case 32: - break; - default: - return OAES_RET_HEADER; - } - - if( (int)data_len != _key_length + OAES_BLOCK_SIZE ) - return OAES_RET_ARG3; - - if( _ctx->key ) - oaes_key_destroy( &(_ctx->key) ); - - _ctx->key = (oaes_key *) calloc( sizeof( oaes_key ), 1 ); - - if( NULL == _ctx->key ) - return OAES_RET_MEM; - - _ctx->key->data_len = _key_length; - _ctx->key->data = (uint8_t *) - calloc( _key_length, sizeof( uint8_t )); - - if( NULL == _ctx->key->data ) - { - oaes_key_destroy( &(_ctx->key) ); - return OAES_RET_MEM; - } - - memcpy( _ctx->key->data, data + OAES_BLOCK_SIZE, _key_length ); - _rc = _rc || oaes_key_expand( ctx ); - - if( _rc != OAES_RET_SUCCESS ) - { - oaes_key_destroy( &(_ctx->key) ); - return _rc; - } - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_key_import_data( OAES_CTX * ctx, - const uint8_t * data, size_t data_len ) -{ - oaes_ctx * _ctx = (oaes_ctx *) ctx; - OAES_RET _rc = OAES_RET_SUCCESS; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == data ) - return OAES_RET_ARG2; - - switch( data_len ) - { - case 16: - case 24: - case 32: - break; - default: - return OAES_RET_ARG3; - } - - if( _ctx->key ) - oaes_key_destroy( &(_ctx->key) ); - - _ctx->key = (oaes_key *) calloc( sizeof( oaes_key ), 1 ); - - if( NULL == _ctx->key ) - return OAES_RET_MEM; - - _ctx->key->data_len = data_len; - _ctx->key->data = (uint8_t *) - calloc( data_len, sizeof( uint8_t )); - - if( NULL == _ctx->key->data ) - { - oaes_key_destroy( &(_ctx->key) ); - return OAES_RET_MEM; - } - - memcpy( _ctx->key->data, data, data_len ); - _rc = _rc || oaes_key_expand( ctx ); - - if( _rc != OAES_RET_SUCCESS ) - { - oaes_key_destroy( &(_ctx->key) ); - return _rc; - } - - return OAES_RET_SUCCESS; -} - -OAES_CTX * oaes_alloc(void) -{ - oaes_ctx * _ctx = (oaes_ctx *) calloc( sizeof( oaes_ctx ), 1 ); - - if( NULL == _ctx ) - return NULL; - -#ifdef OAES_HAVE_ISAAC - { - ub4 _i = 0; - char _seed[RANDSIZ + 1]; - - _ctx->rctx = (randctx *) calloc( sizeof( randctx ), 1 ); - - if( NULL == _ctx->rctx ) - { - free( _ctx ); - return NULL; - } - - oaes_get_seed( _seed ); - memset( _ctx->rctx->randrsl, 0, RANDSIZ ); - memcpy( _ctx->rctx->randrsl, _seed, RANDSIZ ); - randinit( _ctx->rctx, TRUE); - } -#else - srand( oaes_get_seed() ); -#endif // OAES_HAVE_ISAAC - - _ctx->key = NULL; - oaes_set_option( _ctx, OAES_OPTION_CBC, NULL ); - -#ifdef OAES_DEBUG - _ctx->step_cb = NULL; - oaes_set_option( _ctx, OAES_OPTION_STEP_OFF, NULL ); -#endif // OAES_DEBUG - - return (OAES_CTX *) _ctx; -} - -OAES_RET oaes_free( OAES_CTX ** ctx ) -{ - oaes_ctx ** _ctx = (oaes_ctx **) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == *_ctx ) - return OAES_RET_SUCCESS; - - if( (*_ctx)->key ) - oaes_key_destroy( &((*_ctx)->key) ); - -#ifdef OAES_HAVE_ISAAC - if( (*_ctx)->rctx ) - { - free( (*_ctx)->rctx ); - (*_ctx)->rctx = NULL; - } -#endif // OAES_HAVE_ISAAC - - free( *_ctx ); - *_ctx = NULL; - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_set_option( OAES_CTX * ctx, - OAES_OPTION option, const void * value ) -{ - size_t _i; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - switch( option ) - { - case OAES_OPTION_ECB: - _ctx->options &= ~OAES_OPTION_CBC; - memset( _ctx->iv, 0, OAES_BLOCK_SIZE ); - break; - - case OAES_OPTION_CBC: - _ctx->options &= ~OAES_OPTION_ECB; - if( value ) - memcpy( _ctx->iv, value, OAES_BLOCK_SIZE ); - else - { - for( _i = 0; _i < OAES_BLOCK_SIZE; _i++ ) -#ifdef OAES_HAVE_ISAAC - _ctx->iv[_i] = (uint8_t) rand( _ctx->rctx ); -#else - _ctx->iv[_i] = (uint8_t) rand(); -#endif // OAES_HAVE_ISAAC - } - break; - -#ifdef OAES_DEBUG - - case OAES_OPTION_STEP_ON: - if( value ) - { - _ctx->options &= ~OAES_OPTION_STEP_OFF; - _ctx->step_cb = value; - } - else - { - _ctx->options &= ~OAES_OPTION_STEP_ON; - _ctx->options |= OAES_OPTION_STEP_OFF; - _ctx->step_cb = NULL; - return OAES_RET_ARG3; - } - break; - - case OAES_OPTION_STEP_OFF: - _ctx->options &= ~OAES_OPTION_STEP_ON; - _ctx->step_cb = NULL; - break; - -#endif // OAES_DEBUG - - default: - return OAES_RET_ARG2; - } - - _ctx->options |= option; - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_encrypt_block( - OAES_CTX * ctx, uint8_t * c, size_t c_len ) -{ - size_t _i, _j; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == c ) - return OAES_RET_ARG2; - - if( c_len != OAES_BLOCK_SIZE ) - return OAES_RET_ARG3; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "input", 1, NULL ); -#endif // OAES_DEBUG - - // AddRoundKey(State, K0) - for( _i = 0; _i < c_len; _i++ ) - c[_i] = c[_i] ^ _ctx->key->exp_data[_i]; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - { - _ctx->step_cb( _ctx->key->exp_data, "k_sch", 1, NULL ); - _ctx->step_cb( c, "k_add", 1, NULL ); - } -#endif // OAES_DEBUG - - // for round = 1 step 1 to Nr–1 - for( _i = 1; _i < _ctx->key->num_keys - 1; _i++ ) - { - // SubBytes(state) - for( _j = 0; _j < c_len; _j++ ) - oaes_sub_byte( c + _j ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "s_box", _i, NULL ); -#endif // OAES_DEBUG - - // ShiftRows(state) - oaes_shift_rows( c ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "s_row", _i, NULL ); -#endif // OAES_DEBUG - - // MixColumns(state) - oaes_mix_cols( c ); - oaes_mix_cols( c + 4 ); - oaes_mix_cols( c + 8 ); - oaes_mix_cols( c + 12 ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "m_col", _i, NULL ); -#endif // OAES_DEBUG - - // AddRoundKey(state, w[round*Nb, (round+1)*Nb-1]) - for( _j = 0; _j < c_len; _j++ ) - c[_j] = c[_j] ^ - _ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN + _j]; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - { - _ctx->step_cb( _ctx->key->exp_data + _i * OAES_RKEY_LEN * OAES_COL_LEN, - "k_sch", _i, NULL ); - _ctx->step_cb( c, "k_add", _i, NULL ); - } -#endif // OAES_DEBUG - - } - - // SubBytes(state) - for( _i = 0; _i < c_len; _i++ ) - oaes_sub_byte( c + _i ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "s_box", _ctx->key->num_keys - 1, NULL ); -#endif // OAES_DEBUG - - // ShiftRows(state) - oaes_shift_rows( c ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "s_row", _ctx->key->num_keys - 1, NULL ); -#endif // OAES_DEBUG - - // AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1]) - for( _i = 0; _i < c_len; _i++ ) - c[_i] = c[_i] ^ _ctx->key->exp_data[ - ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN + _i ]; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - { - _ctx->step_cb( _ctx->key->exp_data + - ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN, - "k_sch", _ctx->key->num_keys - 1, NULL ); - _ctx->step_cb( c, "output", _ctx->key->num_keys - 1, NULL ); - } -#endif // OAES_DEBUG - - return OAES_RET_SUCCESS; -} - -static OAES_RET oaes_decrypt_block( - OAES_CTX * ctx, uint8_t * c, size_t c_len ) -{ - size_t _i, _j; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == c ) - return OAES_RET_ARG2; - - if( c_len != OAES_BLOCK_SIZE ) - return OAES_RET_ARG3; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "iinput", _ctx->key->num_keys - 1, NULL ); -#endif // OAES_DEBUG - - // AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1]) - for( _i = 0; _i < c_len; _i++ ) - c[_i] = c[_i] ^ _ctx->key->exp_data[ - ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN + _i ]; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - { - _ctx->step_cb( _ctx->key->exp_data + - ( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN, - "ik_sch", _ctx->key->num_keys - 1, NULL ); - _ctx->step_cb( c, "ik_add", _ctx->key->num_keys - 1, NULL ); - } -#endif // OAES_DEBUG - - for( _i = _ctx->key->num_keys - 2; _i > 0; _i-- ) - { - // InvShiftRows(state) - oaes_inv_shift_rows( c ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "is_row", _i, NULL ); -#endif // OAES_DEBUG - - // InvSubBytes(state) - for( _j = 0; _j < c_len; _j++ ) - oaes_inv_sub_byte( c + _j ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "is_box", _i, NULL ); -#endif // OAES_DEBUG - - // AddRoundKey(state, w[round*Nb, (round+1)*Nb-1]) - for( _j = 0; _j < c_len; _j++ ) - c[_j] = c[_j] ^ - _ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN + _j]; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - { - _ctx->step_cb( _ctx->key->exp_data + _i * OAES_RKEY_LEN * OAES_COL_LEN, - "ik_sch", _i, NULL ); - _ctx->step_cb( c, "ik_add", _i, NULL ); - } -#endif // OAES_DEBUG - - // InvMixColums(state) - oaes_inv_mix_cols( c ); - oaes_inv_mix_cols( c + 4 ); - oaes_inv_mix_cols( c + 8 ); - oaes_inv_mix_cols( c + 12 ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "im_col", _i, NULL ); -#endif // OAES_DEBUG - - } - - // InvShiftRows(state) - oaes_inv_shift_rows( c ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "is_row", 1, NULL ); -#endif // OAES_DEBUG - - // InvSubBytes(state) - for( _i = 0; _i < c_len; _i++ ) - oaes_inv_sub_byte( c + _i ); - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - _ctx->step_cb( c, "is_box", 1, NULL ); -#endif // OAES_DEBUG - - // AddRoundKey(state, w[0, Nb-1]) - for( _i = 0; _i < c_len; _i++ ) - c[_i] = c[_i] ^ _ctx->key->exp_data[_i]; - -#ifdef OAES_DEBUG - if( _ctx->step_cb ) - { - _ctx->step_cb( _ctx->key->exp_data, "ik_sch", 1, NULL ); - _ctx->step_cb( c, "ioutput", 1, NULL ); - } -#endif // OAES_DEBUG - - return OAES_RET_SUCCESS; -} - -OAES_RET oaes_encrypt( OAES_CTX * ctx, - const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len ) -{ - size_t _i, _j, _c_len_in, _c_data_len; - size_t _pad_len = m_len % OAES_BLOCK_SIZE == 0 ? - 0 : OAES_BLOCK_SIZE - m_len % OAES_BLOCK_SIZE; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - OAES_RET _rc = OAES_RET_SUCCESS; - uint8_t _flags = _pad_len ? OAES_FLAG_PAD : 0; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == m ) - return OAES_RET_ARG2; - - if( NULL == c_len ) - return OAES_RET_ARG5; - - _c_len_in = *c_len; - // data + pad - _c_data_len = m_len + _pad_len; - // header + iv + data + pad - *c_len = 2 * OAES_BLOCK_SIZE + m_len + _pad_len; - - if( NULL == c ) - return OAES_RET_SUCCESS; - - if( _c_len_in < *c_len ) - return OAES_RET_BUF; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - - // header - memcpy(c, oaes_header, OAES_BLOCK_SIZE ); - memcpy(c + 6, &_ctx->options, sizeof(_ctx->options)); - memcpy(c + 8, &_flags, sizeof(_flags)); - // iv - memcpy(c + OAES_BLOCK_SIZE, _ctx->iv, OAES_BLOCK_SIZE ); - // data - memcpy(c + 2 * OAES_BLOCK_SIZE, m, m_len ); - - for( _i = 0; _i < _c_data_len; _i += OAES_BLOCK_SIZE ) - { - uint8_t _block[OAES_BLOCK_SIZE]; - size_t _block_size = min( m_len - _i, OAES_BLOCK_SIZE ); - - memcpy( _block, c + 2 * OAES_BLOCK_SIZE + _i, _block_size ); - - // insert pad - for( _j = 0; _j < OAES_BLOCK_SIZE - _block_size; _j++ ) - _block[ _block_size + _j ] = _j + 1; - - // CBC - if( _ctx->options & OAES_OPTION_CBC ) - { - for( _j = 0; _j < OAES_BLOCK_SIZE; _j++ ) - _block[_j] = _block[_j] ^ _ctx->iv[_j]; - } - - _rc = _rc || - oaes_encrypt_block( ctx, _block, OAES_BLOCK_SIZE ); - memcpy( c + 2 * OAES_BLOCK_SIZE + _i, _block, OAES_BLOCK_SIZE ); - - if( _ctx->options & OAES_OPTION_CBC ) - memcpy( _ctx->iv, _block, OAES_BLOCK_SIZE ); - } - - return _rc; -} - -OAES_RET oaes_decrypt( OAES_CTX * ctx, - const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len ) -{ - size_t _i, _j, _m_len_in; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - OAES_RET _rc = OAES_RET_SUCCESS; - uint8_t _iv[OAES_BLOCK_SIZE]; - uint8_t _flags; - OAES_OPTION _options; - - if( NULL == ctx ) - return OAES_RET_ARG1; - - if( NULL == c ) - return OAES_RET_ARG2; - - if( c_len % OAES_BLOCK_SIZE ) - return OAES_RET_ARG3; - - if( NULL == m_len ) - return OAES_RET_ARG5; - - _m_len_in = *m_len; - *m_len = c_len - 2 * OAES_BLOCK_SIZE; - - if( NULL == m ) - return OAES_RET_SUCCESS; - - if( _m_len_in < *m_len ) - return OAES_RET_BUF; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - - // header - if( 0 != memcmp( c, oaes_header, 4 ) ) - return OAES_RET_HEADER; - - // header version - switch( c[4] ) - { - case 0x01: - break; - default: - return OAES_RET_HEADER; - } - - // header type - switch( c[5] ) - { - case 0x02: - break; - default: - return OAES_RET_HEADER; - } - - // options - memcpy(&_options, c + 6, sizeof(_options)); - // validate that all options are valid - if( _options & ~( - OAES_OPTION_ECB - | OAES_OPTION_CBC -#ifdef OAES_DEBUG - | OAES_OPTION_STEP_ON - | OAES_OPTION_STEP_OFF -#endif // OAES_DEBUG - ) ) - return OAES_RET_HEADER; - if( ( _options & OAES_OPTION_ECB ) && - ( _options & OAES_OPTION_CBC ) ) - return OAES_RET_HEADER; - if( _options == OAES_OPTION_NONE ) - return OAES_RET_HEADER; - - // flags - memcpy(&_flags, c + 8, sizeof(_flags)); - // validate that all flags are valid - if( _flags & ~( - OAES_FLAG_PAD - ) ) - return OAES_RET_HEADER; - - // iv - memcpy( _iv, c + OAES_BLOCK_SIZE, OAES_BLOCK_SIZE); - // data + pad - memcpy( m, c + 2 * OAES_BLOCK_SIZE, *m_len ); - - for( _i = 0; _i < *m_len; _i += OAES_BLOCK_SIZE ) - { - if( ( _options & OAES_OPTION_CBC ) && _i > 0 ) - memcpy( _iv, c + OAES_BLOCK_SIZE + _i, OAES_BLOCK_SIZE ); - - _rc = _rc || - oaes_decrypt_block( ctx, m + _i, min( *m_len - _i, OAES_BLOCK_SIZE ) ); - - // CBC - if( _options & OAES_OPTION_CBC ) - { - for( _j = 0; _j < OAES_BLOCK_SIZE; _j++ ) - m[ _i + _j ] = m[ _i + _j ] ^ _iv[_j]; - } - } - - // remove pad - if( _flags & OAES_FLAG_PAD ) - { - int _is_pad = 1; - size_t _temp = (size_t) m[*m_len - 1]; - - if( _temp <= 0x00 || _temp > 0x0f ) - return OAES_RET_HEADER; - for( _i = 0; _i < _temp; _i++ ) - if( m[*m_len - 1 - _i] != _temp - _i ) - _is_pad = 0; - if( _is_pad ) - { - memset( m + *m_len - _temp, 0, _temp ); - *m_len -= _temp; - } - else - return OAES_RET_HEADER; - } - - return OAES_RET_SUCCESS; -} - - -OAES_API OAES_RET oaes_encryption_round( const uint8_t * key, uint8_t * c ) -{ - size_t _i; - - if( NULL == key ) - return OAES_RET_ARG1; - - if( NULL == c ) - return OAES_RET_ARG2; - - // SubBytes(state) - for( _i = 0; _i < OAES_BLOCK_SIZE; _i++ ) - oaes_sub_byte( c + _i ); - - // ShiftRows(state) - oaes_shift_rows( c ); - - // MixColumns(state) - oaes_mix_cols( c ); - oaes_mix_cols( c + 4 ); - oaes_mix_cols( c + 8 ); - oaes_mix_cols( c + 12 ); - - // AddRoundKey(State, key) - for( _i = 0; _i < OAES_BLOCK_SIZE; _i++ ) - c[_i] ^= key[_i]; - - return OAES_RET_SUCCESS; -} - -OAES_API OAES_RET oaes_pseudo_encrypt_ecb( OAES_CTX * ctx, uint8_t * c ) -{ - size_t _i; - oaes_ctx * _ctx = (oaes_ctx *) ctx; - - if( NULL == _ctx ) - return OAES_RET_ARG1; - - if( NULL == c ) - return OAES_RET_ARG2; - - if( NULL == _ctx->key ) - return OAES_RET_NOKEY; - - for ( _i = 0; _i < 10; ++_i ) - { - oaes_encryption_round( &_ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN], c ); - } - - return OAES_RET_SUCCESS; -} diff --git a/cryptonight/c/oaes_lib.h b/cryptonight/c/oaes_lib.h deleted file mode 100644 index fd19428..0000000 --- a/cryptonight/c/oaes_lib.h +++ /dev/null @@ -1,215 +0,0 @@ -/* - * --------------------------------------------------------------------------- - * OpenAES License - * --------------------------------------------------------------------------- - * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * --------------------------------------------------------------------------- - */ - -#ifndef _OAES_LIB_H -#define _OAES_LIB_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef _WIN32 -# ifdef OAES_SHARED -# ifdef oaes_lib_EXPORTS -# define OAES_API __declspec(dllexport) -# else -# define OAES_API __declspec(dllimport) -# endif -# else -# define OAES_API -# endif -#else -# define OAES_API -#endif // WIN32 - -#define OAES_VERSION "0.8.1" -#define OAES_BLOCK_SIZE 16 - -typedef void OAES_CTX; - -typedef enum -{ - OAES_RET_FIRST = 0, - OAES_RET_SUCCESS = 0, - OAES_RET_UNKNOWN, - OAES_RET_ARG1, - OAES_RET_ARG2, - OAES_RET_ARG3, - OAES_RET_ARG4, - OAES_RET_ARG5, - OAES_RET_NOKEY, - OAES_RET_MEM, - OAES_RET_BUF, - OAES_RET_HEADER, - OAES_RET_COUNT -} OAES_RET; - -/* - * oaes_set_option() takes one of these values for its [option] parameter - * some options accept either an optional or a required [value] parameter - */ -// no option -#define OAES_OPTION_NONE 0 -// enable ECB mode, disable CBC mode -#define OAES_OPTION_ECB 1 -// enable CBC mode, disable ECB mode -// value is optional, may pass uint8_t iv[OAES_BLOCK_SIZE] to specify -// the value of the initialization vector, iv -#define OAES_OPTION_CBC 2 - -#ifdef OAES_DEBUG -typedef int ( * oaes_step_cb ) ( - const uint8_t state[OAES_BLOCK_SIZE], - const char * step_name, - int step_count, - void * user_data ); -// enable state stepping mode -// value is required, must pass oaes_step_cb to receive the state at each step -#define OAES_OPTION_STEP_ON 4 -// disable state stepping mode -#define OAES_OPTION_STEP_OFF 8 -#endif // OAES_DEBUG - -typedef uint16_t OAES_OPTION; - -typedef struct _oaes_key -{ - size_t data_len; - uint8_t *data; - size_t exp_data_len; - uint8_t *exp_data; - size_t num_keys; - size_t key_base; -} oaes_key; - -typedef struct _oaes_ctx -{ -#ifdef OAES_HAVE_ISAAC - randctx * rctx; -#endif // OAES_HAVE_ISAAC - -#ifdef OAES_DEBUG - oaes_step_cb step_cb; -#endif // OAES_DEBUG - - oaes_key * key; - OAES_OPTION options; - uint8_t iv[OAES_BLOCK_SIZE]; -} oaes_ctx; -/* - * // usage: - * - * OAES_CTX * ctx = oaes_alloc(); - * . - * . - * . - * { - * oaes_gen_key_xxx( ctx ); - * { - * oaes_key_export( ctx, _buf, &_buf_len ); - * // or - * oaes_key_export_data( ctx, _buf, &_buf_len );\ - * } - * } - * // or - * { - * oaes_key_import( ctx, _buf, _buf_len ); - * // or - * oaes_key_import_data( ctx, _buf, _buf_len ); - * } - * . - * . - * . - * oaes_encrypt( ctx, m, m_len, c, &c_len ); - * . - * . - * . - * oaes_decrypt( ctx, c, c_len, m, &m_len ); - * . - * . - * . - * oaes_free( &ctx ); - */ - -OAES_API OAES_CTX * oaes_alloc(void); - -OAES_API OAES_RET oaes_free( OAES_CTX ** ctx ); - -OAES_API OAES_RET oaes_set_option( OAES_CTX * ctx, - OAES_OPTION option, const void * value ); - -OAES_API OAES_RET oaes_key_gen_128( OAES_CTX * ctx ); - -OAES_API OAES_RET oaes_key_gen_192( OAES_CTX * ctx ); - -OAES_API OAES_RET oaes_key_gen_256( OAES_CTX * ctx ); - -// export key with header information -// set data == NULL to get the required data_len -OAES_API OAES_RET oaes_key_export( OAES_CTX * ctx, - uint8_t * data, size_t * data_len ); - -// directly export the data from key -// set data == NULL to get the required data_len -OAES_API OAES_RET oaes_key_export_data( OAES_CTX * ctx, - uint8_t * data, size_t * data_len ); - -// import key with header information -OAES_API OAES_RET oaes_key_import( OAES_CTX * ctx, - const uint8_t * data, size_t data_len ); - -// directly import data into key -OAES_API OAES_RET oaes_key_import_data( OAES_CTX * ctx, - const uint8_t * data, size_t data_len ); - -// set c == NULL to get the required c_len -OAES_API OAES_RET oaes_encrypt( OAES_CTX * ctx, - const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len ); - -// set m == NULL to get the required m_len -OAES_API OAES_RET oaes_decrypt( OAES_CTX * ctx, - const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len ); - -// set buf == NULL to get the required buf_len -OAES_API OAES_RET oaes_sprintf( - char * buf, size_t * buf_len, const uint8_t * data, size_t data_len ); - -OAES_API OAES_RET oaes_encryption_round( const uint8_t * key, uint8_t * c ); - -OAES_API OAES_RET oaes_pseudo_encrypt_ecb( OAES_CTX * ctx, uint8_t * c ); - -#ifdef __cplusplus -} -#endif - -#endif // _OAES_LIB_H diff --git a/cryptonight/c/skein.c b/cryptonight/c/skein.c deleted file mode 100644 index 65e4525..0000000 --- a/cryptonight/c/skein.c +++ /dev/null @@ -1,2036 +0,0 @@ -/*********************************************************************** -** -** Implementation of the Skein hash function. -** -** Source code author: Doug Whiting, 2008. -** -** This algorithm and source code is released to the public domain. -** -************************************************************************/ - -#define SKEIN_PORT_CODE /* instantiate any code in skein_port.h */ - -#include /* get size_t definition */ -#include /* get the memcpy/memset functions */ -#include "skein.h" /* get the Skein API definitions */ - -#define DISABLE_UNUSED 0 - -#ifndef SKEIN_256_NIST_MAX_HASHBITS -#define SKEIN_256_NIST_MAX_HASHBITS (0) -#endif - -#ifndef SKEIN_512_NIST_MAX_HASHBITS -#define SKEIN_512_NIST_MAX_HASHBITS (512) -#endif - -#define SKEIN_MODIFIER_WORDS ( 2) /* number of modifier (tweak) words */ - -#define SKEIN_256_STATE_WORDS ( 4) -#define SKEIN_512_STATE_WORDS ( 8) -#define SKEIN1024_STATE_WORDS (16) -#define SKEIN_MAX_STATE_WORDS (16) - -#define SKEIN_256_STATE_BYTES ( 8*SKEIN_256_STATE_WORDS) -#define SKEIN_512_STATE_BYTES ( 8*SKEIN_512_STATE_WORDS) -#define SKEIN1024_STATE_BYTES ( 8*SKEIN1024_STATE_WORDS) - -#define SKEIN_256_STATE_BITS (64*SKEIN_256_STATE_WORDS) -#define SKEIN_512_STATE_BITS (64*SKEIN_512_STATE_WORDS) -#define SKEIN1024_STATE_BITS (64*SKEIN1024_STATE_WORDS) - -#define SKEIN_256_BLOCK_BYTES ( 8*SKEIN_256_STATE_WORDS) -#define SKEIN_512_BLOCK_BYTES ( 8*SKEIN_512_STATE_WORDS) -#define SKEIN1024_BLOCK_BYTES ( 8*SKEIN1024_STATE_WORDS) - -#define SKEIN_RND_SPECIAL (1000u) -#define SKEIN_RND_KEY_INITIAL (SKEIN_RND_SPECIAL+0u) -#define SKEIN_RND_KEY_INJECT (SKEIN_RND_SPECIAL+1u) -#define SKEIN_RND_FEED_FWD (SKEIN_RND_SPECIAL+2u) - -typedef struct -{ - size_t hashBitLen; /* size of hash result, in bits */ - size_t bCnt; /* current byte count in buffer b[] */ - u64b_t T[SKEIN_MODIFIER_WORDS]; /* tweak words: T[0]=byte cnt, T[1]=flags */ -} Skein_Ctxt_Hdr_t; - -typedef struct /* 256-bit Skein hash context structure */ -{ - Skein_Ctxt_Hdr_t h; /* common header context variables */ - u64b_t X[SKEIN_256_STATE_WORDS]; /* chaining variables */ - u08b_t b[SKEIN_256_BLOCK_BYTES]; /* partial block buffer (8-byte aligned) */ -} Skein_256_Ctxt_t; - -typedef struct /* 512-bit Skein hash context structure */ -{ - Skein_Ctxt_Hdr_t h; /* common header context variables */ - u64b_t X[SKEIN_512_STATE_WORDS]; /* chaining variables */ - u08b_t b[SKEIN_512_BLOCK_BYTES]; /* partial block buffer (8-byte aligned) */ -} Skein_512_Ctxt_t; - -typedef struct /* 1024-bit Skein hash context structure */ -{ - Skein_Ctxt_Hdr_t h; /* common header context variables */ - u64b_t X[SKEIN1024_STATE_WORDS]; /* chaining variables */ - u08b_t b[SKEIN1024_BLOCK_BYTES]; /* partial block buffer (8-byte aligned) */ -} Skein1024_Ctxt_t; - -/* Skein APIs for (incremental) "straight hashing" */ -#if SKEIN_256_NIST_MAX_HASHBITS -static int Skein_256_Init (Skein_256_Ctxt_t *ctx, size_t hashBitLen); -#endif -static int Skein_512_Init (Skein_512_Ctxt_t *ctx, size_t hashBitLen); -static int Skein1024_Init (Skein1024_Ctxt_t *ctx, size_t hashBitLen); - -static int Skein_256_Update(Skein_256_Ctxt_t *ctx, const u08b_t *msg, size_t msgByteCnt); -static int Skein_512_Update(Skein_512_Ctxt_t *ctx, const u08b_t *msg, size_t msgByteCnt); -static int Skein1024_Update(Skein1024_Ctxt_t *ctx, const u08b_t *msg, size_t msgByteCnt); - -static int Skein_256_Final (Skein_256_Ctxt_t *ctx, u08b_t * hashVal); -static int Skein_512_Final (Skein_512_Ctxt_t *ctx, u08b_t * hashVal); -static int Skein1024_Final (Skein1024_Ctxt_t *ctx, u08b_t * hashVal); - -/* -** Skein APIs for "extended" initialization: MAC keys, tree hashing. -** After an InitExt() call, just use Update/Final calls as with Init(). -** -** Notes: Same parameters as _Init() calls, plus treeInfo/key/keyBytes. -** When keyBytes == 0 and treeInfo == SKEIN_SEQUENTIAL, -** the results of InitExt() are identical to calling Init(). -** The function Init() may be called once to "precompute" the IV for -** a given hashBitLen value, then by saving a copy of the context -** the IV computation may be avoided in later calls. -** Similarly, the function InitExt() may be called once per MAC key -** to precompute the MAC IV, then a copy of the context saved and -** reused for each new MAC computation. -**/ -#if 0 -static int Skein_256_InitExt(Skein_256_Ctxt_t *ctx, size_t hashBitLen, u64b_t treeInfo, const u08b_t *key, size_t keyBytes); -static int Skein_512_InitExt(Skein_512_Ctxt_t *ctx, size_t hashBitLen, u64b_t treeInfo, const u08b_t *key, size_t keyBytes); -static int Skein1024_InitExt(Skein1024_Ctxt_t *ctx, size_t hashBitLen, u64b_t treeInfo, const u08b_t *key, size_t keyBytes); -#endif - -/* -** Skein APIs for MAC and tree hash: -** Final_Pad: pad, do final block, but no OUTPUT type -** Output: do just the output stage -*/ -#if 0 -static int Skein_256_Final_Pad(Skein_256_Ctxt_t *ctx, u08b_t * hashVal); -static int Skein_512_Final_Pad(Skein_512_Ctxt_t *ctx, u08b_t * hashVal); -static int Skein1024_Final_Pad(Skein1024_Ctxt_t *ctx, u08b_t * hashVal); -#endif - -#ifndef SKEIN_TREE_HASH -#define SKEIN_TREE_HASH (1) -#endif -#if 0 -#if SKEIN_TREE_HASH -static int Skein_256_Output (Skein_256_Ctxt_t *ctx, u08b_t * hashVal); -static int Skein_512_Output (Skein_512_Ctxt_t *ctx, u08b_t * hashVal); -static int Skein1024_Output (Skein1024_Ctxt_t *ctx, u08b_t * hashVal); -#endif -#endif - -/***************************************************************** -** "Internal" Skein definitions -** -- not needed for sequential hashing API, but will be -** helpful for other uses of Skein (e.g., tree hash mode). -** -- included here so that they can be shared between -** reference and optimized code. -******************************************************************/ - -/* tweak word T[1]: bit field starting positions */ -#define SKEIN_T1_BIT(BIT) ((BIT) - 64) /* offset 64 because it's the second word */ - -#define SKEIN_T1_POS_TREE_LVL SKEIN_T1_BIT(112) /* bits 112..118: level in hash tree */ -#define SKEIN_T1_POS_BIT_PAD SKEIN_T1_BIT(119) /* bit 119 : partial final input byte */ -#define SKEIN_T1_POS_BLK_TYPE SKEIN_T1_BIT(120) /* bits 120..125: type field */ -#define SKEIN_T1_POS_FIRST SKEIN_T1_BIT(126) /* bits 126 : first block flag */ -#define SKEIN_T1_POS_FINAL SKEIN_T1_BIT(127) /* bit 127 : final block flag */ - -/* tweak word T[1]: flag bit definition(s) */ -#define SKEIN_T1_FLAG_FIRST (((u64b_t) 1 ) << SKEIN_T1_POS_FIRST) -#define SKEIN_T1_FLAG_FINAL (((u64b_t) 1 ) << SKEIN_T1_POS_FINAL) -#define SKEIN_T1_FLAG_BIT_PAD (((u64b_t) 1 ) << SKEIN_T1_POS_BIT_PAD) - -/* tweak word T[1]: tree level bit field mask */ -#define SKEIN_T1_TREE_LVL_MASK (((u64b_t)0x7F) << SKEIN_T1_POS_TREE_LVL) -#define SKEIN_T1_TREE_LEVEL(n) (((u64b_t) (n)) << SKEIN_T1_POS_TREE_LVL) - -/* tweak word T[1]: block type field */ -#define SKEIN_BLK_TYPE_KEY ( 0) /* key, for MAC and KDF */ -#define SKEIN_BLK_TYPE_CFG ( 4) /* configuration block */ -#define SKEIN_BLK_TYPE_PERS ( 8) /* personalization string */ -#define SKEIN_BLK_TYPE_PK (12) /* public key (for digital signature hashing) */ -#define SKEIN_BLK_TYPE_KDF (16) /* key identifier for KDF */ -#define SKEIN_BLK_TYPE_NONCE (20) /* nonce for PRNG */ -#define SKEIN_BLK_TYPE_MSG (48) /* message processing */ -#define SKEIN_BLK_TYPE_OUT (63) /* output stage */ -#define SKEIN_BLK_TYPE_MASK (63) /* bit field mask */ - -#define SKEIN_T1_BLK_TYPE(T) (((u64b_t) (SKEIN_BLK_TYPE_##T)) << SKEIN_T1_POS_BLK_TYPE) -#define SKEIN_T1_BLK_TYPE_KEY SKEIN_T1_BLK_TYPE(KEY) /* key, for MAC and KDF */ -#define SKEIN_T1_BLK_TYPE_CFG SKEIN_T1_BLK_TYPE(CFG) /* configuration block */ -#define SKEIN_T1_BLK_TYPE_PERS SKEIN_T1_BLK_TYPE(PERS) /* personalization string */ -#define SKEIN_T1_BLK_TYPE_PK SKEIN_T1_BLK_TYPE(PK) /* public key (for digital signature hashing) */ -#define SKEIN_T1_BLK_TYPE_KDF SKEIN_T1_BLK_TYPE(KDF) /* key identifier for KDF */ -#define SKEIN_T1_BLK_TYPE_NONCE SKEIN_T1_BLK_TYPE(NONCE)/* nonce for PRNG */ -#define SKEIN_T1_BLK_TYPE_MSG SKEIN_T1_BLK_TYPE(MSG) /* message processing */ -#define SKEIN_T1_BLK_TYPE_OUT SKEIN_T1_BLK_TYPE(OUT) /* output stage */ -#define SKEIN_T1_BLK_TYPE_MASK SKEIN_T1_BLK_TYPE(MASK) /* field bit mask */ - -#define SKEIN_T1_BLK_TYPE_CFG_FINAL (SKEIN_T1_BLK_TYPE_CFG | SKEIN_T1_FLAG_FINAL) -#define SKEIN_T1_BLK_TYPE_OUT_FINAL (SKEIN_T1_BLK_TYPE_OUT | SKEIN_T1_FLAG_FINAL) - -#define SKEIN_VERSION (1) - -#ifndef SKEIN_ID_STRING_LE /* allow compile-time personalization */ -#define SKEIN_ID_STRING_LE (0x33414853) /* "SHA3" (little-endian)*/ -#endif - -#define SKEIN_MK_64(hi32,lo32) ((lo32) + (((u64b_t) (hi32)) << 32)) -#define SKEIN_SCHEMA_VER SKEIN_MK_64(SKEIN_VERSION,SKEIN_ID_STRING_LE) -#define SKEIN_KS_PARITY SKEIN_MK_64(0x1BD11BDA,0xA9FC1A22) - -#define SKEIN_CFG_STR_LEN (4*8) - -/* bit field definitions in config block treeInfo word */ -#define SKEIN_CFG_TREE_LEAF_SIZE_POS ( 0) -#define SKEIN_CFG_TREE_NODE_SIZE_POS ( 8) -#define SKEIN_CFG_TREE_MAX_LEVEL_POS (16) - -#define SKEIN_CFG_TREE_LEAF_SIZE_MSK (((u64b_t) 0xFF) << SKEIN_CFG_TREE_LEAF_SIZE_POS) -#define SKEIN_CFG_TREE_NODE_SIZE_MSK (((u64b_t) 0xFF) << SKEIN_CFG_TREE_NODE_SIZE_POS) -#define SKEIN_CFG_TREE_MAX_LEVEL_MSK (((u64b_t) 0xFF) << SKEIN_CFG_TREE_MAX_LEVEL_POS) - -#define SKEIN_CFG_TREE_INFO(leaf,node,maxLvl) \ - ( (((u64b_t)(leaf )) << SKEIN_CFG_TREE_LEAF_SIZE_POS) | \ - (((u64b_t)(node )) << SKEIN_CFG_TREE_NODE_SIZE_POS) | \ - (((u64b_t)(maxLvl)) << SKEIN_CFG_TREE_MAX_LEVEL_POS) ) - -#define SKEIN_CFG_TREE_INFO_SEQUENTIAL SKEIN_CFG_TREE_INFO(0,0,0) /* use as treeInfo in InitExt() call for sequential processing */ - -/* -** Skein macros for getting/setting tweak words, etc. -** These are useful for partial input bytes, hash tree init/update, etc. -**/ -#define Skein_Get_Tweak(ctxPtr,TWK_NUM) ((ctxPtr)->h.T[TWK_NUM]) -#define Skein_Set_Tweak(ctxPtr,TWK_NUM,tVal) {(ctxPtr)->h.T[TWK_NUM] = (tVal);} - -#define Skein_Get_T0(ctxPtr) Skein_Get_Tweak(ctxPtr,0) -#define Skein_Get_T1(ctxPtr) Skein_Get_Tweak(ctxPtr,1) -#define Skein_Set_T0(ctxPtr,T0) Skein_Set_Tweak(ctxPtr,0,T0) -#define Skein_Set_T1(ctxPtr,T1) Skein_Set_Tweak(ctxPtr,1,T1) - -/* set both tweak words at once */ -#define Skein_Set_T0_T1(ctxPtr,T0,T1) \ -{ \ - Skein_Set_T0(ctxPtr,(T0)); \ - Skein_Set_T1(ctxPtr,(T1)); \ -} - -#define Skein_Set_Type(ctxPtr,BLK_TYPE) \ - Skein_Set_T1(ctxPtr,SKEIN_T1_BLK_TYPE_##BLK_TYPE) - -/* set up for starting with a new type: h.T[0]=0; h.T[1] = NEW_TYPE; h.bCnt=0; */ -#define Skein_Start_New_Type(ctxPtr,BLK_TYPE) \ -{ Skein_Set_T0_T1(ctxPtr,0,SKEIN_T1_FLAG_FIRST | SKEIN_T1_BLK_TYPE_##BLK_TYPE); (ctxPtr)->h.bCnt=0; } - -#define Skein_Clear_First_Flag(hdr) { (hdr).T[1] &= ~SKEIN_T1_FLAG_FIRST; } -#define Skein_Set_Bit_Pad_Flag(hdr) { (hdr).T[1] |= SKEIN_T1_FLAG_BIT_PAD; } - -#define Skein_Set_Tree_Level(hdr,height) { (hdr).T[1] |= SKEIN_T1_TREE_LEVEL(height);} - -/***************************************************************** -** "Internal" Skein definitions for debugging and error checking -******************************************************************/ -#define Skein_Show_Block(bits,ctx,X,blkPtr,wPtr,ksEvenPtr,ksOddPtr) -#define Skein_Show_Round(bits,ctx,r,X) -#define Skein_Show_R_Ptr(bits,ctx,r,X_ptr) -#define Skein_Show_Final(bits,ctx,cnt,outPtr) -#define Skein_Show_Key(bits,ctx,key,keyBytes) - - -#ifndef SKEIN_ERR_CHECK /* run-time checks (e.g., bad params, uninitialized context)? */ -#define Skein_Assert(x,retCode)/* default: ignore all Asserts, for performance */ -#define Skein_assert(x) -#elif defined(SKEIN_ASSERT) -#include -#define Skein_Assert(x,retCode) assert(x) -#define Skein_assert(x) assert(x) -#else -#include -#define Skein_Assert(x,retCode) { if (!(x)) return retCode; } /* caller error */ -#define Skein_assert(x) assert(x) /* internal error */ -#endif - -/***************************************************************** -** Skein block function constants (shared across Ref and Opt code) -******************************************************************/ -enum -{ - /* Skein_256 round rotation constants */ - R_256_0_0=14, R_256_0_1=16, - R_256_1_0=52, R_256_1_1=57, - R_256_2_0=23, R_256_2_1=40, - R_256_3_0= 5, R_256_3_1=37, - R_256_4_0=25, R_256_4_1=33, - R_256_5_0=46, R_256_5_1=12, - R_256_6_0=58, R_256_6_1=22, - R_256_7_0=32, R_256_7_1=32, - - /* Skein_512 round rotation constants */ - R_512_0_0=46, R_512_0_1=36, R_512_0_2=19, R_512_0_3=37, - R_512_1_0=33, R_512_1_1=27, R_512_1_2=14, R_512_1_3=42, - R_512_2_0=17, R_512_2_1=49, R_512_2_2=36, R_512_2_3=39, - R_512_3_0=44, R_512_3_1= 9, R_512_3_2=54, R_512_3_3=56, - R_512_4_0=39, R_512_4_1=30, R_512_4_2=34, R_512_4_3=24, - R_512_5_0=13, R_512_5_1=50, R_512_5_2=10, R_512_5_3=17, - R_512_6_0=25, R_512_6_1=29, R_512_6_2=39, R_512_6_3=43, - R_512_7_0= 8, R_512_7_1=35, R_512_7_2=56, R_512_7_3=22, - - /* Skein1024 round rotation constants */ - R1024_0_0=24, R1024_0_1=13, R1024_0_2= 8, R1024_0_3=47, R1024_0_4= 8, R1024_0_5=17, R1024_0_6=22, R1024_0_7=37, - R1024_1_0=38, R1024_1_1=19, R1024_1_2=10, R1024_1_3=55, R1024_1_4=49, R1024_1_5=18, R1024_1_6=23, R1024_1_7=52, - R1024_2_0=33, R1024_2_1= 4, R1024_2_2=51, R1024_2_3=13, R1024_2_4=34, R1024_2_5=41, R1024_2_6=59, R1024_2_7=17, - R1024_3_0= 5, R1024_3_1=20, R1024_3_2=48, R1024_3_3=41, R1024_3_4=47, R1024_3_5=28, R1024_3_6=16, R1024_3_7=25, - R1024_4_0=41, R1024_4_1= 9, R1024_4_2=37, R1024_4_3=31, R1024_4_4=12, R1024_4_5=47, R1024_4_6=44, R1024_4_7=30, - R1024_5_0=16, R1024_5_1=34, R1024_5_2=56, R1024_5_3=51, R1024_5_4= 4, R1024_5_5=53, R1024_5_6=42, R1024_5_7=41, - R1024_6_0=31, R1024_6_1=44, R1024_6_2=47, R1024_6_3=46, R1024_6_4=19, R1024_6_5=42, R1024_6_6=44, R1024_6_7=25, - R1024_7_0= 9, R1024_7_1=48, R1024_7_2=35, R1024_7_3=52, R1024_7_4=23, R1024_7_5=31, R1024_7_6=37, R1024_7_7=20 -}; - -#ifndef SKEIN_ROUNDS -#define SKEIN_256_ROUNDS_TOTAL (72) /* number of rounds for the different block sizes */ -#define SKEIN_512_ROUNDS_TOTAL (72) -#define SKEIN1024_ROUNDS_TOTAL (80) -#else /* allow command-line define in range 8*(5..14) */ -#define SKEIN_256_ROUNDS_TOTAL (8*((((SKEIN_ROUNDS/100) + 5) % 10) + 5)) -#define SKEIN_512_ROUNDS_TOTAL (8*((((SKEIN_ROUNDS/ 10) + 5) % 10) + 5)) -#define SKEIN1024_ROUNDS_TOTAL (8*((((SKEIN_ROUNDS ) + 5) % 10) + 5)) -#endif - - -/* -***************** Pre-computed Skein IVs ******************* -** -** NOTE: these values are not "magic" constants, but -** are generated using the Threefish block function. -** They are pre-computed here only for speed; i.e., to -** avoid the need for a Threefish call during Init(). -** -** The IV for any fixed hash length may be pre-computed. -** Only the most common values are included here. -** -************************************************************ -**/ - -#define MK_64 SKEIN_MK_64 - -/* blkSize = 256 bits. hashSize = 128 bits */ -const u64b_t SKEIN_256_IV_128[] = - { - MK_64(0xE1111906,0x964D7260), - MK_64(0x883DAAA7,0x7C8D811C), - MK_64(0x10080DF4,0x91960F7A), - MK_64(0xCCF7DDE5,0xB45BC1C2) - }; - -/* blkSize = 256 bits. hashSize = 160 bits */ -const u64b_t SKEIN_256_IV_160[] = - { - MK_64(0x14202314,0x72825E98), - MK_64(0x2AC4E9A2,0x5A77E590), - MK_64(0xD47A5856,0x8838D63E), - MK_64(0x2DD2E496,0x8586AB7D) - }; - -/* blkSize = 256 bits. hashSize = 224 bits */ -const u64b_t SKEIN_256_IV_224[] = - { - MK_64(0xC6098A8C,0x9AE5EA0B), - MK_64(0x876D5686,0x08C5191C), - MK_64(0x99CB88D7,0xD7F53884), - MK_64(0x384BDDB1,0xAEDDB5DE) - }; - -/* blkSize = 256 bits. hashSize = 256 bits */ -const u64b_t SKEIN_256_IV_256[] = - { - MK_64(0xFC9DA860,0xD048B449), - MK_64(0x2FCA6647,0x9FA7D833), - MK_64(0xB33BC389,0x6656840F), - MK_64(0x6A54E920,0xFDE8DA69) - }; - -/* blkSize = 512 bits. hashSize = 128 bits */ -const u64b_t SKEIN_512_IV_128[] = - { - MK_64(0xA8BC7BF3,0x6FBF9F52), - MK_64(0x1E9872CE,0xBD1AF0AA), - MK_64(0x309B1790,0xB32190D3), - MK_64(0xBCFBB854,0x3F94805C), - MK_64(0x0DA61BCD,0x6E31B11B), - MK_64(0x1A18EBEA,0xD46A32E3), - MK_64(0xA2CC5B18,0xCE84AA82), - MK_64(0x6982AB28,0x9D46982D) - }; - -/* blkSize = 512 bits. hashSize = 160 bits */ -const u64b_t SKEIN_512_IV_160[] = - { - MK_64(0x28B81A2A,0xE013BD91), - MK_64(0xC2F11668,0xB5BDF78F), - MK_64(0x1760D8F3,0xF6A56F12), - MK_64(0x4FB74758,0x8239904F), - MK_64(0x21EDE07F,0x7EAF5056), - MK_64(0xD908922E,0x63ED70B8), - MK_64(0xB8EC76FF,0xECCB52FA), - MK_64(0x01A47BB8,0xA3F27A6E) - }; - -/* blkSize = 512 bits. hashSize = 224 bits */ -const u64b_t SKEIN_512_IV_224[] = - { - MK_64(0xCCD06162,0x48677224), - MK_64(0xCBA65CF3,0xA92339EF), - MK_64(0x8CCD69D6,0x52FF4B64), - MK_64(0x398AED7B,0x3AB890B4), - MK_64(0x0F59D1B1,0x457D2BD0), - MK_64(0x6776FE65,0x75D4EB3D), - MK_64(0x99FBC70E,0x997413E9), - MK_64(0x9E2CFCCF,0xE1C41EF7) - }; - -/* blkSize = 512 bits. hashSize = 256 bits */ -const u64b_t SKEIN_512_IV_256[] = - { - MK_64(0xCCD044A1,0x2FDB3E13), - MK_64(0xE8359030,0x1A79A9EB), - MK_64(0x55AEA061,0x4F816E6F), - MK_64(0x2A2767A4,0xAE9B94DB), - MK_64(0xEC06025E,0x74DD7683), - MK_64(0xE7A436CD,0xC4746251), - MK_64(0xC36FBAF9,0x393AD185), - MK_64(0x3EEDBA18,0x33EDFC13) - }; - -/* blkSize = 512 bits. hashSize = 384 bits */ -const u64b_t SKEIN_512_IV_384[] = - { - MK_64(0xA3F6C6BF,0x3A75EF5F), - MK_64(0xB0FEF9CC,0xFD84FAA4), - MK_64(0x9D77DD66,0x3D770CFE), - MK_64(0xD798CBF3,0xB468FDDA), - MK_64(0x1BC4A666,0x8A0E4465), - MK_64(0x7ED7D434,0xE5807407), - MK_64(0x548FC1AC,0xD4EC44D6), - MK_64(0x266E1754,0x6AA18FF8) - }; - -/* blkSize = 512 bits. hashSize = 512 bits */ -const u64b_t SKEIN_512_IV_512[] = - { - MK_64(0x4903ADFF,0x749C51CE), - MK_64(0x0D95DE39,0x9746DF03), - MK_64(0x8FD19341,0x27C79BCE), - MK_64(0x9A255629,0xFF352CB1), - MK_64(0x5DB62599,0xDF6CA7B0), - MK_64(0xEABE394C,0xA9D5C3F4), - MK_64(0x991112C7,0x1A75B523), - MK_64(0xAE18A40B,0x660FCC33) - }; - -/* blkSize = 1024 bits. hashSize = 384 bits */ -const u64b_t SKEIN1024_IV_384[] = - { - MK_64(0x5102B6B8,0xC1894A35), - MK_64(0xFEEBC9E3,0xFE8AF11A), - MK_64(0x0C807F06,0xE32BED71), - MK_64(0x60C13A52,0xB41A91F6), - MK_64(0x9716D35D,0xD4917C38), - MK_64(0xE780DF12,0x6FD31D3A), - MK_64(0x797846B6,0xC898303A), - MK_64(0xB172C2A8,0xB3572A3B), - MK_64(0xC9BC8203,0xA6104A6C), - MK_64(0x65909338,0xD75624F4), - MK_64(0x94BCC568,0x4B3F81A0), - MK_64(0x3EBBF51E,0x10ECFD46), - MK_64(0x2DF50F0B,0xEEB08542), - MK_64(0x3B5A6530,0x0DBC6516), - MK_64(0x484B9CD2,0x167BBCE1), - MK_64(0x2D136947,0xD4CBAFEA) - }; - -/* blkSize = 1024 bits. hashSize = 512 bits */ -const u64b_t SKEIN1024_IV_512[] = - { - MK_64(0xCAEC0E5D,0x7C1B1B18), - MK_64(0xA01B0E04,0x5F03E802), - MK_64(0x33840451,0xED912885), - MK_64(0x374AFB04,0xEAEC2E1C), - MK_64(0xDF25A0E2,0x813581F7), - MK_64(0xE4004093,0x8B12F9D2), - MK_64(0xA662D539,0xC2ED39B6), - MK_64(0xFA8B85CF,0x45D8C75A), - MK_64(0x8316ED8E,0x29EDE796), - MK_64(0x053289C0,0x2E9F91B8), - MK_64(0xC3F8EF1D,0x6D518B73), - MK_64(0xBDCEC3C4,0xD5EF332E), - MK_64(0x549A7E52,0x22974487), - MK_64(0x67070872,0x5B749816), - MK_64(0xB9CD28FB,0xF0581BD1), - MK_64(0x0E2940B8,0x15804974) - }; - -/* blkSize = 1024 bits. hashSize = 1024 bits */ -const u64b_t SKEIN1024_IV_1024[] = - { - MK_64(0xD593DA07,0x41E72355), - MK_64(0x15B5E511,0xAC73E00C), - MK_64(0x5180E5AE,0xBAF2C4F0), - MK_64(0x03BD41D3,0xFCBCAFAF), - MK_64(0x1CAEC6FD,0x1983A898), - MK_64(0x6E510B8B,0xCDD0589F), - MK_64(0x77E2BDFD,0xC6394ADA), - MK_64(0xC11E1DB5,0x24DCB0A3), - MK_64(0xD6D14AF9,0xC6329AB5), - MK_64(0x6A9B0BFC,0x6EB67E0D), - MK_64(0x9243C60D,0xCCFF1332), - MK_64(0x1A1F1DDE,0x743F02D4), - MK_64(0x0996753C,0x10ED0BB8), - MK_64(0x6572DD22,0xF2B4969A), - MK_64(0x61FD3062,0xD00A579A), - MK_64(0x1DE0536E,0x8682E539) - }; - - -#ifndef SKEIN_USE_ASM -#define SKEIN_USE_ASM (0) /* default is all C code (no ASM) */ -#endif - -#ifndef SKEIN_LOOP -#define SKEIN_LOOP 001 /* default: unroll 256 and 512, but not 1024 */ -#endif - -#define BLK_BITS (WCNT*64) /* some useful definitions for code here */ -#define KW_TWK_BASE (0) -#define KW_KEY_BASE (3) -#define ks (kw + KW_KEY_BASE) -#define ts (kw + KW_TWK_BASE) - -#ifdef SKEIN_DEBUG -#define DebugSaveTweak(ctx) { ctx->h.T[0] = ts[0]; ctx->h.T[1] = ts[1]; } -#else -#define DebugSaveTweak(ctx) -#endif - -/***************************** Skein_256 ******************************/ -#if !(SKEIN_USE_ASM & 256) -static void Skein_256_Process_Block(Skein_256_Ctxt_t *ctx,const u08b_t *blkPtr,size_t blkCnt,size_t byteCntAdd) - { /* do it in C */ - enum - { - WCNT = SKEIN_256_STATE_WORDS - }; -#undef RCNT -#define RCNT (SKEIN_256_ROUNDS_TOTAL/8) - -#ifdef SKEIN_LOOP /* configure how much to unroll the loop */ -#define SKEIN_UNROLL_256 (((SKEIN_LOOP)/100)%10) -#else -#define SKEIN_UNROLL_256 (0) -#endif - -#if SKEIN_UNROLL_256 -#if (RCNT % SKEIN_UNROLL_256) -#error "Invalid SKEIN_UNROLL_256" /* sanity check on unroll count */ -#endif - size_t r; - u64b_t kw[WCNT+4+RCNT*2]; /* key schedule words : chaining vars + tweak + "rotation"*/ -#else - u64b_t kw[WCNT+4]; /* key schedule words : chaining vars + tweak */ -#endif - u64b_t X0,X1,X2,X3; /* local copy of context vars, for speed */ - u64b_t w [WCNT]; /* local copy of input block */ -#ifdef SKEIN_DEBUG - const u64b_t *Xptr[4]; /* use for debugging (help compiler put Xn in registers) */ - Xptr[0] = &X0; Xptr[1] = &X1; Xptr[2] = &X2; Xptr[3] = &X3; -#endif - Skein_assert(blkCnt != 0); /* never call with blkCnt == 0! */ - ts[0] = ctx->h.T[0]; - ts[1] = ctx->h.T[1]; - do { - /* this implementation only supports 2**64 input bytes (no carry out here) */ - ts[0] += byteCntAdd; /* update processed length */ - - /* precompute the key schedule for this block */ - ks[0] = ctx->X[0]; - ks[1] = ctx->X[1]; - ks[2] = ctx->X[2]; - ks[3] = ctx->X[3]; - ks[4] = ks[0] ^ ks[1] ^ ks[2] ^ ks[3] ^ SKEIN_KS_PARITY; - - ts[2] = ts[0] ^ ts[1]; - - Skein_Get64_LSB_First(w,blkPtr,WCNT); /* get input block in little-endian format */ - DebugSaveTweak(ctx); - Skein_Show_Block(BLK_BITS,&ctx->h,ctx->X,blkPtr,w,ks,ts); - - X0 = w[0] + ks[0]; /* do the first full key injection */ - X1 = w[1] + ks[1] + ts[0]; - X2 = w[2] + ks[2] + ts[1]; - X3 = w[3] + ks[3]; - - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INITIAL,Xptr); /* show starting state values */ - - blkPtr += SKEIN_256_BLOCK_BYTES; - - /* run the rounds */ - -#define Round256(p0,p1,p2,p3,ROT,rNum) \ - X##p0 += X##p1; X##p1 = RotL_64(X##p1,ROT##_0); X##p1 ^= X##p0; \ - X##p2 += X##p3; X##p3 = RotL_64(X##p3,ROT##_1); X##p3 ^= X##p2; \ - -#if SKEIN_UNROLL_256 == 0 -#define R256(p0,p1,p2,p3,ROT,rNum) /* fully unrolled */ \ - Round256(p0,p1,p2,p3,ROT,rNum) \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,rNum,Xptr); - -#define I256(R) \ - X0 += ks[((R)+1) % 5]; /* inject the key schedule value */ \ - X1 += ks[((R)+2) % 5] + ts[((R)+1) % 3]; \ - X2 += ks[((R)+3) % 5] + ts[((R)+2) % 3]; \ - X3 += ks[((R)+4) % 5] + (R)+1; \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INJECT,Xptr); -#else /* looping version */ -#define R256(p0,p1,p2,p3,ROT,rNum) \ - Round256(p0,p1,p2,p3,ROT,rNum) \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,4*(r-1)+rNum,Xptr); - -#define I256(R) \ - X0 += ks[r+(R)+0]; /* inject the key schedule value */ \ - X1 += ks[r+(R)+1] + ts[r+(R)+0]; \ - X2 += ks[r+(R)+2] + ts[r+(R)+1]; \ - X3 += ks[r+(R)+3] + r+(R) ; \ - ks[r + (R)+4 ] = ks[r+(R)-1]; /* rotate key schedule */\ - ts[r + (R)+2 ] = ts[r+(R)-1]; \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INJECT,Xptr); - - for (r=1;r < 2*RCNT;r+=2*SKEIN_UNROLL_256) /* loop thru it */ -#endif - { -#define R256_8_rounds(R) \ - R256(0,1,2,3,R_256_0,8*(R) + 1); \ - R256(0,3,2,1,R_256_1,8*(R) + 2); \ - R256(0,1,2,3,R_256_2,8*(R) + 3); \ - R256(0,3,2,1,R_256_3,8*(R) + 4); \ - I256(2*(R)); \ - R256(0,1,2,3,R_256_4,8*(R) + 5); \ - R256(0,3,2,1,R_256_5,8*(R) + 6); \ - R256(0,1,2,3,R_256_6,8*(R) + 7); \ - R256(0,3,2,1,R_256_7,8*(R) + 8); \ - I256(2*(R)+1); - - R256_8_rounds( 0); - -#define R256_Unroll_R(NN) ((SKEIN_UNROLL_256 == 0 && SKEIN_256_ROUNDS_TOTAL/8 > (NN)) || (SKEIN_UNROLL_256 > (NN))) - - #if R256_Unroll_R( 1) - R256_8_rounds( 1); - #endif - #if R256_Unroll_R( 2) - R256_8_rounds( 2); - #endif - #if R256_Unroll_R( 3) - R256_8_rounds( 3); - #endif - #if R256_Unroll_R( 4) - R256_8_rounds( 4); - #endif - #if R256_Unroll_R( 5) - R256_8_rounds( 5); - #endif - #if R256_Unroll_R( 6) - R256_8_rounds( 6); - #endif - #if R256_Unroll_R( 7) - R256_8_rounds( 7); - #endif - #if R256_Unroll_R( 8) - R256_8_rounds( 8); - #endif - #if R256_Unroll_R( 9) - R256_8_rounds( 9); - #endif - #if R256_Unroll_R(10) - R256_8_rounds(10); - #endif - #if R256_Unroll_R(11) - R256_8_rounds(11); - #endif - #if R256_Unroll_R(12) - R256_8_rounds(12); - #endif - #if R256_Unroll_R(13) - R256_8_rounds(13); - #endif - #if R256_Unroll_R(14) - R256_8_rounds(14); - #endif - #if (SKEIN_UNROLL_256 > 14) -#error "need more unrolling in Skein_256_Process_Block" - #endif - } - /* do the final "feedforward" xor, update context chaining vars */ - ctx->X[0] = X0 ^ w[0]; - ctx->X[1] = X1 ^ w[1]; - ctx->X[2] = X2 ^ w[2]; - ctx->X[3] = X3 ^ w[3]; - - Skein_Show_Round(BLK_BITS,&ctx->h,SKEIN_RND_FEED_FWD,ctx->X); - - ts[1] &= ~SKEIN_T1_FLAG_FIRST; - } - while (--blkCnt); - ctx->h.T[0] = ts[0]; - ctx->h.T[1] = ts[1]; - } - -#if defined(SKEIN_CODE_SIZE) || defined(SKEIN_PERF) -static size_t Skein_256_Process_Block_CodeSize(void) - { - return ((u08b_t *) Skein_256_Process_Block_CodeSize) - - ((u08b_t *) Skein_256_Process_Block); - } -static uint_t Skein_256_Unroll_Cnt(void) - { - return SKEIN_UNROLL_256; - } -#endif -#endif - -/***************************** Skein_512 ******************************/ -#if !(SKEIN_USE_ASM & 512) -static void Skein_512_Process_Block(Skein_512_Ctxt_t *ctx,const u08b_t *blkPtr,size_t blkCnt,size_t byteCntAdd) - { /* do it in C */ - enum - { - WCNT = SKEIN_512_STATE_WORDS - }; -#undef RCNT -#define RCNT (SKEIN_512_ROUNDS_TOTAL/8) - -#ifdef SKEIN_LOOP /* configure how much to unroll the loop */ -#define SKEIN_UNROLL_512 (((SKEIN_LOOP)/10)%10) -#else -#define SKEIN_UNROLL_512 (0) -#endif - -#if SKEIN_UNROLL_512 -#if (RCNT % SKEIN_UNROLL_512) -#error "Invalid SKEIN_UNROLL_512" /* sanity check on unroll count */ -#endif - size_t r; - u64b_t kw[WCNT+4+RCNT*2]; /* key schedule words : chaining vars + tweak + "rotation"*/ -#else - u64b_t kw[WCNT+4]; /* key schedule words : chaining vars + tweak */ -#endif - u64b_t X0,X1,X2,X3,X4,X5,X6,X7; /* local copy of vars, for speed */ - u64b_t w [WCNT]; /* local copy of input block */ -#ifdef SKEIN_DEBUG - const u64b_t *Xptr[8]; /* use for debugging (help compiler put Xn in registers) */ - Xptr[0] = &X0; Xptr[1] = &X1; Xptr[2] = &X2; Xptr[3] = &X3; - Xptr[4] = &X4; Xptr[5] = &X5; Xptr[6] = &X6; Xptr[7] = &X7; -#endif - - Skein_assert(blkCnt != 0); /* never call with blkCnt == 0! */ - ts[0] = ctx->h.T[0]; - ts[1] = ctx->h.T[1]; - do { - /* this implementation only supports 2**64 input bytes (no carry out here) */ - ts[0] += byteCntAdd; /* update processed length */ - - /* precompute the key schedule for this block */ - ks[0] = ctx->X[0]; - ks[1] = ctx->X[1]; - ks[2] = ctx->X[2]; - ks[3] = ctx->X[3]; - ks[4] = ctx->X[4]; - ks[5] = ctx->X[5]; - ks[6] = ctx->X[6]; - ks[7] = ctx->X[7]; - ks[8] = ks[0] ^ ks[1] ^ ks[2] ^ ks[3] ^ - ks[4] ^ ks[5] ^ ks[6] ^ ks[7] ^ SKEIN_KS_PARITY; - - ts[2] = ts[0] ^ ts[1]; - - Skein_Get64_LSB_First(w,blkPtr,WCNT); /* get input block in little-endian format */ - DebugSaveTweak(ctx); - Skein_Show_Block(BLK_BITS,&ctx->h,ctx->X,blkPtr,w,ks,ts); - - X0 = w[0] + ks[0]; /* do the first full key injection */ - X1 = w[1] + ks[1]; - X2 = w[2] + ks[2]; - X3 = w[3] + ks[3]; - X4 = w[4] + ks[4]; - X5 = w[5] + ks[5] + ts[0]; - X6 = w[6] + ks[6] + ts[1]; - X7 = w[7] + ks[7]; - - blkPtr += SKEIN_512_BLOCK_BYTES; - - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INITIAL,Xptr); - /* run the rounds */ -#define Round512(p0,p1,p2,p3,p4,p5,p6,p7,ROT,rNum) \ - X##p0 += X##p1; X##p1 = RotL_64(X##p1,ROT##_0); X##p1 ^= X##p0; \ - X##p2 += X##p3; X##p3 = RotL_64(X##p3,ROT##_1); X##p3 ^= X##p2; \ - X##p4 += X##p5; X##p5 = RotL_64(X##p5,ROT##_2); X##p5 ^= X##p4; \ - X##p6 += X##p7; X##p7 = RotL_64(X##p7,ROT##_3); X##p7 ^= X##p6; \ - -#if SKEIN_UNROLL_512 == 0 -#define R512(p0,p1,p2,p3,p4,p5,p6,p7,ROT,rNum) /* unrolled */ \ - Round512(p0,p1,p2,p3,p4,p5,p6,p7,ROT,rNum) \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,rNum,Xptr); - -#define I512(R) \ - X0 += ks[((R)+1) % 9]; /* inject the key schedule value */ \ - X1 += ks[((R)+2) % 9]; \ - X2 += ks[((R)+3) % 9]; \ - X3 += ks[((R)+4) % 9]; \ - X4 += ks[((R)+5) % 9]; \ - X5 += ks[((R)+6) % 9] + ts[((R)+1) % 3]; \ - X6 += ks[((R)+7) % 9] + ts[((R)+2) % 3]; \ - X7 += ks[((R)+8) % 9] + (R)+1; \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INJECT,Xptr); -#else /* looping version */ -#define R512(p0,p1,p2,p3,p4,p5,p6,p7,ROT,rNum) \ - Round512(p0,p1,p2,p3,p4,p5,p6,p7,ROT,rNum) \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,4*(r-1)+rNum,Xptr); - -#define I512(R) \ - X0 += ks[r+(R)+0]; /* inject the key schedule value */ \ - X1 += ks[r+(R)+1]; \ - X2 += ks[r+(R)+2]; \ - X3 += ks[r+(R)+3]; \ - X4 += ks[r+(R)+4]; \ - X5 += ks[r+(R)+5] + ts[r+(R)+0]; \ - X6 += ks[r+(R)+6] + ts[r+(R)+1]; \ - X7 += ks[r+(R)+7] + r+(R) ; \ - ks[r + (R)+8] = ks[r+(R)-1]; /* rotate key schedule */ \ - ts[r + (R)+2] = ts[r+(R)-1]; \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INJECT,Xptr); - - for (r=1;r < 2*RCNT;r+=2*SKEIN_UNROLL_512) /* loop thru it */ -#endif /* end of looped code definitions */ - { -#define R512_8_rounds(R) /* do 8 full rounds */ \ - R512(0,1,2,3,4,5,6,7,R_512_0,8*(R)+ 1); \ - R512(2,1,4,7,6,5,0,3,R_512_1,8*(R)+ 2); \ - R512(4,1,6,3,0,5,2,7,R_512_2,8*(R)+ 3); \ - R512(6,1,0,7,2,5,4,3,R_512_3,8*(R)+ 4); \ - I512(2*(R)); \ - R512(0,1,2,3,4,5,6,7,R_512_4,8*(R)+ 5); \ - R512(2,1,4,7,6,5,0,3,R_512_5,8*(R)+ 6); \ - R512(4,1,6,3,0,5,2,7,R_512_6,8*(R)+ 7); \ - R512(6,1,0,7,2,5,4,3,R_512_7,8*(R)+ 8); \ - I512(2*(R)+1); /* and key injection */ - - R512_8_rounds( 0); - -#define R512_Unroll_R(NN) ((SKEIN_UNROLL_512 == 0 && SKEIN_512_ROUNDS_TOTAL/8 > (NN)) || (SKEIN_UNROLL_512 > (NN))) - - #if R512_Unroll_R( 1) - R512_8_rounds( 1); - #endif - #if R512_Unroll_R( 2) - R512_8_rounds( 2); - #endif - #if R512_Unroll_R( 3) - R512_8_rounds( 3); - #endif - #if R512_Unroll_R( 4) - R512_8_rounds( 4); - #endif - #if R512_Unroll_R( 5) - R512_8_rounds( 5); - #endif - #if R512_Unroll_R( 6) - R512_8_rounds( 6); - #endif - #if R512_Unroll_R( 7) - R512_8_rounds( 7); - #endif - #if R512_Unroll_R( 8) - R512_8_rounds( 8); - #endif - #if R512_Unroll_R( 9) - R512_8_rounds( 9); - #endif - #if R512_Unroll_R(10) - R512_8_rounds(10); - #endif - #if R512_Unroll_R(11) - R512_8_rounds(11); - #endif - #if R512_Unroll_R(12) - R512_8_rounds(12); - #endif - #if R512_Unroll_R(13) - R512_8_rounds(13); - #endif - #if R512_Unroll_R(14) - R512_8_rounds(14); - #endif - #if (SKEIN_UNROLL_512 > 14) -#error "need more unrolling in Skein_512_Process_Block" - #endif - } - - /* do the final "feedforward" xor, update context chaining vars */ - ctx->X[0] = X0 ^ w[0]; - ctx->X[1] = X1 ^ w[1]; - ctx->X[2] = X2 ^ w[2]; - ctx->X[3] = X3 ^ w[3]; - ctx->X[4] = X4 ^ w[4]; - ctx->X[5] = X5 ^ w[5]; - ctx->X[6] = X6 ^ w[6]; - ctx->X[7] = X7 ^ w[7]; - Skein_Show_Round(BLK_BITS,&ctx->h,SKEIN_RND_FEED_FWD,ctx->X); - - ts[1] &= ~SKEIN_T1_FLAG_FIRST; - } - while (--blkCnt); - ctx->h.T[0] = ts[0]; - ctx->h.T[1] = ts[1]; - } - -#if defined(SKEIN_CODE_SIZE) || defined(SKEIN_PERF) -static size_t Skein_512_Process_Block_CodeSize(void) - { - return ((u08b_t *) Skein_512_Process_Block_CodeSize) - - ((u08b_t *) Skein_512_Process_Block); - } -static uint_t Skein_512_Unroll_Cnt(void) - { - return SKEIN_UNROLL_512; - } -#endif -#endif - -/***************************** Skein1024 ******************************/ -#if !(SKEIN_USE_ASM & 1024) -static void Skein1024_Process_Block(Skein1024_Ctxt_t *ctx,const u08b_t *blkPtr,size_t blkCnt,size_t byteCntAdd) - { /* do it in C, always looping (unrolled is bigger AND slower!) */ - enum - { - WCNT = SKEIN1024_STATE_WORDS - }; -#undef RCNT -#define RCNT (SKEIN1024_ROUNDS_TOTAL/8) - -#ifdef SKEIN_LOOP /* configure how much to unroll the loop */ -#define SKEIN_UNROLL_1024 ((SKEIN_LOOP)%10) -#else -#define SKEIN_UNROLL_1024 (0) -#endif - -#if (SKEIN_UNROLL_1024 != 0) -#if (RCNT % SKEIN_UNROLL_1024) -#error "Invalid SKEIN_UNROLL_1024" /* sanity check on unroll count */ -#endif - size_t r; - u64b_t kw[WCNT+4+RCNT*2]; /* key schedule words : chaining vars + tweak + "rotation"*/ -#else - u64b_t kw[WCNT+4]; /* key schedule words : chaining vars + tweak */ -#endif - - u64b_t X00,X01,X02,X03,X04,X05,X06,X07, /* local copy of vars, for speed */ - X08,X09,X10,X11,X12,X13,X14,X15; - u64b_t w [WCNT]; /* local copy of input block */ -#ifdef SKEIN_DEBUG - const u64b_t *Xptr[16]; /* use for debugging (help compiler put Xn in registers) */ - Xptr[ 0] = &X00; Xptr[ 1] = &X01; Xptr[ 2] = &X02; Xptr[ 3] = &X03; - Xptr[ 4] = &X04; Xptr[ 5] = &X05; Xptr[ 6] = &X06; Xptr[ 7] = &X07; - Xptr[ 8] = &X08; Xptr[ 9] = &X09; Xptr[10] = &X10; Xptr[11] = &X11; - Xptr[12] = &X12; Xptr[13] = &X13; Xptr[14] = &X14; Xptr[15] = &X15; -#endif - - Skein_assert(blkCnt != 0); /* never call with blkCnt == 0! */ - ts[0] = ctx->h.T[0]; - ts[1] = ctx->h.T[1]; - do { - /* this implementation only supports 2**64 input bytes (no carry out here) */ - ts[0] += byteCntAdd; /* update processed length */ - - /* precompute the key schedule for this block */ - ks[ 0] = ctx->X[ 0]; - ks[ 1] = ctx->X[ 1]; - ks[ 2] = ctx->X[ 2]; - ks[ 3] = ctx->X[ 3]; - ks[ 4] = ctx->X[ 4]; - ks[ 5] = ctx->X[ 5]; - ks[ 6] = ctx->X[ 6]; - ks[ 7] = ctx->X[ 7]; - ks[ 8] = ctx->X[ 8]; - ks[ 9] = ctx->X[ 9]; - ks[10] = ctx->X[10]; - ks[11] = ctx->X[11]; - ks[12] = ctx->X[12]; - ks[13] = ctx->X[13]; - ks[14] = ctx->X[14]; - ks[15] = ctx->X[15]; - ks[16] = ks[ 0] ^ ks[ 1] ^ ks[ 2] ^ ks[ 3] ^ - ks[ 4] ^ ks[ 5] ^ ks[ 6] ^ ks[ 7] ^ - ks[ 8] ^ ks[ 9] ^ ks[10] ^ ks[11] ^ - ks[12] ^ ks[13] ^ ks[14] ^ ks[15] ^ SKEIN_KS_PARITY; - - ts[2] = ts[0] ^ ts[1]; - - Skein_Get64_LSB_First(w,blkPtr,WCNT); /* get input block in little-endian format */ - DebugSaveTweak(ctx); - Skein_Show_Block(BLK_BITS,&ctx->h,ctx->X,blkPtr,w,ks,ts); - - X00 = w[ 0] + ks[ 0]; /* do the first full key injection */ - X01 = w[ 1] + ks[ 1]; - X02 = w[ 2] + ks[ 2]; - X03 = w[ 3] + ks[ 3]; - X04 = w[ 4] + ks[ 4]; - X05 = w[ 5] + ks[ 5]; - X06 = w[ 6] + ks[ 6]; - X07 = w[ 7] + ks[ 7]; - X08 = w[ 8] + ks[ 8]; - X09 = w[ 9] + ks[ 9]; - X10 = w[10] + ks[10]; - X11 = w[11] + ks[11]; - X12 = w[12] + ks[12]; - X13 = w[13] + ks[13] + ts[0]; - X14 = w[14] + ks[14] + ts[1]; - X15 = w[15] + ks[15]; - - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INITIAL,Xptr); - -#define Round1024(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pA,pB,pC,pD,pE,pF,ROT,rNum) \ - X##p0 += X##p1; X##p1 = RotL_64(X##p1,ROT##_0); X##p1 ^= X##p0; \ - X##p2 += X##p3; X##p3 = RotL_64(X##p3,ROT##_1); X##p3 ^= X##p2; \ - X##p4 += X##p5; X##p5 = RotL_64(X##p5,ROT##_2); X##p5 ^= X##p4; \ - X##p6 += X##p7; X##p7 = RotL_64(X##p7,ROT##_3); X##p7 ^= X##p6; \ - X##p8 += X##p9; X##p9 = RotL_64(X##p9,ROT##_4); X##p9 ^= X##p8; \ - X##pA += X##pB; X##pB = RotL_64(X##pB,ROT##_5); X##pB ^= X##pA; \ - X##pC += X##pD; X##pD = RotL_64(X##pD,ROT##_6); X##pD ^= X##pC; \ - X##pE += X##pF; X##pF = RotL_64(X##pF,ROT##_7); X##pF ^= X##pE; \ - -#if SKEIN_UNROLL_1024 == 0 -#define R1024(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pA,pB,pC,pD,pE,pF,ROT,rn) \ - Round1024(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pA,pB,pC,pD,pE,pF,ROT,rn) \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,rn,Xptr); - -#define I1024(R) \ - X00 += ks[((R)+ 1) % 17]; /* inject the key schedule value */ \ - X01 += ks[((R)+ 2) % 17]; \ - X02 += ks[((R)+ 3) % 17]; \ - X03 += ks[((R)+ 4) % 17]; \ - X04 += ks[((R)+ 5) % 17]; \ - X05 += ks[((R)+ 6) % 17]; \ - X06 += ks[((R)+ 7) % 17]; \ - X07 += ks[((R)+ 8) % 17]; \ - X08 += ks[((R)+ 9) % 17]; \ - X09 += ks[((R)+10) % 17]; \ - X10 += ks[((R)+11) % 17]; \ - X11 += ks[((R)+12) % 17]; \ - X12 += ks[((R)+13) % 17]; \ - X13 += ks[((R)+14) % 17] + ts[((R)+1) % 3]; \ - X14 += ks[((R)+15) % 17] + ts[((R)+2) % 3]; \ - X15 += ks[((R)+16) % 17] + (R)+1; \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INJECT,Xptr); -#else /* looping version */ -#define R1024(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pA,pB,pC,pD,pE,pF,ROT,rn) \ - Round1024(p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,pA,pB,pC,pD,pE,pF,ROT,rn) \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,4*(r-1)+rn,Xptr); - -#define I1024(R) \ - X00 += ks[r+(R)+ 0]; /* inject the key schedule value */ \ - X01 += ks[r+(R)+ 1]; \ - X02 += ks[r+(R)+ 2]; \ - X03 += ks[r+(R)+ 3]; \ - X04 += ks[r+(R)+ 4]; \ - X05 += ks[r+(R)+ 5]; \ - X06 += ks[r+(R)+ 6]; \ - X07 += ks[r+(R)+ 7]; \ - X08 += ks[r+(R)+ 8]; \ - X09 += ks[r+(R)+ 9]; \ - X10 += ks[r+(R)+10]; \ - X11 += ks[r+(R)+11]; \ - X12 += ks[r+(R)+12]; \ - X13 += ks[r+(R)+13] + ts[r+(R)+0]; \ - X14 += ks[r+(R)+14] + ts[r+(R)+1]; \ - X15 += ks[r+(R)+15] + r+(R) ; \ - ks[r + (R)+16] = ks[r+(R)-1]; /* rotate key schedule */ \ - ts[r + (R)+ 2] = ts[r+(R)-1]; \ - Skein_Show_R_Ptr(BLK_BITS,&ctx->h,SKEIN_RND_KEY_INJECT,Xptr); - - for (r=1;r <= 2*RCNT;r+=2*SKEIN_UNROLL_1024) /* loop thru it */ -#endif - { -#define R1024_8_rounds(R) /* do 8 full rounds */ \ - R1024(00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,R1024_0,8*(R) + 1); \ - R1024(00,09,02,13,06,11,04,15,10,07,12,03,14,05,08,01,R1024_1,8*(R) + 2); \ - R1024(00,07,02,05,04,03,06,01,12,15,14,13,08,11,10,09,R1024_2,8*(R) + 3); \ - R1024(00,15,02,11,06,13,04,09,14,01,08,05,10,03,12,07,R1024_3,8*(R) + 4); \ - I1024(2*(R)); \ - R1024(00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,R1024_4,8*(R) + 5); \ - R1024(00,09,02,13,06,11,04,15,10,07,12,03,14,05,08,01,R1024_5,8*(R) + 6); \ - R1024(00,07,02,05,04,03,06,01,12,15,14,13,08,11,10,09,R1024_6,8*(R) + 7); \ - R1024(00,15,02,11,06,13,04,09,14,01,08,05,10,03,12,07,R1024_7,8*(R) + 8); \ - I1024(2*(R)+1); - - R1024_8_rounds( 0); - -#define R1024_Unroll_R(NN) ((SKEIN_UNROLL_1024 == 0 && SKEIN1024_ROUNDS_TOTAL/8 > (NN)) || (SKEIN_UNROLL_1024 > (NN))) - - #if R1024_Unroll_R( 1) - R1024_8_rounds( 1); - #endif - #if R1024_Unroll_R( 2) - R1024_8_rounds( 2); - #endif - #if R1024_Unroll_R( 3) - R1024_8_rounds( 3); - #endif - #if R1024_Unroll_R( 4) - R1024_8_rounds( 4); - #endif - #if R1024_Unroll_R( 5) - R1024_8_rounds( 5); - #endif - #if R1024_Unroll_R( 6) - R1024_8_rounds( 6); - #endif - #if R1024_Unroll_R( 7) - R1024_8_rounds( 7); - #endif - #if R1024_Unroll_R( 8) - R1024_8_rounds( 8); - #endif - #if R1024_Unroll_R( 9) - R1024_8_rounds( 9); - #endif - #if R1024_Unroll_R(10) - R1024_8_rounds(10); - #endif - #if R1024_Unroll_R(11) - R1024_8_rounds(11); - #endif - #if R1024_Unroll_R(12) - R1024_8_rounds(12); - #endif - #if R1024_Unroll_R(13) - R1024_8_rounds(13); - #endif - #if R1024_Unroll_R(14) - R1024_8_rounds(14); - #endif - #if (SKEIN_UNROLL_1024 > 14) -#error "need more unrolling in Skein_1024_Process_Block" - #endif - } - /* do the final "feedforward" xor, update context chaining vars */ - - ctx->X[ 0] = X00 ^ w[ 0]; - ctx->X[ 1] = X01 ^ w[ 1]; - ctx->X[ 2] = X02 ^ w[ 2]; - ctx->X[ 3] = X03 ^ w[ 3]; - ctx->X[ 4] = X04 ^ w[ 4]; - ctx->X[ 5] = X05 ^ w[ 5]; - ctx->X[ 6] = X06 ^ w[ 6]; - ctx->X[ 7] = X07 ^ w[ 7]; - ctx->X[ 8] = X08 ^ w[ 8]; - ctx->X[ 9] = X09 ^ w[ 9]; - ctx->X[10] = X10 ^ w[10]; - ctx->X[11] = X11 ^ w[11]; - ctx->X[12] = X12 ^ w[12]; - ctx->X[13] = X13 ^ w[13]; - ctx->X[14] = X14 ^ w[14]; - ctx->X[15] = X15 ^ w[15]; - - Skein_Show_Round(BLK_BITS,&ctx->h,SKEIN_RND_FEED_FWD,ctx->X); - - ts[1] &= ~SKEIN_T1_FLAG_FIRST; - blkPtr += SKEIN1024_BLOCK_BYTES; - } - while (--blkCnt); - ctx->h.T[0] = ts[0]; - ctx->h.T[1] = ts[1]; - } - -#if defined(SKEIN_CODE_SIZE) || defined(SKEIN_PERF) -static size_t Skein1024_Process_Block_CodeSize(void) - { - return ((u08b_t *) Skein1024_Process_Block_CodeSize) - - ((u08b_t *) Skein1024_Process_Block); - } -static uint_t Skein1024_Unroll_Cnt(void) - { - return SKEIN_UNROLL_1024; - } -#endif -#endif - - -#if 0 -/*****************************************************************/ -/* 256-bit Skein */ -/*****************************************************************/ - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* init the context for a straight hashing operation */ -static int Skein_256_Init(Skein_256_Ctxt_t *ctx, size_t hashBitLen) - { - union - { - u08b_t b[SKEIN_256_STATE_BYTES]; - u64b_t w[SKEIN_256_STATE_WORDS]; - } cfg; /* config block */ - - Skein_Assert(hashBitLen > 0,SKEIN_BAD_HASHLEN); - ctx->h.hashBitLen = hashBitLen; /* output hash bit count */ - - switch (hashBitLen) - { /* use pre-computed values, where available */ -#ifndef SKEIN_NO_PRECOMP - case 256: memcpy(ctx->X,SKEIN_256_IV_256,sizeof(ctx->X)); break; - case 224: memcpy(ctx->X,SKEIN_256_IV_224,sizeof(ctx->X)); break; - case 160: memcpy(ctx->X,SKEIN_256_IV_160,sizeof(ctx->X)); break; - case 128: memcpy(ctx->X,SKEIN_256_IV_128,sizeof(ctx->X)); break; -#endif - default: - /* here if there is no precomputed IV value available */ - /* build/process the config block, type == CONFIG (could be precomputed) */ - Skein_Start_New_Type(ctx,CFG_FINAL); /* set tweaks: T0=0; T1=CFG | FINAL */ - - cfg.w[0] = Skein_Swap64(SKEIN_SCHEMA_VER); /* set the schema, version */ - cfg.w[1] = Skein_Swap64(hashBitLen); /* hash result length in bits */ - cfg.w[2] = Skein_Swap64(SKEIN_CFG_TREE_INFO_SEQUENTIAL); - memset(&cfg.w[3],0,sizeof(cfg) - 3*sizeof(cfg.w[0])); /* zero pad config block */ - - /* compute the initial chaining values from config block */ - memset(ctx->X,0,sizeof(ctx->X)); /* zero the chaining variables */ - Skein_256_Process_Block(ctx,cfg.b,1,SKEIN_CFG_STR_LEN); - break; - } - /* The chaining vars ctx->X are now initialized for the given hashBitLen. */ - /* Set up to process the data message portion of the hash (default) */ - Skein_Start_New_Type(ctx,MSG); /* T0=0, T1= MSG type */ - - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* init the context for a MAC and/or tree hash operation */ -/* [identical to Skein_256_Init() when keyBytes == 0 && treeInfo == SKEIN_CFG_TREE_INFO_SEQUENTIAL] */ -static int Skein_256_InitExt(Skein_256_Ctxt_t *ctx,size_t hashBitLen,u64b_t treeInfo, const u08b_t *key, size_t keyBytes) - { - union - { - u08b_t b[SKEIN_256_STATE_BYTES]; - u64b_t w[SKEIN_256_STATE_WORDS]; - } cfg; /* config block */ - - Skein_Assert(hashBitLen > 0,SKEIN_BAD_HASHLEN); - Skein_Assert(keyBytes == 0 || key != NULL,SKEIN_FAIL); - - /* compute the initial chaining values ctx->X[], based on key */ - if (keyBytes == 0) /* is there a key? */ - { - memset(ctx->X,0,sizeof(ctx->X)); /* no key: use all zeroes as key for config block */ - } - else /* here to pre-process a key */ - { - Skein_assert(sizeof(cfg.b) >= sizeof(ctx->X)); - /* do a mini-Init right here */ - ctx->h.hashBitLen=8*sizeof(ctx->X); /* set output hash bit count = state size */ - Skein_Start_New_Type(ctx,KEY); /* set tweaks: T0 = 0; T1 = KEY type */ - memset(ctx->X,0,sizeof(ctx->X)); /* zero the initial chaining variables */ - Skein_256_Update(ctx,key,keyBytes); /* hash the key */ - Skein_256_Final_Pad(ctx,cfg.b); /* put result into cfg.b[] */ - memcpy(ctx->X,cfg.b,sizeof(cfg.b)); /* copy over into ctx->X[] */ -#if SKEIN_NEED_SWAP - { - uint_t i; - for (i=0;iX[i] = Skein_Swap64(ctx->X[i]); - } -#endif - } - /* build/process the config block, type == CONFIG (could be precomputed for each key) */ - ctx->h.hashBitLen = hashBitLen; /* output hash bit count */ - Skein_Start_New_Type(ctx,CFG_FINAL); - - memset(&cfg.w,0,sizeof(cfg.w)); /* pre-pad cfg.w[] with zeroes */ - cfg.w[0] = Skein_Swap64(SKEIN_SCHEMA_VER); - cfg.w[1] = Skein_Swap64(hashBitLen); /* hash result length in bits */ - cfg.w[2] = Skein_Swap64(treeInfo); /* tree hash config info (or SKEIN_CFG_TREE_INFO_SEQUENTIAL) */ - - Skein_Show_Key(256,&ctx->h,key,keyBytes); - - /* compute the initial chaining values from config block */ - Skein_256_Process_Block(ctx,cfg.b,1,SKEIN_CFG_STR_LEN); - - /* The chaining vars ctx->X are now initialized */ - /* Set up to process the data message portion of the hash (default) */ - ctx->h.bCnt = 0; /* buffer b[] starts out empty */ - Skein_Start_New_Type(ctx,MSG); - - return SKEIN_SUCCESS; - } -#endif - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* process the input bytes */ -static int Skein_256_Update(Skein_256_Ctxt_t *ctx, const u08b_t *msg, size_t msgByteCnt) - { - size_t n; - - Skein_Assert(ctx->h.bCnt <= SKEIN_256_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - /* process full blocks, if any */ - if (msgByteCnt + ctx->h.bCnt > SKEIN_256_BLOCK_BYTES) - { - if (ctx->h.bCnt) /* finish up any buffered message data */ - { - n = SKEIN_256_BLOCK_BYTES - ctx->h.bCnt; /* # bytes free in buffer b[] */ - if (n) - { - Skein_assert(n < msgByteCnt); /* check on our logic here */ - memcpy(&ctx->b[ctx->h.bCnt],msg,n); - msgByteCnt -= n; - msg += n; - ctx->h.bCnt += n; - } - Skein_assert(ctx->h.bCnt == SKEIN_256_BLOCK_BYTES); - Skein_256_Process_Block(ctx,ctx->b,1,SKEIN_256_BLOCK_BYTES); - ctx->h.bCnt = 0; - } - /* now process any remaining full blocks, directly from input message data */ - if (msgByteCnt > SKEIN_256_BLOCK_BYTES) - { - n = (msgByteCnt-1) / SKEIN_256_BLOCK_BYTES; /* number of full blocks to process */ - Skein_256_Process_Block(ctx,msg,n,SKEIN_256_BLOCK_BYTES); - msgByteCnt -= n * SKEIN_256_BLOCK_BYTES; - msg += n * SKEIN_256_BLOCK_BYTES; - } - Skein_assert(ctx->h.bCnt == 0); - } - - /* copy any remaining source message data bytes into b[] */ - if (msgByteCnt) - { - Skein_assert(msgByteCnt + ctx->h.bCnt <= SKEIN_256_BLOCK_BYTES); - memcpy(&ctx->b[ctx->h.bCnt],msg,msgByteCnt); - ctx->h.bCnt += msgByteCnt; - } - - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize the hash computation and output the result */ -static int Skein_256_Final(Skein_256_Ctxt_t *ctx, u08b_t *hashVal) - { - size_t i,n,byteCnt; - u64b_t X[SKEIN_256_STATE_WORDS]; - Skein_Assert(ctx->h.bCnt <= SKEIN_256_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - ctx->h.T[1] |= SKEIN_T1_FLAG_FINAL; /* tag as the final block */ - if (ctx->h.bCnt < SKEIN_256_BLOCK_BYTES) /* zero pad b[] if necessary */ - memset(&ctx->b[ctx->h.bCnt],0,SKEIN_256_BLOCK_BYTES - ctx->h.bCnt); - - Skein_256_Process_Block(ctx,ctx->b,1,ctx->h.bCnt); /* process the final block */ - - /* now output the result */ - byteCnt = (ctx->h.hashBitLen + 7) >> 3; /* total number of output bytes */ - - /* run Threefish in "counter mode" to generate output */ - memset(ctx->b,0,sizeof(ctx->b)); /* zero out b[], so it can hold the counter */ - memcpy(X,ctx->X,sizeof(X)); /* keep a local copy of counter mode "key" */ - for (i=0;i*SKEIN_256_BLOCK_BYTES < byteCnt;i++) - { - ((u64b_t *)ctx->b)[0]= Skein_Swap64((u64b_t) i); /* build the counter block */ - Skein_Start_New_Type(ctx,OUT_FINAL); - Skein_256_Process_Block(ctx,ctx->b,1,sizeof(u64b_t)); /* run "counter mode" */ - n = byteCnt - i*SKEIN_256_BLOCK_BYTES; /* number of output bytes left to go */ - if (n >= SKEIN_256_BLOCK_BYTES) - n = SKEIN_256_BLOCK_BYTES; - Skein_Put64_LSB_First(hashVal+i*SKEIN_256_BLOCK_BYTES,ctx->X,n); /* "output" the ctr mode bytes */ - Skein_Show_Final(256,&ctx->h,n,hashVal+i*SKEIN_256_BLOCK_BYTES); - memcpy(ctx->X,X,sizeof(X)); /* restore the counter mode key for next time */ - } - return SKEIN_SUCCESS; - } - -#if defined(SKEIN_CODE_SIZE) || defined(SKEIN_PERF) -static size_t Skein_256_API_CodeSize(void) - { - return ((u08b_t *) Skein_256_API_CodeSize) - - ((u08b_t *) Skein_256_Init); - } -#endif - -/*****************************************************************/ -/* 512-bit Skein */ -/*****************************************************************/ - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* init the context for a straight hashing operation */ -static int Skein_512_Init(Skein_512_Ctxt_t *ctx, size_t hashBitLen) - { - union - { - u08b_t b[SKEIN_512_STATE_BYTES]; - u64b_t w[SKEIN_512_STATE_WORDS]; - } cfg; /* config block */ - - Skein_Assert(hashBitLen > 0,SKEIN_BAD_HASHLEN); - ctx->h.hashBitLen = hashBitLen; /* output hash bit count */ - - switch (hashBitLen) - { /* use pre-computed values, where available */ -#ifndef SKEIN_NO_PRECOMP - case 512: memcpy(ctx->X,SKEIN_512_IV_512,sizeof(ctx->X)); break; - case 384: memcpy(ctx->X,SKEIN_512_IV_384,sizeof(ctx->X)); break; - case 256: memcpy(ctx->X,SKEIN_512_IV_256,sizeof(ctx->X)); break; - case 224: memcpy(ctx->X,SKEIN_512_IV_224,sizeof(ctx->X)); break; -#endif - default: - /* here if there is no precomputed IV value available */ - /* build/process the config block, type == CONFIG (could be precomputed) */ - Skein_Start_New_Type(ctx,CFG_FINAL); /* set tweaks: T0=0; T1=CFG | FINAL */ - - cfg.w[0] = Skein_Swap64(SKEIN_SCHEMA_VER); /* set the schema, version */ - cfg.w[1] = Skein_Swap64(hashBitLen); /* hash result length in bits */ - cfg.w[2] = Skein_Swap64(SKEIN_CFG_TREE_INFO_SEQUENTIAL); - memset(&cfg.w[3],0,sizeof(cfg) - 3*sizeof(cfg.w[0])); /* zero pad config block */ - - /* compute the initial chaining values from config block */ - memset(ctx->X,0,sizeof(ctx->X)); /* zero the chaining variables */ - Skein_512_Process_Block(ctx,cfg.b,1,SKEIN_CFG_STR_LEN); - break; - } - - /* The chaining vars ctx->X are now initialized for the given hashBitLen. */ - /* Set up to process the data message portion of the hash (default) */ - Skein_Start_New_Type(ctx,MSG); /* T0=0, T1= MSG type */ - - return SKEIN_SUCCESS; - } - -#if 0 -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* init the context for a MAC and/or tree hash operation */ -/* [identical to Skein_512_Init() when keyBytes == 0 && treeInfo == SKEIN_CFG_TREE_INFO_SEQUENTIAL] */ -static int Skein_512_InitExt(Skein_512_Ctxt_t *ctx,size_t hashBitLen,u64b_t treeInfo, const u08b_t *key, size_t keyBytes) - { - union - { - u08b_t b[SKEIN_512_STATE_BYTES]; - u64b_t w[SKEIN_512_STATE_WORDS]; - } cfg; /* config block */ - - Skein_Assert(hashBitLen > 0,SKEIN_BAD_HASHLEN); - Skein_Assert(keyBytes == 0 || key != NULL,SKEIN_FAIL); - - /* compute the initial chaining values ctx->X[], based on key */ - if (keyBytes == 0) /* is there a key? */ - { - memset(ctx->X,0,sizeof(ctx->X)); /* no key: use all zeroes as key for config block */ - } - else /* here to pre-process a key */ - { - Skein_assert(sizeof(cfg.b) >= sizeof(ctx->X)); - /* do a mini-Init right here */ - ctx->h.hashBitLen=8*sizeof(ctx->X); /* set output hash bit count = state size */ - Skein_Start_New_Type(ctx,KEY); /* set tweaks: T0 = 0; T1 = KEY type */ - memset(ctx->X,0,sizeof(ctx->X)); /* zero the initial chaining variables */ - Skein_512_Update(ctx,key,keyBytes); /* hash the key */ - Skein_512_Final_Pad(ctx,cfg.b); /* put result into cfg.b[] */ - memcpy(ctx->X,cfg.b,sizeof(cfg.b)); /* copy over into ctx->X[] */ -#if SKEIN_NEED_SWAP - { - uint_t i; - for (i=0;iX[i] = Skein_Swap64(ctx->X[i]); - } -#endif - } - /* build/process the config block, type == CONFIG (could be precomputed for each key) */ - ctx->h.hashBitLen = hashBitLen; /* output hash bit count */ - Skein_Start_New_Type(ctx,CFG_FINAL); - - memset(&cfg.w,0,sizeof(cfg.w)); /* pre-pad cfg.w[] with zeroes */ - cfg.w[0] = Skein_Swap64(SKEIN_SCHEMA_VER); - cfg.w[1] = Skein_Swap64(hashBitLen); /* hash result length in bits */ - cfg.w[2] = Skein_Swap64(treeInfo); /* tree hash config info (or SKEIN_CFG_TREE_INFO_SEQUENTIAL) */ - - Skein_Show_Key(512,&ctx->h,key,keyBytes); - - /* compute the initial chaining values from config block */ - Skein_512_Process_Block(ctx,cfg.b,1,SKEIN_CFG_STR_LEN); - - /* The chaining vars ctx->X are now initialized */ - /* Set up to process the data message portion of the hash (default) */ - ctx->h.bCnt = 0; /* buffer b[] starts out empty */ - Skein_Start_New_Type(ctx,MSG); - - return SKEIN_SUCCESS; - } -#endif - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* process the input bytes */ -static int Skein_512_Update(Skein_512_Ctxt_t *ctx, const u08b_t *msg, size_t msgByteCnt) - { - size_t n; - - Skein_Assert(ctx->h.bCnt <= SKEIN_512_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - /* process full blocks, if any */ - if (msgByteCnt + ctx->h.bCnt > SKEIN_512_BLOCK_BYTES) - { - if (ctx->h.bCnt) /* finish up any buffered message data */ - { - n = SKEIN_512_BLOCK_BYTES - ctx->h.bCnt; /* # bytes free in buffer b[] */ - if (n) - { - Skein_assert(n < msgByteCnt); /* check on our logic here */ - memcpy(&ctx->b[ctx->h.bCnt],msg,n); - msgByteCnt -= n; - msg += n; - ctx->h.bCnt += n; - } - Skein_assert(ctx->h.bCnt == SKEIN_512_BLOCK_BYTES); - Skein_512_Process_Block(ctx,ctx->b,1,SKEIN_512_BLOCK_BYTES); - ctx->h.bCnt = 0; - } - /* now process any remaining full blocks, directly from input message data */ - if (msgByteCnt > SKEIN_512_BLOCK_BYTES) - { - n = (msgByteCnt-1) / SKEIN_512_BLOCK_BYTES; /* number of full blocks to process */ - Skein_512_Process_Block(ctx,msg,n,SKEIN_512_BLOCK_BYTES); - msgByteCnt -= n * SKEIN_512_BLOCK_BYTES; - msg += n * SKEIN_512_BLOCK_BYTES; - } - Skein_assert(ctx->h.bCnt == 0); - } - - /* copy any remaining source message data bytes into b[] */ - if (msgByteCnt) - { - Skein_assert(msgByteCnt + ctx->h.bCnt <= SKEIN_512_BLOCK_BYTES); - memcpy(&ctx->b[ctx->h.bCnt],msg,msgByteCnt); - ctx->h.bCnt += msgByteCnt; - } - - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize the hash computation and output the result */ -static int Skein_512_Final(Skein_512_Ctxt_t *ctx, u08b_t *hashVal) - { - size_t i,n,byteCnt; - u64b_t X[SKEIN_512_STATE_WORDS]; - Skein_Assert(ctx->h.bCnt <= SKEIN_512_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - ctx->h.T[1] |= SKEIN_T1_FLAG_FINAL; /* tag as the final block */ - if (ctx->h.bCnt < SKEIN_512_BLOCK_BYTES) /* zero pad b[] if necessary */ - memset(&ctx->b[ctx->h.bCnt],0,SKEIN_512_BLOCK_BYTES - ctx->h.bCnt); - - Skein_512_Process_Block(ctx,ctx->b,1,ctx->h.bCnt); /* process the final block */ - - /* now output the result */ - byteCnt = (ctx->h.hashBitLen + 7) >> 3; /* total number of output bytes */ - - /* run Threefish in "counter mode" to generate output */ - memset(ctx->b,0,sizeof(ctx->b)); /* zero out b[], so it can hold the counter */ - memcpy(X,ctx->X,sizeof(X)); /* keep a local copy of counter mode "key" */ - for (i=0;i*SKEIN_512_BLOCK_BYTES < byteCnt;i++) - { - ((u64b_t *)ctx->b)[0]= Skein_Swap64((u64b_t) i); /* build the counter block */ - Skein_Start_New_Type(ctx,OUT_FINAL); - Skein_512_Process_Block(ctx,ctx->b,1,sizeof(u64b_t)); /* run "counter mode" */ - n = byteCnt - i*SKEIN_512_BLOCK_BYTES; /* number of output bytes left to go */ - if (n >= SKEIN_512_BLOCK_BYTES) - n = SKEIN_512_BLOCK_BYTES; - Skein_Put64_LSB_First(hashVal+i*SKEIN_512_BLOCK_BYTES,ctx->X,n); /* "output" the ctr mode bytes */ - Skein_Show_Final(512,&ctx->h,n,hashVal+i*SKEIN_512_BLOCK_BYTES); - memcpy(ctx->X,X,sizeof(X)); /* restore the counter mode key for next time */ - } - return SKEIN_SUCCESS; - } - -#if defined(SKEIN_CODE_SIZE) || defined(SKEIN_PERF) -static size_t Skein_512_API_CodeSize(void) - { - return ((u08b_t *) Skein_512_API_CodeSize) - - ((u08b_t *) Skein_512_Init); - } -#endif - -/*****************************************************************/ -/* 1024-bit Skein */ -/*****************************************************************/ -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* init the context for a straight hashing operation */ -static int Skein1024_Init(Skein1024_Ctxt_t *ctx, size_t hashBitLen) - { - union - { - u08b_t b[SKEIN1024_STATE_BYTES]; - u64b_t w[SKEIN1024_STATE_WORDS]; - } cfg; /* config block */ - - Skein_Assert(hashBitLen > 0,SKEIN_BAD_HASHLEN); - ctx->h.hashBitLen = hashBitLen; /* output hash bit count */ - - switch (hashBitLen) - { /* use pre-computed values, where available */ -#ifndef SKEIN_NO_PRECOMP - case 512: memcpy(ctx->X,SKEIN1024_IV_512 ,sizeof(ctx->X)); break; - case 384: memcpy(ctx->X,SKEIN1024_IV_384 ,sizeof(ctx->X)); break; - case 1024: memcpy(ctx->X,SKEIN1024_IV_1024,sizeof(ctx->X)); break; -#endif - default: - /* here if there is no precomputed IV value available */ - /* build/process the config block, type == CONFIG (could be precomputed) */ - Skein_Start_New_Type(ctx,CFG_FINAL); /* set tweaks: T0=0; T1=CFG | FINAL */ - - cfg.w[0] = Skein_Swap64(SKEIN_SCHEMA_VER); /* set the schema, version */ - cfg.w[1] = Skein_Swap64(hashBitLen); /* hash result length in bits */ - cfg.w[2] = Skein_Swap64(SKEIN_CFG_TREE_INFO_SEQUENTIAL); - memset(&cfg.w[3],0,sizeof(cfg) - 3*sizeof(cfg.w[0])); /* zero pad config block */ - - /* compute the initial chaining values from config block */ - memset(ctx->X,0,sizeof(ctx->X)); /* zero the chaining variables */ - Skein1024_Process_Block(ctx,cfg.b,1,SKEIN_CFG_STR_LEN); - break; - } - - /* The chaining vars ctx->X are now initialized for the given hashBitLen. */ - /* Set up to process the data message portion of the hash (default) */ - Skein_Start_New_Type(ctx,MSG); /* T0=0, T1= MSG type */ - - return SKEIN_SUCCESS; - } - -#if 0 -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* init the context for a MAC and/or tree hash operation */ -/* [identical to Skein1024_Init() when keyBytes == 0 && treeInfo == SKEIN_CFG_TREE_INFO_SEQUENTIAL] */ -static int Skein1024_InitExt(Skein1024_Ctxt_t *ctx,size_t hashBitLen,u64b_t treeInfo, const u08b_t *key, size_t keyBytes) - { - union - { - u08b_t b[SKEIN1024_STATE_BYTES]; - u64b_t w[SKEIN1024_STATE_WORDS]; - } cfg; /* config block */ - - Skein_Assert(hashBitLen > 0,SKEIN_BAD_HASHLEN); - Skein_Assert(keyBytes == 0 || key != NULL,SKEIN_FAIL); - - /* compute the initial chaining values ctx->X[], based on key */ - if (keyBytes == 0) /* is there a key? */ - { - memset(ctx->X,0,sizeof(ctx->X)); /* no key: use all zeroes as key for config block */ - } - else /* here to pre-process a key */ - { - Skein_assert(sizeof(cfg.b) >= sizeof(ctx->X)); - /* do a mini-Init right here */ - ctx->h.hashBitLen=8*sizeof(ctx->X); /* set output hash bit count = state size */ - Skein_Start_New_Type(ctx,KEY); /* set tweaks: T0 = 0; T1 = KEY type */ - memset(ctx->X,0,sizeof(ctx->X)); /* zero the initial chaining variables */ - Skein1024_Update(ctx,key,keyBytes); /* hash the key */ - Skein1024_Final_Pad(ctx,cfg.b); /* put result into cfg.b[] */ - memcpy(ctx->X,cfg.b,sizeof(cfg.b)); /* copy over into ctx->X[] */ -#if SKEIN_NEED_SWAP - { - uint_t i; - for (i=0;iX[i] = Skein_Swap64(ctx->X[i]); - } -#endif - } - /* build/process the config block, type == CONFIG (could be precomputed for each key) */ - ctx->h.hashBitLen = hashBitLen; /* output hash bit count */ - Skein_Start_New_Type(ctx,CFG_FINAL); - - memset(&cfg.w,0,sizeof(cfg.w)); /* pre-pad cfg.w[] with zeroes */ - cfg.w[0] = Skein_Swap64(SKEIN_SCHEMA_VER); - cfg.w[1] = Skein_Swap64(hashBitLen); /* hash result length in bits */ - cfg.w[2] = Skein_Swap64(treeInfo); /* tree hash config info (or SKEIN_CFG_TREE_INFO_SEQUENTIAL) */ - - Skein_Show_Key(1024,&ctx->h,key,keyBytes); - - /* compute the initial chaining values from config block */ - Skein1024_Process_Block(ctx,cfg.b,1,SKEIN_CFG_STR_LEN); - - /* The chaining vars ctx->X are now initialized */ - /* Set up to process the data message portion of the hash (default) */ - ctx->h.bCnt = 0; /* buffer b[] starts out empty */ - Skein_Start_New_Type(ctx,MSG); - - return SKEIN_SUCCESS; - } -#endif - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* process the input bytes */ -static int Skein1024_Update(Skein1024_Ctxt_t *ctx, const u08b_t *msg, size_t msgByteCnt) - { - size_t n; - - Skein_Assert(ctx->h.bCnt <= SKEIN1024_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - /* process full blocks, if any */ - if (msgByteCnt + ctx->h.bCnt > SKEIN1024_BLOCK_BYTES) - { - if (ctx->h.bCnt) /* finish up any buffered message data */ - { - n = SKEIN1024_BLOCK_BYTES - ctx->h.bCnt; /* # bytes free in buffer b[] */ - if (n) - { - Skein_assert(n < msgByteCnt); /* check on our logic here */ - memcpy(&ctx->b[ctx->h.bCnt],msg,n); - msgByteCnt -= n; - msg += n; - ctx->h.bCnt += n; - } - Skein_assert(ctx->h.bCnt == SKEIN1024_BLOCK_BYTES); - Skein1024_Process_Block(ctx,ctx->b,1,SKEIN1024_BLOCK_BYTES); - ctx->h.bCnt = 0; - } - /* now process any remaining full blocks, directly from input message data */ - if (msgByteCnt > SKEIN1024_BLOCK_BYTES) - { - n = (msgByteCnt-1) / SKEIN1024_BLOCK_BYTES; /* number of full blocks to process */ - Skein1024_Process_Block(ctx,msg,n,SKEIN1024_BLOCK_BYTES); - msgByteCnt -= n * SKEIN1024_BLOCK_BYTES; - msg += n * SKEIN1024_BLOCK_BYTES; - } - Skein_assert(ctx->h.bCnt == 0); - } - - /* copy any remaining source message data bytes into b[] */ - if (msgByteCnt) - { - Skein_assert(msgByteCnt + ctx->h.bCnt <= SKEIN1024_BLOCK_BYTES); - memcpy(&ctx->b[ctx->h.bCnt],msg,msgByteCnt); - ctx->h.bCnt += msgByteCnt; - } - - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize the hash computation and output the result */ -static int Skein1024_Final(Skein1024_Ctxt_t *ctx, u08b_t *hashVal) - { - size_t i,n,byteCnt; - u64b_t X[SKEIN1024_STATE_WORDS]; - Skein_Assert(ctx->h.bCnt <= SKEIN1024_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - ctx->h.T[1] |= SKEIN_T1_FLAG_FINAL; /* tag as the final block */ - if (ctx->h.bCnt < SKEIN1024_BLOCK_BYTES) /* zero pad b[] if necessary */ - memset(&ctx->b[ctx->h.bCnt],0,SKEIN1024_BLOCK_BYTES - ctx->h.bCnt); - - Skein1024_Process_Block(ctx,ctx->b,1,ctx->h.bCnt); /* process the final block */ - - /* now output the result */ - byteCnt = (ctx->h.hashBitLen + 7) >> 3; /* total number of output bytes */ - - /* run Threefish in "counter mode" to generate output */ - memset(ctx->b,0,sizeof(ctx->b)); /* zero out b[], so it can hold the counter */ - memcpy(X,ctx->X,sizeof(X)); /* keep a local copy of counter mode "key" */ - for (i=0;i*SKEIN1024_BLOCK_BYTES < byteCnt;i++) - { - ((u64b_t *)ctx->b)[0]= Skein_Swap64((u64b_t) i); /* build the counter block */ - Skein_Start_New_Type(ctx,OUT_FINAL); - Skein1024_Process_Block(ctx,ctx->b,1,sizeof(u64b_t)); /* run "counter mode" */ - n = byteCnt - i*SKEIN1024_BLOCK_BYTES; /* number of output bytes left to go */ - if (n >= SKEIN1024_BLOCK_BYTES) - n = SKEIN1024_BLOCK_BYTES; - Skein_Put64_LSB_First(hashVal+i*SKEIN1024_BLOCK_BYTES,ctx->X,n); /* "output" the ctr mode bytes */ - Skein_Show_Final(1024,&ctx->h,n,hashVal+i*SKEIN1024_BLOCK_BYTES); - memcpy(ctx->X,X,sizeof(X)); /* restore the counter mode key for next time */ - } - return SKEIN_SUCCESS; - } - -#if defined(SKEIN_CODE_SIZE) || defined(SKEIN_PERF) -static size_t Skein1024_API_CodeSize(void) - { - return ((u08b_t *) Skein1024_API_CodeSize) - - ((u08b_t *) Skein1024_Init); - } -#endif - -/**************** Functions to support MAC/tree hashing ***************/ -/* (this code is identical for Optimized and Reference versions) */ - -#if 0 -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize the hash computation and output the block, no OUTPUT stage */ -static int Skein_256_Final_Pad(Skein_256_Ctxt_t *ctx, u08b_t *hashVal) - { - Skein_Assert(ctx->h.bCnt <= SKEIN_256_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - ctx->h.T[1] |= SKEIN_T1_FLAG_FINAL; /* tag as the final block */ - if (ctx->h.bCnt < SKEIN_256_BLOCK_BYTES) /* zero pad b[] if necessary */ - memset(&ctx->b[ctx->h.bCnt],0,SKEIN_256_BLOCK_BYTES - ctx->h.bCnt); - Skein_256_Process_Block(ctx,ctx->b,1,ctx->h.bCnt); /* process the final block */ - - Skein_Put64_LSB_First(hashVal,ctx->X,SKEIN_256_BLOCK_BYTES); /* "output" the state bytes */ - - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize the hash computation and output the block, no OUTPUT stage */ -static int Skein_512_Final_Pad(Skein_512_Ctxt_t *ctx, u08b_t *hashVal) - { - Skein_Assert(ctx->h.bCnt <= SKEIN_512_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - ctx->h.T[1] |= SKEIN_T1_FLAG_FINAL; /* tag as the final block */ - if (ctx->h.bCnt < SKEIN_512_BLOCK_BYTES) /* zero pad b[] if necessary */ - memset(&ctx->b[ctx->h.bCnt],0,SKEIN_512_BLOCK_BYTES - ctx->h.bCnt); - Skein_512_Process_Block(ctx,ctx->b,1,ctx->h.bCnt); /* process the final block */ - - Skein_Put64_LSB_First(hashVal,ctx->X,SKEIN_512_BLOCK_BYTES); /* "output" the state bytes */ - - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize the hash computation and output the block, no OUTPUT stage */ -static int Skein1024_Final_Pad(Skein1024_Ctxt_t *ctx, u08b_t *hashVal) - { - Skein_Assert(ctx->h.bCnt <= SKEIN1024_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - ctx->h.T[1] |= SKEIN_T1_FLAG_FINAL; /* tag as the final block */ - if (ctx->h.bCnt < SKEIN1024_BLOCK_BYTES) /* zero pad b[] if necessary */ - memset(&ctx->b[ctx->h.bCnt],0,SKEIN1024_BLOCK_BYTES - ctx->h.bCnt); - Skein1024_Process_Block(ctx,ctx->b,1,ctx->h.bCnt); /* process the final block */ - - Skein_Put64_LSB_First(hashVal,ctx->X,SKEIN1024_BLOCK_BYTES); /* "output" the state bytes */ - - return SKEIN_SUCCESS; - } - - -#if SKEIN_TREE_HASH -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* just do the OUTPUT stage */ -static int Skein_256_Output(Skein_256_Ctxt_t *ctx, u08b_t *hashVal) - { - size_t i,n,byteCnt; - u64b_t X[SKEIN_256_STATE_WORDS]; - Skein_Assert(ctx->h.bCnt <= SKEIN_256_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - /* now output the result */ - byteCnt = (ctx->h.hashBitLen + 7) >> 3; /* total number of output bytes */ - - /* run Threefish in "counter mode" to generate output */ - memset(ctx->b,0,sizeof(ctx->b)); /* zero out b[], so it can hold the counter */ - memcpy(X,ctx->X,sizeof(X)); /* keep a local copy of counter mode "key" */ - for (i=0;i*SKEIN_256_BLOCK_BYTES < byteCnt;i++) - { - ((u64b_t *)ctx->b)[0]= Skein_Swap64((u64b_t) i); /* build the counter block */ - Skein_Start_New_Type(ctx,OUT_FINAL); - Skein_256_Process_Block(ctx,ctx->b,1,sizeof(u64b_t)); /* run "counter mode" */ - n = byteCnt - i*SKEIN_256_BLOCK_BYTES; /* number of output bytes left to go */ - if (n >= SKEIN_256_BLOCK_BYTES) - n = SKEIN_256_BLOCK_BYTES; - Skein_Put64_LSB_First(hashVal+i*SKEIN_256_BLOCK_BYTES,ctx->X,n); /* "output" the ctr mode bytes */ - Skein_Show_Final(256,&ctx->h,n,hashVal+i*SKEIN_256_BLOCK_BYTES); - memcpy(ctx->X,X,sizeof(X)); /* restore the counter mode key for next time */ - } - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* just do the OUTPUT stage */ -static int Skein_512_Output(Skein_512_Ctxt_t *ctx, u08b_t *hashVal) - { - size_t i,n,byteCnt; - u64b_t X[SKEIN_512_STATE_WORDS]; - Skein_Assert(ctx->h.bCnt <= SKEIN_512_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - /* now output the result */ - byteCnt = (ctx->h.hashBitLen + 7) >> 3; /* total number of output bytes */ - - /* run Threefish in "counter mode" to generate output */ - memset(ctx->b,0,sizeof(ctx->b)); /* zero out b[], so it can hold the counter */ - memcpy(X,ctx->X,sizeof(X)); /* keep a local copy of counter mode "key" */ - for (i=0;i*SKEIN_512_BLOCK_BYTES < byteCnt;i++) - { - ((u64b_t *)ctx->b)[0]= Skein_Swap64((u64b_t) i); /* build the counter block */ - Skein_Start_New_Type(ctx,OUT_FINAL); - Skein_512_Process_Block(ctx,ctx->b,1,sizeof(u64b_t)); /* run "counter mode" */ - n = byteCnt - i*SKEIN_512_BLOCK_BYTES; /* number of output bytes left to go */ - if (n >= SKEIN_512_BLOCK_BYTES) - n = SKEIN_512_BLOCK_BYTES; - Skein_Put64_LSB_First(hashVal+i*SKEIN_512_BLOCK_BYTES,ctx->X,n); /* "output" the ctr mode bytes */ - Skein_Show_Final(256,&ctx->h,n,hashVal+i*SKEIN_512_BLOCK_BYTES); - memcpy(ctx->X,X,sizeof(X)); /* restore the counter mode key for next time */ - } - return SKEIN_SUCCESS; - } - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* just do the OUTPUT stage */ -static int Skein1024_Output(Skein1024_Ctxt_t *ctx, u08b_t *hashVal) - { - size_t i,n,byteCnt; - u64b_t X[SKEIN1024_STATE_WORDS]; - Skein_Assert(ctx->h.bCnt <= SKEIN1024_BLOCK_BYTES,SKEIN_FAIL); /* catch uninitialized context */ - - /* now output the result */ - byteCnt = (ctx->h.hashBitLen + 7) >> 3; /* total number of output bytes */ - - /* run Threefish in "counter mode" to generate output */ - memset(ctx->b,0,sizeof(ctx->b)); /* zero out b[], so it can hold the counter */ - memcpy(X,ctx->X,sizeof(X)); /* keep a local copy of counter mode "key" */ - for (i=0;i*SKEIN1024_BLOCK_BYTES < byteCnt;i++) - { - ((u64b_t *)ctx->b)[0]= Skein_Swap64((u64b_t) i); /* build the counter block */ - Skein_Start_New_Type(ctx,OUT_FINAL); - Skein1024_Process_Block(ctx,ctx->b,1,sizeof(u64b_t)); /* run "counter mode" */ - n = byteCnt - i*SKEIN1024_BLOCK_BYTES; /* number of output bytes left to go */ - if (n >= SKEIN1024_BLOCK_BYTES) - n = SKEIN1024_BLOCK_BYTES; - Skein_Put64_LSB_First(hashVal+i*SKEIN1024_BLOCK_BYTES,ctx->X,n); /* "output" the ctr mode bytes */ - Skein_Show_Final(256,&ctx->h,n,hashVal+i*SKEIN1024_BLOCK_BYTES); - memcpy(ctx->X,X,sizeof(X)); /* restore the counter mode key for next time */ - } - return SKEIN_SUCCESS; - } -#endif -#endif - -typedef struct -{ - uint_t statebits; /* 256, 512, or 1024 */ - union - { - Skein_Ctxt_Hdr_t h; /* common header "overlay" */ - Skein_256_Ctxt_t ctx_256; - Skein_512_Ctxt_t ctx_512; - Skein1024_Ctxt_t ctx1024; - } u; -} -hashState; - -/* "incremental" hashing API */ -static HashReturn Init (hashState *state, int hashbitlen); -static HashReturn Update(hashState *state, const BitSequence *data, DataLength databitlen); -static HashReturn Final (hashState *state, BitSequence *hashval); - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* select the context size and init the context */ -static HashReturn Init(hashState *state, int hashbitlen) -{ -#if SKEIN_256_NIST_MAX_HASHBITS - if (hashbitlen <= SKEIN_256_NIST_MAX_HASHBITS) - { - Skein_Assert(hashbitlen > 0,BAD_HASHLEN); - state->statebits = 64*SKEIN_256_STATE_WORDS; - return Skein_256_Init(&state->u.ctx_256,(size_t) hashbitlen); - } -#endif - if (hashbitlen <= SKEIN_512_NIST_MAX_HASHBITS) - { - state->statebits = 64*SKEIN_512_STATE_WORDS; - return Skein_512_Init(&state->u.ctx_512,(size_t) hashbitlen); - } - else - { - state->statebits = 64*SKEIN1024_STATE_WORDS; - return Skein1024_Init(&state->u.ctx1024,(size_t) hashbitlen); - } -} - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* process data to be hashed */ -static HashReturn Update(hashState *state, const BitSequence *data, DataLength databitlen) -{ - /* only the final Update() call is allowed do partial bytes, else assert an error */ - Skein_Assert((state->u.h.T[1] & SKEIN_T1_FLAG_BIT_PAD) == 0 || databitlen == 0, SKEIN_FAIL); - - Skein_Assert(state->statebits % 256 == 0 && (state->statebits-256) < 1024,SKEIN_FAIL); - if ((databitlen & 7) == 0) /* partial bytes? */ - { - switch ((state->statebits >> 8) & 3) - { - case 2: return Skein_512_Update(&state->u.ctx_512,data,databitlen >> 3); - case 1: return Skein_256_Update(&state->u.ctx_256,data,databitlen >> 3); - case 0: return Skein1024_Update(&state->u.ctx1024,data,databitlen >> 3); - default: return SKEIN_FAIL; - } - } - else - { /* handle partial final byte */ - size_t bCnt = (databitlen >> 3) + 1; /* number of bytes to handle (nonzero here!) */ - u08b_t b,mask; - - mask = (u08b_t) (1u << (7 - (databitlen & 7))); /* partial byte bit mask */ - b = (u08b_t) ((data[bCnt-1] & (0-mask)) | mask); /* apply bit padding on final byte */ - - switch ((state->statebits >> 8) & 3) - { - case 2: Skein_512_Update(&state->u.ctx_512,data,bCnt-1); /* process all but the final byte */ - Skein_512_Update(&state->u.ctx_512,&b , 1 ); /* process the (masked) partial byte */ - break; - case 1: Skein_256_Update(&state->u.ctx_256,data,bCnt-1); /* process all but the final byte */ - Skein_256_Update(&state->u.ctx_256,&b , 1 ); /* process the (masked) partial byte */ - break; - case 0: Skein1024_Update(&state->u.ctx1024,data,bCnt-1); /* process all but the final byte */ - Skein1024_Update(&state->u.ctx1024,&b , 1 ); /* process the (masked) partial byte */ - break; - default: return SKEIN_FAIL; - } - Skein_Set_Bit_Pad_Flag(state->u.h); /* set tweak flag for the final call */ - - return SKEIN_SUCCESS; - } -} - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* finalize hash computation and output the result (hashbitlen bits) */ -static HashReturn Final(hashState *state, BitSequence *hashval) -{ - Skein_Assert(state->statebits % 256 == 0 && (state->statebits-256) < 1024,FAIL); - switch ((state->statebits >> 8) & 3) - { - case 2: return Skein_512_Final(&state->u.ctx_512,hashval); - case 1: return Skein_256_Final(&state->u.ctx_256,hashval); - case 0: return Skein1024_Final(&state->u.ctx1024,hashval); - default: return SKEIN_FAIL; - } -} - -/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ -/* all-in-one hash function */ -HashReturn skein_hash(int hashbitlen, const BitSequence *data, /* all-in-one call */ - DataLength databitlen,BitSequence *hashval) -{ - hashState state; - HashReturn r = Init(&state,hashbitlen); - if (r == SKEIN_SUCCESS) - { /* these calls do not fail when called properly */ - r = Update(&state,data,databitlen); - Final(&state,hashval); - } - return r; -} diff --git a/cryptonight/c/skein.h b/cryptonight/c/skein.h deleted file mode 100644 index 5c9cc55..0000000 --- a/cryptonight/c/skein.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _SKEIN_H_ -#define _SKEIN_H_ 1 -/************************************************************************** -** -** Interface declarations and internal definitions for Skein hashing. -** -** Source code author: Doug Whiting, 2008. -** -** This algorithm and source code is released to the public domain. -** -*************************************************************************** -** -** The following compile-time switches may be defined to control some -** tradeoffs between speed, code size, error checking, and security. -** -** The "default" note explains what happens when the switch is not defined. -** -** SKEIN_DEBUG -- make callouts from inside Skein code -** to examine/display intermediate values. -** [default: no callouts (no overhead)] -** -** SKEIN_ERR_CHECK -- how error checking is handled inside Skein -** code. If not defined, most error checking -** is disabled (for performance). Otherwise, -** the switch value is interpreted as: -** 0: use assert() to flag errors -** 1: return SKEIN_FAIL to flag errors -** -***************************************************************************/ -#include "skein_port.h" /* get platform-specific definitions */ - -typedef enum -{ - SKEIN_SUCCESS = 0, /* return codes from Skein calls */ - SKEIN_FAIL = 1, - SKEIN_BAD_HASHLEN = 2 -} -HashReturn; - -typedef size_t DataLength; /* bit count type */ -typedef u08b_t BitSequence; /* bit stream type */ - -/* "all-in-one" call */ -HashReturn skein_hash(int hashbitlen, const BitSequence *data, - DataLength databitlen, BitSequence *hashval); - -#endif /* ifndef _SKEIN_H_ */ diff --git a/cryptonight/c/skein_port.h b/cryptonight/c/skein_port.h deleted file mode 100644 index 8ef6d66..0000000 --- a/cryptonight/c/skein_port.h +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef _SKEIN_PORT_H_ -#define _SKEIN_PORT_H_ - -#include -#include - -#ifndef RETURN_VALUES -# define RETURN_VALUES -# if defined( DLL_EXPORT ) -# if defined( _MSC_VER ) || defined ( __INTEL_COMPILER ) -# define VOID_RETURN __declspec( dllexport ) void __stdcall -# define INT_RETURN __declspec( dllexport ) int __stdcall -# elif defined( __GNUC__ ) -# define VOID_RETURN __declspec( __dllexport__ ) void -# define INT_RETURN __declspec( __dllexport__ ) int -# else -# error Use of the DLL is only available on the Microsoft, Intel and GCC compilers -# endif -# elif defined( DLL_IMPORT ) -# if defined( _MSC_VER ) || defined ( __INTEL_COMPILER ) -# define VOID_RETURN __declspec( dllimport ) void __stdcall -# define INT_RETURN __declspec( dllimport ) int __stdcall -# elif defined( __GNUC__ ) -# define VOID_RETURN __declspec( __dllimport__ ) void -# define INT_RETURN __declspec( __dllimport__ ) int -# else -# error Use of the DLL is only available on the Microsoft, Intel and GCC compilers -# endif -# elif defined( __WATCOMC__ ) -# define VOID_RETURN void __cdecl -# define INT_RETURN int __cdecl -# else -# define VOID_RETURN void -# define INT_RETURN int -# endif -#endif - -/* These defines are used to declare buffers in a way that allows - faster operations on longer variables to be used. In all these - defines 'size' must be a power of 2 and >= 8 - - dec_unit_type(size,x) declares a variable 'x' of length - 'size' bits - - dec_bufr_type(size,bsize,x) declares a buffer 'x' of length 'bsize' - bytes defined as an array of variables - each of 'size' bits (bsize must be a - multiple of size / 8) - - ptr_cast(x,size) casts a pointer to a pointer to a - variable of length 'size' bits -*/ - -#define ui_type(size) uint##size##_t -#define dec_unit_type(size,x) typedef ui_type(size) x -#define dec_bufr_type(size,bsize,x) typedef ui_type(size) x[bsize / (size >> 3)] -#define ptr_cast(x,size) ((ui_type(size)*)(x)) - -typedef unsigned int uint_t; /* native unsigned integer */ -typedef uint8_t u08b_t; /* 8-bit unsigned integer */ -typedef uint64_t u64b_t; /* 64-bit unsigned integer */ - -#ifndef RotL_64 -#define RotL_64(x,N) (((x) << (N)) | ((x) >> (64-(N)))) -#endif - -/* - * Skein is "natively" little-endian (unlike SHA-xxx), for optimal - * performance on x86 CPUs. The Skein code requires the following - * definitions for dealing with endianness: - * - * SKEIN_NEED_SWAP: 0 for little-endian, 1 for big-endian - * Skein_Put64_LSB_First - * Skein_Get64_LSB_First - * Skein_Swap64 - * - * If SKEIN_NEED_SWAP is defined at compile time, it is used here - * along with the portable versions of Put64/Get64/Swap64, which - * are slow in general. - * - * Otherwise, an "auto-detect" of endianness is attempted below. - * If the default handling doesn't work well, the user may insert - * platform-specific code instead (e.g., for big-endian CPUs). - * - */ -#ifndef SKEIN_NEED_SWAP /* compile-time "override" for endianness? */ - - -#include "int-util.h" - -#define IS_BIG_ENDIAN 4321 /* byte 0 is most significant (mc68k) */ -#define IS_LITTLE_ENDIAN 1234 /* byte 0 is least significant (i386) */ - -#if BYTE_ORDER == LITTLE_ENDIAN -# define PLATFORM_BYTE_ORDER IS_LITTLE_ENDIAN -#endif - -#if BYTE_ORDER == BIG_ENDIAN -# define PLATFORM_BYTE_ORDER IS_BIG_ENDIAN -#endif - -/* special handler for IA64, which may be either endianness (?) */ -/* here we assume little-endian, but this may need to be changed */ -#if defined(__ia64) || defined(__ia64__) || defined(_M_IA64) -# define PLATFORM_MUST_ALIGN (1) -#ifndef PLATFORM_BYTE_ORDER -# define PLATFORM_BYTE_ORDER IS_LITTLE_ENDIAN -#endif -#endif - -#ifndef PLATFORM_MUST_ALIGN -# define PLATFORM_MUST_ALIGN (0) -#endif - - -#if PLATFORM_BYTE_ORDER == IS_BIG_ENDIAN - /* here for big-endian CPUs */ -#define SKEIN_NEED_SWAP (1) -#elif PLATFORM_BYTE_ORDER == IS_LITTLE_ENDIAN - /* here for x86 and x86-64 CPUs (and other detected little-endian CPUs) */ -#define SKEIN_NEED_SWAP (0) -#if PLATFORM_MUST_ALIGN == 0 /* ok to use "fast" versions? */ -#define Skein_Put64_LSB_First(dst08,src64,bCnt) memcpy(dst08,src64,bCnt) -#define Skein_Get64_LSB_First(dst64,src08,wCnt) memcpy(dst64,src08,8*(wCnt)) -#endif -#else -#error "Skein needs endianness setting!" -#endif - -#endif /* ifndef SKEIN_NEED_SWAP */ - -/* - ****************************************************************** - * Provide any definitions still needed. - ****************************************************************** - */ -#ifndef Skein_Swap64 /* swap for big-endian, nop for little-endian */ -#if SKEIN_NEED_SWAP -#define Skein_Swap64(w64) \ - ( (( ((u64b_t)(w64)) & 0xFF) << 56) | \ - (((((u64b_t)(w64)) >> 8) & 0xFF) << 48) | \ - (((((u64b_t)(w64)) >>16) & 0xFF) << 40) | \ - (((((u64b_t)(w64)) >>24) & 0xFF) << 32) | \ - (((((u64b_t)(w64)) >>32) & 0xFF) << 24) | \ - (((((u64b_t)(w64)) >>40) & 0xFF) << 16) | \ - (((((u64b_t)(w64)) >>48) & 0xFF) << 8) | \ - (((((u64b_t)(w64)) >>56) & 0xFF) ) ) -#else -#define Skein_Swap64(w64) (w64) -#endif -#endif /* ifndef Skein_Swap64 */ - - -#ifndef Skein_Put64_LSB_First -void Skein_Put64_LSB_First(u08b_t *dst,const u64b_t *src,size_t bCnt) -#ifdef SKEIN_PORT_CODE /* instantiate the function code here? */ - { /* this version is fully portable (big-endian or little-endian), but slow */ - size_t n; - - for (n=0;n>3] >> (8*(n&7))); - } -#else - ; /* output only the function prototype */ -#endif -#endif /* ifndef Skein_Put64_LSB_First */ - - -#ifndef Skein_Get64_LSB_First -void Skein_Get64_LSB_First(u64b_t *dst,const u08b_t *src,size_t wCnt) -#ifdef SKEIN_PORT_CODE /* instantiate the function code here? */ - { /* this version is fully portable (big-endian or little-endian), but slow */ - size_t n; - - for (n=0;n<8*wCnt;n+=8) - dst[n/8] = (((u64b_t) src[n ]) ) + - (((u64b_t) src[n+1]) << 8) + - (((u64b_t) src[n+2]) << 16) + - (((u64b_t) src[n+3]) << 24) + - (((u64b_t) src[n+4]) << 32) + - (((u64b_t) src[n+5]) << 40) + - (((u64b_t) src[n+6]) << 48) + - (((u64b_t) src[n+7]) << 56) ; - } -#else - ; /* output only the function prototype */ -#endif -#endif /* ifndef Skein_Get64_LSB_First */ - -#endif /* ifndef _SKEIN_PORT_H_ */ diff --git a/cryptonight/c/slow-hash.c b/cryptonight/c/slow-hash.c deleted file mode 100644 index 2bdc7d7..0000000 --- a/cryptonight/c/slow-hash.c +++ /dev/null @@ -1,1885 +0,0 @@ -// Copyright (c) 2014-2023, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include -#include -#include -#include -#include - -#include "int-util.h" -#include "hash-ops.h" -#include "oaes_lib.h" -#include "variant2_int_sqrt.h" -#include "variant4_random_math.h" -#include "CryptonightR_JIT.h" - -#include - -#define MEMORY (1 << 21) // 2MB scratchpad -#define ITER (1 << 20) -#define AES_BLOCK_SIZE 16 -#define AES_KEY_SIZE 32 -#define INIT_SIZE_BLK 8 -#define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) - -#if defined(_MSC_VER) -#define THREADV __declspec(thread) -#else -#define THREADV __thread -#endif - -extern void aesb_single_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); -extern void aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); - -static void local_abort(const char *msg) -{ - fprintf(stderr, "%s\n", msg); -#ifdef NDEBUG - _exit(1); -#else - abort(); -#endif -} - -volatile int use_v4_jit_flag = -1; - -static inline int use_v4_jit(void) -{ -#if defined(__x86_64__) - - if (use_v4_jit_flag != -1) - return use_v4_jit_flag; - - const char *env = getenv("MONERO_USE_CNV4_JIT"); - if (!env) { - use_v4_jit_flag = 1; - } - else if (!strcmp(env, "0") || !strcmp(env, "no")) { - use_v4_jit_flag = 0; - } - else { - use_v4_jit_flag = 1; - } - return use_v4_jit_flag; -#else - return 0; -#endif -} - -#if defined(__x86_64__) || defined(__aarch64__) -static inline int force_software_aes(void) -{ - static int use = -1; - - if (use != -1) - return use; - - const char *env = getenv("MONERO_USE_SOFTWARE_AES"); - if (!env) { - use = 0; - } - else if (!strcmp(env, "0") || !strcmp(env, "no")) { - use = 0; - } - else { - use = 1; - } - return use; -} -#endif - -#define VARIANT1_1(p) \ - do if (variant == 1) \ - { \ - const uint8_t tmp = ((const uint8_t*)(p))[11]; \ - static const uint32_t table = 0x75310; \ - const uint8_t index = (((tmp >> 3) & 6) | (tmp & 1)) << 1; \ - ((uint8_t*)(p))[11] = tmp ^ ((table >> index) & 0x30); \ - } while(0) - -#define VARIANT1_2(p) \ - do if (variant == 1) \ - { \ - xor64(p, tweak1_2); \ - } while(0) - -#define VARIANT1_CHECK() \ - do if (length < 43) \ - { \ - fprintf(stderr, "Cryptonight variant 1 needs at least 43 bytes of data"); \ - _exit(1); \ - } while(0) - -#define NONCE_POINTER (((const uint8_t*)data)+35) - -#define VARIANT1_PORTABLE_INIT() \ - uint8_t tweak1_2[8]; \ - do if (variant == 1) \ - { \ - VARIANT1_CHECK(); \ - memcpy(&tweak1_2, &state.hs.b[192], sizeof(tweak1_2)); \ - xor64(tweak1_2, NONCE_POINTER); \ - } while(0) - -#define VARIANT1_INIT64() \ - if (variant == 1) \ - { \ - VARIANT1_CHECK(); \ - } \ - const uint64_t tweak1_2 = (variant == 1) ? (state.hs.w[24] ^ (*((const uint64_t*)NONCE_POINTER))) : 0 - -#define VARIANT2_INIT64() \ - uint64_t division_result = 0; \ - uint64_t sqrt_result = 0; \ - do if (variant >= 2) \ - { \ - U64(b)[2] = state.hs.w[8] ^ state.hs.w[10]; \ - U64(b)[3] = state.hs.w[9] ^ state.hs.w[11]; \ - division_result = SWAP64LE(state.hs.w[12]); \ - sqrt_result = SWAP64LE(state.hs.w[13]); \ - } while (0) - -#define VARIANT2_PORTABLE_INIT() \ - uint64_t division_result = 0; \ - uint64_t sqrt_result = 0; \ - do if (variant >= 2) \ - { \ - memcpy(b + AES_BLOCK_SIZE, state.hs.b + 64, AES_BLOCK_SIZE); \ - xor64(b + AES_BLOCK_SIZE, state.hs.b + 80); \ - xor64(b + AES_BLOCK_SIZE + 8, state.hs.b + 88); \ - division_result = SWAP64LE(state.hs.w[12]); \ - sqrt_result = SWAP64LE(state.hs.w[13]); \ - } while (0) - -#define VARIANT2_SHUFFLE_ADD_SSE2(base_ptr, offset) \ - do if (variant >= 2) \ - { \ - __m128i chunk1 = _mm_load_si128((__m128i *)((base_ptr) + ((offset) ^ 0x10))); \ - const __m128i chunk2 = _mm_load_si128((__m128i *)((base_ptr) + ((offset) ^ 0x20))); \ - const __m128i chunk3 = _mm_load_si128((__m128i *)((base_ptr) + ((offset) ^ 0x30))); \ - _mm_store_si128((__m128i *)((base_ptr) + ((offset) ^ 0x10)), _mm_add_epi64(chunk3, _b1)); \ - _mm_store_si128((__m128i *)((base_ptr) + ((offset) ^ 0x20)), _mm_add_epi64(chunk1, _b)); \ - _mm_store_si128((__m128i *)((base_ptr) + ((offset) ^ 0x30)), _mm_add_epi64(chunk2, _a)); \ - if (variant >= 4) \ - { \ - chunk1 = _mm_xor_si128(chunk1, chunk2); \ - _c = _mm_xor_si128(_c, chunk3); \ - _c = _mm_xor_si128(_c, chunk1); \ - } \ - } while (0) - -#define VARIANT2_SHUFFLE_ADD_NEON(base_ptr, offset) \ - do if (variant >= 2) \ - { \ - uint64x2_t chunk1 = vld1q_u64(U64((base_ptr) + ((offset) ^ 0x10))); \ - const uint64x2_t chunk2 = vld1q_u64(U64((base_ptr) + ((offset) ^ 0x20))); \ - const uint64x2_t chunk3 = vld1q_u64(U64((base_ptr) + ((offset) ^ 0x30))); \ - vst1q_u64(U64((base_ptr) + ((offset) ^ 0x10)), vaddq_u64(chunk3, vreinterpretq_u64_u8(_b1))); \ - vst1q_u64(U64((base_ptr) + ((offset) ^ 0x20)), vaddq_u64(chunk1, vreinterpretq_u64_u8(_b))); \ - vst1q_u64(U64((base_ptr) + ((offset) ^ 0x30)), vaddq_u64(chunk2, vreinterpretq_u64_u8(_a))); \ - if (variant >= 4) \ - { \ - chunk1 = veorq_u64(chunk1, chunk2); \ - _c = vreinterpretq_u8_u64(veorq_u64(vreinterpretq_u64_u8(_c), chunk3)); \ - _c = vreinterpretq_u8_u64(veorq_u64(vreinterpretq_u64_u8(_c), chunk1)); \ - } \ - } while (0) - -#define VARIANT2_PORTABLE_SHUFFLE_ADD(out, a_, base_ptr, offset) \ - do if (variant >= 2) \ - { \ - uint64_t* chunk1 = U64((base_ptr) + ((offset) ^ 0x10)); \ - uint64_t* chunk2 = U64((base_ptr) + ((offset) ^ 0x20)); \ - uint64_t* chunk3 = U64((base_ptr) + ((offset) ^ 0x30)); \ - \ - uint64_t chunk1_old[2] = { SWAP64LE(chunk1[0]), SWAP64LE(chunk1[1]) }; \ - const uint64_t chunk2_old[2] = { SWAP64LE(chunk2[0]), SWAP64LE(chunk2[1]) }; \ - const uint64_t chunk3_old[2] = { SWAP64LE(chunk3[0]), SWAP64LE(chunk3[1]) }; \ - \ - uint64_t b1[2]; \ - memcpy_swap64le(b1, b + 16, 2); \ - chunk1[0] = SWAP64LE(chunk3_old[0] + b1[0]); \ - chunk1[1] = SWAP64LE(chunk3_old[1] + b1[1]); \ - \ - uint64_t a0[2]; \ - memcpy_swap64le(a0, a_, 2); \ - chunk3[0] = SWAP64LE(chunk2_old[0] + a0[0]); \ - chunk3[1] = SWAP64LE(chunk2_old[1] + a0[1]); \ - \ - uint64_t b0[2]; \ - memcpy_swap64le(b0, b, 2); \ - chunk2[0] = SWAP64LE(chunk1_old[0] + b0[0]); \ - chunk2[1] = SWAP64LE(chunk1_old[1] + b0[1]); \ - if (variant >= 4) \ - { \ - uint64_t out_copy[2]; \ - memcpy_swap64le(out_copy, out, 2); \ - chunk1_old[0] ^= chunk2_old[0]; \ - chunk1_old[1] ^= chunk2_old[1]; \ - out_copy[0] ^= chunk3_old[0]; \ - out_copy[1] ^= chunk3_old[1]; \ - out_copy[0] ^= chunk1_old[0]; \ - out_copy[1] ^= chunk1_old[1]; \ - memcpy_swap64le(out, out_copy, 2); \ - } \ - } while (0) - -#define VARIANT2_INTEGER_MATH_DIVISION_STEP(b, ptr) \ - uint64_t tmpx = division_result ^ (sqrt_result << 32); \ - ((uint64_t*)(b))[0] ^= SWAP64LE(tmpx); \ - { \ - const uint64_t dividend = SWAP64LE(((uint64_t*)(ptr))[1]); \ - const uint32_t divisor = (SWAP64LE(((uint64_t*)(ptr))[0]) + (uint32_t)(sqrt_result << 1)) | 0x80000001UL; \ - division_result = ((uint32_t)(dividend / divisor)) + \ - (((uint64_t)(dividend % divisor)) << 32); \ - } \ - const uint64_t sqrt_input = SWAP64LE(((uint64_t*)(ptr))[0]) + division_result - -#define VARIANT2_INTEGER_MATH_SSE2(b, ptr) \ - do if ((variant == 2) || (variant == 3)) \ - { \ - VARIANT2_INTEGER_MATH_DIVISION_STEP(b, ptr); \ - VARIANT2_INTEGER_MATH_SQRT_STEP_SSE2(); \ - VARIANT2_INTEGER_MATH_SQRT_FIXUP(sqrt_result); \ - } while(0) - -#if defined DBL_MANT_DIG && (DBL_MANT_DIG >= 50) - // double precision floating point type has enough bits of precision on current platform - #define VARIANT2_PORTABLE_INTEGER_MATH(b, ptr) \ - do if ((variant == 2) || (variant == 3)) \ - { \ - VARIANT2_INTEGER_MATH_DIVISION_STEP(b, ptr); \ - VARIANT2_INTEGER_MATH_SQRT_STEP_FP64(); \ - VARIANT2_INTEGER_MATH_SQRT_FIXUP(sqrt_result); \ - } while (0) -#else - // double precision floating point type is not good enough on current platform - // fall back to the reference code (integer only) - #define VARIANT2_PORTABLE_INTEGER_MATH(b, ptr) \ - do if ((variant == 2) || (variant == 3)) \ - { \ - VARIANT2_INTEGER_MATH_DIVISION_STEP(b, ptr); \ - VARIANT2_INTEGER_MATH_SQRT_STEP_REF(); \ - } while (0) -#endif - -#define VARIANT2_2_PORTABLE() \ - if (variant == 2 || variant == 3) { \ - xor_blocks(long_state + (j ^ 0x10), d); \ - xor_blocks(d, long_state + (j ^ 0x20)); \ - } - -#define VARIANT2_2() \ - do if (variant == 2 || variant == 3) \ - { \ - *U64(local_hp_state + (j ^ 0x10)) ^= SWAP64LE(hi); \ - *(U64(local_hp_state + (j ^ 0x10)) + 1) ^= SWAP64LE(lo); \ - hi ^= SWAP64LE(*U64(local_hp_state + (j ^ 0x20))); \ - lo ^= SWAP64LE(*(U64(local_hp_state + (j ^ 0x20)) + 1)); \ - } while (0) - -#define V4_REG_LOAD(dst, src) \ - do { \ - memcpy((dst), (src), sizeof(v4_reg)); \ - if (sizeof(v4_reg) == sizeof(uint32_t)) \ - *(dst) = SWAP32LE(*(dst)); \ - else \ - *(dst) = SWAP64LE(*(dst)); \ - } while (0) - -#define VARIANT4_RANDOM_MATH_INIT() \ - v4_reg r[9]; \ - struct V4_Instruction code[NUM_INSTRUCTIONS_MAX + 1]; \ - int jit = use_v4_jit(); \ - do if (variant >= 4) \ - { \ - for (int i = 0; i < 4; ++i) \ - V4_REG_LOAD(r + i, (uint8_t*)(state.hs.w + 12) + sizeof(v4_reg) * i); \ - v4_random_math_init(code, height); \ - if (jit) \ - { \ - int ret = v4_generate_JIT_code(code, hp_jitfunc, 4096); \ - if (ret < 0) \ - local_abort("Error generating CryptonightR code"); \ - } \ - } while (0) - -#define VARIANT4_RANDOM_MATH(a, b, r, _b, _b1) \ - do if (variant >= 4) \ - { \ - uint64_t t[2]; \ - memcpy(t, b, sizeof(uint64_t)); \ - \ - if (sizeof(v4_reg) == sizeof(uint32_t)) \ - t[0] ^= SWAP64LE((r[0] + r[1]) | ((uint64_t)(r[2] + r[3]) << 32)); \ - else \ - t[0] ^= SWAP64LE((r[0] + r[1]) ^ (r[2] + r[3])); \ - \ - memcpy(b, t, sizeof(uint64_t)); \ - \ - V4_REG_LOAD(r + 4, a); \ - V4_REG_LOAD(r + 5, (uint64_t*)(a) + 1); \ - V4_REG_LOAD(r + 6, _b); \ - V4_REG_LOAD(r + 7, _b1); \ - V4_REG_LOAD(r + 8, (uint64_t*)(_b1) + 1); \ - \ - if (jit) \ - (*hp_jitfunc)(r); \ - else \ - v4_random_math(code, r); \ - \ - memcpy(t, a, sizeof(uint64_t) * 2); \ - \ - if (sizeof(v4_reg) == sizeof(uint32_t)) { \ - t[0] ^= SWAP64LE(r[2] | ((uint64_t)(r[3]) << 32)); \ - t[1] ^= SWAP64LE(r[0] | ((uint64_t)(r[1]) << 32)); \ - } else { \ - t[0] ^= SWAP64LE(r[2] ^ r[3]); \ - t[1] ^= SWAP64LE(r[0] ^ r[1]); \ - } \ - memcpy(a, t, sizeof(uint64_t) * 2); \ - } while (0) - - -#if !defined NO_AES && (defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64))) -// Optimised code below, uses x86-specific intrinsics, SSE2, AES-NI -// Fall back to more portable code is down at the bottom - -#include - -#if defined(_MSC_VER) -#include -#include -#define STATIC -#define INLINE __inline -#if !defined(RDATA_ALIGN16) -#define RDATA_ALIGN16 __declspec(align(16)) -#endif -#elif defined(__MINGW32__) -#include -#include -#define STATIC static -#define INLINE inline -#if !defined(RDATA_ALIGN16) -#define RDATA_ALIGN16 __attribute__ ((aligned(16))) -#endif -#else -#include -#include -#define STATIC static -#define INLINE inline -#if !defined(RDATA_ALIGN16) -#define RDATA_ALIGN16 __attribute__ ((aligned(16))) -#endif -#endif - -#if defined(__INTEL_COMPILER) -#define ASM __asm__ -#elif !defined(_MSC_VER) -#define ASM __asm__ -#else -#define ASM __asm -#endif - -#define TOTALBLOCKS (MEMORY / AES_BLOCK_SIZE) - -#define U64(x) ((uint64_t *) (x)) -#define R128(x) ((__m128i *) (x)) - -#define state_index(x) (((*((uint64_t *)x) >> 4) & (TOTALBLOCKS - 1)) << 4) -#if defined(_MSC_VER) -#if !defined(_WIN64) -#define __mul() lo = mul128(c[0], b[0], &hi); -#else -#define __mul() lo = _umul128(c[0], b[0], &hi); -#endif -#else -#if defined(__x86_64__) -#define __mul() ASM("mulq %3\n\t" : "=d"(hi), "=a"(lo) : "%a" (c[0]), "rm" (b[0]) : "cc"); -#else -#define __mul() lo = mul128(c[0], b[0], &hi); -#endif -#endif - -#define pre_aes() \ - j = state_index(a); \ - _c = _mm_load_si128(R128(&local_hp_state[j])); \ - _a = _mm_load_si128(R128(a)); \ - -/* - * An SSE-optimized implementation of the second half of CryptoNight step 3. - * After using AES to mix a scratchpad value into _c (done by the caller), - * this macro xors it with _b and stores the result back to the same index (j) that it - * loaded the scratchpad value from. It then performs a second random memory - * read/write from the scratchpad, but this time mixes the values using a 64 - * bit multiply. - * This code is based upon an optimized implementation by dga. - */ -#define post_aes() \ - VARIANT2_SHUFFLE_ADD_SSE2(local_hp_state, j); \ - _mm_store_si128(R128(c), _c); \ - _mm_store_si128(R128(&local_hp_state[j]), _mm_xor_si128(_b, _c)); \ - VARIANT1_1(&local_hp_state[j]); \ - j = state_index(c); \ - p = U64(&local_hp_state[j]); \ - b[0] = p[0]; b[1] = p[1]; \ - VARIANT2_INTEGER_MATH_SSE2(b, c); \ - VARIANT4_RANDOM_MATH(a, b, r, &_b, &_b1); \ - __mul(); \ - VARIANT2_2(); \ - VARIANT2_SHUFFLE_ADD_SSE2(local_hp_state, j); \ - a[0] += hi; a[1] += lo; \ - p = U64(&local_hp_state[j]); \ - p[0] = a[0]; p[1] = a[1]; \ - a[0] ^= b[0]; a[1] ^= b[1]; \ - VARIANT1_2(p + 1); \ - _b1 = _b; \ - _b = _c; \ - -#pragma pack(push, 1) -union cn_slow_hash_state -{ - union hash_state hs; - struct - { - uint8_t k[64]; - uint8_t init[INIT_SIZE_BYTE]; - }; -}; -#pragma pack(pop) - -THREADV uint8_t *hp_state = NULL; -THREADV int hp_allocated = 0; -THREADV v4_random_math_JIT_func hp_jitfunc = NULL; -THREADV uint8_t *hp_jitfunc_memory = NULL; -THREADV int hp_jitfunc_allocated = 0; - -#if defined(_MSC_VER) -#define cpuid(info,x) __cpuidex(info,x,0) -#else -void cpuid(int CPUInfo[4], int InfoType) -{ - ASM __volatile__ - ( - "cpuid": - "=a" (CPUInfo[0]), - "=b" (CPUInfo[1]), - "=c" (CPUInfo[2]), - "=d" (CPUInfo[3]) : - "a" (InfoType), "c" (0) - ); -} -#endif - -/** - * @brief a = (a xor b), where a and b point to 128 bit values - */ - -STATIC INLINE void xor_blocks(uint8_t *a, const uint8_t *b) -{ - U64(a)[0] ^= U64(b)[0]; - U64(a)[1] ^= U64(b)[1]; -} - -STATIC INLINE void xor64(uint64_t *a, const uint64_t b) -{ - *a ^= b; -} - -/** - * @brief uses cpuid to determine if the CPU supports the AES instructions - * @return true if the CPU supports AES, false otherwise - */ - - -STATIC INLINE int check_aes_hw(void) -{ - int cpuid_results[4]; - static int supported = -1; - - if(supported >= 0) - return supported; - - cpuid(cpuid_results,1); - return supported = cpuid_results[2] & (1 << 25); -} - -STATIC INLINE void aes_256_assist1(__m128i* t1, __m128i * t2) -{ - __m128i t4; - *t2 = _mm_shuffle_epi32(*t2, 0xff); - t4 = _mm_slli_si128(*t1, 0x04); - *t1 = _mm_xor_si128(*t1, t4); - t4 = _mm_slli_si128(t4, 0x04); - *t1 = _mm_xor_si128(*t1, t4); - t4 = _mm_slli_si128(t4, 0x04); - *t1 = _mm_xor_si128(*t1, t4); - *t1 = _mm_xor_si128(*t1, *t2); -} - -STATIC INLINE void aes_256_assist2(__m128i* t1, __m128i * t3) -{ - __m128i t2, t4; - t4 = _mm_aeskeygenassist_si128(*t1, 0x00); - t2 = _mm_shuffle_epi32(t4, 0xaa); - t4 = _mm_slli_si128(*t3, 0x04); - *t3 = _mm_xor_si128(*t3, t4); - t4 = _mm_slli_si128(t4, 0x04); - *t3 = _mm_xor_si128(*t3, t4); - t4 = _mm_slli_si128(t4, 0x04); - *t3 = _mm_xor_si128(*t3, t4); - *t3 = _mm_xor_si128(*t3, t2); -} - -/** - * @brief expands 'key' into a form it can be used for AES encryption. - * - * This is an SSE-optimized implementation of AES key schedule generation. It - * expands the key into multiple round keys, each of which is used in one round - * of the AES encryption used to fill (and later, extract randomness from) - * the large 2MB buffer. Note that CryptoNight does not use a completely - * standard AES encryption for its buffer expansion, so do not copy this - * function outside of Monero without caution! This version uses the hardware - * AESKEYGENASSIST instruction to speed key generation, and thus requires - * CPU AES support. - * For more information about these functions, see page 19 of Intel's AES instructions - * white paper: - * https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf - * - * @param key the input 128 bit key - * @param expandedKey An output buffer to hold the generated key schedule - */ - -STATIC INLINE void aes_expand_key(const uint8_t *key, uint8_t *expandedKey) -{ - __m128i *ek = R128(expandedKey); - __m128i t1, t2, t3; - - t1 = _mm_loadu_si128(R128(key)); - t3 = _mm_loadu_si128(R128(key + 16)); - - ek[0] = t1; - ek[1] = t3; - - t2 = _mm_aeskeygenassist_si128(t3, 0x01); - aes_256_assist1(&t1, &t2); - ek[2] = t1; - aes_256_assist2(&t1, &t3); - ek[3] = t3; - - t2 = _mm_aeskeygenassist_si128(t3, 0x02); - aes_256_assist1(&t1, &t2); - ek[4] = t1; - aes_256_assist2(&t1, &t3); - ek[5] = t3; - - t2 = _mm_aeskeygenassist_si128(t3, 0x04); - aes_256_assist1(&t1, &t2); - ek[6] = t1; - aes_256_assist2(&t1, &t3); - ek[7] = t3; - - t2 = _mm_aeskeygenassist_si128(t3, 0x08); - aes_256_assist1(&t1, &t2); - ek[8] = t1; - aes_256_assist2(&t1, &t3); - ek[9] = t3; - - t2 = _mm_aeskeygenassist_si128(t3, 0x10); - aes_256_assist1(&t1, &t2); - ek[10] = t1; -} - -/** - * @brief a "pseudo" round of AES (similar to but slightly different from normal AES encryption) - * - * To fill its 2MB scratch buffer, CryptoNight uses a nonstandard implementation - * of AES encryption: It applies 10 rounds of the basic AES encryption operation - * to an input 128 bit chunk of data . Unlike normal AES, however, this is - * all it does; it does not perform the initial AddRoundKey step (this is done - * in subsequent steps by aesenc_si128), and it does not use the simpler final round. - * Hence, this is a "pseudo" round - though the function actually implements 10 rounds together. - * - * Note that unlike aesb_pseudo_round, this function works on multiple data chunks. - * - * @param in a pointer to nblocks * 128 bits of data to be encrypted - * @param out a pointer to an nblocks * 128 bit buffer where the output will be stored - * @param expandedKey the expanded AES key - * @param nblocks the number of 128 blocks of data to be encrypted - */ - -STATIC INLINE void aes_pseudo_round(const uint8_t *in, uint8_t *out, - const uint8_t *expandedKey, int nblocks) -{ - __m128i *k = R128(expandedKey); - __m128i d; - int i; - - for(i = 0; i < nblocks; i++) - { - d = _mm_loadu_si128(R128(in + i * AES_BLOCK_SIZE)); - d = _mm_aesenc_si128(d, *R128(&k[0])); - d = _mm_aesenc_si128(d, *R128(&k[1])); - d = _mm_aesenc_si128(d, *R128(&k[2])); - d = _mm_aesenc_si128(d, *R128(&k[3])); - d = _mm_aesenc_si128(d, *R128(&k[4])); - d = _mm_aesenc_si128(d, *R128(&k[5])); - d = _mm_aesenc_si128(d, *R128(&k[6])); - d = _mm_aesenc_si128(d, *R128(&k[7])); - d = _mm_aesenc_si128(d, *R128(&k[8])); - d = _mm_aesenc_si128(d, *R128(&k[9])); - _mm_storeu_si128((R128(out + i * AES_BLOCK_SIZE)), d); - } -} - -/** - * @brief aes_pseudo_round that loads data from *in and xors it with *xor first - * - * This function performs the same operations as aes_pseudo_round, but before - * performing the encryption of each 128 bit block from , it xors - * it with the corresponding block from . - * - * @param in a pointer to nblocks * 128 bits of data to be encrypted - * @param out a pointer to an nblocks * 128 bit buffer where the output will be stored - * @param expandedKey the expanded AES key - * @param xor a pointer to an nblocks * 128 bit buffer that is xored into in before encryption (in is left unmodified) - * @param nblocks the number of 128 blocks of data to be encrypted - */ - -STATIC INLINE void aes_pseudo_round_xor(const uint8_t *in, uint8_t *out, - const uint8_t *expandedKey, const uint8_t *xor, int nblocks) -{ - __m128i *k = R128(expandedKey); - __m128i *x = R128(xor); - __m128i d; - int i; - - for(i = 0; i < nblocks; i++) - { - d = _mm_loadu_si128(R128(in + i * AES_BLOCK_SIZE)); - d = _mm_xor_si128(d, *R128(x++)); - d = _mm_aesenc_si128(d, *R128(&k[0])); - d = _mm_aesenc_si128(d, *R128(&k[1])); - d = _mm_aesenc_si128(d, *R128(&k[2])); - d = _mm_aesenc_si128(d, *R128(&k[3])); - d = _mm_aesenc_si128(d, *R128(&k[4])); - d = _mm_aesenc_si128(d, *R128(&k[5])); - d = _mm_aesenc_si128(d, *R128(&k[6])); - d = _mm_aesenc_si128(d, *R128(&k[7])); - d = _mm_aesenc_si128(d, *R128(&k[8])); - d = _mm_aesenc_si128(d, *R128(&k[9])); - _mm_storeu_si128((R128(out + i * AES_BLOCK_SIZE)), d); - } -} - -#if defined(_MSC_VER) || defined(__MINGW32__) -BOOL SetLockPagesPrivilege(HANDLE hProcess, BOOL bEnable) -{ - struct - { - DWORD count; - LUID_AND_ATTRIBUTES privilege[1]; - } info; - - HANDLE token; - if(!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &token)) - return FALSE; - - info.count = 1; - info.privilege[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0; - - if(!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &(info.privilege[0].Luid))) - return FALSE; - - if(!AdjustTokenPrivileges(token, FALSE, (PTOKEN_PRIVILEGES) &info, 0, NULL, NULL)) - return FALSE; - - if (GetLastError() != ERROR_SUCCESS) - return FALSE; - - CloseHandle(token); - - return TRUE; - -} -#endif - -/** - * @brief allocate the 2MB scratch buffer using OS support for huge pages, if available - * - * This function tries to allocate the 2MB scratch buffer using a single - * 2MB "huge page" (instead of the usual 4KB page sizes) to reduce TLB misses - * during the random accesses to the scratch buffer. This is one of the - * important speed optimizations needed to make CryptoNight faster. - * - * No parameters. Updates a thread-local pointer, hp_state, to point to - * the allocated buffer. - */ - -void cn_slow_hash_allocate_state(void) -{ - if(hp_state != NULL) - return; - -#if defined(_MSC_VER) || defined(__MINGW32__) - SetLockPagesPrivilege(GetCurrentProcess(), TRUE); - hp_state = (uint8_t *) VirtualAlloc(hp_state, MEMORY, MEM_LARGE_PAGES | - MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); -#else -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ - defined(__DragonFly__) || defined(__NetBSD__) - hp_state = mmap(0, MEMORY, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, -1, 0); -#else - hp_state = mmap(0, MEMORY, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); -#endif - if(hp_state == MAP_FAILED) - hp_state = NULL; -#endif - hp_allocated = 1; - if(hp_state == NULL) - { - hp_allocated = 0; - hp_state = (uint8_t *) malloc(MEMORY); - } - - -#if defined(_MSC_VER) || defined(__MINGW32__) - hp_jitfunc_memory = (uint8_t *) VirtualAlloc(hp_jitfunc_memory, 4096 + 4095, - MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); -#else -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ - defined(__DragonFly__) || defined(__NetBSD__) -#ifdef __NetBSD__ -#define RESERVED_FLAGS PROT_MPROTECT(PROT_EXEC) -#else -#define RESERVED_FLAGS 0 -#endif - hp_jitfunc_memory = mmap(0, 4096 + 4096, PROT_READ | PROT_WRITE | RESERVED_FLAGS, - MAP_PRIVATE | MAP_ANON, -1, 0); -#else - hp_jitfunc_memory = mmap(0, 4096 + 4096, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); -#endif - if(hp_jitfunc_memory == MAP_FAILED) - hp_jitfunc_memory = NULL; -#endif - hp_jitfunc_allocated = 1; - if (hp_jitfunc_memory == NULL) - { - hp_jitfunc_allocated = 0; - hp_jitfunc_memory = malloc(4096 + 4095); - } - hp_jitfunc = (v4_random_math_JIT_func)((size_t)(hp_jitfunc_memory + 4095) & ~4095); -} - -/** - *@brief frees the state allocated by slow_hash_allocate_state - */ - -void cn_slow_hash_free_state(void) -{ - if(hp_state == NULL) - return; - - if(!hp_allocated) - free(hp_state); - else - { -#if defined(_MSC_VER) || defined(__MINGW32__) - VirtualFree(hp_state, 0, MEM_RELEASE); -#else - munmap(hp_state, MEMORY); -#endif - } - - if(!hp_jitfunc_allocated) - free(hp_jitfunc_memory); - else - { -#if defined(_MSC_VER) || defined(__MINGW32__) - VirtualFree(hp_jitfunc_memory, 0, MEM_RELEASE); -#else - munmap(hp_jitfunc_memory, 4096 + 4095); -#endif - } - - hp_state = NULL; - hp_allocated = 0; - hp_jitfunc = NULL; - hp_jitfunc_memory = NULL; - hp_jitfunc_allocated = 0; -} - -/** - * @brief the hash function implementing CryptoNight, used for the Monero proof-of-work - * - * Computes the hash of (which consists of bytes), returning the - * hash in . The CryptoNight hash operates by first using Keccak 1600, - * the 1600 bit variant of the Keccak hash used in SHA-3, to create a 200 byte - * buffer of pseudorandom data by hashing the supplied data. It then uses this - * random data to fill a large 2MB buffer with pseudorandom data by iteratively - * encrypting it using 10 rounds of AES per entry. After this initialization, - * it executes 524,288 rounds of mixing through the random 2MB buffer using - * AES (typically provided in hardware on modern CPUs) and a 64 bit multiply. - * Finally, it re-mixes this large buffer back into - * the 200 byte "text" buffer, and then hashes this buffer using one of four - * pseudorandomly selected hash functions (Blake, Groestl, JH, or Skein) - * to populate the output. - * - * The 2MB buffer and choice of functions for mixing are designed to make the - * algorithm "CPU-friendly" (and thus, reduce the advantage of GPU, FPGA, - * or ASIC-based implementations): the functions used are fast on modern - * CPUs, and the 2MB size matches the typical amount of L3 cache available per - * core on 2013-era CPUs. When available, this implementation will use hardware - * AES support on x86 CPUs. - * - * A diagram of the inner loop of this function can be found at - * https://www.cs.cmu.edu/~dga/crypto/xmr/cryptonight.png - * - * @param data the data to hash - * @param length the length in bytes of the data - * @param hash a pointer to a buffer in which the final 256 bit hash will be stored - */ -void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height) -{ - RDATA_ALIGN16 uint8_t expandedKey[240]; /* These buffers are aligned to use later with SSE functions */ - - uint8_t text[INIT_SIZE_BYTE]; - RDATA_ALIGN16 uint64_t a[2]; - RDATA_ALIGN16 uint64_t b[4]; - RDATA_ALIGN16 uint64_t c[2]; - union cn_slow_hash_state state; - __m128i _a, _b, _b1, _c; - uint64_t hi, lo; - - size_t i, j; - uint64_t *p = NULL; - oaes_ctx *aes_ctx = NULL; - int useAes = !force_software_aes() && check_aes_hw(); - - static void (*const extra_hashes[4])(const void *, size_t, char *) = - { - hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein - }; - - // this isn't supposed to happen, but guard against it for now. - if(hp_state == NULL) - cn_slow_hash_allocate_state(); - - // locals to avoid constant TLS dereferencing - uint8_t *local_hp_state = hp_state; - - /* CryptoNight Step 1: Use Keccak1600 to initialize the 'state' (and 'text') buffers from the data. */ - if (prehashed) { - memcpy(&state.hs, data, length); - } else { - hash_process(&state.hs, data, length); - } - memcpy(text, state.init, INIT_SIZE_BYTE); - - VARIANT1_INIT64(); - VARIANT2_INIT64(); - VARIANT4_RANDOM_MATH_INIT(); - - /* CryptoNight Step 2: Iteratively encrypt the results from Keccak to fill - * the 2MB large random access buffer. - */ - - if(useAes) - { - aes_expand_key(state.hs.b, expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK); - memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - } - else - { - aes_ctx = (oaes_ctx *) oaes_alloc(); - oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); - - memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - } - - U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0]; - U64(a)[1] = U64(&state.k[0])[1] ^ U64(&state.k[32])[1]; - U64(b)[0] = U64(&state.k[16])[0] ^ U64(&state.k[48])[0]; - U64(b)[1] = U64(&state.k[16])[1] ^ U64(&state.k[48])[1]; - - /* CryptoNight Step 3: Bounce randomly 1,048,576 times (1<<20) through the mixing buffer, - * using 524,288 iterations of the following mixing function. Each execution - * performs two reads and writes from the mixing buffer. - */ - - _b = _mm_load_si128(R128(b)); - _b1 = _mm_load_si128(R128(b) + 1); - // Two independent versions, one with AES, one without, to ensure that - // the useAes test is only performed once, not every iteration. - if(useAes) - { - for(i = 0; i < ITER / 2; i++) - { - pre_aes(); - _c = _mm_aesenc_si128(_c, _a); - post_aes(); - } - } - else - { - for(i = 0; i < ITER / 2; i++) - { - pre_aes(); - aesb_single_round((uint8_t *) &_c, (uint8_t *) &_c, (uint8_t *) &_a); - post_aes(); - } - } - - /* CryptoNight Step 4: Sequentially pass through the mixing buffer and use 10 rounds - * of AES encryption to mix the random data back into the 'text' buffer. 'text' - * was originally created with the output of Keccak1600. */ - - memcpy(text, state.init, INIT_SIZE_BYTE); - if(useAes) - { - aes_expand_key(&state.hs.b[32], expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - // add the xor to the pseudo round - aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK); - } - } - else - { - oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - { - xor_blocks(&text[j * AES_BLOCK_SIZE], &local_hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); - } - } - oaes_free((OAES_CTX **) &aes_ctx); - } - - /* CryptoNight Step 5: Apply Keccak to the state again, and then - * use the resulting data to select which of four finalizer - * hash functions to apply to the data (Blake, Groestl, JH, or Skein). - * Use this hash to squeeze the state array down - * to the final 256 bit hash output. - */ - - memcpy(state.init, text, INIT_SIZE_BYTE); - hash_permutation(&state.hs); - extra_hashes[state.hs.b[0] & 3](&state, 200, hash); -} - -#elif !defined NO_AES && (defined(__arm__) || defined(__aarch64__)) -#ifdef __aarch64__ -#include -THREADV uint8_t *hp_state = NULL; -THREADV int hp_malloced = 0; - -void cn_slow_hash_allocate_state(void) -{ - if(hp_state != NULL) - return; - -#ifndef MAP_HUGETLB -#define MAP_HUGETLB 0 -#endif - hp_state = mmap(0, MEMORY, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0); - - if(hp_state == MAP_FAILED) - hp_state = NULL; - if(hp_state == NULL) - { - hp_malloced = 1; - hp_state = (uint8_t *) malloc(MEMORY); - } -} - -void cn_slow_hash_free_state(void) -{ - if(hp_state == NULL) - return; - - if (hp_malloced) - free(hp_state); - else - munmap(hp_state, MEMORY); - hp_state = NULL; - hp_malloced = 0; -} -#else -void cn_slow_hash_allocate_state(void) -{ - // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c - return; -} - -void cn_slow_hash_free_state(void) -{ - // As above - return; -} -#endif - -#if defined(__GNUC__) -#define RDATA_ALIGN16 __attribute__ ((aligned(16))) -#define STATIC static -#define INLINE inline -#else -#define RDATA_ALIGN16 -#define STATIC static -#define INLINE -#endif - -#define U64(x) ((uint64_t *) (x)) - -#define hp_jitfunc ((v4_random_math_JIT_func)NULL) - -STATIC INLINE void xor64(uint64_t *a, const uint64_t b) -{ - *a ^= b; -} - -#pragma pack(push, 1) -union cn_slow_hash_state -{ - union hash_state hs; - struct - { - uint8_t k[64]; - uint8_t init[INIT_SIZE_BYTE]; - }; -}; -#pragma pack(pop) - -#if defined(__aarch64__) && defined(__ARM_FEATURE_CRYPTO) - -/* ARMv8-A optimized with NEON and AES instructions. - * Copied from the x86-64 AES-NI implementation. It has much the same - * characteristics as x86-64: there's no 64x64=128 multiplier for vectors, - * and moving between vector and regular registers stalls the pipeline. - */ -#include -#ifndef __APPLE__ -#include -#include -#endif - -STATIC INLINE int check_aes_hw(void) -{ -#ifdef __APPLE__ - return 1; -#else - static int supported = -1; - - if(supported < 0) - supported = (getauxval(AT_HWCAP) & HWCAP_AES) != 0; - return supported; -#endif -} - -#define TOTALBLOCKS (MEMORY / AES_BLOCK_SIZE) - -#define state_index(x) (((*((uint64_t *)x) >> 4) & (TOTALBLOCKS - 1)) << 4) -#define __mul() __asm__("mul %0, %1, %2\n\t" : "=r"(lo) : "r"(c[0]), "r"(b[0]) ); \ - __asm__("umulh %0, %1, %2\n\t" : "=r"(hi) : "r"(c[0]), "r"(b[0]) ); - -#define pre_aes() \ - j = state_index(a); \ - _c = vld1q_u8(&local_hp_state[j]); \ - _a = vld1q_u8((const uint8_t *)a); \ - -#define post_aes() \ - VARIANT2_SHUFFLE_ADD_NEON(local_hp_state, j); \ - vst1q_u8((uint8_t *)c, _c); \ - vst1q_u8(&local_hp_state[j], veorq_u8(_b, _c)); \ - VARIANT1_1(&local_hp_state[j]); \ - j = state_index(c); \ - p = U64(&local_hp_state[j]); \ - b[0] = p[0]; b[1] = p[1]; \ - VARIANT2_PORTABLE_INTEGER_MATH(b, c); \ - VARIANT4_RANDOM_MATH(a, b, r, &_b, &_b1); \ - __mul(); \ - VARIANT2_2(); \ - VARIANT2_SHUFFLE_ADD_NEON(local_hp_state, j); \ - a[0] += hi; a[1] += lo; \ - p = U64(&local_hp_state[j]); \ - p[0] = a[0]; p[1] = a[1]; \ - a[0] ^= b[0]; a[1] ^= b[1]; \ - VARIANT1_2(p + 1); \ - _b1 = _b; \ - _b = _c; \ - - -/* Note: this was based on a standard 256bit key schedule but - * it's been shortened since Cryptonight doesn't use the full - * key schedule. Don't try to use this for vanilla AES. -*/ -static void aes_expand_key(const uint8_t *key, uint8_t *expandedKey) { -static const int rcon[] = { - 0x01,0x01,0x01,0x01, - 0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d, // rotate-n-splat - 0x1b,0x1b,0x1b,0x1b }; -__asm__( -" eor v0.16b,v0.16b,v0.16b\n" -" ld1 {v3.16b},[%0],#16\n" -" ld1 {v1.4s,v2.4s},[%2],#32\n" -" ld1 {v4.16b},[%0]\n" -" mov w2,#5\n" -" st1 {v3.4s},[%1],#16\n" -"\n" -"1:\n" -" tbl v6.16b,{v4.16b},v2.16b\n" -" ext v5.16b,v0.16b,v3.16b,#12\n" -" st1 {v4.4s},[%1],#16\n" -" aese v6.16b,v0.16b\n" -" subs w2,w2,#1\n" -"\n" -" eor v3.16b,v3.16b,v5.16b\n" -" ext v5.16b,v0.16b,v5.16b,#12\n" -" eor v3.16b,v3.16b,v5.16b\n" -" ext v5.16b,v0.16b,v5.16b,#12\n" -" eor v6.16b,v6.16b,v1.16b\n" -" eor v3.16b,v3.16b,v5.16b\n" -" shl v1.16b,v1.16b,#1\n" -" eor v3.16b,v3.16b,v6.16b\n" -" st1 {v3.4s},[%1],#16\n" -" b.eq 2f\n" -"\n" -" dup v6.4s,v3.s[3] // just splat\n" -" ext v5.16b,v0.16b,v4.16b,#12\n" -" aese v6.16b,v0.16b\n" -"\n" -" eor v4.16b,v4.16b,v5.16b\n" -" ext v5.16b,v0.16b,v5.16b,#12\n" -" eor v4.16b,v4.16b,v5.16b\n" -" ext v5.16b,v0.16b,v5.16b,#12\n" -" eor v4.16b,v4.16b,v5.16b\n" -"\n" -" eor v4.16b,v4.16b,v6.16b\n" -" b 1b\n" -"\n" -"2:\n" : : "r"(key), "r"(expandedKey), "r"(rcon)); -} - -/* An ordinary AES round is a sequence of SubBytes, ShiftRows, MixColumns, AddRoundKey. There - * is also an InitialRound which consists solely of AddRoundKey. The ARM instructions slice - * this sequence differently; the aese instruction performs AddRoundKey, SubBytes, ShiftRows. - * The aesmc instruction does the MixColumns. Since the aese instruction moves the AddRoundKey - * up front, and Cryptonight's hash skips the InitialRound step, we have to kludge it here by - * feeding in a vector of zeros for our first step. Also we have to do our own Xor explicitly - * at the last step, to provide the AddRoundKey that the ARM instructions omit. - */ -STATIC INLINE void aes_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey, int nblocks) -{ - const uint8x16_t *k = (const uint8x16_t *)expandedKey, zero = {0}; - int i; - - for (i=0; ikey->exp_data); - - memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - } - - U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0]; - U64(a)[1] = U64(&state.k[0])[1] ^ U64(&state.k[32])[1]; - U64(b)[0] = U64(&state.k[16])[0] ^ U64(&state.k[48])[0]; - U64(b)[1] = U64(&state.k[16])[1] ^ U64(&state.k[48])[1]; - - /* CryptoNight Step 3: Bounce randomly 1,048,576 times (1<<20) through the mixing buffer, - * using 524,288 iterations of the following mixing function. Each execution - * performs two reads and writes from the mixing buffer. - */ - - _b = vld1q_u8((const uint8_t *)b); - _b1 = vld1q_u8(((const uint8_t *)b) + AES_BLOCK_SIZE); - - if(useAes) - { - for(i = 0; i < ITER / 2; i++) - { - pre_aes(); - _c = vaeseq_u8(_c, zero); - _c = vaesmcq_u8(_c); - _c = veorq_u8(_c, _a); - post_aes(); - } - } - else - { - for(i = 0; i < ITER / 2; i++) - { - pre_aes(); - aesb_single_round((uint8_t *) &_c, (uint8_t *) &_c, (uint8_t *) &_a); - post_aes(); - } - - } - - /* CryptoNight Step 4: Sequentially pass through the mixing buffer and use 10 rounds - * of AES encryption to mix the random data back into the 'text' buffer. 'text' - * was originally created with the output of Keccak1600. */ - - memcpy(text, state.init, INIT_SIZE_BYTE); - - if(useAes) - { - aes_expand_key(&state.hs.b[32], expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - // add the xor to the pseudo round - aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK); - } - } - else - { - oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - { - xor_blocks(&text[j * AES_BLOCK_SIZE], &local_hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); - } - } - oaes_free((OAES_CTX **) &aes_ctx); - } - - /* CryptoNight Step 5: Apply Keccak to the state again, and then - * use the resulting data to select which of four finalizer - * hash functions to apply to the data (Blake, Groestl, JH, or Skein). - * Use this hash to squeeze the state array down - * to the final 256 bit hash output. - */ - - memcpy(state.init, text, INIT_SIZE_BYTE); - hash_permutation(&state.hs); - extra_hashes[state.hs.b[0] & 3](&state, 200, hash); -} -#else /* aarch64 && crypto */ - -// ND: Some minor optimizations for ARMv7 (raspberry pi 2), effect seems to be ~40-50% faster. -// Needs more work. - -#ifdef NO_OPTIMIZED_MULTIPLY_ON_ARM -/* The asm corresponds to this C code */ -#define SHORT uint32_t -#define LONG uint64_t - -void mul(const uint8_t *ca, const uint8_t *cb, uint8_t *cres) { - const SHORT *aa = (SHORT *)ca; - const SHORT *bb = (SHORT *)cb; - SHORT *res = (SHORT *)cres; - union { - SHORT tmp[8]; - LONG ltmp[4]; - } t; - LONG A = aa[1]; - LONG a = aa[0]; - LONG B = bb[1]; - LONG b = bb[0]; - - // Aa * Bb = ab + aB_ + Ab_ + AB__ - t.ltmp[0] = a * b; - t.ltmp[1] = a * B; - t.ltmp[2] = A * b; - t.ltmp[3] = A * B; - - res[2] = t.tmp[0]; - t.ltmp[1] += t.tmp[1]; - t.ltmp[1] += t.tmp[4]; - t.ltmp[3] += t.tmp[3]; - t.ltmp[3] += t.tmp[5]; - res[3] = t.tmp[2]; - res[0] = t.tmp[6]; - res[1] = t.tmp[7]; -} -#else // !NO_OPTIMIZED_MULTIPLY_ON_ARM - -#ifdef __aarch64__ /* ARM64, no crypto */ -#define mul(a, b, c) cn_mul128((const uint64_t *)a, (const uint64_t *)b, (uint64_t *)c) -STATIC void cn_mul128(const uint64_t *a, const uint64_t *b, uint64_t *r) -{ - uint64_t lo, hi; - __asm__("mul %0, %1, %2\n\t" : "=r"(lo) : "r"(a[0]), "r"(b[0]) ); - __asm__("umulh %0, %1, %2\n\t" : "=r"(hi) : "r"(a[0]), "r"(b[0]) ); - r[0] = hi; - r[1] = lo; -} -#else /* ARM32 */ -#define mul(a, b, c) cn_mul128((const uint32_t *)a, (const uint32_t *)b, (uint32_t *)c) -STATIC void cn_mul128(const uint32_t *aa, const uint32_t *bb, uint32_t *r) -{ - uint32_t t0, t1, t2=0, t3=0; -__asm__ __volatile__( - "umull %[t0], %[t1], %[a], %[b]\n\t" - "str %[t0], %[ll]\n\t" - - // accumulating with 0 can never overflow/carry - "eor %[t0], %[t0]\n\t" - "umlal %[t1], %[t0], %[a], %[B]\n\t" - - "umlal %[t1], %[t2], %[A], %[b]\n\t" - "str %[t1], %[lh]\n\t" - - "umlal %[t0], %[t3], %[A], %[B]\n\t" - - // final add may have a carry - "adds %[t0], %[t0], %[t2]\n\t" - "adc %[t1], %[t3], #0\n\t" - - "str %[t0], %[hl]\n\t" - "str %[t1], %[hh]\n\t" - : [t0]"=&r"(t0), [t1]"=&r"(t1), [t2]"+r"(t2), [t3]"+r"(t3), [hl]"=m"(r[0]), [hh]"=m"(r[1]), [ll]"=m"(r[2]), [lh]"=m"(r[3]) - : [A]"r"(aa[1]), [a]"r"(aa[0]), [B]"r"(bb[1]), [b]"r"(bb[0]) - : "cc"); -} -#endif /* !aarch64 */ -#endif // NO_OPTIMIZED_MULTIPLY_ON_ARM - -STATIC INLINE void copy_block(uint8_t* dst, const uint8_t* src) -{ - memcpy(dst, src, AES_BLOCK_SIZE); -} - -STATIC INLINE void sum_half_blocks(uint8_t* a, const uint8_t* b) -{ - uint64_t a0, a1, b0, b1; - a0 = U64(a)[0]; - a1 = U64(a)[1]; - b0 = U64(b)[0]; - b1 = U64(b)[1]; - a0 += b0; - a1 += b1; - U64(a)[0] = a0; - U64(a)[1] = a1; -} - -STATIC INLINE void swap_blocks(uint8_t *a, uint8_t *b) -{ - uint64_t t[2]; - U64(t)[0] = U64(a)[0]; - U64(t)[1] = U64(a)[1]; - U64(a)[0] = U64(b)[0]; - U64(a)[1] = U64(b)[1]; - U64(b)[0] = U64(t)[0]; - U64(b)[1] = U64(t)[1]; -} - -STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b) -{ - U64(a)[0] ^= U64(b)[0]; - U64(a)[1] ^= U64(b)[1]; -} - -void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height) -{ - uint8_t text[INIT_SIZE_BYTE]; - uint8_t a[AES_BLOCK_SIZE]; - uint8_t a1[AES_BLOCK_SIZE]; - uint8_t b[AES_BLOCK_SIZE * 2]; - uint8_t c[AES_BLOCK_SIZE]; - uint8_t c1[AES_BLOCK_SIZE]; - uint8_t d[AES_BLOCK_SIZE]; - uint8_t aes_key[AES_KEY_SIZE]; - RDATA_ALIGN16 uint8_t expandedKey[256]; - - union cn_slow_hash_state state; - - size_t i, j; - uint8_t *p = NULL; - oaes_ctx *aes_ctx; - static void (*const extra_hashes[4])(const void *, size_t, char *) = - { - hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein - }; - -#ifndef FORCE_USE_HEAP - uint8_t long_state[MEMORY]; -#else - uint8_t *long_state = (uint8_t *)malloc(MEMORY); -#endif - - if (prehashed) { - memcpy(&state.hs, data, length); - } else { - hash_process(&state.hs, data, length); - } - memcpy(text, state.init, INIT_SIZE_BYTE); - - aes_ctx = (oaes_ctx *) oaes_alloc(); - oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE); - - VARIANT1_INIT64(); - VARIANT2_INIT64(); - VARIANT4_RANDOM_MATH_INIT(); - - // use aligned data - memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey); - memcpy(&long_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - - U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0]; - U64(a)[1] = U64(&state.k[0])[1] ^ U64(&state.k[32])[1]; - U64(b)[0] = U64(&state.k[16])[0] ^ U64(&state.k[48])[0]; - U64(b)[1] = U64(&state.k[16])[1] ^ U64(&state.k[48])[1]; - - for(i = 0; i < ITER / 2; i++) - { - #define MASK ((uint32_t)(((MEMORY / AES_BLOCK_SIZE) - 1) << 4)) - #define state_index(x) ((*(uint32_t *) x) & MASK) - - // Iteration 1 - j = state_index(a); - p = &long_state[j]; - aesb_single_round(p, c1, a); - - VARIANT2_PORTABLE_SHUFFLE_ADD(c1, a, long_state, j); - copy_block(p, c1); - xor_blocks(p, b); - VARIANT1_1(p); - - // Iteration 2 - j = state_index(c1); - p = &long_state[j]; - copy_block(c, p); - - copy_block(a1, a); - VARIANT2_PORTABLE_INTEGER_MATH(c, c1); - VARIANT4_RANDOM_MATH(a1, c, r, b, b + AES_BLOCK_SIZE); - mul(c1, c, d); - VARIANT2_2_PORTABLE(); - VARIANT2_PORTABLE_SHUFFLE_ADD(c1, a, long_state, j); - sum_half_blocks(a1, d); - swap_blocks(a1, c); - xor_blocks(a1, c); - VARIANT1_2(U64(c) + 1); - copy_block(p, c); - - if (variant >= 2) { - copy_block(b + AES_BLOCK_SIZE, b); - } - copy_block(b, c1); - copy_block(a, a1); - } - - memcpy(text, state.init, INIT_SIZE_BYTE); - oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); - memcpy(expandedKey, aes_ctx->key->exp_data, aes_ctx->key->exp_data_len); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) - { - for(j = 0; j < INIT_SIZE_BLK; j++) - { - xor_blocks(&text[j * AES_BLOCK_SIZE], &long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], expandedKey); - } - } - - oaes_free((OAES_CTX **) &aes_ctx); - memcpy(state.init, text, INIT_SIZE_BYTE); - hash_permutation(&state.hs); - extra_hashes[state.hs.b[0] & 3](&state, 200, hash); -#ifdef FORCE_USE_HEAP - free(long_state); -#endif -} -#endif /* !aarch64 || !crypto */ - -#else -// Portable implementation as a fallback - -#define hp_jitfunc ((v4_random_math_JIT_func)NULL) - -void cn_slow_hash_allocate_state(void) -{ - // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c - return; -} - -void cn_slow_hash_free_state(void) -{ - // As above - return; -} - -static void (*const extra_hashes[4])(const void *, size_t, char *) = { - hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein -}; - -static size_t e2i(const uint8_t* a, size_t count) { return (SWAP64LE(*((uint64_t*)a)) / AES_BLOCK_SIZE) & (count - 1); } - -static void mul(const uint8_t* a, const uint8_t* b, uint8_t* res) { - uint64_t a0, b0; - uint64_t hi, lo; - - a0 = SWAP64LE(((uint64_t*)a)[0]); - b0 = SWAP64LE(((uint64_t*)b)[0]); - lo = mul128(a0, b0, &hi); - ((uint64_t*)res)[0] = SWAP64LE(hi); - ((uint64_t*)res)[1] = SWAP64LE(lo); -} - -static void sum_half_blocks(uint8_t* a, const uint8_t* b) { - uint64_t a0, a1, b0, b1; - - a0 = SWAP64LE(((uint64_t*)a)[0]); - a1 = SWAP64LE(((uint64_t*)a)[1]); - b0 = SWAP64LE(((uint64_t*)b)[0]); - b1 = SWAP64LE(((uint64_t*)b)[1]); - a0 += b0; - a1 += b1; - ((uint64_t*)a)[0] = SWAP64LE(a0); - ((uint64_t*)a)[1] = SWAP64LE(a1); -} -#define U64(x) ((uint64_t *) (x)) - -static void copy_block(uint8_t* dst, const uint8_t* src) { - memcpy(dst, src, AES_BLOCK_SIZE); -} - -static void swap_blocks(uint8_t *a, uint8_t *b){ - uint64_t t[2]; - U64(t)[0] = U64(a)[0]; - U64(t)[1] = U64(a)[1]; - U64(a)[0] = U64(b)[0]; - U64(a)[1] = U64(b)[1]; - U64(b)[0] = U64(t)[0]; - U64(b)[1] = U64(t)[1]; -} - -static void xor_blocks(uint8_t* a, const uint8_t* b) { - size_t i; - for (i = 0; i < AES_BLOCK_SIZE; i++) { - a[i] ^= b[i]; - } -} - -static void xor64(uint8_t* left, const uint8_t* right) -{ - size_t i; - for (i = 0; i < 8; ++i) - { - left[i] ^= right[i]; - } -} - -#pragma pack(push, 1) -union cn_slow_hash_state { - union hash_state hs; - struct { - uint8_t k[64]; - uint8_t init[INIT_SIZE_BYTE]; - }; -}; -#pragma pack(pop) - -void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height) { -#ifndef FORCE_USE_HEAP - uint8_t long_state[MEMORY]; -#else - uint8_t *long_state = (uint8_t *)malloc(MEMORY); -#endif - - union cn_slow_hash_state state; - uint8_t text[INIT_SIZE_BYTE]; - uint8_t a[AES_BLOCK_SIZE]; - uint8_t a1[AES_BLOCK_SIZE]; - uint8_t b[AES_BLOCK_SIZE * 2]; - uint8_t c1[AES_BLOCK_SIZE]; - uint8_t c2[AES_BLOCK_SIZE]; - uint8_t d[AES_BLOCK_SIZE]; - size_t i, j; - uint8_t aes_key[AES_KEY_SIZE]; - oaes_ctx *aes_ctx; - - if (prehashed) { - memcpy(&state.hs, data, length); - } else { - hash_process(&state.hs, data, length); - } - memcpy(text, state.init, INIT_SIZE_BYTE); - memcpy(aes_key, state.hs.b, AES_KEY_SIZE); - aes_ctx = (oaes_ctx *) oaes_alloc(); - - VARIANT1_PORTABLE_INIT(); - VARIANT2_PORTABLE_INIT(); - VARIANT4_RANDOM_MATH_INIT(); - - oaes_key_import_data(aes_ctx, aes_key, AES_KEY_SIZE); - for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) { - for (j = 0; j < INIT_SIZE_BLK; j++) { - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); - } - memcpy(&long_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); - } - - for (i = 0; i < AES_BLOCK_SIZE; i++) { - a[i] = state.k[ i] ^ state.k[AES_BLOCK_SIZE * 2 + i]; - b[i] = state.k[AES_BLOCK_SIZE + i] ^ state.k[AES_BLOCK_SIZE * 3 + i]; - } - - for (i = 0; i < ITER / 2; i++) { - /* Dependency chain: address -> read value ------+ - * written value <-+ hard function (AES or MUL) <+ - * next address <-+ - */ - /* Iteration 1 */ - j = e2i(a, MEMORY / AES_BLOCK_SIZE) * AES_BLOCK_SIZE; - copy_block(c1, &long_state[j]); - aesb_single_round(c1, c1, a); - VARIANT2_PORTABLE_SHUFFLE_ADD(c1, a, long_state, j); - copy_block(&long_state[j], c1); - xor_blocks(&long_state[j], b); - assert(j == e2i(a, MEMORY / AES_BLOCK_SIZE) * AES_BLOCK_SIZE); - VARIANT1_1(&long_state[j]); - /* Iteration 2 */ - j = e2i(c1, MEMORY / AES_BLOCK_SIZE) * AES_BLOCK_SIZE; - copy_block(c2, &long_state[j]); - copy_block(a1, a); - VARIANT2_PORTABLE_INTEGER_MATH(c2, c1); - VARIANT4_RANDOM_MATH(a1, c2, r, b, b + AES_BLOCK_SIZE); - mul(c1, c2, d); - VARIANT2_2_PORTABLE(); - VARIANT2_PORTABLE_SHUFFLE_ADD(c1, a, long_state, j); - sum_half_blocks(a1, d); - swap_blocks(a1, c2); - xor_blocks(a1, c2); - VARIANT1_2(c2 + 8); - copy_block(&long_state[j], c2); - if (variant >= 2) { - copy_block(b + AES_BLOCK_SIZE, b); - } - copy_block(b, c1); - copy_block(a, a1); - } - - memcpy(text, state.init, INIT_SIZE_BYTE); - oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); - for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) { - for (j = 0; j < INIT_SIZE_BLK; j++) { - xor_blocks(&text[j * AES_BLOCK_SIZE], &long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); - aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); - } - } - memcpy(state.init, text, INIT_SIZE_BYTE); - hash_permutation(&state.hs); - /*memcpy(hash, &state, 32);*/ - extra_hashes[state.hs.b[0] & 3](&state, 200, hash); - oaes_free((OAES_CTX **) &aes_ctx); - -#ifdef FORCE_USE_HEAP - free(long_state); -#endif -} - -#endif - -void slow_hash_allocate_state(void) -{ - cn_slow_hash_allocate_state(); -} - -void slow_hash_free_state(void) -{ - cn_slow_hash_free_state(); -} diff --git a/cryptonight/c/variant2_int_sqrt.h b/cryptonight/c/variant2_int_sqrt.h deleted file mode 100644 index b405bb7..0000000 --- a/cryptonight/c/variant2_int_sqrt.h +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef VARIANT2_INT_SQRT_H -#define VARIANT2_INT_SQRT_H - -#include -#include - -#define VARIANT2_INTEGER_MATH_SQRT_STEP_SSE2() \ - do { \ - const __m128i exp_double_bias = _mm_set_epi64x(0, 1023ULL << 52); \ - __m128d x = _mm_castsi128_pd(_mm_add_epi64(_mm_cvtsi64_si128(sqrt_input >> 12), exp_double_bias)); \ - x = _mm_sqrt_sd(_mm_setzero_pd(), x); \ - sqrt_result = (uint64_t)(_mm_cvtsi128_si64(_mm_sub_epi64(_mm_castpd_si128(x), exp_double_bias))) >> 19; \ - } while(0) - -#define VARIANT2_INTEGER_MATH_SQRT_STEP_FP64() \ - do { \ - sqrt_result = sqrt(sqrt_input + 18446744073709551616.0) * 2.0 - 8589934592.0; \ - } while(0) - -#define VARIANT2_INTEGER_MATH_SQRT_STEP_REF() \ - sqrt_result = integer_square_root_v2(sqrt_input) - -// Reference implementation of the integer square root for Cryptonight variant 2 -// Computes integer part of "sqrt(2^64 + n) * 2 - 2^33" -// -// In other words, given 64-bit unsigned integer n: -// 1) Write it as x = 1.NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN000... in binary (1 <= x < 2, all 64 bits of n are used) -// 2) Calculate sqrt(x) = 1.0RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR... (1 <= sqrt(x) < sqrt(2), so it will always start with "1.0" in binary) -// 3) Take 32 bits that come after "1.0" and return them as a 32-bit unsigned integer, discard all remaining bits -// -// Some sample inputs and outputs: -// -// Input | Output | Exact value of "sqrt(2^64 + n) * 2 - 2^33" -// -----------------|------------|------------------------------------------- -// 0 | 0 | 0 -// 2^32 | 0 | 0.99999999994179233909330885695244... -// 2^32 + 1 | 1 | 1.0000000001746229827200734316305... -// 2^50 | 262140 | 262140.00012206565608606978175873... -// 2^55 + 20963331 | 8384515 | 8384515.9999999997673963974959744... -// 2^55 + 20963332 | 8384516 | 8384516 -// 2^62 + 26599786 | 1013904242 | 1013904242.9999999999479374853545... -// 2^62 + 26599787 | 1013904243 | 1013904243.0000000001561875439364... -// 2^64 - 1 | 3558067407 | 3558067407.9041987696409179931096... - -// The reference implementation as it is now uses only unsigned int64 arithmetic, so it can't have undefined behavior -// It was tested once for all edge cases and confirmed correct -static inline uint32_t integer_square_root_v2(uint64_t n) -{ - uint64_t r = 1ULL << 63; - - for (uint64_t bit = 1ULL << 60; bit; bit >>= 2) - { - const bool b = (n < r + bit); - const uint64_t n_next = n - (r + bit); - const uint64_t r_next = r + bit * 2; - n = b ? n : n_next; - r = b ? r : r_next; - r >>= 1; - } - - return r * 2 + ((n > r) ? 1 : 0); -} - -/* -VARIANT2_INTEGER_MATH_SQRT_FIXUP checks that "r" is an integer part of "sqrt(2^64 + sqrt_input) * 2 - 2^33" and adds or subtracts 1 if needed -It's hard to understand how it works, so here is a full calculation of formulas used in VARIANT2_INTEGER_MATH_SQRT_FIXUP - -The following inequalities must hold for r if it's an integer part of "sqrt(2^64 + sqrt_input) * 2 - 2^33": -1) r <= sqrt(2^64 + sqrt_input) * 2 - 2^33 -2) r + 1 > sqrt(2^64 + sqrt_input) * 2 - 2^33 - -We need to check them using only unsigned integer arithmetic to avoid rounding errors and undefined behavior - -First inequality: r <= sqrt(2^64 + sqrt_input) * 2 - 2^33 ------------------------------------------------------------------------------------ -r <= sqrt(2^64 + sqrt_input) * 2 - 2^33 -r + 2^33 <= sqrt(2^64 + sqrt_input) * 2 -r/2 + 2^32 <= sqrt(2^64 + sqrt_input) -(r/2 + 2^32)^2 <= 2^64 + sqrt_input - -Rewrite r as r = s * 2 + b (s = trunc(r/2), b is 0 or 1) - -((s*2+b)/2 + 2^32)^2 <= 2^64 + sqrt_input -(s*2+b)^2/4 + 2*2^32*(s*2+b)/2 + 2^64 <= 2^64 + sqrt_input -(s*2+b)^2/4 + 2*2^32*(s*2+b)/2 <= sqrt_input -(s*2+b)^2/4 + 2^32*r <= sqrt_input -(s^2*4+2*s*2*b+b^2)/4 + 2^32*r <= sqrt_input -s^2+s*b+b^2/4 + 2^32*r <= sqrt_input -s*(s+b) + b^2/4 + 2^32*r <= sqrt_input - -Let r2 = s*(s+b) + r*2^32 -r2 + b^2/4 <= sqrt_input - -If this inequality doesn't hold, then we must decrement r: IF "r2 + b^2/4 > sqrt_input" THEN r = r - 1 - -b can be 0 or 1 -If b is 0 then we need to compare "r2 > sqrt_input" -If b is 1 then b^2/4 = 0.25, so we need to compare "r2 + 0.25 > sqrt_input" -Since both r2 and sqrt_input are integers, we can safely replace it with "r2 + 1 > sqrt_input" ------------------------------------------------------------------------------------ -Both cases can be merged to a single expression "r2 + b > sqrt_input" ------------------------------------------------------------------------------------ -There will be no overflow when calculating "r2 + b", so it's safe to compare with sqrt_input: -r2 + b = s*(s+b) + r*2^32 + b -The largest value s, b and r can have is s = 1779033703, b = 1, r = 3558067407 when sqrt_input = 2^64 - 1 -r2 + b <= 1779033703*1779033704 + 3558067407*2^32 + 1 = 18446744068217447385 < 2^64 - -Second inequality: r + 1 > sqrt(2^64 + sqrt_input) * 2 - 2^33 ------------------------------------------------------------------------------------ -r + 1 > sqrt(2^64 + sqrt_input) * 2 - 2^33 -r + 1 + 2^33 > sqrt(2^64 + sqrt_input) * 2 -((r+1)/2 + 2^32)^2 > 2^64 + sqrt_input - -Rewrite r as r = s * 2 + b (s = trunc(r/2), b is 0 or 1) - -((s*2+b+1)/2 + 2^32)^2 > 2^64 + sqrt_input -(s*2+b+1)^2/4 + 2*(s*2+b+1)/2*2^32 + 2^64 > 2^64 + sqrt_input -(s*2+b+1)^2/4 + (s*2+b+1)*2^32 > sqrt_input -(s*2+b+1)^2/4 + (r+1)*2^32 > sqrt_input -(s*2+(b+1))^2/4 + r*2^32 + 2^32 > sqrt_input -(s^2*4+2*s*2*(b+1)+(b+1)^2)/4 + r*2^32 + 2^32 > sqrt_input -s^2+s*(b+1)+(b+1)^2/4 + r*2^32 + 2^32 > sqrt_input -s*(s+b) + s + (b+1)^2/4 + r*2^32 + 2^32 > sqrt_input - -Let r2 = s*(s+b) + r*2^32 - -r2 + s + (b+1)^2/4 + 2^32 > sqrt_input -r2 + 2^32 + (b+1)^2/4 > sqrt_input - s - -If this inequality doesn't hold, then we must decrement r: IF "r2 + 2^32 + (b+1)^2/4 <= sqrt_input - s" THEN r = r - 1 -b can be 0 or 1 -If b is 0 then we need to compare "r2 + 2^32 + 1/4 <= sqrt_input - s" which is equal to "r2 + 2^32 < sqrt_input - s" because all numbers here are integers -If b is 1 then (b+1)^2/4 = 1, so we need to compare "r2 + 2^32 + 1 <= sqrt_input - s" which is also equal to "r2 + 2^32 < sqrt_input - s" ------------------------------------------------------------------------------------ -Both cases can be merged to a single expression "r2 + 2^32 < sqrt_input - s" ------------------------------------------------------------------------------------ -There will be no overflow when calculating "r2 + 2^32": -r2 + 2^32 = s*(s+b) + r*2^32 + 2^32 = s*(s+b) + (r+1)*2^32 -The largest value s, b and r can have is s = 1779033703, b = 1, r = 3558067407 when sqrt_input = 2^64 - 1 -r2 + b <= 1779033703*1779033704 + 3558067408*2^32 = 18446744072512414680 < 2^64 - -There will be no integer overflow when calculating "sqrt_input - s", i.e. "sqrt_input >= s" at all times: -s = trunc(r/2) = trunc(sqrt(2^64 + sqrt_input) - 2^32) < sqrt(2^64 + sqrt_input) - 2^32 + 1 -sqrt_input > sqrt(2^64 + sqrt_input) - 2^32 + 1 -sqrt_input + 2^32 - 1 > sqrt(2^64 + sqrt_input) -(sqrt_input + 2^32 - 1)^2 > sqrt_input + 2^64 -sqrt_input^2 + 2*sqrt_input*(2^32 - 1) + (2^32-1)^2 > sqrt_input + 2^64 -sqrt_input^2 + sqrt_input*(2^33 - 2) + (2^32-1)^2 > sqrt_input + 2^64 -sqrt_input^2 + sqrt_input*(2^33 - 3) + (2^32-1)^2 > 2^64 -sqrt_input^2 + sqrt_input*(2^33 - 3) + 2^64-2^33+1 > 2^64 -sqrt_input^2 + sqrt_input*(2^33 - 3) - 2^33 + 1 > 0 -This inequality is true if sqrt_input > 1 and it's easy to check that s = 0 if sqrt_input is 0 or 1, so there will be no integer overflow -*/ - -#define VARIANT2_INTEGER_MATH_SQRT_FIXUP(r) \ - do { \ - const uint64_t s = r >> 1; \ - const uint64_t b = r & 1; \ - const uint64_t r2 = (uint64_t)(s) * (s + b) + (r << 32); \ - r += ((r2 + b > sqrt_input) ? -1 : 0) + ((r2 + (1ULL << 32) < sqrt_input - s) ? 1 : 0); \ - } while(0) - -#endif diff --git a/cryptonight/c/variant4_random_math.h b/cryptonight/c/variant4_random_math.h deleted file mode 100644 index f3e41a0..0000000 --- a/cryptonight/c/variant4_random_math.h +++ /dev/null @@ -1,441 +0,0 @@ -#ifndef VARIANT4_RANDOM_MATH_H -#define VARIANT4_RANDOM_MATH_H - -// Register size can be configured to either 32 bit (uint32_t) or 64 bit (uint64_t) -typedef uint32_t v4_reg; - -enum V4_Settings -{ - // Generate code with minimal theoretical latency = 45 cycles, which is equivalent to 15 multiplications - TOTAL_LATENCY = 15 * 3, - - // Always generate at least 60 instructions - NUM_INSTRUCTIONS_MIN = 60, - - // Never generate more than 70 instructions (final RET instruction doesn't count here) - NUM_INSTRUCTIONS_MAX = 70, - - // Available ALUs for MUL - // Modern CPUs typically have only 1 ALU which can do multiplications - ALU_COUNT_MUL = 1, - - // Total available ALUs - // Modern CPUs have 4 ALUs, but we use only 3 because random math executes together with other main loop code - ALU_COUNT = 3, -}; - -enum V4_InstructionList -{ - MUL, // a*b - ADD, // a+b + C, C is an unsigned 32-bit constant - SUB, // a-b - ROR, // rotate right "a" by "b & 31" bits - ROL, // rotate left "a" by "b & 31" bits - XOR, // a^b - RET, // finish execution - V4_INSTRUCTION_COUNT = RET, -}; - -// V4_InstructionDefinition is used to generate code from random data -// Every random sequence of bytes is a valid code -// -// There are 9 registers in total: -// - 4 variable registers -// - 5 constant registers initialized from loop variables -// This is why dst_index is 2 bits -enum V4_InstructionDefinition -{ - V4_OPCODE_BITS = 3, - V4_DST_INDEX_BITS = 2, - V4_SRC_INDEX_BITS = 3, -}; - -struct V4_Instruction -{ - uint8_t opcode; - uint8_t dst_index; - uint8_t src_index; - uint32_t C; -}; - -#ifndef FORCEINLINE -#if defined(__GNUC__) -#define FORCEINLINE __attribute__((always_inline)) inline -#elif defined(_MSC_VER) -#define FORCEINLINE __forceinline -#else -#define FORCEINLINE inline -#endif -#endif - -#ifndef UNREACHABLE_CODE -#if defined(__GNUC__) -#define UNREACHABLE_CODE __builtin_unreachable() -#elif defined(_MSC_VER) -#define UNREACHABLE_CODE __assume(false) -#else -#define UNREACHABLE_CODE -#endif -#endif - -// Random math interpreter's loop is fully unrolled and inlined to achieve 100% branch prediction on CPU: -// every switch-case will point to the same destination on every iteration of Cryptonight main loop -// -// This is about as fast as it can get without using low-level machine code generation -static FORCEINLINE void v4_random_math(const struct V4_Instruction* code, v4_reg* r) -{ - enum - { - REG_BITS = sizeof(v4_reg) * 8, - }; - -#define V4_EXEC(i) \ - { \ - const struct V4_Instruction* op = code + i; \ - const v4_reg src = r[op->src_index]; \ - v4_reg* dst = r + op->dst_index; \ - switch (op->opcode) \ - { \ - case MUL: \ - *dst *= src; \ - break; \ - case ADD: \ - *dst += src + op->C; \ - break; \ - case SUB: \ - *dst -= src; \ - break; \ - case ROR: \ - { \ - const uint32_t shift = src % REG_BITS; \ - *dst = (*dst >> shift) | (*dst << ((REG_BITS - shift) % REG_BITS)); \ - } \ - break; \ - case ROL: \ - { \ - const uint32_t shift = src % REG_BITS; \ - *dst = (*dst << shift) | (*dst >> ((REG_BITS - shift) % REG_BITS)); \ - } \ - break; \ - case XOR: \ - *dst ^= src; \ - break; \ - case RET: \ - return; \ - default: \ - UNREACHABLE_CODE; \ - break; \ - } \ - } - -#define V4_EXEC_10(j) \ - V4_EXEC(j + 0) \ - V4_EXEC(j + 1) \ - V4_EXEC(j + 2) \ - V4_EXEC(j + 3) \ - V4_EXEC(j + 4) \ - V4_EXEC(j + 5) \ - V4_EXEC(j + 6) \ - V4_EXEC(j + 7) \ - V4_EXEC(j + 8) \ - V4_EXEC(j + 9) - - // Generated program can have 60 + a few more (usually 2-3) instructions to achieve required latency - // I've checked all block heights < 10,000,000 and here is the distribution of program sizes: - // - // 60 27960 - // 61 105054 - // 62 2452759 - // 63 5115997 - // 64 1022269 - // 65 1109635 - // 66 153145 - // 67 8550 - // 68 4529 - // 69 102 - - // Unroll 70 instructions here - V4_EXEC_10(0); // instructions 0-9 - V4_EXEC_10(10); // instructions 10-19 - V4_EXEC_10(20); // instructions 20-29 - V4_EXEC_10(30); // instructions 30-39 - V4_EXEC_10(40); // instructions 40-49 - V4_EXEC_10(50); // instructions 50-59 - V4_EXEC_10(60); // instructions 60-69 - -#undef V4_EXEC_10 -#undef V4_EXEC -} - -// If we don't have enough data available, generate more -static FORCEINLINE void check_data(size_t* data_index, const size_t bytes_needed, int8_t* data, const size_t data_size) -{ - if (*data_index + bytes_needed > data_size) - { - hash_extra_blake(data, data_size, (char*) data); - *data_index = 0; - } -} - -// Generates as many random math operations as possible with given latency and ALU restrictions -// "code" array must have space for NUM_INSTRUCTIONS_MAX+1 instructions -static inline int v4_random_math_init(struct V4_Instruction* code, const uint64_t height) -{ - // MUL is 3 cycles, 3-way addition and rotations are 2 cycles, SUB/XOR are 1 cycle - // These latencies match real-life instruction latencies for Intel CPUs starting from Sandy Bridge and up to Skylake/Coffee lake - // - // AMD Ryzen has the same latencies except 1-cycle ROR/ROL, so it'll be a bit faster than Intel Sandy Bridge and newer processors - // Surprisingly, Intel Nehalem also has 1-cycle ROR/ROL, so it'll also be faster than Intel Sandy Bridge and newer processors - // AMD Bulldozer has 4 cycles latency for MUL (slower than Intel) and 1 cycle for ROR/ROL (faster than Intel), so average performance will be the same - // Source: https://www.agner.org/optimize/instruction_tables.pdf - const int op_latency[V4_INSTRUCTION_COUNT] = { 3, 2, 1, 2, 2, 1 }; - - // Instruction latencies for theoretical ASIC implementation - const int asic_op_latency[V4_INSTRUCTION_COUNT] = { 3, 1, 1, 1, 1, 1 }; - - // Available ALUs for each instruction - const int op_ALUs[V4_INSTRUCTION_COUNT] = { ALU_COUNT_MUL, ALU_COUNT, ALU_COUNT, ALU_COUNT, ALU_COUNT, ALU_COUNT }; - - int8_t data[32]; - memset(data, 0, sizeof(data)); - uint64_t tmp = SWAP64LE(height); - memcpy(data, &tmp, sizeof(uint64_t)); - data[20] = -38; // change seed - - // Set data_index past the last byte in data - // to trigger full data update with blake hash - // before we start using it - size_t data_index = sizeof(data); - - int code_size; - - // There is a small chance (1.8%) that register R8 won't be used in the generated program - // So we keep track of it and try again if it's not used - bool r8_used; - do { - int latency[9]; - int asic_latency[9]; - - // Tracks previous instruction and value of the source operand for registers R0-R3 throughout code execution - // byte 0: current value of the destination register - // byte 1: instruction opcode - // byte 2: current value of the source register - // - // Registers R4-R8 are constant and are treated as having the same value because when we do - // the same operation twice with two constant source registers, it can be optimized into a single operation - uint32_t inst_data[9] = { 0, 1, 2, 3, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF }; - - bool alu_busy[TOTAL_LATENCY + 1][ALU_COUNT]; - bool is_rotation[V4_INSTRUCTION_COUNT]; - bool rotated[4]; - int rotate_count = 0; - - memset(latency, 0, sizeof(latency)); - memset(asic_latency, 0, sizeof(asic_latency)); - memset(alu_busy, 0, sizeof(alu_busy)); - memset(is_rotation, 0, sizeof(is_rotation)); - memset(rotated, 0, sizeof(rotated)); - is_rotation[ROR] = true; - is_rotation[ROL] = true; - - int num_retries = 0; - code_size = 0; - - int total_iterations = 0; - r8_used = false; - - // Generate random code to achieve minimal required latency for our abstract CPU - // Try to get this latency for all 4 registers - while (((latency[0] < TOTAL_LATENCY) || (latency[1] < TOTAL_LATENCY) || (latency[2] < TOTAL_LATENCY) || (latency[3] < TOTAL_LATENCY)) && (num_retries < 64)) - { - // Fail-safe to guarantee loop termination - ++total_iterations; - if (total_iterations > 256) - break; - - check_data(&data_index, 1, data, sizeof(data)); - - const uint8_t c = ((uint8_t*)data)[data_index++]; - - // MUL = opcodes 0-2 - // ADD = opcode 3 - // SUB = opcode 4 - // ROR/ROL = opcode 5, shift direction is selected randomly - // XOR = opcodes 6-7 - uint8_t opcode = c & ((1 << V4_OPCODE_BITS) - 1); - if (opcode == 5) - { - check_data(&data_index, 1, data, sizeof(data)); - opcode = (data[data_index++] >= 0) ? ROR : ROL; - } - else if (opcode >= 6) - { - opcode = XOR; - } - else - { - opcode = (opcode <= 2) ? MUL : (opcode - 2); - } - - uint8_t dst_index = (c >> V4_OPCODE_BITS) & ((1 << V4_DST_INDEX_BITS) - 1); - uint8_t src_index = (c >> (V4_OPCODE_BITS + V4_DST_INDEX_BITS)) & ((1 << V4_SRC_INDEX_BITS) - 1); - - const int a = dst_index; - int b = src_index; - - // Don't do ADD/SUB/XOR with the same register - if (((opcode == ADD) || (opcode == SUB) || (opcode == XOR)) && (a == b)) - { - // Use register R8 as source instead - b = 8; - src_index = 8; - } - - // Don't do rotation with the same destination twice because it's equal to a single rotation - if (is_rotation[opcode] && rotated[a]) - { - continue; - } - - // Don't do the same instruction (except MUL) with the same source value twice because all other cases can be optimized: - // 2xADD(a, b, C) = ADD(a, b*2, C1+C2), same for SUB and rotations - // 2xXOR(a, b) = NOP - if ((opcode != MUL) && ((inst_data[a] & 0xFFFF00) == (opcode << 8) + ((inst_data[b] & 255) << 16))) - { - continue; - } - - // Find which ALU is available (and when) for this instruction - int next_latency = (latency[a] > latency[b]) ? latency[a] : latency[b]; - int alu_index = -1; - while (next_latency < TOTAL_LATENCY) - { - for (int i = op_ALUs[opcode] - 1; i >= 0; --i) - { - if (!alu_busy[next_latency][i]) - { - // ADD is implemented as two 1-cycle instructions on a real CPU, so do an additional availability check - if ((opcode == ADD) && alu_busy[next_latency + 1][i]) - { - continue; - } - - // Rotation can only start when previous rotation is finished, so do an additional availability check - if (is_rotation[opcode] && (next_latency < rotate_count * op_latency[opcode])) - { - continue; - } - - alu_index = i; - break; - } - } - if (alu_index >= 0) - { - break; - } - ++next_latency; - } - - // Don't generate instructions that leave some register unchanged for more than 7 cycles - if (next_latency > latency[a] + 7) - { - continue; - } - - next_latency += op_latency[opcode]; - - if (next_latency <= TOTAL_LATENCY) - { - if (is_rotation[opcode]) - { - ++rotate_count; - } - - // Mark ALU as busy only for the first cycle when it starts executing the instruction because ALUs are fully pipelined - alu_busy[next_latency - op_latency[opcode]][alu_index] = true; - latency[a] = next_latency; - - // ASIC is supposed to have enough ALUs to run as many independent instructions per cycle as possible, so latency calculation for ASIC is simple - asic_latency[a] = ((asic_latency[a] > asic_latency[b]) ? asic_latency[a] : asic_latency[b]) + asic_op_latency[opcode]; - - rotated[a] = is_rotation[opcode]; - - inst_data[a] = code_size + (opcode << 8) + ((inst_data[b] & 255) << 16); - - code[code_size].opcode = opcode; - code[code_size].dst_index = dst_index; - code[code_size].src_index = src_index; - code[code_size].C = 0; - - if (src_index == 8) - { - r8_used = true; - } - - if (opcode == ADD) - { - // ADD instruction is implemented as two 1-cycle instructions on a real CPU, so mark ALU as busy for the next cycle too - alu_busy[next_latency - op_latency[opcode] + 1][alu_index] = true; - - // ADD instruction requires 4 more random bytes for 32-bit constant "C" in "a = a + b + C" - check_data(&data_index, sizeof(uint32_t), data, sizeof(data)); - uint32_t t; - memcpy(&t, data + data_index, sizeof(uint32_t)); - code[code_size].C = SWAP32LE(t); - data_index += sizeof(uint32_t); - } - - ++code_size; - if (code_size >= NUM_INSTRUCTIONS_MIN) - { - break; - } - } - else - { - ++num_retries; - } - } - - // ASIC has more execution resources and can extract as much parallelism from the code as possible - // We need to add a few more MUL and ROR instructions to achieve minimal required latency for ASIC - // Get this latency for at least 1 of the 4 registers - const int prev_code_size = code_size; - while ((code_size < NUM_INSTRUCTIONS_MAX) && (asic_latency[0] < TOTAL_LATENCY) && (asic_latency[1] < TOTAL_LATENCY) && (asic_latency[2] < TOTAL_LATENCY) && (asic_latency[3] < TOTAL_LATENCY)) - { - int min_idx = 0; - int max_idx = 0; - for (int i = 1; i < 4; ++i) - { - if (asic_latency[i] < asic_latency[min_idx]) min_idx = i; - if (asic_latency[i] > asic_latency[max_idx]) max_idx = i; - } - - const uint8_t pattern[3] = { ROR, MUL, MUL }; - const uint8_t opcode = pattern[(code_size - prev_code_size) % 3]; - latency[min_idx] = latency[max_idx] + op_latency[opcode]; - asic_latency[min_idx] = asic_latency[max_idx] + asic_op_latency[opcode]; - - code[code_size].opcode = opcode; - code[code_size].dst_index = min_idx; - code[code_size].src_index = max_idx; - code[code_size].C = 0; - ++code_size; - } - - // There is ~98.15% chance that loop condition is false, so this loop will execute only 1 iteration most of the time - // It never does more than 4 iterations for all block heights < 10,000,000 - } while (!r8_used || (code_size < NUM_INSTRUCTIONS_MIN) || (code_size > NUM_INSTRUCTIONS_MAX)); - - // It's guaranteed that NUM_INSTRUCTIONS_MIN <= code_size <= NUM_INSTRUCTIONS_MAX here - // Add final instruction to stop the interpreter - code[code_size].opcode = RET; - code[code_size].dst_index = 0; - code[code_size].src_index = 0; - code[code_size].C = 0; - - return code_size; -} - -#endif diff --git a/cryptonight/c/warnings.h b/cryptonight/c/warnings.h deleted file mode 100644 index df5c7d1..0000000 --- a/cryptonight/c/warnings.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#if defined(_MSC_VER) - -#define PUSH_WARNINGS __pragma(warning(push)) -#define POP_WARNINGS __pragma(warning(pop)) -#define DISABLE_VS_WARNINGS(w) __pragma(warning(disable: w)) -#define DISABLE_GCC_WARNING(w) -#define DISABLE_CLANG_WARNING(w) -#define DISABLE_GCC_AND_CLANG_WARNING(w) - -#else - -#include - -#define PUSH_WARNINGS _Pragma("GCC diagnostic push") -#define POP_WARNINGS _Pragma("GCC diagnostic pop") -#define DISABLE_VS_WARNINGS(w) - -#if defined(__clang__) -#define DISABLE_GCC_WARNING(w) -#define DISABLE_CLANG_WARNING DISABLE_GCC_AND_CLANG_WARNING -#else -#define DISABLE_GCC_WARNING DISABLE_GCC_AND_CLANG_WARNING -#define DISABLE_CLANG_WARNING(w) -#endif - -#define DISABLE_GCC_AND_CLANG_WARNING(w) _Pragma(BOOST_PP_STRINGIZE(GCC diagnostic ignored BOOST_PP_STRINGIZE(-W##w))) - -#endif diff --git a/cryptonight/src/blake256.rs b/cryptonight/src/blake256.rs new file mode 100644 index 0000000..4098848 --- /dev/null +++ b/cryptonight/src/blake256.rs @@ -0,0 +1,589 @@ +//! This module implements the original BLAKE-256 algorithm. +//! +//! The code below is ported from these C files omitting BLAKE-244 and the +//! HMAC methods that are not used by Monero. +//! * +//! * +//! +//! Note: The Rust Crypto project only provides the newer BLAKE2 variants. There +//! is a blake crate, but it is using C wrappers. + +use crate::util::{subarray, subarray_copy}; + +pub(crate) trait Digest { + fn new() -> Self; + + /// Process data, updating the internal state. + fn update(&mut self, data: impl AsRef<[u8]>); + + /// Retrieve result and consume hasher instance. + fn finalize(self) -> [u8; 32]; + + /// Compute hash of `data`. + fn digest(data: impl AsRef<[u8]>) -> [u8; 32]; +} + +pub(crate) struct Blake256 { + h: [u32; 8], + s: [u32; 4], + t: [u32; 2], + buflen: usize, + nullt: bool, + buf: [u8; 64], +} + +const SIGMA: [[u8; 16]; 14] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], +]; + +#[rustfmt::skip] +const CST: [u32; 16] = [ + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, + 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, + 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, +]; + +#[rustfmt::skip] +const PADDING: [u8; 64] = [ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +impl Digest for Blake256 { + fn new() -> Self { + Self { + #[rustfmt::skip] + h: [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, + ], + s: [0, 0, 0, 0], + t: [0, 0], + buflen: 0, + nullt: false, + buf: [0; 64], + } + } + + fn update(&mut self, data: impl AsRef<[u8]>) { + let data = data.as_ref(); + let mut datalenbits = data.len() * 8; + let mut data = data; + let mut left = self.buflen >> 3; + let fill = 64 - left; + + if left != 0 && (datalenbits >> 3) >= fill { + self.buf[left..(left + fill)].copy_from_slice(&data[..fill]); + self.t[0] = self.t[0].wrapping_add(512); + if self.t[0] == 0 { + self.t[1] = self.t[1].wrapping_add(1); + } + self.compress(&self.buf.clone()); + data = &data[fill..]; + datalenbits -= fill << 3; + left = 0; + } + + while datalenbits >= 512 { + self.t[0] = self.t[0].wrapping_add(512); + if self.t[0] == 0 { + self.t[1] = self.t[1].wrapping_add(1); + } + self.compress(subarray(data, 0)); + data = &data[64..]; + datalenbits -= 512; + } + + if datalenbits > 0 { + self.buf[left..left + (datalenbits >> 3)].copy_from_slice(&data[..(datalenbits >> 3)]); + self.buflen = (left << 3) + datalenbits; + } else { + self.buflen = 0; + } + } + + #[expect(clippy::cast_possible_truncation)] + fn finalize(mut self) -> [u8; 32] { + const PA: &[u8; 1] = &[0x81]; + const PB: &[u8; 1] = &[0x01]; + + assert!(u32::try_from(self.buflen).is_ok()); + + let mut msglen = [0_u8; 8]; + let lo = self.t[0].wrapping_add(self.buflen as u32); + let mut hi = self.t[1]; + if lo < self.buflen as u32 { + hi = hi.wrapping_add(1); + } + msglen[0..4].copy_from_slice(&hi.to_be_bytes()); + msglen[4..8].copy_from_slice(&lo.to_be_bytes()); + + if self.buflen == 440 { + self.t[0] = self.t[0].wrapping_sub(8); + self.update(PA); + } else { + if self.buflen < 440 { + if self.buflen == 0 { + self.nullt = true; + } + self.t[0] = self.t[0].wrapping_sub(440_u32.wrapping_sub(self.buflen as u32)); + self.update(&PADDING[..(440 - self.buflen) / 8]); + } else { + self.t[0] = self.t[0].wrapping_sub(512_u32.wrapping_sub(self.buflen as u32)); + self.update(&PADDING[..(512 - self.buflen) / 8]); + self.t[0] = self.t[0].wrapping_sub(440); + self.update(&PADDING[1..=(440 / 8)]); + self.nullt = true; + } + self.update(PB); + self.t[0] = self.t[0].wrapping_sub(8); + } + self.t[0] = self.t[0].wrapping_sub(64); + self.update(msglen); + + let mut digest = [0_u8; 32]; + for (i, chunk) in digest.chunks_mut(4).enumerate() { + chunk.copy_from_slice(&self.h[i].to_be_bytes()); + } + digest + } + + fn digest(data: impl AsRef<[u8]>) -> [u8; 32] { + let mut state = Self::new(); + state.update(data.as_ref()); + state.finalize() + } +} + +impl Blake256 { + fn compress(&mut self, block: &[u8; 64]) { + let mut v = [0_u32; 16]; + let mut m = [0_u32; 16]; + + for (i, m_i) in m.iter_mut().enumerate() { + *m_i = u32::from_be_bytes(subarray_copy(block, i * 4)); + } + + v[0..8].copy_from_slice(&self.h); + + v[8] = self.s[0] ^ 0x243F6A88; + v[9] = self.s[1] ^ 0x85A308D3; + v[10] = self.s[2] ^ 0x13198A2E; + v[11] = self.s[3] ^ 0x03707344; + v[12] = 0xA4093822; + v[13] = 0x299F31D0; + v[14] = 0x082EFA98; + v[15] = 0xEC4E6C89; + + if !self.nullt { + v[12] ^= self.t[0]; + v[13] ^= self.t[0]; + v[14] ^= self.t[1]; + v[15] ^= self.t[1]; + } + + let mut g = |i: usize, a: usize, b: usize, c: usize, d: usize, e: usize| { + v[a] = v[a] + .wrapping_add(m[SIGMA[i][e] as usize] ^ CST[SIGMA[i][e + 1] as usize]) + .wrapping_add(v[b]); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(12); + v[a] = v[a] + .wrapping_add(m[SIGMA[i][e + 1] as usize] ^ CST[SIGMA[i][e] as usize]) + .wrapping_add(v[b]); + v[d] = (v[d] ^ v[a]).rotate_right(8); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(7); + }; + + for i in 0..14 { + g(i, 0, 4, 8, 12, 0); + g(i, 1, 5, 9, 13, 2); + g(i, 2, 6, 10, 14, 4); + g(i, 3, 7, 11, 15, 6); + g(i, 3, 4, 9, 14, 14); + g(i, 2, 7, 8, 13, 12); + g(i, 0, 5, 10, 15, 8); + g(i, 1, 6, 11, 12, 10); + } + + #[expect(clippy::needless_range_loop)] + for i in 0..16 { + self.h[i % 8] ^= v[i]; + } + + for i in 0..8 { + self.h[i] ^= self.s[i % 4]; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct Blake256Test { + expected_digest: &'static str, + input: &'static str, + } + + const fn tc(expected_output: &'static str, input: &'static str) -> Blake256Test { + Blake256Test { + expected_digest: expected_output, + input, + } + } + + // Test vectors are from: + // https://github.com/monero-project/monero/blob/v0.18.3.4/tests/hash/tests-extra-blake.txt + const TESTS: [Blake256Test; 321] = [ + tc("716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", ""), + tc("e104256a2bc501f459d03fac96b9014f593e22d30f4de525fa680c3aa189eb4f", "cc"), + tc("8f341148be7e354fdf38b693d8c6b4e0bd57301a734f6fd35cd85b8491c3ddcd", "41fb"), + tc("bc334d1069099f10c601883ac6f3e7e9787c6aa53171f76a21923cc5ad3ab937", "1f877c"), + tc("b672a16f53982bab1e77685b71c0a5f6703ffd46a1c834be69f614bd128d658e", "c1ecfdfc"), + tc("d9134b2899057a7d8d320cc99e3e116982bc99d3c69d260a7f1ed3da8be68d99", "21f134ac57"), + tc("637923bd29a35aa3ecbbd2a50549fc32c14cf0fdcaf41c3194dd7414fd224815", "c6f50bb74e29"), + tc("70c092fd5c8c21e9ef4bbc82a5c7819e262a530a748caf285ff0cba891954f1e", "119713cc83eeef"), + tc("fdf092993edbb7a0dc7ca67f04051bbd14481639da0808947aff8bfab5abed4b", "4a4f202484512526"), + tc("6f6fc234bf35beae1a366c44c520c59ad5aa70351b5f5085e21e1fe2bfcee709", "1f66ab4185ed9b6375"), + tc("4fdaf89e2a0e78c000061b59455e0ea93a4445b440e7562c8f0cfa165c93de2e", "eed7422227613b6f53c9"), + tc("d6b780eee9c811f664393dc2c58b5a68c92b3c9fe9ceb70371d33ece63b5787e", "eaeed5cdffd89dece455f1"), + tc("d0015071d3e7ed048c764850d76406eceae52b8e2e6e5a2c3aa92ae880485b34", "5be43c90f22902e4fe8ed2d3"), + tc("9b0207902f9932f7a85c24722e93e31f6ed2c75c406509aa0f2f6d1cab046ce4", "a746273228122f381c3b46e4f1"), + tc("258020d5b04a814f2b72c1c661e1f5a5c395d9799e5eee8b8519cf7300e90cb1", "3c5871cd619c69a63b540eb5a625"), + tc("4adae3b55baa907fefc253365fdd99d8398befd0551ed6bf9a2a2784d3c304d1", "fa22874bcc068879e8ef11a69f0722"), + tc("6dd10d772f8d5b4a96c3c5d30878cd9a1073fa835bfe6d2b924fa64a1fab1711", "52a608ab21ccdd8a4457a57ede782176"), + tc("0b8741ddf2259d3af2901eb1ae354f22836442c965556f5c1eb89501191cb46a", "82e192e4043ddcd12ecf52969d0f807eed"), + tc("f48a754ca8193a82643150ab94038b5dd170b4ebd1e0751b78cfb0a98fa5076a", "75683dcb556140c522543bb6e9098b21a21e"), + tc("5698409ab856b74d9fa5e9b259dfa46001f89041752da424e56e491577b88c86", "06e4efe45035e61faaf4287b4d8d1f12ca97e5"), + tc("31d27842634441f452ef6f7319e43c864f9543f04c8dee0bdf02632d20afc2a6", "e26193989d06568fe688e75540aea06747d9f851"), + tc("924c3797a6d97bc2dcbe905e3922b12f4c97bfd1390056678918284da530b37f", "d8dc8fdefbdce9d44e4cbafe78447bae3b5436102a"), + tc("3e5dc9922f82da4b51d2bb202962977fc17546b901335c7ef4e074e8e6b6fadb", "57085fd7e14216ab102d8317b0cb338a786d5fc32d8f"), + tc("efdce9ccc566aa560d77a0c6703694a1136af9b119add173d4923cefc5f4feb1", "a05404df5dbb57697e2c16fa29defac8ab3560d6126fa0"), + tc("bec6b97cc2a873239da37eae2d50a4e6a29de228e68c40d8ad5f5fe0dd139fa2", "aecbb02759f7433d6fcb06963c74061cd83b5b3ffa6f13c6"), + tc("7d10e374784c9275627a6d3401b75216af415a228b40e2c36b113777bb90831c", "aafdc9243d3d4a096558a360cc27c8d862f0be73db5e88aa55"), + tc("8db764ebced55e0fff19a82119c5a3d47a0fb19fc7e1a021d0d0425f5db8b193", "7bc84867f6f9e9fdc3e1046cae3a52c77ed485860ee260e30b15"), + tc("9149f241af9b134bb2fb970bd4586b0ff59de17202706b470d58ca7a4f86e0c2", "fac523575a99ec48279a7a459e98ff901918a475034327efb55843"), + tc("51331e6e7af3a193ed66a2e58e1f8610779196145622a458e57bc5f52ad78c0f", "0f8b2d8fcfd9d68cffc17ccfb117709b53d26462a3f346fb7c79b85e"), + tc("485f559159c58ccec696b756543a2f8b1c4490634b4ec31fb9b3d8913b59da95", "a963c3e895ff5a0be4824400518d81412f875fa50521e26e85eac90c04"), + tc("faf3b2da998fd51d4f6e359790c0bf533f806d39a00613662cf96ce2b09ff6ce", "03a18688b10cc0edf83adf0a84808a9718383c4070c6c4f295098699ac2c"), + tc("6796cc8fd9960890bd42a6f139992ab9bcacc5a17672648ae3e5ce600652627e", "84fb51b517df6c5accb5d022f8f28da09b10232d42320ffc32dbecc3835b29"), + tc("bb603441f9cc752e780ebe371a02ec39bd810ffc7797dd49be728eb1fd50b384", "9f2fcc7c90de090d6b87cd7e9718c1ea6cb21118fc2d5de9f97e5db6ac1e9c10"), + tc("fede0ad668c6bb8d2d65cd65ef6ff6e1a2fa03b98e46e642e1ea1f23f356e330", "de8f1b3faa4b7040ed4563c3b8e598253178e87e4d0df75e4ff2f2dedd5a0be046"), + tc("ee9ade0ce8d1e864451dc8a79f3b36f7259d3f025eb0f405c2da609066408aad", "62f154ec394d0bc757d045c798c8b87a00e0655d0481a7d2d9fb58d93aedc676b5a0"), + tc("9d8fed5ecc492de1bb1d0932ea41d8e6e5bd0ff4b66047fcac179df9c1acd640", "b2dcfe9ff19e2b23ce7da2a4207d3e5ec7c6112a8a22aec9675a886378e14e5bfbad4e"), + tc("759a023d09a25e5f88770190df7dfad00cd34c3c236ff40bf4d95c0f81193851", "47f5697ac8c31409c0868827347a613a3562041c633cf1f1f86865a576e02835ed2c2492"), + tc("47ef7975922884f5fa0567c4306984a92cb2e012565eeb3388994c5c62364e4d", "512a6d292e67ecb2fe486bfe92660953a75484ff4c4f2eca2b0af0edcdd4339c6b2ee4e542"), + tc("73495ae2b0ab412f9f24c73613fb09ff676b9c2a39f2aa5c36f11154320a78bf", "973cf2b4dcf0bfa872b41194cb05bb4e16760a1840d8343301802576197ec19e2a1493d8f4fb"), + tc("fd40278998e7925a5beb6fef313edde96117af5249f54cad7659a3c13e82b714", "80beebcd2e3f8a9451d4499961c9731ae667cdc24ea020ce3b9aa4bbc0a7f79e30a934467da4b0"), + tc("6ee3915a5d8fef80b03e4e3dab9072e4143330675525988450c54554d5e61c94", "7abaa12ec2a7347674e444140ae0fb659d08e1c66decd8d6eae925fa451d65f3c0308e29446b8ed3"), + tc("0c382ff250e6dee2569e5250a1f480b6accd0238f8d3840d9958fbd4d1160d8f", "c88dee9927679b8af422abcbacf283b904ff31e1cac58c7819809f65d5807d46723b20f67ba610c2b7"), + tc("d1efe8700d3d47a80a422fe86540a89f0e103fdead8848f616cfd3b939c29dd3", "01e43fe350fcec450ec9b102053e6b5d56e09896e0ddd9074fe138e6038210270c834ce6eadc2bb86bf6"), + tc("b14bb4c03111e62ee6751d1c35498835df566fddc5ed5b70d2849b453d436b64", "337023370a48b62ee43546f17c4ef2bf8d7ecd1d49f90bab604b839c2e6e5bd21540d29ba27ab8e309a4b7"), + tc("75d95470d72557d86cad03967552b34d925f6e5e9be7e887b57d6d444ec93d70", "6892540f964c8c74bd2db02c0ad884510cb38afd4438af31fc912756f3efec6b32b58ebc38fc2a6b913596a8"), + tc("0d7c8efbee6b3fd4903e60c4ab19602c50b5020990ea248d98f5ad735103c541", "f5961dfd2b1ffffda4ffbf30560c165bfedab8ce0be525845deb8dc61004b7db38467205f5dcfb34a2acfe96c0"), + tc("473bb75be6e05c0b8155c3c6777b34497037a14b24c76ab2e1b1c1e9e4d468ce", "ca061a2eb6ceed8881ce2057172d869d73a1951e63d57261384b80ceb5451e77b06cf0f5a0ea15ca907ee1c27eba"), + tc("9fd55a16ed058649ecb835bed965f32fc9ed16c4a1c1bafd3f1bebc258ff9fc1", "1743a77251d69242750c4f1140532cd3c33f9b5ccdf7514e8584d4a5f9fbd730bcf84d0d4726364b9bf95ab251d9bb"), + tc("9d2b64fe62ad33552545a30c450cbe451c332d4bf6a859f24413782abd1258f1", "d8faba1f5194c4db5f176fabfff856924ef627a37cd08cf55608bba8f1e324d7c7f157298eabc4dce7d89ce5162499f9"), + tc("b38fe07d9ab29794659b678ee6ec8501f23b4e7b5b81dff263429b0e70f1cd29", "be9684be70340860373c9c482ba517e899fc81baaa12e5c6d7727975d1d41ba8bef788cdb5cf4606c9c1c7f61aed59f97d"), + tc("b1457e6086ccc67cfb332325e3a3f4118ded982317798d4410c789b3b770baae", "7e15d2b9ea74ca60f66c8dfab377d9198b7b16deb6a1ba0ea3c7ee2042f89d3786e779cf053c77785aa9e692f821f14a7f51"), + tc("866f44fbfba3978c7a161c11623c5197d3781987b4566b709cc00f11fde4f559", "9a219be43713bd578015e9fda66c0f2d83cac563b776ab9f38f3e4f7ef229cb443304fba401efb2bdbd7ece939102298651c86"), + tc("abd55d3989f3fcd0d0b039fa5a1c8d03df1d65fdc049ac881c008d09235dba00", "c8f2b693bd0d75ef99caebdc22adf4088a95a3542f637203e283bbc3268780e787d68d28cc3897452f6a22aa8573ccebf245972a"), + tc("30c4f40c042a391b8c55fdbf5b2e76bc10df15d053748f39a2719601f99893c4", "ec0f99711016c6a2a07ad80d16427506ce6f441059fd269442baaa28c6ca037b22eeac49d5d894c0bf66219f2c08e9d0e8ab21de52"), + tc("7670067cdbf1868e8bb82c9c73ee6cf39d3e6b2215fe481806e8234babc94c27", "0dc45181337ca32a8222fe7a3bf42fc9f89744259cff653504d6051fe84b1a7ffd20cb47d4696ce212a686bb9be9a8ab1c697b6d6a33"), + tc("ad373db6defaefbeeff69e78e220a4ca9ef510ad5f85f0c698a749e0e6dcaeb5", "de286ba4206e8b005714f80fb1cdfaebde91d29f84603e4a3ebc04686f99a46c9e880b96c574825582e8812a26e5a857ffc6579f63742f"), + tc("cad656a57a59591a03e7b540a1e5a76a7645a60228e1057ea39b34f6b691510d", "eebcc18057252cbf3f9c070f1a73213356d5d4bc19ac2a411ec8cdeee7a571e2e20eaf61fd0c33a0ffeb297ddb77a97f0a415347db66bcaf"), + tc("526fb1365175af61f6d33ee66c51f9eb67658742bd5ad827e3b3f12f535b8a3f", "416b5cdc9fe951bd361bd7abfc120a5054758eba88fdd68fd84e39d3b09ac25497d36b43cbe7b85a6a3cebda8db4e5549c3ee51bb6fcb6ac1e"), + tc("f93ea4fbe593e41d458148486363b9436b0700c0407c05eb88403632bab1efa5", "5c5faf66f32e0f8311c32e8da8284a4ed60891a5a7e50fb2956b3cbaa79fc66ca376460e100415401fc2b8518c64502f187ea14bfc9503759705"), + tc("ff39988c582120d695b00606757074841a75a3ed0f2cc7321858c2ff09085003", "7167e1e02be1a7ca69d788666f823ae4eef39271f3c26a5cf7cee05bca83161066dc2e217b330df821103799df6d74810eed363adc4ab99f36046a"), + tc("94ba07439ed47e21b91ec7709c5605b116ef8caba952bdd27b1a9a0ca59aec4c", "2fda311dbba27321c5329510fae6948f03210b76d43e7448d1689a063877b6d14c4f6d0eaa96c150051371f7dd8a4119f7da5c483cc3e6723c01fb7d"), + tc("f0c7105f3b62d5fb78e82c1761809e9be1f44914f43c5bd96e7972ea6c3b7663", "95d1474a5aab5d2422aca6e481187833a6212bd2d0f91451a67dd786dfc91dfed51b35f47e1deb8a8ab4b9cb67b70179cc26f553ae7b569969ce151b8d"), + tc("34ff717bd5fdc5618ffc8da403a2f2359ecec9078f8c234f275d8cfaa39cc1d6", "c71bd7941f41df044a2927a8ff55b4b467c33d089f0988aa253d294addbdb32530c0d4208b10d9959823f0c0f0734684006df79f7099870f6bf53211a88d"), + tc("a17d2cdd3e3fd0aa2790ee8c3802606b46ae91b66db66684a56768fe52fb9760", "f57c64006d9ea761892e145c99df1b24640883da79d9ed5262859dcda8c3c32e05b03d984f1ab4a230242ab6b78d368dc5aaa1e6d3498d53371e84b0c1d4ba"), + tc("f76305a1bb52917ce0882111f20f57eb51ee545cf064ce331a61d35ef17272c3", "e926ae8b0af6e53176dbffcc2a6b88c6bd765f939d3d178a9bde9ef3aa131c61e31c1e42cdfaf4b4dcde579a37e150efbef5555b4c1cb40439d835a724e2fae7"), + tc("d184236acc1b11b65b13752dd97928519a8504391fd4c8389d3bec3583ece2ef", "16e8b3d8f988e9bb04de9c96f2627811c973ce4a5296b4772ca3eefeb80a652bdf21f50df79f32db23f9f73d393b2d57d9a0297f7a2f2e79cfda39fa393df1ac00"), + tc("29b27ed19703a7d94f3d4262db1970d53d752a2e83ab494fcb8077aa7edbe2f3", "fc424eeb27c18a11c01f39c555d8b78a805b88dba1dc2a42ed5e2c0ec737ff68b2456d80eb85e11714fa3f8eabfb906d3c17964cb4f5e76b29c1765db03d91be37fc"), + tc("277bea584fc97d2eb1bc7ae14c0eafacb5baf03d7d865fcce5b9ef40908a6279", "abe3472b54e72734bdba7d9158736464251c4f21b33fbbc92d7fac9a35c4e3322ff01d2380cbaa4ef8fb07d21a2128b7b9f5b6d9f34e13f39c7ffc2e72e47888599ba5"), + tc("6e7a7bf2379339c004c0acdaa52aa71aea7496eb3e931b9d658427eb22767d77", "36f9f0a65f2ca498d739b944d6eff3da5ebba57e7d9c41598a2b0e4380f3cf4b479ec2348d015ffe6256273511154afcf3b4b4bf09d6c4744fdd0f62d75079d440706b05"), + tc("ec6854b56c285e8b9a8a37aca30581d52e839c569a328f63b7aa07810f49cd68", "abc87763cae1ca98bd8c5b82caba54ac83286f87e9610128ae4de68ac95df5e329c360717bd349f26b872528492ca7c94c2c1e1ef56b74dbb65c2ac351981fdb31d06c77a4"), + tc("c1ddb90c5eab10a482fd25c9575505bd4f9ab3d991b026c8de55d314022eedb5", "94f7ca8e1a54234c6d53cc734bb3d3150c8ba8c5f880eab8d25fed13793a9701ebe320509286fd8e422e931d99c98da4df7e70ae447bab8cffd92382d8a77760a259fc4fbd72"), + tc("975a4986c2af8cc6a1f807dbefa5b15ed433831e7d0d9b00c2628497ababcb6e", "13bd2811f6ed2b6f04ff3895aceed7bef8dcd45eb121791bc194a0f806206bffc3b9281c2b308b1a729ce008119dd3066e9378acdcc50a98a82e20738800b6cddbe5fe9694ad6d"), + tc("ce7e64a7fea919ed5361e92e44579fadadbb95cbd10f87b538cd02f5a7468c00", "1eed9cba179a009ec2ec5508773dd305477ca117e6d569e66b5f64c6bc64801ce25a8424ce4a26d575b8a6fb10ead3fd1992edddeec2ebe7150dc98f63adc3237ef57b91397aa8a7"), + tc("007a1e2c31fbc056a9e7a994c8eebb9b01824506687f4b382064b10680f65886", "ba5b67b5ec3a3ffae2c19dd8176a2ef75c0cd903725d45c9cb7009a900c0b0ca7a2967a95ae68269a6dbf8466c7b6844a1d608ac661f7eff00538e323db5f2c644b78b2d48de1a08aa"), + tc("0289b40b14608a7ff9901b40d9ab66e7f434c42d3b7f4a6ed833903963493a29", "0efa26ac5673167dcacab860932ed612f65ff49b80fa9ae65465e5542cb62075df1c5ae54fba4db807be25b070033efa223bdd5b1d3c94c6e1909c02b620d4b1b3a6c9fed24d70749604"), + tc("0d363cc0d3a7653228ccced5b7c1e0ed58d24e386bc18b578f6cf50e8289befb", "bbfd933d1fd7bf594ac7f435277dc17d8d5a5b8e4d13d96d2f64e771abbd51a5a8aea741beccbddb177bcea05243ebd003cfdeae877cca4da94605b67691919d8b033f77d384ca01593c1b"), + tc("6543286a4799a9f74a948a740fecfab6898517979bf3b77d2488cd037ea0e4a1", "90078999fd3c35b8afbf4066cbde335891365f0fc75c1286cdd88fa51fab94f9b8def7c9ac582a5dbcd95817afb7d1b48f63704e19c2baa4df347f48d4a6d603013c23f1e9611d595ebac37c"), + tc("bd291ad3741aa3c69cc869a1eb81da30c98f5de0452734d00457cc979d86d5ee", "64105eca863515c20e7cfbaa0a0b8809046164f374d691cdbd6508aaabc1819f9ac84b52bafc1b0fe7cddbc554b608c01c8904c669d8db316a0953a4c68ece324ec5a49ffdb59a1bd6a292aa0e"), + tc("4e3dba974a3cdd4995c256a34ae01063c716e760d3602e785170f02127a72f85", "d4654be288b9f3b711c2d02015978a8cc57471d5680a092aa534f7372c71ceaab725a383c4fcf4d8deaa57fca3ce056f312961eccf9b86f14981ba5bed6ab5b4498e1f6c82c6cae6fc14845b3c8a"), + tc("6d8b3ade529979111f8099a9fd1f0cd7fda3dd258a400b29846db8f5387de15e", "12d9394888305ac96e65f2bf0e1b18c29c90fe9d714dd59f651f52b88b3008c588435548066ea2fc4c101118c91f32556224a540de6efddbca296ef1fb00341f5b01fecfc146bdb251b3bdad556cd2"), + tc("917cf68371edab397f933efd7916f0fa18792cbbec331070259546c440de7daf", "871a0d7a5f36c3da1dfce57acd8ab8487c274fad336bc137ebd6ff4658b547c1dcfab65f037aa58f35ef16aff4abe77ba61f65826f7be681b5b6d5a1ea8085e2ae9cd5cf0991878a311b549a6d6af230"), + tc("73277049d2dcc2487a5fa342514de70032de1683d2f0e2f0253ce196f1fbf693", "e90b4ffef4d457bc7711ff4aa72231ca25af6b2e206f8bf859d8758b89a7cd36105db2538d06da83bad5f663ba11a5f6f61f236fd5f8d53c5e89f183a3cec615b50c7c681e773d109ff7491b5cc22296c5"), + tc("e6024aef25ca0a496e6f8fbd030dccb5b22306f3d71e37e51053156a880ec73c", "e728de62d75856500c4c77a428612cd804f30c3f10d36fb219c5ca0aa30726ab190e5f3f279e0733d77e7267c17be27d21650a9a4d1e32f649627638dbada9702c7ca303269ed14014b2f3cf8b894eac8554"), + tc("44405d0508518b43b3c33f2d8e526f0064ee26cc5572584e49e664c2846783b5", "6348f229e7b1df3b770c77544e5166e081850fa1c6c88169db74c76e42eb983facb276ad6a0d1fa7b50d3e3b6fcd799ec97470920a7abed47d288ff883e24ca21c7f8016b93bb9b9e078bdb9703d2b781b616e"), + tc("039eed9e12e0b695cc70ba4fbbc8911a86c174d9165e12b8aa944d657e7aa65f", "4b127fde5de733a1680c2790363627e63ac8a3f1b4707d982caea258655d9bf18f89afe54127482ba01e08845594b671306a025c9a5c5b6f93b0a39522dc877437be5c2436cbf300ce7ab6747934fcfc30aeaaf6"), + tc("6d40d076120ae4a5a5301fbc2fc5764f83fcfcfbb608738527b769108a33bb41", "08461f006cff4cc64b752c957287e5a0faabc05c9bff89d23fd902d324c79903b48fcb8f8f4b01f3e4ddb483593d25f000386698f5ade7faade9615fdc50d32785ea51d49894e45baa3dc707e224688c6408b68b11"), + tc("f2490ed06e97f50bb209a748fa505982897f95814465e41dfa9daffd2f9bea32", "68c8f8849b120e6e0c9969a5866af591a829b92f33cd9a4a3196957a148c49138e1e2f5c7619a6d5edebe995acd81ec8bb9c7b9cfca678d081ea9e25a75d39db04e18d475920ce828b94e72241f24db72546b352a0e4"), + tc("498f1736592b047b26080b6e1dc4e686cef4dce2929da83a8d140963cbb31068", "b8d56472954e31fb54e28fca743f84d8dc34891cb564c64b08f7b71636debd64ca1edbdba7fc5c3e40049ce982bba8c7e0703034e331384695e9de76b5104f2fbc4535ecbeebc33bc27f29f18f6f27e8023b0fbb6f563c"), + tc("689723c496746815869c2b8f2771db492a60edfaacce29ded86e2b4d11ca5118", "0d58ac665fa84342e60cefee31b1a4eacdb092f122dfc68309077aed1f3e528f578859ee9e4cefb4a728e946324927b675cd4f4ac84f64db3dacfe850c1dd18744c74ceccd9fe4dc214085108f404eab6d8f452b5442a47d"), + tc("bf958c62a4bdb72e1fed5cd0d68bed4b569557263183a622478d96c618f63f6a", "1755e2d2e5d1c1b0156456b539753ff416651d44698e87002dcf61dcfa2b4e72f264d9ad591df1fdee7b41b2eb00283c5aebb3411323b672eaa145c5125185104f20f335804b02325b6dea65603f349f4d5d8b782dd3469ccd"), + tc("5367368481ac046bf983f499552b0ca87b226350bd4cdf5023def4c6c7c7beb9", "b180de1a611111ee7584ba2c4b020598cd574ac77e404e853d15a101c6f5a2e5c801d7d85dc95286a1804c870bb9f00fd4dcb03aa8328275158819dcad7253f3e3d237aeaa7979268a5db1c6ce08a9ec7c2579783c8afc1f91a7"), + tc("c9019a4a6f1da3d76ef4008f08dd7bb4e01680142dc6789ede0cc735c02f7b86", "cf3583cbdfd4cbc17063b1e7d90b02f0e6e2ee05f99d77e24e560392535e47e05077157f96813544a17046914f9efb64762a23cf7a49fe52a0a4c01c630cfe8727b81fb99a89ff7cc11dca5173057e0417b8fe7a9efba6d95c555f"), + tc("d8c82b3fa8f39d9f70e5149240a9b03272926621ab306af634e983bf0913cb3e", "072fc02340ef99115bad72f92c01e4c093b9599f6cfc45cb380ee686cb5eb019e806ab9bd55e634ab10aa62a9510cc0672cd3eddb589c7df2b67fcd3329f61b1a4441eca87a33c8f55da4fbbad5cf2b2527b8e983bb31a2fadec7523"), + tc("714fac886919af28a9fa1be1eb1fdb19673814a676d739ebc3a141a48545f504", "76eecf956a52649f877528146de33df249cd800e21830f65e90f0f25ca9d6540fde40603230eca6760f1139c7f268deba2060631eea92b1fff05f93fd5572fbe29579ecd48bc3a8d6c2eb4a6b26e38d6c5fbf2c08044aeea470a8f2f26"), + tc("a5be5b53c877628fd0210e38cfc45cdb5612395ce320aef5be28a742a5d54c99", "7adc0b6693e61c269f278e6944a5a2d8300981e40022f839ac644387bfac9086650085c2cdc585fea47b9d2e52d65a2b29a7dc370401ef5d60dd0d21f9e2b90fae919319b14b8c5565b0423cefb827d5f1203302a9d01523498a4db10374"), + tc("9427036469f4375d424a00cadff752517fec80a690a114f6118aca6be5ae9127", "e1fffa9826cce8b86bccefb8794e48c46cdf372013f782eced1e378269b7be2b7bf51374092261ae120e822be685f2e7a83664bcfbe38fe8633f24e633ffe1988e1bc5acf59a587079a57a910bda60060e85b5f5b6f776f0529639d9cce4bd"), + tc("66ca51e2d44ab7b53d415345781c6f1205823c1e71d9b4cee074b75a8728977f", "69f9abba65592ee01db4dce52dbab90b08fc04193602792ee4daa263033d59081587b09bbe49d0b49c9825d22840b2ff5d9c5155f975f8f2c2e7a90c75d2e4a8040fe39f63bbafb403d9e28cc3b86e04e394a9c9e8065bd3c85fa9f0c7891600"), + tc("67d1761ff1de244dad4cc6fc972f9f1c4451fa91cb630e2a1008202e64cdda47", "38a10a352ca5aedfa8e19c64787d8e9c3a75dbf3b8674bfab29b5dbfc15a63d10fae66cd1a6e6d2452d557967eaad89a4c98449787b0b3164ca5b717a93f24eb0b506ceb70cbbcb8d72b2a72993f909aad92f044e0b5a2c9ac9cb16a0ca2f81f49"), + tc("12d264488aab5f92a89c647c4716b44d0b54bd1c59caa4c63086214419ba3e29", "6d8c6e449bc13634f115749c248c17cd148b72157a2c37bf8969ea83b4d6ba8c0ee2711c28ee11495f43049596520ce436004b026b6c1f7292b9c436b055cbb72d530d860d1276a1502a5140e3c3f54a93663e4d20edec32d284e25564f624955b52"), + tc("2e5df5fd1c5e6d63a478b36a3571a2ddc095e1ffd93056682bf1aaab830582e7", "6efcbcaf451c129dbe00b9cef0c3749d3ee9d41c7bd500ade40cdc65dedbbbadb885a5b14b32a0c0d087825201e303288a733842fa7e599c0c514e078f05c821c7a4498b01c40032e9f1872a1c925fa17ce253e8935e4c3c71282242cb716b2089ccc1"), + tc("1195a9f43d5528e2bcb43d8443e7b18c05f7f80946b61e2ab3d9eb15c8fff7a4", "433c5303131624c0021d868a30825475e8d0bd3052a022180398f4ca4423b98214b6beaac21c8807a2c33f8c93bd42b092cc1b06cedf3224d5ed1ec29784444f22e08a55aa58542b524b02cd3d5d5f6907afe71c5d7462224a3f9d9e53e7e0846dcbb4ce"), + tc("ad30542976e20a93bf2a2e2b341f04253cdc95826a629082fd668580777e488b", "a873e0c67ca639026b6683008f7aa6324d4979550e9bce064ca1e1fb97a30b147a24f3f666c0a72d71348ede701cf2d17e2253c34d1ec3b647dbcef2f879f4eb881c4830b791378c901eb725ea5c172316c6d606e0af7df4df7f76e490cd30b2badf45685f"), + tc("79676a020a388a0da82bf48ed3e92c5abc6e34206ab3a1b3038344ae663f2fb2", "006917b64f9dcdf1d2d87c8a6173b64f6587168e80faa80f82d84f60301e561e312d9fbce62f39a6fb476e01e925f26bcc91de621449be6504c504830aae394096c8fc7694651051365d4ee9070101ec9b68086f2ea8f8ab7b811ea8ad934d5c9b62c60a4771"), + tc("3d422f89bf9114776ae98024197429535113dc4f4ea1b1ba12d6b120dd55fd29", "f13c972c52cb3cc4a4df28c97f2df11ce089b815466be88863243eb318c2adb1a417cb1041308598541720197b9b1cb5ba2318bd5574d1df2174af14884149ba9b2f446d609df240ce335599957b8ec80876d9a085ae084907bc5961b20bf5f6ca58d5dab38adb"), + tc("e6dcdb50f90e7b0fc3bde1a24857927ae57a4f730d6a30d4bb8b4525fea4cf71", "e35780eb9799ad4c77535d4ddb683cf33ef367715327cf4c4a58ed9cbdcdd486f669f80189d549a9364fa82a51a52654ec721bb3aab95dceb4a86a6afa93826db923517e928f33e3fba850d45660ef83b9876accafa2a9987a254b137c6e140a21691e1069413848"), + tc("53b93da8322604c79566119f860b0179a5684e7450aee256fc2dd7e7593dfa39", "64ec021c9585e01ffe6d31bb50d44c79b6993d72678163db474947a053674619d158016adb243f5c8d50aa92f50ab36e579ff2dabb780a2b529370daa299207cfbcdd3a9a25006d19c4f1fe33e4b1eaec315d8c6ee1e730623fd1941875b924eb57d6d0c2edc4e78d6"), + tc("84c2cc80029b26b026342c562e2d31b1ab0be57c4a159e206f41367f5eef9c27", "5954bab512cf327d66b5d9f296180080402624ad7628506b555eea8382562324cf452fba4a2130de3e165d11831a270d9cb97ce8c2d32a96f50d71600bb4ca268cf98e90d6496b0a6619a5a8c63db6d8a0634dfc6c7ec8ea9c006b6c456f1b20cd19e781af20454ac880"), + tc("ae6e977c0ba5e4dc36badceb2b0ddbbfc92383a6f0dfa8d5a8bafd08896e9141", "03d9f92b2c565709a568724a0aff90f8f347f43b02338f94a03ed32e6f33666ff5802da4c81bdce0d0e86c04afd4edc2fc8b4141c2975b6f07639b1994c973d9a9afce3d9d365862003498513bfa166d2629e314d97441667b007414e739d7febf0fe3c32c17aa188a8683"), + tc("59c7e77fcaac2dd7b931fb681de0c7abadf103da0a3956c1a1834370a34830a5", "f31e8b4f9e0621d531d22a380be5d9abd56faec53cbd39b1fab230ea67184440e5b1d15457bd25f56204fa917fa48e669016cb48c1ffc1e1e45274b3b47379e00a43843cf8601a5551411ec12503e5aac43d8676a1b2297ec7a0800dbfee04292e937f21c005f17411473041"), + tc("992a70622862446236a42e353b08503b1f62758e42a944331f4c4140d7420037", "758ea3fea738973db0b8be7e599bbef4519373d6e6dcd7195ea885fc991d896762992759c2a09002912fb08e0cb5b76f49162aeb8cf87b172cf3ad190253df612f77b1f0c532e3b5fc99c2d31f8f65011695a087a35ee4eee5e334c369d8ee5d29f695815d866da99df3f79403"), + tc("c777f7ec527e56a347841df01d3238ad592ce68805b6974056c172f19aabc8e8", "47c6e0c2b74948465921868804f0f7bd50dd323583dc784f998a93cd1ca4c6ef84d41dc81c2c40f34b5bee6a93867b3bdba0052c5f59e6f3657918c382e771d33109122cc8bb0e1e53c4e3d13b43ce44970f5e0c079d2ad7d7a3549cd75760c21bb15b447589e86e8d76b1e9ced2"), + tc("d49ea6685748992f274bb158cd4a936c479f1eba9217518068ffbbf9a44bb968", "f690a132ab46b28edfa6479283d6444e371c6459108afd9c35dbd235e0b6b6ff4c4ea58e7554bd002460433b2164ca51e868f7947d7d7a0d792e4abf0be5f450853cc40d85485b2b8857ea31b5ea6e4ccfa2f3a7ef3380066d7d8979fdac618aad3d7e886dea4f005ae4ad05e5065f"), + tc("166ed4abd425d074e8091f44ec2e9eeed471a9020adec03a4f1f003d7826f5ef", "58d6a99bc6458824b256916770a8417040721cccfd4b79eacd8b65a3767ce5ba7e74104c985ac56b8cc9aebd16febd4cda5adb130b0ff2329cc8d611eb14dac268a2f9e633c99de33997fea41c52a7c5e1317d5b5daed35eba7d5a60e45d1fa7eaabc35f5c2b0a0f2379231953322c4e"), + tc("158dbc6d91cd8403b8803a671e2d64ca2af964d5b61d20ae30b67c258b5acc0c", "befab574396d7f8b6705e2d5b58b2c1c820bb24e3f4bae3e8fbcd36dbf734ee14e5d6ab972aedd3540235466e825850ee4c512ea9795abfd33f330d9fd7f79e62bbb63a6ea85de15beaeea6f8d204a28956059e2632d11861dfb0e65bc07ac8a159388d5c3277e227286f65ff5e5b5aec1"), + tc("133c98d45490eb82d60a7bdb7981927a6e05d0ed82ec841ddef6a0a9c368637b", "8e58144fa9179d686478622ce450c748260c95d1ba43b8f9b59abeca8d93488da73463ef40198b4d16fb0b0707201347e0506ff19d01bea0f42b8af9e71a1f1bd168781069d4d338fdef00bf419fbb003031df671f4a37979564f69282de9c65407847dd0da505ab1641c02dea4f0d834986"), + tc("0b1bccebc785202d23966d8962ab8c030c62a9607267b2fcd56368ca95027105", "b55c10eae0ec684c16d13463f29291bf26c82e2fa0422a99c71db4af14dd9c7f33eda52fd73d017cc0f2dbe734d831f0d820d06d5f89dacc485739144f8cfd4799223b1aff9031a105cb6a029ba71e6e5867d85a554991c38df3c9ef8c1e1e9a7630be61caabca69280c399c1fb7a12d12aefc"), + tc("76eb772a6c20e7ee60b6c22307991f4e418c265907b46b61abbb73558777ed89", "2eeea693f585f4ed6f6f8865bbae47a6908aecd7c429e4bec4f0de1d0ca0183fa201a0cb14a529b7d7ac0e6ff6607a3243ee9fb11bcf3e2304fe75ffcddd6c5c2e2a4cd45f63c962d010645058d36571404a6d2b4f44755434d76998e83409c3205aa1615db44057db991231d2cb42624574f545"), + tc("de391381e7893d29cff28fe50bdba78d9d78daabe7bc13455e5994471bf36471", "dab11dc0b047db0420a585f56c42d93175562852428499f66a0db811fcdddab2f7cdffed1543e5fb72110b64686bc7b6887a538ad44c050f1e42631bc4ec8a9f2a047163d822a38989ee4aab01b4c1f161b062d873b1cfa388fd301514f62224157b9bef423c7783b7aac8d30d65cd1bba8d689c2d"), + tc("9191b96a3ab156ce4f1c8cdeda412f4fbb8fe82fea89e5e06b6e3cb0c31d1ba5", "42e99a2f80aee0e001279a2434f731e01d34a44b1a8101726921c0590c30f3120eb83059f325e894a5ac959dca71ce2214799916424e859d27d789437b9d27240bf8c35adbafcecc322b48aa205b293962d858652abacbd588bcf6cbc388d0993bd622f96ed54614c25b6a9aa527589eaaffcf17ddf7"), + tc("216fa0351b39850c2bd7f2b5b9671cf8e40d9ff61a8e39f12f3a6e4494d7556b", "3c9b46450c0f2cae8e3823f8bdb4277f31b744ce2eb17054bddc6dff36af7f49fb8a2320cc3bdf8e0a2ea29ad3a55de1165d219adeddb5175253e2d1489e9b6fdd02e2c3d3a4b54d60e3a47334c37913c5695378a669e9b72dec32af5434f93f46176ebf044c4784467c700470d0c0b40c8a088c815816"), + tc("97fc8bcdc75df30be496c8a19cc1374cf6a1ccb65f3a1395523aa293806a8dec", "d1e654b77cb155f5c77971a64df9e5d34c26a3cad6c7f6b300d39deb1910094691adaa095be4ba5d86690a976428635d5526f3e946f7dc3bd4dbc78999e653441187a81f9adcd5a3c5f254bc8256b0158f54673dcc1232f6e918ebfc6c51ce67eaeb042d9f57eec4bfe910e169af78b3de48d137df4f2840"), + tc("c9cdda7b2b3cc695d6d5ca1531801a4f3a23e0916b08d721e9e6fee4f7d0810a", "626f68c18a69a6590159a9c46be03d5965698f2dac3de779b878b3d9c421e0f21b955a16c715c1ec1e22ce3eb645b8b4f263f60660ea3028981eebd6c8c3a367285b691c8ee56944a7cd1217997e1d9c21620b536bdbd5de8925ff71dec6fbc06624ab6b21e329813de90d1e572dfb89a18120c3f606355d25"), + tc("d4df2dd32859879f6a5f1c431cad5b87510bd5a0532765366c945ca358b71db0", "651a6fb3c4b80c7c68c6011675e6094eb56abf5fc3057324ebc6477825061f9f27e7a94633abd1fa598a746e4a577caf524c52ec1788471f92b8c37f23795ca19d559d446cab16cbcdce90b79fa1026cee77bf4ab1b503c5b94c2256ad75b3eac6fd5dcb96aca4b03a834bfb4e9af988cecbf2ae597cb9097940"), + tc("d1f095caf1d7442428a58e5fa21478d9b94d39430562074fac9a0dff3432d39d", "8aaf072fce8a2d96bc10b3c91c809ee93072fb205ca7f10abd82ecd82cf040b1bc49ea13d1857815c0e99781de3adbb5443ce1c897e55188ceaf221aa9681638de05ae1b322938f46bce51543b57ecdb4c266272259d1798de13be90e10efec2d07484d9b21a3870e2aa9e06c21aa2d0c9cf420080a80a91dee16f"), + tc("140458c9a70ab6980f1c292a4da574dc9bce8cd5855321dc67d108e54bfa03ec", "53f918fd00b1701bd504f8cdea803acca21ac18c564ab90c2a17da592c7d69688f6580575395551e8cd33e0fef08ca6ed4588d4d140b3e44c032355df1c531564d7f4835753344345a6781e11cd5e095b73df5f82c8ae3ad00877936896671e947cc52e2b29dcd463d90a0c9929128da222b5a211450bbc0e02448e2"), + tc("a5e935286f44db39a0b08726238ae3ee89038064eb4ba311f3954707613c6186", "a64599b8a61b5ccec9e67aed69447459c8da3d1ec6c7c7c82a7428b9b584fa67e90f68e2c00fbbed4613666e5168da4a16f395f7a3c3832b3b134bfc9cbaa95d2a0fe252f44ac6681eb6d40ab91c1d0282fed6701c57463d3c5f2bb8c6a7301fb4576aa3b5f15510db8956ff77478c26a7c09bea7b398cfc83503f538e"), + tc("eff0707442c2b43e455302819dec3894a2e41a9aab63ad168dd437a37408324a", "0e3ab0e054739b00cdb6a87bd12cae024b54cb5e550e6c425360c2e87e59401f5ec24ef0314855f0f56c47695d56a7fb1417693af2a1ed5291f2fee95f75eed54a1b1c2e81226fbff6f63ade584911c71967a8eb70933bc3f5d15bc91b5c2644d9516d3c3a8c154ee48e118bd1442c043c7a0dba5ac5b1d5360aae5b9065"), + tc("45d0808107836411153667b2ca1b0b68476dad3eb8a46627923aceb32a920aaa", "a62fc595b4096e6336e53fcdfc8d1cc175d71dac9d750a6133d23199eaac288207944cea6b16d27631915b4619f743da2e30a0c00bbdb1bbb35ab852ef3b9aec6b0a8dcc6e9e1abaa3ad62ac0a6c5de765de2c3711b769e3fde44a74016fff82ac46fa8f1797d3b2a726b696e3dea5530439acee3a45c2a51bc32dd055650b"), + tc("e9916891100d60a4f6686701b0ec5bc5ad200a496a89658e18ad10505b898ee6", "2b6db7ced8665ebe9deb080295218426bdaa7c6da9add2088932cdffbaa1c14129bccdd70f369efb149285858d2b1d155d14de2fdb680a8b027284055182a0cae275234cc9c92863c1b4ab66f304cf0621cd54565f5bff461d3b461bd40df28198e3732501b4860eadd503d26d6e69338f4e0456e9e9baf3d827ae685fb1d817"), + tc("38443bdd9d99dd16d7570d0529f0662bc1797a516276d063327a722e43667fe8", "10db509b2cdcaba6c062ae33be48116a29eb18e390e1bbada5ca0a2718afbcd23431440106594893043cc7f2625281bf7de2655880966a23705f0c5155c2f5cca9f2c2142e96d0a2e763b70686cd421b5db812daced0c6d65035fde558e94f26b3e6dde5bd13980cc80292b723013bd033284584bff27657871b0cf07a849f4ae2"), + tc("5ce13a4c40ad6e564e2eb90d0f34690f527d07d146443e88b263d0d943d0f820", "9334de60c997bda6086101a6314f64e4458f5ff9450c509df006e8c547983c651ca97879175aaba0c539e82d05c1e02c480975cbb30118121061b1ebac4f8d9a3781e2db6b18042e01ecf9017a64a0e57447ec7fcbe6a7f82585f7403ee2223d52d37b4bf426428613d6b4257980972a0acab508a7620c1cb28eb4e9d30fc41361ec"), + tc("19cb674d35076b2083f0fc9826fe184b5ea2e358300259cc934bf265ef121789", "e88ab086891693aa535ceb20e64c7ab97c7dd3548f3786339897a5f0c39031549ca870166e477743ccfbe016b4428d89738e426f5ffe81626137f17aecff61b72dbee2dc20961880cfe281dfab5ee38b1921881450e16032de5e4d55ad8d4fca609721b0692bac79be5a06e177fe8c80c0c83519fb3347de9f43d5561cb8107b9b5edc"), + tc("4f25eefba9cea8b323c6917a72027c668e9859ae025d2618467444800f695414", "fd19e01a83eb6ec810b94582cb8fbfa2fcb992b53684fb748d2264f020d3b960cb1d6b8c348c2b54a9fcea72330c2aaa9a24ecdb00c436abc702361a82bb8828b85369b8c72ece0082fe06557163899c2a0efa466c33c04343a839417057399a63a3929be1ee4805d6ce3e5d0d0967fe9004696a5663f4cac9179006a2ceb75542d75d68"), + tc("da316803110775e55eaf40e85ba50f470acee5c7429dd9bc53bd70133071feeb", "59ae20b6f7e0b3c7a989afb28324a40fca25d8651cf1f46ae383ef6d8441587aa1c04c3e3bf88e8131ce6145cfb8973d961e8432b202fa5af3e09d625faad825bc19da9b5c6c20d02abda2fcc58b5bd3fe507bf201263f30543819510c12bc23e2ddb4f711d087a86edb1b355313363a2de996b891025e147036087401ccf3ca7815bf3c49"), + tc("dcedc570a476055725f062daa0651383fe997cf539b2f1228a8da225befdb7d3", "77ee804b9f3295ab2362798b72b0a1b2d3291dceb8139896355830f34b3b328561531f8079b79a6e9980705150866402fdc176c05897e359a6cb1a7ab067383eb497182a7e5aef7038e4c96d133b2782917417e391535b5e1b51f47d8ed7e4d4025fe98dc87b9c1622614bff3d1029e68e372de719803857ca52067cddaad958951cb2068cc6"), + tc("9213860134edd9693b7455405a1139e72fc1637fc7c6f4deb05950fe1cc4c8ae", "b771d5cef5d1a41a93d15643d7181d2a2ef0a8e84d91812f20ed21f147bef732bf3a60ef4067c3734b85bc8cd471780f10dc9e8291b58339a677b960218f71e793f2797aea349406512829065d37bb55ea796fa4f56fd8896b49b2cd19b43215ad967c712b24e5032d065232e02c127409d2ed4146b9d75d763d52db98d949d3b0fed6a8052fbb"), + tc("2feb9de1b8f6706b05c594ac6ba953d2eb1077bcdc273586a1e9a7693b719b35", "b32d95b0b9aad2a8816de6d06d1f86008505bd8c14124f6e9a163b5a2ade55f835d0ec3880ef50700d3b25e42cc0af050ccd1be5e555b23087e04d7bf9813622780c7313a1954f8740b6ee2d3f71f768dd417f520482bd3a08d4f222b4ee9dbd015447b33507dd50f3ab4247c5de9a8abd62a8decea01e3b87c8b927f5b08beb37674c6f8e380c04"), + tc("a7170c8b1c7c2ff96ff4324f1f91b49a06129d1e3b29dc70b303a735771c42c2", "04410e31082a47584b406f051398a6abe74e4da59bb6f85e6b49e8a1f7f2ca00dfba5462c2cd2bfde8b64fb21d70c083f11318b56a52d03b81cac5eec29eb31bd0078b6156786da3d6d8c33098c5c47bb67ac64db14165af65b44544d806dde5f487d5373c7f9792c299e9686b7e5821e7c8e2458315b996b5677d926dac57b3f22da873c601016a0d"), + tc("17f6b2a1f965b02a9b00c9af67308f45baac0e2702c4fe1f473ebd8ac865ff78", "8b81e9badde026f14d95c019977024c9e13db7a5cd21f9e9fc491d716164bbacdc7060d882615d411438aea056c340cdf977788f6e17d118de55026855f93270472d1fd18b9e7e812bae107e0dfde7063301b71f6cfe4e225cab3b232905a56e994f08ee2891ba922d49c3dafeb75f7c69750cb67d822c96176c46bd8a29f1701373fb09a1a6e3c7158f"), + tc("ff5c60968e17a08f9ecddf3327a95aec0964ec03ba5dab4ccdd1f0386b0b0ee1", "fa6eed24da6666a22208146b19a532c2ec9ba94f09f1def1e7fc13c399a48e41acc2a589d099276296348f396253b57cb0e40291bd282773656b6e0d8bea1cda084a3738816a840485fcf3fb307f777fa5feac48695c2af4769720258c77943fb4556c362d9cba8bf103aeb9034baa8ea8bfb9c4f8e6742ce0d52c49ea8e974f339612e830e9e7a9c29065"), + tc("caf601a14e6f040318605c4134e845f9110c28358b91e9d059f426eb0e6af94b", "9bb4af1b4f09c071ce3cafa92e4eb73ce8a6f5d82a85733440368dee4eb1cbc7b55ac150773b6fe47dbe036c45582ed67e23f4c74585dab509df1b83610564545642b2b1ec463e18048fc23477c6b2aa035594ecd33791af6af4cbc2a1166aba8d628c57e707f0b0e8707caf91cd44bdb915e0296e0190d56d33d8dde10b5b60377838973c1d943c22ed335e"), + tc("44679d891ed6e8afe8d57b6decd41cb7a376c38ab6d0240f81806db625d02f09", "2167f02118cc62043e9091a647cadbed95611a521fe0d64e8518f16c808ab297725598ae296880a773607a798f7c3cfce80d251ebec6885015f9abf7eaabae46798f82cb5926de5c23f44a3f9f9534b3c6f405b5364c2f8a8bdc5ca49c749bed8ce4ba48897062ae8424ca6dde5f55c0e42a95d1e292ca54fb46a84fbc9cd87f2d0c9e7448de3043ae22fdd229"), + tc("7b2ebd05afd8d80da41e30d4751454919d4274ac3dcc7d68043e66baae1cecf4", "94b7fa0bc1c44e949b1d7617d31b4720cbe7ca57c6fa4f4094d4761567e389ecc64f6968e4064df70df836a47d0c713336b5028b35930d29eb7a7f9a5af9ad5cf441745baec9bb014ceeff5a41ba5c1ce085feb980bab9cf79f2158e03ef7e63e29c38d7816a84d4f71e0f548b7fc316085ae38a060ff9b8dec36f91ad9ebc0a5b6c338cbb8f6659d342a24368cf"), + tc("b65e629abbfdfb1ea74835fdece61da229198d71d2408627c9afb30da8af89e7", "ea40e83cb18b3a242c1ecc6ccd0b7853a439dab2c569cfc6dc38a19f5c90acbf76aef9ea3742ff3b54ef7d36eb7ce4ff1c9ab3bc119cff6be93c03e208783335c0ab8137be5b10cdc66ff3f89a1bddc6a1eed74f504cbe7290690bb295a872b9e3fe2cee9e6c67c41db8efd7d863cf10f840fe618e7936da3dca5ca6df933f24f6954ba0801a1294cd8d7e66dfafec"), + tc("f2e3f93d6d23a16bf97a2ad32c3e1293de59805ee26277a04fe42f166570d296", "157d5b7e4507f66d9a267476d33831e7bb768d4d04cc3438da12f9010263ea5fcafbde2579db2f6b58f911d593d5f79fb05fe3596e3fa80ff2f761d1b0e57080055c118c53e53cdb63055261d7c9b2b39bd90acc32520cbbdbda2c4fd8856dbcee173132a2679198daf83007a9b5c51511ae49766c792a29520388444ebefe28256fb33d4260439cba73a9479ee00c63"), + tc("0c647ea28d33c91160a56b0444400c2b1fbbf05cbcd579674c84c3911a705cc9", "836b34b515476f613fe447a4e0c3f3b8f20910ac89a3977055c960d2d5d2b72bd8acc715a9035321b86703a411dde0466d58a59769672aa60ad587b8481de4bba552a1645779789501ec53d540b904821f32b0bd1855b04e4848f9f8cfe9ebd8911be95781a759d7ad9724a7102dbe576776b7c632bc39b9b5e19057e226552a5994c1dbb3b5c7871a11f5537011044c53"), + tc("78b62c2d16293639013f16fc5472f61a95e6ec004f67e96e2151d70708994476", "cc7784a4912a7ab5ad3620aab29ba87077cd3cb83636adc9f3dc94f51edf521b2161ef108f21a0a298557981c0e53ce6ced45bdf782c1ef200d29bab81dd6460586964edab7cebdbbec75fd7925060f7da2b853b2b089588fa0f8c16ec6498b14c55dcee335cb3a91d698e4d393ab8e8eac0825f8adebeee196df41205c011674e53426caa453f8de1cbb57932b0b741d4c6"), + tc("c8f3afdae102094517caa3210d02da830531964863eefbd015c46f69810ab33d", "7639b461fff270b2455ac1d1afce782944aea5e9087eb4a39eb96bb5c3baaf0e868c8526d3404f9405e79e77bfac5ffb89bf1957b523e17d341d7323c302ea7083872dd5e8705694acdda36d5a1b895aaa16eca6104c82688532c8bfe1790b5dc9f4ec5fe95baed37e1d287be710431f1e5e8ee105bc42ed37d74b1e55984bf1c09fe6a1fa13ef3b96faeaed6a2a1950a12153"), + tc("8912c6802e940c57664e714dd848d93dfa710ca879c75089595999a6082b5df6", "eb6513fc61b30cfba58d4d7e80f94d14589090cf1d80b1df2e68088dc6104959ba0d583d585e9578ab0aec0cf36c48435eb52ed9ab4bbce7a5abe679c97ae2dbe35e8cc1d45b06dda3cf418665c57cbee4bbb47fa4caf78f4ee656fec237fe4eebbafa206e1ef2bd0ee4ae71bd0e9b2f54f91daadf1febfd7032381d636b733dcb3bf76fb14e23aff1f68ed3dbcf75c9b99c6f26"), + tc("70cc75de5ab9b56d4080afa804da9fcb951bb05e2c22f4045f856e54ad2f1b16", "1594d74bf5dde444265d4c04dad9721ff3e34cbf622daf341fe16b96431f6c4df1f760d34f296eb97d98d560ad5286fec4dce1724f20b54fd7df51d4bf137add656c80546fb1bf516d62ee82baa992910ef4cc18b70f3f8698276fcfb44e0ec546c2c39cfd8ee91034ff9303058b4252462f86c823eb15bf481e6b79cc3a02218595b3658e8b37382bd5048eaed5fd02c37944e73b"), + tc("b1e1b0c8bc208702f72e7d6fa5f7b977c6553cc729fee45e43bd372f1e47a2e4", "4cfa1278903026f66fedd41374558be1b585d03c5c55dac94361df286d4bd39c7cb8037ed3b267b07c346626449d0cc5b0dd2cf221f7e4c3449a4be99985d2d5e67bff2923357ddeab5abcb4619f3a3a57b2cf928a022eb27676c6cf805689004fca4d41ea6c2d0a4789c7605f7bb838dd883b3ad3e6027e775bcf262881428099c7fff95b14c095ea130e0b9938a5e22fc52650f591"), + tc("91731e07a6b2d25c15895ecc7935029ec2977e74642efba76034446fbda7d11b", "d3e65cb92cfa79662f6af493d696a07ccf32aaadcceff06e73e8d9f6f909209e66715d6e978788c49efb9087b170ecf3aa86d2d4d1a065ae0efc8924f365d676b3cb9e2bec918fd96d0b43dee83727c9a93bf56ca2b2e59adba85696546a815067fc7a78039629d4948d157e7b0d826d1bf8e81237bab7321312fdaa4d521744f988db6fdf04549d0fdca393d639c729af716e9c8bba48"), + tc("0290b3a745c53f38a6698f1ab888f09a427eec3db47a151d13c5b7e91bd63a49", "842cc583504539622d7f71e7e31863a2b885c56a0ba62db4c2a3f2fd12e79660dc7205ca29a0dc0a87db4dc62ee47a41db36b9ddb3293b9ac4baae7df5c6e7201e17f717ab56e12cad476be49608ad2d50309e7d48d2d8de4fa58ac3cfeafeee48c0a9eec88498e3efc51f54d300d828dddccb9d0b06dd021a29cf5cb5b2506915beb8a11998b8b886e0f9b7a80e97d91a7d01270f9a7717"), + tc("87b31ba330c464b08b0f9f93197534d415a958217a65c6cec77aba7c7f1e3faa", "6c4b0a0719573e57248661e98febe326571f9a1ca813d3638531ae28b4860f23c3a3a8ac1c250034a660e2d71e16d3acc4bf9ce215c6f15b1c0fc7e77d3d27157e66da9ceec9258f8f2bf9e02b4ac93793dd6e29e307ede3695a0df63cbdc0fc66fb770813eb149ca2a916911bee4902c47c7802e69e405fe3c04ceb5522792a5503fa829f707272226621f7c488a7698c0d69aa561be9f378"), + tc("ae21d00bbe5899e35926e38666975ce50eac4d4edb9cdd6d8f2bb7b06902c014", "51b7dbb7ce2ffeb427a91ccfe5218fd40f9e0b7e24756d4c47cd55606008bdc27d16400933906fd9f30effdd4880022d081155342af3fb6cd53672ab7fb5b3a3bcbe47be1fd3a2278cae8a5fd61c1433f7d350675dd21803746cadca574130f01200024c6340ab0cc2cf74f2234669f34e9009ef2eb94823d62b31407f4ba46f1a1eec41641e84d77727b59e746b8a671bef936f05be820759fa"), + tc("461e3b5ac9591519c6708a0f48134213274bbca65a66acf1c760edd7800a3a7e", "83599d93f5561e821bd01a472386bc2ff4efbd4aed60d5821e84aae74d8071029810f5e286f8f17651cd27da07b1eb4382f754cd1c95268783ad09220f5502840370d494beb17124220f6afce91ec8a0f55231f9652433e5ce3489b727716cf4aeba7dcda20cd29aa9a859201253f948dd94395aba9e3852bd1d60dda7ae5dc045b283da006e1cbad83cc13292a315db5553305c628dd091146597"), + tc("17f7c34dd3fde49241a36d36feae4fc3f9623758e07a5b1fd6d6b2bbc19080fd", "2be9bf526c9d5a75d565dd11ef63b979d068659c7f026c08bea4af161d85a462d80e45040e91f4165c074c43ac661380311a8cbed59cc8e4c4518e80cd2c78ab1cabf66bff83eab3a80148550307310950d034a6286c93a1ece8929e6385c5e3bb6ea8a7c0fb6d6332e320e71cc4eb462a2a62e2bfe08f0ccad93e61bedb5dd0b786a728ab666f07e0576d189c92bf9fb20dca49ac2d3956d47385e2"), + tc("68562edbbd1ceefc12bb9930444a810090f8d08720e1fbf00b2e8605db81033f", "ca76d3a12595a817682617006848675547d3e8f50c2210f9af906c0e7ce50b4460186fe70457a9e879e79fd4d1a688c70a347361c847ba0dd6aa52936eaf8e58a1be2f5c1c704e20146d366aeb3853bed9de9befe9569ac8aaea37a9fb7139a1a1a7d5c748605a8defb297869ebedd71d615a5da23496d11e11abbb126b206fa0a7797ee7de117986012d0362dcef775c2fe145ada6bda1ccb326bf644"), + tc("1a9a95676b6032d9cc66ffcfb8d424411bdb7d163562d70d9eb538a2eb9df46a", "f76b85dc67421025d64e93096d1d712b7baf7fb001716f02d33b2160c2c882c310ef13a576b1c2d30ef8f78ef8d2f465007109aad93f74cb9e7d7bef7c9590e8af3b267c89c15db238138c45833c98cc4a471a7802723ef4c744a853cf80a0c2568dd4ed58a2c9644806f42104cee53628e5bdf7b63b0b338e931e31b87c24b146c6d040605567ceef5960df9e022cb469d4c787f4cba3c544a1ac91f95f"), + tc("4732298d9c2825fe41274b71031afcdc963e39e1a8028e066361737bf959affc", "25b8c9c032ea6bcd733ffc8718fbb2a503a4ea8f71dea1176189f694304f0ff68e862a8197b839957549ef243a5279fc2646bd4c009b6d1edebf24738197abb4c992f6b1dc9ba891f570879accd5a6b18691a93c7d0a8d38f95b639c1daeb48c4c2f15ccf5b9d508f8333c32de78781b41850f261b855c4bebcc125a380c54d501c5d3bd07e6b52102116088e53d76583b0161e2a58d0778f091206aabd5a1"), + tc("d55722366e397cd9740fdb19e3120c4d5df92e3d6e0a1d7daabfb1bbe1d61165", "21cfdc2a7ccb7f331b3d2eefff37e48ad9fa9c788c3f3c200e0173d99963e1cbca93623b264e920394ae48bb4c3a5bb96ffbc8f0e53f30e22956adabc2765f57fb761e147ecbf8567533db6e50c8a1f894310a94edf806dd8ca6a0e141c0fa7c9fae6c6ae65f18c93a8529e6e5b553bf55f25be2e80a9882bd37f145fecbeb3d447a3c4e46c21524cc55cdd62f521ab92a8ba72b897996c49bb273198b7b1c9e"), + tc("0862390d0191bdd152788f63e44ebd827b88f911919069083366019ef2709fdd", "4e452ba42127dcc956ef4f8f35dd68cb225fb73b5bc7e1ec5a898bba2931563e74faff3b67314f241ec49f4a7061e3bd0213ae826bab380f1f14faab8b0efddd5fd1bb49373853a08f30553d5a55ccbbb8153de4704f29ca2bdeef0419468e05dd51557ccc80c0a96190bbcc4d77ecff21c66bdf486459d427f986410f883a80a5bcc32c20f0478bb9a97a126fc5f95451e40f292a4614930d054c851acd019ccf"), + tc("04d01f5e37b134563dc69111f042d8060a80e8ff71fc0b947aaa032887e17678", "fa85671df7dadf99a6ffee97a3ab9991671f5629195049880497487867a6c446b60087fac9a0f2fcc8e3b24e97e42345b93b5f7d3691829d3f8ccd4bb36411b85fc2328eb0c51cb3151f70860ad3246ce0623a8dc8b3c49f958f8690f8e3860e71eb2b1479a5cea0b3f8befd87acaf5362435eaeccb52f38617bc6c5c2c6e269ead1fbd69e941d4ad2012da2c5b21bcfbf98e4a77ab2af1f3fda3233f046d38f1dc8"), + tc("9a8e5b17512c41a4b2d05248f0468e26f4c55d64f51a0c7b6e8a9af1db6f5f8d", "e90847ae6797fbc0b6b36d6e588c0a743d725788ca50b6d792352ea8294f5ba654a15366b8e1b288d84f5178240827975a763bc45c7b0430e8a559df4488505e009c63da994f1403f407958203cebb6e37d89c94a5eacf6039a327f6c4dbbc7a2a307d976aa39e41af6537243fc218dfa6ab4dd817b6a397df5ca69107a9198799ed248641b63b42cb4c29bfdd7975ac96edfc274ac562d0474c60347a078ce4c25e88"), + tc("353185898990d860f1d17d886970643715c0e6e3bbbbe908688f01c1292a23fc", "f6d5c2b6c93954fc627602c00c4ca9a7d3ed12b27173f0b2c9b0e4a5939398a665e67e69d0b12fb7e4ceb253e8083d1ceb724ac07f009f094e42f2d6f2129489e846eaff0700a8d4453ef453a3eddc18f408c77a83275617fabc4ea3a2833aa73406c0e966276079d38e8e38539a70e194cc5513aaa457c699383fd1900b1e72bdfb835d1fd321b37ba80549b078a49ea08152869a918ca57f5b54ed71e4fd3ac5c06729"), + tc("afd00d9023347ea28e4217a64f0ff709e3ba9d19a78e80152d61fcfc244d3f4a", "cf8562b1bed89892d67ddaaf3deeb28246456e972326dbcdb5cf3fb289aca01e68da5d59896e3a6165358b071b304d6ab3d018944be5049d5e0e2bb819acf67a6006111089e6767132d72dd85beddcbb2d64496db0cc92955ab4c6234f1eea24f2d51483f2e209e4589bf9519fac51b4d061e801125e605f8093bb6997bc163d551596fe4ab7cfae8fb9a90f6980480ce0c229fd1675409bd788354daf316240cfe0af93eb"), + tc("b9d6c27d1e95fe186d5d7f7ec080f82ed2e0710838c741e426de537d65128fe6", "2ace31abb0a2e3267944d2f75e1559985db7354c6e605f18dc8470423fca30b7331d9b33c4a4326783d1caae1b4f07060eff978e4746bf0c7e30cd61040bd5ec2746b29863eb7f103ebda614c4291a805b6a4c8214230564a0557bc7102e0bd3ed23719252f7435d64d210ee2aafc585be903fa41e1968c50fd5d5367926df7a05e3a42cf07e656ff92de73b036cf8b19898c0cb34557c0c12c2d8b84e91181af467bc75a9d1"), + tc("b79172c5f852a390e76616bc1c4dc65579608f64fd76f1729ec8f1899c6e60eb", "0d8d09aed19f1013969ce5e7eb92f83a209ae76be31c754844ea9116ceb39a22ebb6003017bbcf26555fa6624185187db8f0cb3564b8b1c06bf685d47f3286eda20b83358f599d2044bbf0583fab8d78f854fe0a596183230c5ef8e54426750eaf2cc4e29d3bdd037e734d863c2bd9789b4c243096138f7672c232314effdfc6513427e2da76916b5248933be312eb5dde4cf70804fb258ac5fb82d58d08177ac6f4756017fff5"), + tc("ff15b4aa0ce77a3179d9b1079d7187c737ba37c7999c89d899e42bbd9e7e0d59", "c3236b73deb7662bf3f3daa58f137b358ba610560ef7455785a9befdb035a066e90704f929bd9689cef0ce3bda5acf4480bceb8d09d10b098ad8500d9b6071dfc3a14af6c77511d81e3aa8844986c3bea6f469f9e02194c92868cd5f51646256798ff0424954c1434bdfed9facb390b07d342e992936e0f88bfd0e884a0ddb679d0547ccdec6384285a45429d115ac7d235a717242021d1dc35641f5f0a48e8445dba58e6cb2c8ea"), + tc("0766425f0911053564d7ad11bae97aeafe3f83142390bac6c802917598949d73", "b39feb8283eadc63e8184b51df5ae3fd41aac8a963bb0be1cd08aa5867d8d910c669221e73243360646f6553d1ca05a84e8dc0de05b6419ec349ca994480193d01c92525f3fb3dcefb08afc6d26947bdbbfd85193f53b50609c6140905c53a6686b58e53a319a57b962331ede98149af3de3118a819da4d76706a0424b4e1d2910b0ed26af61d150ebcb46595d4266a0bd7f651ba47d0c7f179ca28545007d92e8419d48fdfbd744ce"), + tc("4433e407c250469f6264a1e33e98c53f342ddf1c07273983a231117f05c73529", "a983d54f503803e8c7999f4edbbe82e9084f422143a932ddddc47a17b0b7564a7f37a99d0786e99476428d29e29d3c197a72bfab1342c12a0fc4787fd7017d7a6174049ea43b5779169ef7472bdbbd941dcb82fc73aac45a8a94c9f2bd3477f61fd3b796f02a1b8264a214c6fea74b7051b226c722099ec7883a462b83b6afdd4009248b8a237f605fe5a08fe7d8b45321421ebba67bd70a0b00ddbf94baab7f359d5d1eea105f28dcfb"), + tc("2883e69c655ef374f61fc28171eeec8568945ca51ca782f27b111a83be5c8605", "e4d1c1897a0a866ce564635b74222f9696bf2c7f640dd78d7e2aca66e1b61c642bb03ea7536aae597811e9bf4a7b453ede31f97b46a5f0ef51a071a2b3918df16b152519ae3776f9f1edab4c2a377c3292e96408359d3613844d5eb393000283d5ad3401a318b12fd1474b8612f2bb50fb6a8b9e023a54d7dde28c43d6d8854c8d9d1155935c199811dbfc87e9e0072e90eb88681cc7529714f8fb8a2c9d88567adfb974ee205a9bf7b848"), + tc("a8b47e1fccbf494fbf02d16330c8d7386526b418d9ac85e921573e9a0d9e6ce5", "b10c59723e3dcadd6d75df87d0a1580e73133a9b7d00cb95ec19f5547027323be75158b11f80b6e142c6a78531886d9047b08e551e75e6261e79785366d7024bd7cd9cf322d9be7d57fb661069f2481c7bb759cd71b4b36ca2bc2df6d3a328faebdb995a9794a8d72155ed551a1f87c80bf6059b43fc764900b18a1c2441f7487743cf84e565f61f8dd2ece6b6ccc9444049197aaaf53e926fbee3bfca8be588ec77f29d211be89de18b15f6"), + tc("3cf7e2f6bfa41354403a16b18644005289d97a725ee4b32a9642beb7fef4d576", "db11f609baba7b0ca634926b1dd539c8cbada24967d7add4d9876f77c2d80c0f4dcefbd7121548373582705cca2495bd2a43716fe64ed26d059cfb566b3364bd49ee0717bdd9810dd14d8fad80dbbdc4cafb37cc60fb0fe2a80fb4541b8ca9d59dce457738a9d3d8f641af8c3fd6da162dc16fc01aac527a4a0255b4d231c0be50f44f0db0b713af03d968fe7f0f61ed0824c55c4b5265548febd6aad5c5eedf63efe793489c39b8fd29d104ce"), + tc("108a3fbb93af7c0c2713116407767f08610c6e856cdf3474e406e35d8c64a140", "bebd4f1a84fc8b15e4452a54bd02d69e304b7f32616aadd90537937106ae4e28de9d8aab02d19bc3e2fde1d651559e296453e4dba94370a14dbbb2d1d4e2022302ee90e208321efcd8528ad89e46dc839ea9df618ea8394a6bff308e7726bae0c19bcd4be52da6258e2ef4e96aa21244429f49ef5cb486d7ff35cac1bacb7e95711944bccb2ab34700d42d1eb38b5d536b947348a458ede3dc6bd6ec547b1b0cae5b257be36a7124e1060c170ffa"), + tc("8fc89f8288be14a0b56e39a366bd112dddfd9af92f86366c16bbf9ffb77425b8", "5aca56a03a13784bdc3289d9364f79e2a85c12276b49b92db0adaa4f206d5028f213f678c3510e111f9dc4c1c1f8b6acb17a6413aa227607c515c62a733817ba5e762cc6748e7e0d6872c984d723c9bb3b117eb8963185300a80bfa65cde495d70a46c44858605fccbed086c2b45cef963d33294dbe9706b13af22f1b7c4cd5a001cfec251fba18e722c6e1c4b1166918b4f6f48a98b64b3c07fc86a6b17a6d0480ab79d4e6415b520f1c484d675b1"), + tc("a92eb887c9ba1129f4315401c8aec9c4bd46b0c38539004f57b4e144938fa855", "a5aad0e4646a32c85cfcac73f02fc5300f1982fabb2f2179e28303e447854094cdfc854310e5c0f60993ceff54d84d6b46323d930adb07c17599b35b505f09e784bca5985e0172257797fb53649e2e9723efd16865c31b5c3d5113b58bb0bfc8920fabdda086d7537e66d709d050bd14d0c960873f156fad5b3d3840cdfcdc9be6af519db262a27f40896ab25cc39f96984d650611c0d5a3080d5b3a1bf186abd42956588b3b58cd948970d298776060"), + tc("c2b7a22def0e5583a2ced2d4a295a0dc5cac7765e5ab94e9609cbf155ccbbf3a", "06cbbe67e94a978203ead6c057a1a5b098478b4b4cbef5a97e93c8e42f5572713575fc2a884531d7622f8f879387a859a80f10ef02708cd8f7413ab385afc357678b9578c0ebf641ef076a1a30f1f75379e9dcb2a885bdd295905ee80c0168a62a9597d10cf12dd2d8cee46645c7e5a141f6e0e23aa482abe5661c16e69ef1e28371e2e236c359ba4e92c25626a7b7ff13f6ea4ae906e1cfe163e91719b1f750a96cbde5fbc953d9e576cd216afc90323a"), + tc("3ccb149e7e8025d854b5a4ec8bab3622837ed7a5f89c019d26a42bb6dfef3074", "f1c528cf7739874707d4d8ad5b98f7c77169de0b57188df233b2dc8a5b31eda5db4291dd9f68e6bad37b8d7f6c9c0044b3bf74bbc3d7d1798e138709b0d75e7c593d3cccdc1b20c7174b4e692add820ace262d45ccfae2077e878796347168060a162ecca8c38c1a88350bd63bb539134f700fd4addd5959e255337daa06bc86358fabcbefdfb5bc889783d843c08aadc6c4f6c36f65f156e851c9a0f917e4a367b5ad93d874812a1de6a7b93cd53ad97232"), + tc("4856403fe69f0293fc655b95d01a026826d5fbc4ac5cd906c89e370afbc523ab", "9d9f3a7ecd51b41f6572fd0d0881e30390dfb780991dae7db3b47619134718e6f987810e542619dfaa7b505c76b7350c6432d8bf1cfebdf1069b90a35f0d04cbdf130b0dfc7875f4a4e62cdb8e525aadd7ce842520a482ac18f09442d78305fe85a74e39e760a4837482ed2f437dd13b2ec1042afcf9decdc3e877e50ff4106ad10a525230d11920324a81094da31deab6476aa42f20c84843cfc1c58545ee80352bdd3740dd6a16792ae2d86f11641bb717c2"), + tc("41d77e6571e39028e57bb5896d57c085d30f12ed423de8994f146b0c244cfcf5", "5179888724819fbad3afa927d3577796660e6a81c52d98e9303261d5a4a83232f6f758934d50aa83ff9e20a5926dfebaac49529d006eb923c5ae5048ed544ec471ed7191edf46363383824f915769b3e688094c682b02151e5ee01e510b431c8865aff8b6b6f2f59cb6d129da79e97c6d2b8fa6c6da3f603199d2d1bcab547682a81cd6cf65f6551121391d78bcc23b5bd0e922ec6d8bf97c952e84dd28aef909aba31edb903b28fbfc33b7703cd996215a11238"), + tc("13fb8443c7490b1cbe9842a99a69d760ce51c347037f9ae1a01066953641f6e9", "576ef3520d30b7a4899b8c0d5e359e45c5189add100e43be429a02fb3de5ff4f8fd0e79d9663acca72cd29c94582b19292a557c5b1315297d168fbb54e9e2ecd13809c2b5fce998edc6570545e1499dbe7fb74d47cd7f35823b212b05bf3f5a79caa34224fdd670d335fcb106f5d92c3946f44d3afcbae2e41ac554d8e6759f332b76be89a0324aa12c5482d1ea3ee89ded4936f3e3c080436f539fa137e74c6d3389bdf5a45074c47bc7b20b0948407a66d855e2f"), + tc("7f4d4f09cf585ab8ff2b8bd432fd2c090b8eb9e6d9ee2a87f63f7f4b6de6a88e", "0df2152fa4f4357c8741529dd77e783925d3d76e95bafa2b542a2c33f3d1d117d159cf473f82310356fee4c90a9e505e70f8f24859656368ba09381fa245eb6c3d763f3093f0c89b972e66b53d59406d9f01aea07f8b3b615cac4ee4d05f542e7d0dab45d67ccccd3a606ccbeb31ea1fa7005ba07176e60dab7d78f6810ef086f42f08e595f0ec217372b98970cc6321576d92ce38f7c397a403bada1548d205c343ac09deca86325373c3b76d9f32028fea8eb32515"), + tc("f25fe32f288cda7a485074918a49cbd4c2290b13f3e4960d530b99a0d3661c66", "3e15350d87d6ebb5c8ad99d42515cfe17980933c7a8f6b8bbbf0a63728cefaad2052623c0bd5931839112a48633fb3c2004e0749c87a41b26a8b48945539d1ff41a4b269462fd199bfecd45374756f55a9116e92093ac99451aefb2af9fd32d6d7f5fbc7f7a540d5097c096ebc3b3a721541de073a1cc02f7fb0fb1b9327fb0b1218ca49c9487ab5396622a13ae546c97abdef6b56380dda7012a8384091b6656d0ab272d363cea78163ff765cdd13ab1738b940d16cae"), + tc("5a3fd997324783c6b7758cce29e641e580c2aac62dcb38c7b1dcab2922807521", "c38d6b0b757cb552be40940ece0009ef3b0b59307c1451686f1a22702922800d58bce7a636c1727ee547c01b214779e898fc0e560f8ae7f61bef4d75eaa696b921fd6b735d171535e9edd267c192b99880c87997711002009095d8a7a437e258104a41a505e5ef71e5613ddd2008195f0c574e6ba3fe40099cfa116e5f1a2fa8a6da04badcb4e2d5d0de31fdc4800891c45781a0aac7c907b56d631fca5ce8b2cde620d11d1777ed9fa603541de794ddc5758fcd5fad78c0"), + tc("aa17fda17d2ecf6d17dd48725fc72b2f0e81f53349a4923ef6b556b2245b6773", "8d2de3f0b37a6385c90739805b170057f091cd0c7a0bc951540f26a5a75b3e694631bb64c7635eed316f51318e9d8de13c70a2aba04a14836855f35e480528b776d0a1e8a23b547c8b8d6a0d09b241d3be9377160cca4e6793d00a515dc2992cb7fc741daca171431da99cce6f7789f129e2ac5cf65b40d703035cd2185bb936c82002daf8cbc27a7a9e554b06196630446a6f0a14ba155ed26d95bd627b7205c072d02b60db0fd7e49ea058c2e0ba202daff0de91e845cf79"), + tc("6a9561677ebbe041fa2c90875e147a63ab8da53ba0864580aeaa0679c8180fab", "c464bbdad275c50dcd983b65ad1019b9ff85a1e71c807f3204bb2c921dc31fbcd8c5fc45868ae9ef85b6c9b83bba2a5a822201ed68586ec5ec27fb2857a5d1a2d09d09115f22dcc39fe61f5e1ba0ff6e8b4acb4c6da748be7f3f0839739394ff7fa8e39f7f7e84a33c3866875c01bcb1263c9405d91908e9e0b50e7459fabb63d8c6bbb73d8e3483c099b55bc30ff092ff68b6adedfd477d63570c9f5515847f36e24ba0b705557130cec57ebad1d0b31a378e91894ee26e3a04"), + tc("90e235e455596158a9022afe81b86d89cdf03ae5738b25e78727c9d46eb5aa1b", "8b8d68bb8a75732fe272815a68a1c9c5aa31b41dedc8493e76525d1d013d33cebd9e21a5bb95db2616976a8c07fcf411f5f6bc6f7e0b57aca78cc2790a6f9b898858ac9c79b165ff24e66677531e39f572be5d81eb3264524181115f32780257bfb9aeec6af12af28e587cac068a1a2953b59ad680f4c245b2e3ec36f59940d37e1d3db38e13edb29b5c0f404f6ff87f80fc8be7a225ff22fbb9c8b6b1d7330c57840d24bc75b06b80d30dad6806544d510af6c4785e823ac3e0b8"), + tc("3fb8d2afa87a90ab98955b4e82240a7bd7cf779d03a5375c049bfbc05be32fad", "6b018710446f368e7421f1bc0ccf562d9c1843846bc8d98d1c9bf7d9d6fcb48bfc3bf83b36d44c4fa93430af75cd190bde36a7f92f867f58a803900df8018150384d85d82132f123006ac2aeba58e02a037fe6afbd65eca7c44977dd3dc74f48b6e7a1bfd5cc4dcf24e4d52e92bd4455848e4928b0eac8b7476fe3cc03e862aa4dff4470dbfed6de48e410f25096487ecfc32a27277f3f5023b2725ade461b1355889554a8836c9cf53bd767f5737d55184eea1ab3f53edd0976c485"), + tc("348228c78f0d6051ac26bb293a25c85ecca518fa9ec0bcde0e8a76ed71dd8d02", "c9534a24714bd4be37c88a3da1082eda7cabd154c309d7bd670dccd95aa535594463058a29f79031d6ecaa9f675d1211e9359be82669a79c855ea8d89dd38c2c761ddd0ec0ce9e97597432e9a1beae062cdd71edfdfd464119be9e69d18a7a7fd7ce0e2106f0c8b0abf4715e2ca48ef9f454dc203c96656653b727083513f8efb86e49c513bb758b3b052fe21f1c05bb33c37129d6cc81f1aef6adc45b0e8827a830fe545cf57d0955802c117d23ccb55ea28f95c0d8c2f9c5a242b33f"), + tc("135fdd756450b9cd60ae96e8f94fba1dae68bb115c441e7b025556c1d4c5542a", "07906c87297b867abf4576e9f3cc7f82f22b154afcbf293b9319f1b0584da6a40c27b32e0b1b7f412c4f1b82480e70a9235b12ec27090a5a33175a2bb28d8adc475cefe33f7803f8ce27967217381f02e67a3b4f84a71f1c5228e0c2ad971373f6f672624fcea8d1a9f85170fad30fa0bbd25035c3b41a6175d467998bd1215f6f3866f53847f9cf68ef3e2fbb54bc994de2302b829c5eea68ec441fcbafd7d16ae4fe9fff98bf00e5bc2ad54dd91ff9fda4dd77b6c754a91955d1fbaad0"), + tc("733b20df55216003deeb53f846ae05945073ad2463ddf38932a1f89f6487a15b", "588e94b9054abc2189df69b8ba34341b77cdd528e7860e5defcaa79b0c9a452ad4b82aa306be84536eb7cedcbe058d7b84a6aef826b028b8a0271b69ac3605a9635ea9f5ea0aa700f3eb7835bc54611b922964300c953efe7491e3677c2cebe0822e956cd16433b02c68c4a23252c3f9e151a416b4963257b783e038f6b4d5c9f110f871652c7a649a7bcedcbccc6f2d0725bb903cc196ba76c76aa9f10a190b1d1168993baa9ffc96a1655216773458bec72b0e39c9f2c121378feab4e76a"), + tc("e668c1b29fa473b1b408a6572883ad1f6d19f9e98c2dbf9dd80a806e01971316", "08959a7e4baae874928813364071194e2939772f20db7c3157078987c557c2a6d5abe68d520eef3dc491692e1e21bcd880adebf63bb4213b50897fa005256ed41b5690f78f52855c8d9168a4b666fce2da2b456d7a7e7c17ab5f2fb1ee90b79e698712e963715983fd07641ae4b4e9dc73203fac1ae11fa1f8c7941fcc82eab247addb56e2638447e9d609e610b60ce086656aaebf1da3c8a231d7d94e2fd0afe46b391ff14a72eaeb3f44ad4df85866def43d4781a0b3578bc996c87970b132"), + tc("16d95c2c49428e83a2b233f9a457fc063c8060b8baee9e6e14330a2e4697bb36", "cb2a234f45e2ecd5863895a451d389a369aab99cfef0d5c9ffca1e6e63f763b5c14fb9b478313c8e8c0efeb3ac9500cf5fd93791b789e67eac12fd038e2547cc8e0fc9db591f33a1e4907c64a922dda23ec9827310b306098554a4a78f050262db5b545b159e1ff1dca6eb734b872343b842c57eafcfda8405eedbb48ef32e99696d135979235c3a05364e371c2d76f1902f1d83146df9495c0a6c57d7bf9ee77e80f9787aee27be1fe126cdc9ef893a4a7dcbbc367e40fe4e1ee90b42ea25af01"), + tc("d6e589f9efdb02f8eadceb0c3cbd2f6c124d21ffc285c865c37210a542d679e7", "d16beadf02ab1d4dc6f88b8c4554c51e866df830b89c06e786a5f8757e8909310af51c840efe8d20b35331f4355d80f73295974653ddd620cdde4730fb6c8d0d2dcb2b45d92d4fbdb567c0a3e86bd1a8a795af26fbf29fc6c65941cddb090ff7cd230ac5268ab4606fccba9eded0a2b5d014ee0c34f0b2881ac036e24e151be89eeb6cd9a7a790afccff234d7cb11b99ebf58cd0c589f20bdac4f9f0e28f75e3e04e5b3debce607a496d848d67fa7b49132c71b878fd5557e082a18eca1fbda94d4b"), + tc("71ce35be3f5678864e97967efe59ae7aa043ec80760264d8374838aa87364310", "8f65f6bc59a85705016e2bae7fe57980de3127e5ab275f573d334f73f8603106ec3553016608ef2dd6e69b24be0b7113bf6a760ba6e9ce1c48f9e186012cf96a1d4849d75df5bb8315387fd78e9e153e76f8ba7ec6c8849810f59fb4bb9b004318210b37f1299526866f44059e017e22e96cbe418699d014c6ea01c9f0038b10299884dbec3199bb05adc94e955a1533219c1115fed0e5f21228b071f40dd57c4240d98d37b73e412fe0fa4703120d7c0c67972ed233e5deb300a22605472fa3a3ba86"), + tc("8ebb40af11c7572012e0de2aca9260f7276c8fd3e7419d2c047ac238ecb7b5b5", "84891e52e0d451813210c3fd635b39a03a6b7a7317b221a7abc270dfa946c42669aacbbbdf801e1584f330e28c729847ea14152bd637b3d0f2b38b4bd5bf9c791c58806281103a3eabbaede5e711e539e6a8b2cf297cf351c078b4fa8f7f35cf61bebf8814bf248a01d41e86c5715ea40c63f7375379a7eb1d78f27622fb468ab784aaaba4e534a6dfd1df6fa15511341e725ed2e87f98737ccb7b6a6dfae416477472b046bf1811187d151bfa9f7b2bf9acdb23a3be507cdf14cfdf517d2cb5fb9e4ab6"), + tc("0c828f3467d2cd08edd9badcf1963444c3ad270adf6e511f03c88349d8869b54", "fdd7a9433a3b4afabd7a3a5e3457e56debf78e84b7a0b0ca0e8c6d53bd0c2dae31b2700c6128334f43981be3b213b1d7a118d59c7e6b6493a86f866a1635c12859cfb9ad17460a77b4522a5c1883c3d6acc86e6162667ec414e9a104aa892053a2b1d72165a855bacd8faf8034a5dd9b716f47a0818c09bb6baf22aa503c06b4ca261f557761989d2afbd88b6a678ad128af68672107d0f1fc73c5ca740459297b3292b281e93bceb761bde7221c3a55708e5ec84472cddcaa84ecf23723cc0991355c6280"), + tc("313a38d32b97476b9b28ce8fe6b81377713f9833d6256e7ba2570adbefdc15a4", "70a40bfbef92277a1aad72f6b79d0177197c4ebd432668cfec05d099accb651062b5dff156c0b27336687a94b26679cfdd9daf7ad204338dd9c4d14114033a5c225bd11f217b5f4732da167ee3f939262d4043fc9cba92303b7b5e96aea12adda64859df4b86e9ee0b58e39091e6b188b408ac94e1294a8911245ee361e60e601eff58d1d37639f3753bec80ebb4efde25817436076623fc65415fe51d1b0280366d12c554d86743f3c3b6572e400361a60726131441ba493a83fbe9afda90f7af1ae717238d"), + tc("72880b5673f50c071b59a625f4210d4e5104e76d8bcc250a636d86f359f03d52", "74356e449f4bf8644f77b14f4d67cb6bd9c1f5ae357621d5b8147e562b65c66585caf2e491b48529a01a34d226d436959153815380d5689e30b35357cdac6e08d3f2b0e88e200600d62bd9f5eaf488df86a4470ea227006182e44809009868c4c280c43d7d64a5268fa719074960087b3a6abc837882f882c837834535929389a12b2c78187e2ea07ef8b8eef27dc85002c3ae35f1a50bee6a1c48ba7e175f3316670b27983472aa6a61eed0a683a39ee323080620ea44a9f74411ae5ce99030528f9ab49c79f2"), + tc("0f7dce85d2ccdd765235e016caf17075c278727c1e433608aeffa2799352225e", "8c3798e51bc68482d7337d3abb75dc9ffe860714a9ad73551e120059860dde24ab87327222b64cf774415a70f724cdf270de3fe47dda07b61c9ef2a3551f45a5584860248fabde676e1cd75f6355aa3eaeabe3b51dc813d9fb2eaa4f0f1d9f834d7cad9c7c695ae84b329385bc0bef895b9f1edf44a03d4b410cc23a79a6b62e4f346a5e8dd851c2857995ddbf5b2d717aeb847310e1f6a46ac3d26a7f9b44985af656d2b7c9406e8a9e8f47dcb4ef6b83caacf9aefb6118bfcff7e44bef6937ebddc89186839b77"), + tc("cbdc6acd2ec50b59d14aab724285f541f44860acb559dc8c34a0d2510febfe0e", "fa56bf730c4f8395875189c10c4fb251605757a8fecc31f9737e3c2503b02608e6731e85d7a38393c67de516b85304824bfb135e33bf22b3a23b913bf6acd2b7ab85198b8187b2bcd454d5e3318cacb32fd6261c31ae7f6c54ef6a7a2a4c9f3ecb81ce3555d4f0ad466dd4c108a90399d70041997c3b25345a9653f3c9a6711ab1b91d6a9d2216442da2c973cbd685ee7643bfd77327a2f7ae9cb283620a08716dfb462e5c1d65432ca9d56a90e811443cd1ecb8f0de179c9cb48ba4f6fec360c66f252f6e64edc96b"), + tc("c5306c224bebafd19eb92fa480bd10a7d332279856f1ad7b37d2cae1b9b999ed", "b6134f9c3e91dd8000740d009dd806240811d51ab1546a974bcb18d344642baa5cd5903af84d58ec5ba17301d5ec0f10ccd0509cbb3fd3fff9172d193af0f782252fd1338c7244d40e0e42362275b22d01c4c3389f19dd69bdf958ebe28e31a4ffe2b5f18a87831cfb7095f58a87c9fa21db72ba269379b2dc2384b3da953c7925761fed324620acea435e52b424a7723f6a2357374157a34cd8252351c25a1b232826cefe1bd3e70ffc15a31e7c0598219d7f00436294d11891b82497bc78aa5363892a2495df8c1eef"), + tc("8d2e8fcc878cfc6843247debf62a89e3844c94d32920937f83a9c378ffe97cd0", "c941cdb9c28ab0a791f2e5c8e8bb52850626aa89205bec3a7e22682313d198b1fa33fc7295381354858758ae6c8ec6fac3245c6e454d16fa2f51c4166fab51df272858f2d603770c40987f64442d487af49cd5c3991ce858ea2a60dab6a65a34414965933973ac2457089e359160b7cdedc42f29e10a91921785f6b7224ee0b349393cdcff6151b50b377d609559923d0984cda6000829b916ab6896693ef6a2199b3c22f7dc5500a15b8258420e314c222bc000bc4e5413e6dd82c993f8330f5c6d1be4bc79f08a1a0a46"), + tc("eafe83d51c716d4761554f55be42d586f50fdc9dc39a2f932dc06e950669dfc4", "4499efffac4bcea52747efd1e4f20b73e48758be915c88a1ffe5299b0b005837a46b2f20a9cb3c6e64a9e3c564a27c0f1c6ad1960373036ec5bfe1a8fc6a435c2185ed0f114c50e8b3e4c7ed96b06a036819c9463e864a58d6286f785e32a804443a56af0b4df6abc57ed5c2b185ddee8489ea080deeee66aa33c2e6dab36251c402682b6824821f998c32163164298e1fafd31babbcffb594c91888c6219079d907fdb438ed89529d6d96212fd55abe20399dbefd342248507436931cdead496eb6e4a80358acc78647d043"), + tc("82a80f1b11ed92bbddc5ef6b90b1bca76a26878359c1b14b59b90282f71b0b2c", "eecbb8fdfa4da62170fd06727f697d81f83f601ff61e478105d3cb7502f2c89bf3e8f56edd469d049807a38882a7eefbc85fc9a950952e9fa84b8afebd3ce782d4da598002827b1eb98882ea1f0a8f7aa9ce013a6e9bc462fb66c8d4a18da21401e1b93356eb12f3725b6db1684f2300a98b9a119e5d27ff704affb618e12708e77e6e5f34139a5a41131fd1d6336c272a8fc37080f041c71341bee6ab550cb4a20a6ddb6a8e0299f2b14bc730c54b8b1c1c487b494bdccfd3a53535ab2f231590bf2c4062fd2ad58f906a2d0d"), + tc("eff240f24a8e7f37ceff92b73e18f34afcad14fc419055e9ec359e6c2bd3af4e", "e64f3e4ace5c8418d65fec2bc5d2a303dd458034736e3b0df719098be7a206deaf52d6ba82316caf330ef852375188cde2b39cc94aa449578a7e2a8e3f5a9d68e816b8d16889fbc0ebf0939d04f63033ae9ae2bdab73b88c26d6bd25ee460ee1ef58fb0afa92cc539f8c76d3d097e7a6a63ebb9b5887edf3cf076028c5bbd5b9db3211371ad3fe121d4e9bf44229f4e1ecf5a0f9f0eba4d5ceb72878ab22c3f0eb5a625323ac66f7061f4a81fac834471e0c59553f108475fe290d43e6a055ae3ee46fb67422f814a68c4be3e8c9"), + tc("7985f2ebee310f6f307cf438fe162a57cba90e1bdd5522c51c927525f98c7e97", "d2cb2d733033f9e91395312808383cc4f0ca974e87ec68400d52e96b3fa6984ac58d9ad0938dde5a973008d818c49607d9de2284e7618f1b8aed8372fbd52ed54557af4220fac09dfa8443011699b97d743f8f2b1aef3537ebb45dcc9e13dfb438428ee190a4efdb3caeb7f3933117bf63abdc7e57beb4171c7e1ad260ab0587806c4d137b6316b50abc9cce0dff3acada47bbb86be777e617bbe578ff4519844db360e0a96c6701290e76bb95d26f0f804c8a4f2717eac4e7de9f2cff3bbc55a17e776c0d02856032a6cd10ad2838"), + tc("0146b3848391f9bd60ac650ccfdd49a0fe60f18aa044a16d10d10189fce7f15b", "f2998955613dd414cc111df5ce30a995bb792e260b0e37a5b1d942fe90171a4ac2f66d4928d7ad377f4d0554cbf4c523d21f6e5f379d6f4b028cdcb9b1758d3b39663242ff3cb6ede6a36a6f05db3bc41e0d861b384b6dec58bb096d0a422fd542df175e1be1571fb52ae66f2d86a2f6824a8cfaacbac4a7492ad0433eeb15454af8f312b3b2a577750e3efbd370e8a8cac1582581971fba3ba4bd0d76e718dacf8433d33a59d287f8cc92234e7a271041b526e389efb0e40b6a18b3aaf658e82ed1c78631fd23b4c3eb27c3faec8685"), + tc("718a00ed31d5375aa4e9dc26d5621e21d0901f9e1569e237e6535bd4e5d5f363", "447797e2899b72a356ba55bf4df3acca6cdb1041eb477bd1834a9f9acbc340a294d729f2f97df3a610be0ff15edb9c6d5db41644b9874360140fc64f52aa03f0286c8a640670067a84e017926a70438db1bb361defee7317021425f8821def26d1efd77fc853b818545d055adc9284796e583c76e6fe74c9ac2587aa46aa8f8804f2feb5836cc4b3ababab8429a5783e17d5999f32242eb59ef30cd7adabc16d72dbdb097623047c98989f88d14eaf02a7212be16ec2d07981aaa99949ddf89ecd90333a77bc4e1988a82abf7c7caf3291"), + tc("b17366f76307d583f52c211f4a025d032a1eb573a995ff9c3c1e31df014bd709", "9f2c18ade9b380c784e170fb763e9aa205f64303067eb1bcea93df5dac4bf5a2e00b78195f808df24fc76e26cb7be31dc35f0844cded1567bba29858cffc97fb29010331b01d6a3fb3159cc1b973d255da9843e34a0a4061cabdb9ed37f241bfabb3c20d32743f4026b59a4ccc385a2301f83c0b0a190b0f2d01acb8f0d41111e10f2f4e149379275599a52dc089b35fdd5234b0cfb7b6d8aebd563ca1fa653c5c021dfd6f5920e6f18bfafdbecbf0ab00281333ed50b9a999549c1c8f8c63d7626c48322e9791d5ff72294049bde91e73f8"), + tc("389724e7e0f8af0fae8dea24ab3c3e343df97adbafb624f341bd4df0bf1de40c", "ae159f3fa33619002ae6bcce8cbbdd7d28e5ed9d61534595c4c9f43c402a9bb31f3b301cbfd4a43ce4c24cd5c9849cc6259eca90e2a79e01ffbac07ba0e147fa42676a1d668570e0396387b5bcd599e8e66aaed1b8a191c5a47547f61373021fa6deadcb55363d233c24440f2c73dbb519f7c9fa5a8962efd5f6252c0407f190dfefad707f3c7007d69ff36b8489a5b6b7c557e79dd4f50c06511f599f56c896b35c917b63ba35c6ff8092baf7d1658e77fc95d8a6a43eeb4c01f33f03877f92774be89c1114dd531c011e53a34dc248a2f0e6"), + tc("d6c2cb98a9f5ce5ca862f0188cb928aec4e3db5ab341829fda81753abb71db64", "3b8e97c5ffc2d6a40fa7de7fcefc90f3b12c940e7ab415321e29ee692dfac799b009c99dcddb708fce5a178c5c35ee2b8617143edc4c40b4d313661f49abdd93cea79d117518805496fe6acf292c4c2a1f76b403a97d7c399daf85b46ad84e16246c67d6836757bde336c290d5d401e6c1386ab32797af6bb251e9b2d8fe754c47482b72e0b394eab76916126fd68ea7d65eb93d59f5b4c5ac40f7c3b37e7f3694f29424c24af8c8f0ef59cd9dbf1d28e0e10f799a6f78cad1d45b9db3d7dee4a7059abe99182714983b9c9d44d7f5643596d4f3"), + tc("77e624db0016d71adfa33d77eabac4912a2f32c5a403d12b13ae9dffa6e65492", "3434ec31b10fafdbfeec0dd6bd94e80f7ba9dca19ef075f7eb017512af66d6a4bcf7d16ba0819a1892a6372f9b35bcc7ca8155ee19e8428bc22d214856ed5fa9374c3c09bde169602cc219679f65a1566fc7316f4cc3b631a18fb4449fa6afa16a3db2bc4212eff539c67cf184680826535589c7111d73bffce431b4c40492e763d9279560aaa38eb2dc14a212d723f994a1fe656ff4dd14551ce4e7c621b2aa5604a10001b2878a897a28a08095c325e10a26d2fb1a75bfd64c250309bb55a44f23bbac0d5516a1c687d3b41ef2fbbf9cc56d4739"), + tc("88c4852ec2ceaf975d72a6d47bdfcd800797ed730fa528446f6168da6140005d", "7c7953d81c8d208fd1c97681d48f49dd003456de60475b84070ef4847c333b74575b1fc8d2a186964485a3b8634feaa3595aaa1a2f4595a7d6b6153563dee31bbac443c8a33eed6d5d956a980a68366c2527b550ee950250dfb691eacbd5d56ae14b970668be174c89df2fea43ae52f13142639c884fd62a3683c0c3792f0f24ab1318bcb27e21f4737fab62c77ea38bc8fd1cf41f7dab64c13febe7152bf5bb7ab5a78f5346d43cc741cb6f72b7b8980f268b68bf62abdfb1577a52438fe14b591498cc95f071228460c7c5d5ceb4a7bde588e7f21c"), + tc("30f68700be6fd947d523cfa5710435364cf1ccd4ae2c472759ef47404c4be0f2", "7a6a4f4fdc59a1d223381ae5af498d74b7252ecf59e389e49130c7eaee626e7bd9897effd92017f4ccde66b0440462cdedfd352d8153e6a4c8d7a0812f701cc737b5178c2556f07111200eb627dbc299caa792dfa58f35935299fa3a3519e9b03166dffa159103ffa35e8577f7c0a86c6b46fe13db8e2cdd9dcfba85bdddcce0a7a8e155f81f712d8e9fe646153d3d22c811bd39f830433b2213dd46301941b59293fd0a33e2b63adbd95239bc01315c46fdb678875b3c81e053a40f581cfbec24a1404b1671a1b88a6d06120229518fb13a74ca0ac5ae"), + tc("a1a1230b5c360e14d1a8f538abc89efa63eeebb567c4833a49a184eab6e5960b", "d9faa14cebe9b7de551b6c0765409a33938562013b5e8e0e1e0a6418df7399d0a6a771fb81c3ca9bd3bb8e2951b0bc792525a294ebd1083688806fe5e7f1e17fd4e3a41d00c89e8fcf4a363caedb1acb558e3d562f1302b3d83bb886ed27b76033798131dab05b4217381eaaa7ba15ec820bb5c13b516dd640eaec5a27d05fdfca0f35b3a5312146806b4c0275bcd0aaa3b2017f346975db566f9b4d137f4ee10644c2a2da66deeca5342e236495c3c6280528bfd32e90af4cd9bb908f34012b52b4bc56d48cc8a6b59bab014988eabd12e1a0a1c2e170e7"), + tc("0be6ab58bb34ae7640c0d31e548c8100defb9e9013187d7628331432fdda37a0", "2d8427433d0c61f2d96cfe80cf1e932265a191365c3b61aaa3d6dcc039f6ba2ad52a6a8cc30fc10f705e6b7705105977fa496c1c708a277a124304f1fc40911e7441d1b5e77b951aad7b01fd5db1b377d165b05bbf898042e39660caf8b279fe5229d1a8db86c0999ed65e53d01ccbc4b43173ccf992b3a14586f6ba42f5fe30afa8ae40c5df29966f9346da5f8b35f16a1de3ab6de0f477d8d8660918060e88b9b9e9ca6a4207033b87a812dbf5544d39e4882010f82b6ce005f8e8ff6fe3c3806bc2b73c2b83afb704345629304f9f86358712e9fae3ca3e"), + tc("8539f8d93bb669908b34440c219980a3c0195768e5cf751e07045afe4fcfea05", "5e19d97887fcaac0387e22c6f803c34a3dacd2604172433f7a8a7a526ca4a2a1271ecfc5d5d7be5ac0d85d921095350dfc65997d443c21c8094e0a3fefd2961bcb94aed03291ae310ccda75d8ace4bc7d89e7d3e5d1650bda5d668b8b50bfc8e608e184f4d3a9a2badc4ff5f07e0c0bc8a9f2e0b2a26fd6d8c550008faaab75fd71af2a424bec9a7cd9d83fad4c8e9319115656a8717d3b523a68ff8004258b9990ed362308461804ba3e3a7e92d8f2ffae5c2fba55ba5a3c27c0a2f71bd711d2fe1799c2adb31b200035481e9ee5c4adf2ab9c0fa50b23975cf"), + tc("1fa468b1b220013035925acb87dee25c5bb4f734599784c66628cb81b6a8d7a4", "c8e976ab4638909387ce3b8d4e510c3230e5690e02c45093b1d297910abc481e56eea0f296f98379dfc9080af69e73b2399d1c143bee80ae1328162ce1ba7f6a8374679b20aacd380eb4e61382c99998704d62701afa914f9a2705cdb065885f50d086c3eb5753700c387118bb142f3e6da1e988dfb31ac75d7368931e45d1391a274b22f83ceb072f9bcabc0b216685bfd789f5023971024b1878a205442522f9ea7d8797a4102a3df41703768251fd5e017c85d1200a464118aa35654e7ca39f3c375b8ef8cbe7534dbc64bc20befb417cf60ec92f63d9ee7397"), + tc("5bedb760cb1b6f713eb4479069d2962a50befec45850064651191f1bcc536be2", "7145fa124b7429a1fc2231237a949ba7201bcc1822d3272de005b682398196c25f7e5cc2f289fbf44415f699cb7fe6757791b1443410234ae061edf623359e2b4e32c19bf88450432dd01caa5eb16a1dc378f391ca5e3c4e5f356728bddd4975db7c890da8bbc84cc73ff244394d0d48954978765e4a00b593f70f2ca082673a261ed88dbcef1127728d8cd89bc2c597e9102ced6010f65fa75a14ebe467fa57ce3bd4948b6867d74a9df5c0ec6f530cbf2ee61ce6f06bc8f2864dff5583776b31df8c7ffcb61428a56bf7bd37188b4a5123bbf338393af46eda85e6"), + tc("a9f6370705bd8be565645567ba02a77cf84f359acc912993a72aee709f9687f1", "7fdfadcc9d29bad23ae038c6c65cda1aef757221b8872ed3d75ff8df7da0627d266e224e812c39f7983e4558bfd0a1f2bef3feb56ba09120ef762917b9c093867948547aee98600d10d87b20106878a8d22c64378bf634f7f75900c03986b077b0bf8b740a82447b61b99fee5376c5eb6680ec9e3088f0bdd0c56883413d60c1357d3c811950e5890e7600103c916341b80c743c6a852b7b4fb60c3ba21f3bc15b8382437a68454779cf3cd7f9f90ccc8ef28d0b706535b1e4108eb5627bb45d719cb046839aee311ca1abdc8319e050d67972cb35a6b1601b25dbf487"), + tc("6afbd061f024f85fadf64d1151f87be6c5d0322580a36a0ec6c7a439182404ca", "988638219fd3095421f826f56e4f09e356296b628c3ce6930c9f2e758fd1a80c8273f2f61e4daae65c4f110d3e7ca0965ac7d24e34c0dc4ba2d6ff0bf5bbe93b3585f354d7543cb542a1aa54674d375077f2d360a8f4d42f3db131c3b7ab7306267ba107659864a90c8c909460a73621d1f5d9d3fd95beb19b23db1cb6c0d0fba91d36891529b8bd8263caa1bab56a4affaed44962df096d8d5b1eb845ef31188b3e10f1af811a13f156beb7a288aae593ebd1471b624aa1a7c6adf01e2200b3d72d88a3aed3100c88231e41efc376906f0b580dc895f080fda5741db1cb"), + tc("39945efa3a76c7a7d1caa901d912d42c3e36d9759c16e1bf15abb39f53856f8d", "5aab62756d307a669d146aba988d9074c5a159b3de85151a819b117ca1ff6597f6156e80fdd28c9c3176835164d37da7da11d94e09add770b68a6e081cd22ca0c004bfe7cd283bf43a588da91f509b27a6584c474a4a2f3ee0f1f56447379240a5ab1fb77fdca49b305f07ba86b62756fb9efb4fc225c86845f026ea542076b91a0bc2cdd136e122c659be259d98e5841df4c2f60330d4d8cdee7bf1a0a244524eecc68ff2aef5bf0069c9e87a11c6e519de1a4062a10c83837388f7ef58598a3846f49d499682b683c4a062b421594fafbc1383c943ba83bdef515efcf10d"), + tc("c88f730c14a7b34d3b41e6d368a1370d6d773665bff573c32effe950e0045dc8", "47b8216aa0fbb5d67966f2e82c17c07aa2d6327e96fcd83e3de7333689f3ee79994a1bf45082c4d725ed8d41205cb5bcdf5c341f77facb1da46a5b9b2cbc49eadf786bcd881f371a95fa17df73f606519aea0ff79d5a11427b98ee7f13a5c00637e2854134691059839121fea9abe2cd1bcbbbf27c74caf3678e05bfb1c949897ea01f56ffa4dafbe8644611685c617a3206c7a7036e4ac816799f693dafe7f19f303ce4eba09d21e03610201bfc665b72400a547a1e00fa9b7ad8d84f84b34aef118515e74def11b9188bd1e1f97d9a12c30132ec2806339bdadacda2fd8b78"), + tc("593be66e2c5c96c5019ce038e84d5e3f5c99d745b91cf4507cdbaf4d846009c8", "8cff1f67fe53c098896d9136389bd8881816ccab34862bb67a656e3d98896f3ce6ffd4da73975809fcdf9666760d6e561c55238b205d8049c1cedeef374d1735daa533147bfa960b2cce4a4f254176bb4d1bd1e89654432b8dbe1a135c42115b394b024856a2a83dc85d6782be4b444239567ccec4b184d4548eae3ff6a192f343292ba2e32a0f267f31cc26719eb85245d415fb897ac2da433ee91a99424c9d7f1766a44171d1651001c38fc79294accc68ceb5665d36218454d3ba169ae058a831338c17743603f81ee173bfc0927464f9bd728dee94c6aeab7aae6ee3a627e8"), + tc("c1455e3d7f3dcdd64eba0b32d64518578713c241bb73d88847df3a16cabde318", "eacd07971cff9b9939903f8c1d8cbb5d4db1b548a85d04e037514a583604e787f32992bf2111b97ac5e8a938233552731321522ab5e8583561260b7d13ebeef785b23a41fd8576a6da764a8ed6d822d4957a545d5244756c18aa80e1aad4d1f9c20d259dee1711e2cc8fd013169fb7cc4ce38b362f8e0936ae9198b7e838dcea4f7a5b9429bb3f6bbcf2dc92565e3676c1c5e6eb3dd2a0f86aa23edd3d0891f197447692794b3dfa269611ad97f72b795602b4fdb198f3fd3eb41b415064256e345e8d8c51c555dc8a21904a9b0f1ad0effab7786aac2da3b196507e9f33ca356427"), + tc("85c14348d396111ec5ce76500d87cf46f99a22e6f569dbc4cedc5d9d129b38f5", "23ac4e9a42c6ef45c3336ce6dfc2ff7de8884cd23dc912fef0f7756c09d335c189f3ad3a23697abda851a81881a0c8ccafc980ab2c702564c2be15fe4c4b9f10dfb2248d0d0cb2e2887fd4598a1d4acda897944a2ffc580ff92719c95cf2aa42dc584674cb5a9bc5765b9d6ddf5789791d15f8dd925aa12bffafbce60827b490bb7df3dda6f2a143c8bf96abc903d83d59a791e2d62814a89b8080a28060568cf24a80ae61179fe84e0ffad00388178cb6a617d37efd54cc01970a4a41d1a8d3ddce46edbba4ab7c90ad565398d376f431189ce8c1c33e132feae6a8cd17a61c630012"), + tc("2072345a273c2f8f5a060e97a567ea70a4471a78953fd7af0fbd9acba9978990", "0172df732282c9d488669c358e3492260cbe91c95cfbc1e3fea6c4b0ec129b45f242ace09f152fc6234e1bee8aab8cd56e8b486e1dcba9c05407c2f95da8d8f1c0af78ee2ed82a3a79ec0cb0709396ee62aadb84f8a4ee8a7ccca3c1ee84e302a09ea802204afecf04097e67d0f8e8a9d2651126c0a598a37081e42d168b0ae8a71951c524259e4e2054e535b779679bdade566fe55700858618e626b4a0faf895bcce9011504a49e05fd56127eae3d1f8917afb548ecadabda1020111fec9314c413498a360b08640549a22cb23c731ace743252a8227a0d2689d4c6001606678dfb921"), + tc("81dff0b99f2d0af83f56700ed14bf214c2c94181c6f3abacc5b33e07a2b7ef89", "3875b9240cf3e0a8b59c658540f26a701cf188496e2c2174788b126fd29402d6a75453ba0635284d08835f40051a2a9683dc92afb9383719191231170379ba6f4adc816fecbb0f9c446b785bf520796841e58878b73c58d3ebb097ce4761fdeabe15de2f319dfbaf1742cdeb389559c788131a6793e193856661376c81ce9568da19aa6925b47ffd77a43c7a0e758c37d69254909ff0fbd415ef8eb937bcd49f91468b49974c07dc819abd67395db0e05874ff83dddab895344abd0e7111b2df9e58d76d85ad98106b36295826be04d435615595605e4b4bb824b33c4afeb5e7bb0d19f909"), + tc("a90fba78a75aa9cda52d0b0937330edf55d46fc2474c29388e1a40978ccc6ffc", "747cc1a59fefba94a9c75ba866c30dc5c1cb0c0f8e9361d98484956dd5d1a40f6184afbe3dac9f76028d1caeccfbf69199c6ce2b4c092a3f4d2a56fe5a33a00757f4d7dee5dfb0524311a97ae0668a47971b95766e2f6dd48c3f57841f91f04a00ad5ea70f2d479a2620dc5cd78eaab3a3b011719b7e78d19ddf70d9423798af77517ebc55392fcd01fc600d8d466b9e7a7a85bf33f9cc5419e9bd874ddfd60981150ddaf8d7febaa4374f0872a5628d318000311e2f5655365ad4d407c20e5c04df17a222e7deec79c5ab1116d8572f91cd06e1ccc7ced53736fc867fd49ecebe6bf8082e8a"), + tc("b953444533c87532ebde64faef511eb8cced6a99007cce8cbffda7f86dbaa482", "57af971fccaec97435dc2ec9ef0429bcedc6b647729ea168858a6e49ac1071e706f4a5a645ca14e8c7746d65511620682c906c8b86ec901f3dded4167b3f00b06cbfac6aee3728051b3e5ff10b4f9ed8bd0b8da94303c833755b3ca3aeddf0b54bc8d6632138b5d25bab03d17b3458a9d782108006f5bb7de75b5c0ba854b423d8bb801e701e99dc4feaad59bc1c7112453b04d33ea3635639fb802c73c2b71d58a56bbd671b18fe34ed2e3dca38827d63fdb1d4fb3285405004b2b3e26081a8ff08cd6d2b08f8e7b7e90a2ab1ed7a41b1d0128522c2f8bff56a7fe67969422ce839a9d4608f03"), + tc("89ba5316a8c4aaa899d63b23ce0d30a54dd9f3f03eda58d9e83b1bc3b7f20902", "04e16dedc1227902baaf332d3d08923601bdd64f573faa1bb7201918cfe16b1e10151dae875da0c0d63c59c3dd050c4c6a874011b018421afc4623ab0381831b2da2a8ba42c96e4f70864ac44e106f94311051e74c77c1291bf5db9539e69567bf6a11cf6932bbbad33f8946bf5814c066d851633d1a513510039b349939bfd42b858c21827c8ff05f1d09b1b0765dc78a135b5ca4dfba0801bcaddfa175623c8b647eacfb4444b85a44f73890607d06d507a4f8393658788669f6ef4deb58d08c50ca0756d5e2f49d1a7ad73e0f0b3d3b5f090acf622b1878c59133e4a848e05153592ea81c6fbf"), + tc("df00136d02abce412f093ac9dd83448cd9e50535c9565d105d723ae0859fc3c9", "7c815c384eee0f288ece27cced52a01603127b079c007378bc5d1e6c5e9e6d1c735723acbbd5801ac49854b2b569d4472d33f40bbb8882956245c366dc3582d71696a97a4e19557e41e54dee482a14229005f93afd2c4a7d8614d10a97a9dfa07f7cd946fa45263063ddd29db8f9e34db60daa32684f0072ea2a9426ecebfa5239fb67f29c18cbaa2af6ed4bf4283936823ac1790164fec5457a9cba7c767ca59392d94cab7448f50eb34e9a93a80027471ce59736f099c886dea1ab4cba4d89f5fc7ae2f21ccd27f611eca4626b2d08dc22382e92c1efb2f6afdc8fdc3d2172604f5035c46b8197d3"), + tc("b8fc7e6eda0086aa84ff61f92f2b7be06aa3b2465196f5580a6c0dcf1386c516", "e29d505158dbdd937d9e3d2145658ee6f5992a2fc790f4f608d9cdb44a091d5b94b88e81fac4fdf5c49442f13b911c55886469629551189eaff62488f1a479b7db11a1560e198ddccccf50159093425ff7f1cb8d1d1246d0978764087d6bac257026b090efae8cec5f22b6f21c59ace1ac7386f5b8837ca6a12b6fbf5534dd0560ef05ca78104d3b943ddb220feaec89aa5e692a00f822a2ab9a2fe60350d75e7be16ff2526dc643872502d01f42f188abed0a6e9a6f5fd0d1ce7d5755c9ffa66b0af0b20bd806f08e06156690d81ac811778ca3dac2c249b96002017fce93e507e3b953acf99964b847"), + tc("df47d9543ea09b04851efa7df647b63d3b991699af4b277f14829d6c75f47906", "d85588696f576e65eca0155f395f0cfacd83f36a99111ed5768df2d116d2121e32357ba4f54ede927f189f297d3a97fad4e9a0f5b41d8d89dd7fe20156799c2b7b6bf9c957ba0d6763f5c3bc5129747bbb53652b49290cff1c87e2cdf2c4b95d8aaee09bc8fbfa6883e62d237885810491bfc101f1d8c636e3d0ede838ad05c207a3df4fad76452979eb99f29afaecedd1c63b8d36cf378454a1bb67a741c77ac6b6b3f95f4f02b64dabc15438613ea49750df42ee90101f115aa9abb9ff64324dde9dabbb01054e1bd6b4bcdc7930a44c2300d87ca78c06924d0323ad7887e46c90e8c4d100acd9eed21e"), + tc("85d560b1517bd45c88c130a7e8fe3b080291deb162c7c5977a6ace13fcfeffec", "3a12f8508b40c32c74492b66323375dcfe49184c78f73179f3314b79e63376b8ac683f5a51f1534bd729b02b04d002f55cbd8e8fc9b5ec1ea6bbe6a0d0e7431518e6ba45d124035f9d3dce0a8bb7bf1430a9f657e0b4ea9f20eb20c786a58181a1e20a96f1628f8728a13bdf7a4b4b32fc8aa7054cc4881ae7fa19afa65c6c3ee1b3ade3192af42054a8a911b8ec1826865d46d93f1e7c5e2b7813c92a506e53886f3d4701bb93d2a681ad109c845904bb861af8af0646b6e399b38b614051d34f6842563a0f37ec00cb3d865fc5d746c4987de2a65071100883a2a9c7a2bfe1e2dd603d9ea24dc7c5fd06be"), + tc("96f65533fd89b8a48a0cb5e3b7e60fec3b1773f8bbc75d89e4773da423d4f3c7", "1861edce46fa5ad17e1ff1deae084dec580f97d0a67885dfe834b9dfac1ae076742ce9e267512ca51f6df5a455af0c5fd6abf94acea103a3370c354485a7846fb84f3ac7c2904b5b2fbf227002ce512133bb7e1c4e50057bfd1e44db33c7cdb969a99e284b184f50a14b068a1fc5009d9b298dbe92239572a7627aac02abe8f3e3b473417f36d4d2505d16b7577f4526c9d94a270a2dfe450d06da8f6fa956879a0a55cfe99e742ea555ea477ba3e9b44ccd508c375423611af92e55345dc215779b2d5119eba49c71d49b9fe3f1569fa24e5ca3e332d042422a8b8158d3ec66a80012976f31ffdf305f0c9c5e"), + tc("122a894106b7ab1e52677fdb432d1db0b70187815d704b978acd5c2548a31626", "08d0ffde3a6e4ef65608ea672e4830c12943d7187ccff08f4941cfc13e545f3b9c7ad5eebbe2b01642b486caf855c2c73f58c1e4e3391da8e2d63d96e15fd84953ae5c231911b00ad6050cd7aafdaac9b0f663ae6aab45519d0f5391a541707d479034e73a6ad805ae3598096af078f1393301493d663dd71f83869ca27ba508b7e91e81e128c1716dc3acfe3084b2201e04cf8006617eecf1b640474a5d45cfde9f4d3ef92d6d055b909892194d8a8218db6d8203a84261d200d71473d7488f3427416b6896c137d455f231071cacbc86e0415ab88aec841d96b7b8af41e05bb461a40645bf176601f1e760de5f"), + tc("ce1056a8ae81b0159931a803e0bb7498393292dd31bf38937ce2e11a2f1f8f6b", "d782abb72a5be3392757be02d3e45be6e2099d6f000d042c8a543f50ed6ebc055a7f133b0dd8e9bc348536edcaae2e12ec18e8837df7a1b3c87ec46d50c241dee820fd586197552dc20beea50f445a07a38f1768a39e2b2ff05dddedf751f1def612d2e4d810daa3a0cc904516f9a43af660315385178a529e51f8aae141808c8bc5d7b60cac26bb984ac1890d0436ef780426c547e94a7b08f01acbfc4a3825eae04f520a9016f2fb8bf5165ed12736fc71e36a49a73614739eaa3ec834069b1b40f1350c2b3ab885c02c640b9f7686ed5f99527e41cfcd796fe4c256c9173186c226169ff257954ebda81c0e5f99"), + tc("930ce249bc62194653f326268d33cd349535498f48a808e22032b064dfe99bb4", "5fce8109a358570e40983e1184e541833bb9091e280f258cfb144387b05d190e431cb19baa67273ba0c58abe91308e1844dcd0b3678baa42f335f2fa05267a0240b3c718a5942b3b3e3bfa98a55c25a1466e8d7a603722cb2bbf03afa54cd769a99f310735ee5a05dae2c22d397bd95635f58c48a67f90e1b73aafcd3f82117f0166657838691005b18da6f341d6e90fc1cdb352b30fae45d348294e501b63252de14740f2b85ae5299ddec3172de8b6d0ba219a20a23bb5e10ff434d39db3f583305e9f5c039d98569e377b75a70ab837d1df269b8a4b566f40bb91b577455fd3c356c914fa06b9a7ce24c7317a172d"), + tc("295b04a3eb72bae7d1af8c97778e317e0c9d3bfe797a568d0e942434f0e30143", "6172f1971a6e1e4e6170afbad95d5fec99bf69b24b674bc17dd78011615e502de6f56b86b1a71d3f4348087218ac7b7d09302993be272e4a591968aef18a1262d665610d1070ee91cc8da36e1f841a69a7a682c580e836941d21d909a3afc1f0b963e1ca5ab193e124a1a53df1c587470e5881fb54dae1b0d840f0c8f9d1b04c645ba1041c7d8dbf22030a623aa15638b3d99a2c400ff76f3252079af88d2b37f35ee66c1ad7801a28d3d388ac450b97d5f0f79e4541755356b3b1a5696b023f39ab7ab5f28df4202936bc97393b93bc915cb159ea1bd7a0a414cb4b7a1ac3af68f50d79f0c9c7314e750f7d02faa58bfa"), + tc("e29ff387a5dcb86ac6e3442e0590172f4161c34e1f3b4f83146c094cfd02fd99", "5668ecd99dfbe215c4118398ac9c9eaf1a1433fab4ccdd3968064752b625ea944731f75d48a27d047d67547f14dd0ffaa55fa5e29f7af0d161d85eafc4f2029b717c918eab9d304543290bdba7158b68020c0ba4e079bc95b5bc0fc044a992b94b4ccd3bd66d0eabb5dbbab904d62e00752c4e3b0091d773bcf4c14b4377da3efff824b1cb2fa01b32d1e46c909e626ed2dae920f4c7dbeb635bc754facbd8d49beba3f23c1c41ccbfcd0ee0c114e69737f5597c0bf1d859f0c767e18002ae8e39c26261ffde2920d3d0baf0e906138696cfe5b7e32b600f45df3aaa39932f3a7df95b60fa8712a2271fcaf3911ce7b511b1"), + tc("1ea1e16a92d23d49b84bee4cc022c4b3d11ac26273be1e9440d358b8b7b3fe45", "03d625488354df30e3f875a68edfcf340e8366a8e1ab67f9d5c5486a96829dfac0578289082b2a62117e1cf418b43b90e0adc881fc6ae8105c888e9ecd21aea1c9ae1a4038dfd17378fed71d02ae492087d7cdcd98f746855227967cb1ab4714261ee3bead3f4db118329d3ebef4bc48a875c19ba763966da0ebea800e01b2f50b00e9dd4caca6dcb314d00184ef71ea2391d760c950710db4a70f9212ffc54861f9dc752ce18867b8ad0c48df8466ef7231e7ac567f0eb55099e622ebb86cb237520190a61c66ad34f1f4e289cb3282ae3eaac6152ed24d2c92bae5a7658252a53c49b7b02dfe54fdb2e90074b6cf310ac661"), + tc("ba95a7e5eaa57cd8339286b53b6aca811763cfed558aea46f0e9518e45312d7f", "2edc282ffb90b97118dd03aaa03b145f363905e3cbd2d50ecd692b37bf000185c651d3e9726c690d3773ec1e48510e42b17742b0b0377e7de6b8f55e00a8a4db4740cee6db0830529dd19617501dc1e9359aa3bcf147e0a76b3ab70c4984c13e339e6806bb35e683af8527093670859f3d8a0fc7d493bcba6bb12b5f65e71e705ca5d6c948d66ed3d730b26db395b3447737c26fad089aa0ad0e306cb28bf0acf106f89af3745f0ec72d534968cca543cd2ca50c94b1456743254e358c1317c07a07bf2b0eca438a709367fafc89a57239028fc5fecfd53b8ef958ef10ee0608b7f5cb9923ad97058ec067700cc746c127a61ee3"), + tc("b1b21afb252748a178212650ddc4d25dd75e3608ce9418ea3e3a77026752037f", "90b28a6aa1fe533915bcb8e81ed6cacdc10962b7ff82474f845eeb86977600cf70b07ba8e3796141ee340e3fce842a38a50afbe90301a3bdcc591f2e7d9de53e495525560b908c892439990a2ca2679c5539ffdf636777ad9c1cdef809cda9e8dcdb451abb9e9c17efa4379abd24b182bd981cafc792640a183b61694301d04c5b3eaad694a6bd4cc06ef5da8fa23b4fa2a64559c5a68397930079d250c51bcf00e2b16a6c49171433b0aadfd80231276560b80458dd77089b7a1bbcc9e7e4b9f881eacd6c92c4318348a13f4914eb27115a1cfc5d16d7fd94954c3532efaca2cab025103b2d02c6fd71da3a77f417d7932685888a"), + tc("05fad405421d9aac1e2a0bb281f290044db798c9c81d2e7e37b8cf27ea3dbde7", "2969447d175490f2aa9bb055014dbef2e6854c95f8d60950bfe8c0be8de254c26b2d31b9e4de9c68c9adf49e4ee9b1c2850967f29f5d08738483b417bb96b2a56f0c8aca632b552059c59aac3f61f7b45c966b75f1d9931ff4e596406378cee91aaa726a3a84c33f37e9cdbe626b5745a0b06064a8a8d56e53aaf102d23dd9df0a3fdf7a638509a6761a33fa42fa8ddbd8e16159c93008b53765019c3f0e9f10b144ce2ac57f5d7297f9c9949e4ff68b70d339f87501ce8550b772f32c6da8ad2ce2100a895d8b08fa1eead7c376b407709703c510b50f87e73e43f8e7348f87c3832a547ef2bbe5799abedcf5e1f372ea809233f006"), + tc("1067006f5ba77614c85654e29e6b8731d626da0e106a5fce8c60f40548ce3ee9", "721645633a44a2c78b19024eaecf58575ab23c27190833c26875dc0f0d50b46aea9c343d82ea7d5b3e50ec700545c615daeaea64726a0f05607576dcd396d812b03fb6551c641087856d050b10e6a4d5577b82a98afb89cee8594c9dc19e79feff0382fcfd127f1b803a4b9946f4ac9a4378e1e6e041b1389a53e3450cd32d9d2941b0cbabdb50da8ea2513145164c3ab6bcbd251c448d2d4b087ac57a59c2285d564f16da4ed5e607ed979592146ffb0ef3f3db308fb342df5eb5924a48256fc763141a278814c82d6d6348577545870ae3a83c7230ac02a1540fe1798f7ef09e335a865a2ae0949b21e4f748fb8a51f44750e213a8fb"), + tc("04cb00e26c2956c95b4271d63b354128bf91377352903a0c8f3cc81316a45c52", "6b860d39725a14b498bb714574b4d37ca787404768f64c648b1751b353ac92bac2c3a28ea909fdf0423336401a02e63ec24325300d823b6864bb701f9d7c7a1f8ec9d0ae3584aa6dd62ea1997cd831b4babd9a4da50932d4efda745c61e4130890e156aee6113716daf95764222a91187db2effea49d5d0596102d619bd26a616bbfda8335505fbb0d90b4c180d1a2335b91538e1668f9f9642790b4e55f9cab0fe2bdd2935d001ee6419abab5457880d0dbff20ed8758f4c20fe759efb33141cf0e892587fe8187e5fbc57786b7e8b089612c936dfc03d27efbbe7c8673f1606bd51d5ff386f4a7ab68edf59f385eb1291f117bfe717399"), + tc("99eaa72ed6a8e331edbd368c47cf409604969e7f7c26f5f4958cc01f5cdf92aa", "6a01830af3889a25183244decb508bd01253d5b508ab490d3124afbf42626b2e70894e9b562b288d0a2450cfacf14a0ddae5c04716e5a0082c33981f6037d23d5e045ee1ef2283fb8b6378a914c5d9441627a722c282ff452e25a7ea608d69cee4393a0725d17963d0342684f255496d8a18c2961145315130549311fc07f0312fb78e6077334f87eaa873bee8aa95698996eb21375eb2b4ef53c14401207deb4568398e5dd9a7cf97e8c9663e23334b46912f8344c19efcf8c2ba6f04325f1a27e062b62a58d0766fc6db4d2c6a1928604b0175d872d16b7908ebc041761187cc785526c2a3873feac3a642bb39f5351550af9770c328af7b"), + tc("09242088f93a06bc379e4c5a0df13c85294b6b7d8a1694e4cf1e82cbba70e6f3", "b3c5e74b69933c2533106c563b4ca20238f2b6e675e8681e34a389894785bdade59652d4a73d80a5c85bd454fd1e9ffdad1c3815f5038e9ef432aac5c3c4fe840cc370cf86580a6011778bbedaf511a51b56d1a2eb68394aa299e26da9ada6a2f39b9faff7fba457689b9c1a577b2a1e505fdf75c7a0a64b1df81b3a356001bf0df4e02a1fc59f651c9d585ec6224bb279c6beba2966e8882d68376081b987468e7aed1ef90ebd090ae825795cdca1b4f09a979c8dfc21a48d8a53cdbb26c4db547fc06efe2f9850edd2685a4661cb4911f165d4b63ef25b87d0a96d3dff6ab0758999aad214d07bd4f133a6734fde445fe474711b69a98f7e2b"), + tc("03c9894656d0d1a69702c1ae8418f59d22d62b6de7c1e86e7425a2f5a1e97bde", "83af34279ccb5430febec07a81950d30f4b66f484826afee7456f0071a51e1bbc55570b5cc7ec6f9309c17bf5befdd7c6ba6e968cf218a2b34bd5cf927ab846e38a40bbd81759e9e33381016a755f699df35d660007b5eadf292feefb735207ebf70b5bd17834f7bfa0e16cb219ad4af524ab1ea37334aa66435e5d397fc0a065c411ebbce32c240b90476d307ce802ec82c1c49bc1bec48c0675ec2a6c6f3ed3e5b741d13437095707c565e10d8a20b8c20468ff9514fcf31b4249cd82dcee58c0a2af538b291a87e3390d737191a07484a5d3f3fb8c8f15ce056e5e5f8febe5e1fb59d6740980aa06ca8a0c20f5712b4cde5d032e92ab89f0ae1"), + tc("d16b9501702423d6497a1922352b8b298df15b72ebaf83687ea808e1b4c2fb17", "a7ed84749ccc56bb1dfba57119d279d412b8a986886d810f067af349e8749e9ea746a60b03742636c464fc1ee233acc52c1983914692b64309edfdf29f1ab912ec3e8da074d3f1d231511f5756f0b6eead3e89a6a88fe330a10face267bffbfc3e3090c7fd9a850561f363ad75ea881e7244f80ff55802d5ef7a1a4e7b89fcfa80f16df54d1b056ee637e6964b9e0ffd15b6196bdd7db270c56b47251485348e49813b4eb9ed122a01b3ea45ad5e1a929df61d5c0f3e77e1fdc356b63883a60e9cbb9fc3e00c2f32dbd469659883f690c6772e335f617bc33f161d6f6984252ee12e62b6000ac5231e0c9bc65be223d8dfd94c5004a101af9fd6c0fb"), + tc("0066894354c34948d24296383063e73ccdd2600611bfee86a143e7c21e4ab094", "a6fe30dcfcda1a329e82ab50e32b5f50eb25c873c5d2305860a835aecee6264aa36a47429922c4b8b3afd00da16035830edb897831c4e7b00f2c23fc0b15fdc30d85fb70c30c431c638e1a25b51caf1d7e8b050b7f89bfb30f59f0f20fecff3d639abc4255b3868fc45dd81e47eb12ab40f2aac735df5d1dc1ad997cefc4d836b854cee9ac02900036f3867fe0d84afff37bde3308c2206c62c4743375094108877c73b87b2546fe05ea137bedfc06a2796274099a0d554da8f7d7223a48cbf31b7decaa1ebc8b145763e3673168c1b1b715c1cd99ecd3ddb238b06049885ecad9347c2436dff32c771f34a38587a44a82c5d3d137a03caa27e66c8ff6"), + tc("8843695ff3055fa63c6190500ebfe5a4ba4b64706d0f4fdbdc49b5834b34b28a", "83167ff53704c3aa19e9fb3303539759c46dd4091a52ddae9ad86408b69335989e61414bc20ab4d01220e35241eff5c9522b079fba597674c8d716fe441e566110b6211531ceccf8fd06bc8e511d00785e57788ed9a1c5c73524f01830d2e1148c92d0edc97113e3b7b5cd3049627abdb8b39dd4d6890e0ee91993f92b03354a88f52251c546e64434d9c3d74544f23fb93e5a2d2f1fb15545b4e1367c97335b0291944c8b730ad3d4789273fa44fb98d78a36c3c3764abeeac7c569c1e43a352e5b770c3504f87090dee075a1c4c85c0c39cf421bdcc615f9eff6cb4fe6468004aece5f30e1ecc6db22ad9939bb2b0ccc96521dfbf4ae008b5b46bc006e"), + tc("29a2913d7b509828fc8dbd420dc76ecac0f007569d9df431294bf3d6cdd9ae9f", "3a3a819c48efde2ad914fbf00e18ab6bc4f14513ab27d0c178a188b61431e7f5623cb66b23346775d386b50e982c493adbbfc54b9a3cd383382336a1a0b2150a15358f336d03ae18f666c7573d55c4fd181c29e6ccfde63ea35f0adf5885cfc0a3d84a2b2e4dd24496db789e663170cef74798aa1bbcd4574ea0bba40489d764b2f83aadc66b148b4a0cd95246c127d5871c4f11418690a5ddf01246a0c80a43c70088b6183639dcfda4125bd113a8f49ee23ed306faac576c3fb0c1e256671d817fc2534a52f5b439f72e424de376f4c565cca82307dd9ef76da5b7c4eb7e085172e328807c02d011ffbf33785378d79dc266f6a5be6bb0e4a92eceebaeb1"), + tc("df7f65c50b8670aa5f33684d5de178e4cbabea4ff20bde5561c2c70b7f5ddf08", "724627916c50338643e6996f07877eafd96bdf01da7e991d4155b9be1295ea7d21c9391f4c4a41c75f77e5d27389253393725f1427f57914b273ab862b9e31dabce506e558720520d33352d119f699e784f9e548ff91bc35ca147042128709820d69a8287ea3257857615eb0321270e94b84f446942765ce882b191faee7e1c87e0f0bd4e0cd8a927703524b559b769ca4ece1f6dbf313fdcf67c572ec4185c1a88e86ec11b6454b371980020f19633b6b95bd280e4fbcb0161e1a82470320cec6ecfa25ac73d09f1536f286d3f9dacafb2cd1d0ce72d64d197f5c7520b3ccb2fd74eb72664ba93853ef41eabf52f015dd591500d018dd162815cc993595b195"), + tc("ff4115399db3d9191ff17588c947f78733eebab655103737d5bd264fa72b27c3", "3139840b8ad4bcd39092916fd9d01798ff5aa1e48f34702c72dfe74b12e98a114e318cdd2d47a9c320fff908a8dbc2a5b1d87267c8e983829861a567558b37b292d4575e200de9f1de45755faff9efae34964e4336c259f1e66599a7c904ec02539f1a8eab8706e0b4f48f72fec2794909ee4a7b092d6061c74481c9e21b9332dc7c6e482d7f9cc3210b38a6f88f7918c2d8c55e64a428ce2b68fd07ab572a8b0a2388664f99489f04eb54df1376271810e0e7bce396f52807710e0dea94eb49f4b367271260c3456b9818fc7a72234e6bf2205ff6a36546205015ebd7d8c2527aa430f58e0e8ac97a7b6b793cd403d517d66295f37a34d0b7d2fa7bc345ac04ca1e266480deec39f5c88641c9dc0bd1358158fdecdd96685bbbb5c1fe5ea89d2cb4a9d5d12bb8c893281ff38e87d6b4841f0650092d447e013f20ea934e18"), + tc("85416179e046d3921c196f6f8cd1dbee20ba83bb3c1f8971e90cd6ce6e3c8b33", "023d91ac532601c7ca3942d62827566d9268bb4276fcaa1ae927693a6961652676dba09219a01b3d5adfa12547a946e78f3c5c62dd880b02d2eeeb4b96636529c6b01120b23efc49ccfb36b8497cd19767b53710a636683bc5e0e5c9534cfc004691e87d1bee39b86b953572927bd668620eab87836d9f3f8f28ace41150776c0bc6657178ebf297fe1f7214edd9f215ffb491b681b06ac2032d35e6fdf832a8b06056da70d77f1e9b4d26ae712d8523c86f79250718405f91b0a87c725f2d3f52088965f887d8cf87206dfde422386e58edda34dde2783b3049b86917b4628027a05d4d1f429d2b49c4b1c898dddcb82f343e145596de11a54182f39f4718ecae8f506bd9739f5cd5d5686d7fefc834514cd1b2c91c33b381b45e2e5335d7a8720a8f17afc8c2cb2bd88b14aa2dca099b00aa575d0a0ccf099cdec4870fb710d2680e60c48bfc291ff0cef2eebf9b36902e9fba8c889bf6b4b9f5ce53a19b0d9399cd19d61bd08c0c2ec25e099959848e6a550ca7137b63f43138d7b651"), + tc("aebc4e7b9af59a03cf87d2bee44f7ed8bc4fc5f95b99a241967a7a8649b054ba", "20ff454369a5d05b81a78f3db05819fea9b08c2384f75cb0ab6aa115dd690da3131874a1ca8f708ad1519ea952c1e249cb540d196392c79e87755424fee7c890808c562722359eea52e8a12fbbb969dd7961d2ba52037493755a5fa04f0d50a1aa26c9b44148c0d3b94d1c4a59a31aca15ae8bd44acb7833d8e91c4b86fa3135a423387b8151b4133ed23f6d7187b50ec2204ad901ad74d396e44274e0ecafaae17b3b9085e22260b35ca53b15cc52abba758af6798fbd04eceeced648f3af4fdb3ded7557a9a5cfb7382612a8a8f3f45947d1a29ce29072928ec193ca25d51071bd5e1984ecf402f306ea762f0f25282f5296d997658be3f983696ffa6d095c6369b4daf79e9a5d3136229128f8eb63c12b9e9fa78aff7a3e9e19a62022493cd136defbb5bb7ba1b938f367fd2f63eb5ca76c0b0ff21b9e36c3f07230cf3c3074e5da587040a76975d7e39f4494ace5486fcbf380ab7558c4fe89656335b82e4db8659509eab46a19613126e594042732dd4c411f41aa8cdeac71c0fb40a94e6da558c05e77b6182806f26d9afdf3da00c69419222c8186a6efad600b410e6ce2f2a797e49dc1f135319801fa6f396b06f975e2a190a023e474b618e7"), + tc("b6a6ce4a895b2deadc38f22151de8cfb7a5b5232f2068777ebbda3750e402eb1", "4fbdc596508d24a2a0010e140980b809fb9c6d55ec75125891dd985d37665bd80f9beb6a50207588abf3ceee8c77cd8a5ad48a9e0aa074ed388738362496d2fb2c87543bb3349ea64997ce3e7b424ea92d122f57dbb0855a803058437fe08afb0c8b5e7179b9044bbf4d81a7163b3139e30888b536b0f957eff99a7162f4ca5aa756a4a982dfadbf31ef255083c4b5c6c1b99a107d7d3afffdb89147c2cc4c9a2643f478e5e2d393aea37b4c7cb4b5e97dadcf16b6b50aae0f3b549ece47746db6ce6f67dd4406cd4e75595d5103d13f9dfa79372924d328f8dd1fcbeb5a8e2e8bf4c76de08e3fc46aa021f989c49329c7acac5a688556d7bcbcb2a5d4be69d3284e9c40ec4838ee8592120ce20a0b635ecadaa84fd5690509f54f77e35a417c584648bc9839b974e07bfab0038e90295d0b13902530a830d1c2bdd53f1f9c9faed43ca4eed0a8dd761bc7edbdda28a287c60cd42af5f9c758e5c7250231c09a582563689afc65e2b79a7a2b68200667752e9101746f03184e2399e4ed8835cb8e9ae90e296af220ae234259fe0bd0bcc60f7a4a5ff3f70c5ed4de9c8c519a10e962f673c82c5e9351786a8a3bfd570031857bd4c87f4fca31ed4d50e14f2107da02cb5058700b74ea241a8b41d78461658f1b2b90bfd84a4c2c9d6543861ab3c56451757dcfb9ba60333488dbdd02d601b41aae317ca7474eb6e6dd"), + tc("cb2d5a48170bbcd8dc17228d63de0a4727b8ad3cfe12c3bce62a9de460d1479f", "fe06a4706468b369f7624f62d04f9fac020f05152f13e350016b2a29efff9a393940c138553356b0e2848c01b622b95ffa11ab07585f7dcbbf90e9f8ec5fa2fb7b4cee0d0a4e8d33490abd058cf3bb85f0cd9b1bd3e9823082d70b1a92aca6f2c87216b4ba09feddcaa4cf254336146cc75604fb1f286918fa2434ca36be2621049438a400bdeea6c657f0301503cd7e6e38350838f60ea7f001755da4142ce4579b39029da83f1646b7ecb9947ee89aba377099b82026960b9ee600779bf00d6eb0cd09226db6915a7aded27e6749e2cbc2c8b030ce1850ebfbe24c0658f29e9e709cd10db8a77efdefc90fdd7b9ad7a7e0334412a53d248c4c12bf2987b7accd2a8a602f184583aa560c016093b56b100154477b834664e6b85a19f8dc909b4d79816af12266c731e29a304e9bed8ef1c8030365b7deaf3d436957308117c7c5767e0cda6e342ddaf824233cbf4e699dc667357cb35c602ac6bddee71b352af55cb93941a1a6301a9904447af9ee486114d57ae03901f10084adc0096e465e2ead2496273112f2fae626e230d42ec22ea10a8289b3e35eee42150769d6e663a3ca29174316ec93a24f148d984053b8f98664eaca3e0dea0b42e8ee30f81a2cd6e42c189a25fecb6e643e693e1f8871b837c3f5ff2aafd1650a465dc8e5c1993be65cffd87f2c680c86b0ad3118834a5f2e490015137ba945c2775dbd77fb3e5c67819a9a7a94a656fc4761659c5b30ed2ac55a6d249b700bc9c93d590490aaaaa75a9fc34a90d5a9106f2860bde19fe5815436068a7f8ea4636a"), + tc("8de83bb217c6dbc8697361388665c921ae81ad8b1fc29f1d117faab5bb2f2481", "d0ff6e045f4b636f75a389799f314066644854821b6e7ae4047adfde2d0c0e02c250f0be582bec94011189b964a8af430f5921ed9d9f4446e4c788515b89ca69e5f7cdfccc9e83e8f9460145b43ddc41c07cc512b7e6fdd0e1e7aaba29a6c016ccb7bd54b145f3951eab9bc4908f623e5a9b0c5b36056292540b79fd15c53457dc74a65fd773a34d6b313a056f79bc29a3fac15f6a1446bfaeeaafbac8ecf8168dde5f6ae6b6e579bd3ce74e7abfadf361d0fd32d56586a8d2d4ff4cfdf8a750fafde4c2e9eb32b06847fa30b13cc273532d1a23c8257f80c60b8fa94fa976f534145cd61c41c0a511b62cadd5848ceff643f83ce43f8e6969c5a559afad60e310599a34b2e5e029fbddf2988fce59269c7128a1fc79a74b154d8aa2850dcfdbf594684e74099e37882b440367c1dd3003f61cafb46ac75d30e677af54559a5dab70c506cf61a9c35e0e56e1430746916ddeec8d89b0c10daa02c5d7e9f42621d2b312eaffc9ff306297952a32d26c2148570aec90501ca739ce5e689e7066d9580a4fc25e2023897c74c6856273133e1275a0d275dc5b75db724cd12c9c01bb95ab5a227b7850020630506096878d289923177183ea9282a4c78ec212d2e898cb99d81a3364df20927ee34d4475a5cf5cdb24088ed75b60201922e9c972d8556ca75f8274d15f3fb88a6b42c766def6b21329dee7c457446dde8c26405fe5d0309a04229f449e8490cf9000ee8df400cb7c7ee831bd7059d24088fb42d61681cde45050fca78ff64d9c8d1f58b55f802fa5d2f2e723f3e4eed4338b060d31c8acc46d26870bd45d0de0798d48e32aad1a6d4322e69f5e72309b9d7fa1f24bb1d63ff09ed47391c232497bf222c542a70975c8292d275197a4ca"), + tc("3a2c7b8656ae28c125a588e20eda947a50d4fe1b8754cb80575b701f01891bc0", "c9a0b51f3199339c11a5753c9d9c95104b400127087177f14d3e15be881ee89bf416f97d4f7aa9636c5ef29ebbf8930804006eb26839415d3984b920882499752c36a5bbd583308e716db16f28db98b3a3e718a2ebcdfefa75d7ccbb6a746e32feaa38d034843461dbcfca7f983e19f5b23ec2e3d74d2b4402f3bdce7d7a9df1384668c746cce466b0654cdd5ca56a77d68a100ae06d63cd9af512aebae78993808ce672330e2065c0f71e05c0fe821cd5f85732315f04666f3e4e6c4f3e9f415e31fe0980d874994108e12464609c6d5c19cedfc85846b0a84ef3ccbd3b655fb0de8cb892bdf774df39c955f04f322856d470981beed0f752d82ead73b4131c5dbf0424b4ab654f72e26ef0cd292b87bd5b66fbd5e7eba96c62cffefd4b1d870bfef312b86e38ed5c0f50935e2ecf9fcb8c95d35d19a8fc8f202bd9dba768ca48fbbb071179413c0de4fde86762ed4b13e1eef2a00add5d1c2a3af7937787debaf1f6f12f3f0225ffe587a5540cb550f7bc11dd6af864d6b6d16db1488cb226ebb2199da75fdd59a71158867298f63961b40a31209f8f16629b1cce7ffb617072748fde8920d11cc0d0678389d1f719df3ed19846fe9893b04db60fa603170d9573b1bfc7da663e07254552ab6253dde814117185f84373fecd7f98ded170c884d41d1ac81814c73ea48ad94eca1b7733d8393e320c8c467ef6189b4ac324c68c8a2ecf679c893b7dbc4310cd05dadd80bb6326b51ecd99a441bff77be8d644d752f3c5c794e7d45ba76283acf8913bbf2a420f9e5d14d7a23e95fdebc69b455e466423012e9d1de9353e40d4bdc3107c3144b96c6dafd41ccefeb2b7ff848cb9fdbea8648be32160fab7e7dfccb807fb019ebf159231edcf2d487d76a3e882f3f50ca48dd08a23b06ee887bd7df9d7c6cc2bdbe348e3125c4532448f400cd57a8679abab13e582b13d49442638e2698fb7a563c7fb264157"), + tc("34ceb42384c798a41a6b25e5866827fb533e23ac512570789dd46bacc7177235", "da5b2118cbd59f408ce56362489fda69ef73172f46dec16c0d4a66e3f3cca373927cb6784ce62bbcb08a490d003a8f77391e82cdd87860909383b054db95816720112e2f96de9e0ffc8babc1350ed46b9aeb5d46b66940efa311f4853f3118063c550733e7679d871729001c12270e98439e375e0cd24769d318e03cbc97d643f1dc5f1b5ef59d12789f22449bd8fa43a13ce78a4186166523f3807b0b621079cc528f4e17c258d4f9bdd095a3e365614b94f2cd778cacabf26244891531eedc24e1bf4598319eb8b7527736bb1e734429360f47f04b2e95d5aae997763a467cc5303d1141c2c01f7ab9edab860d180697c906ce1558aeff5adce603f8a4a10fc6c1699189e73b489f64cefb89ac557c5bb8826c0317eaef2302e855fbd96777898104365d96ae8a8ed8669c568c4dbeb0a70f6cf4c22fd933014473f91871c08d5a7b44a928295fb2aa56d5e2c39ca79d2837fb39b35c6810c378d749aacb543368a19c137b872229e0a6f469022aa93bcb2be38b761c85ac09415ca7deeb38ff2a2101507206fe322a89666d32750af0f4d7b2e59571f02a048b8f94fe2d231072e301c8641d1cdb4f841ab165af297495a348bccd937712e68852a4aca948c1294f8b733d06d67de89f206498dd401e149fcc1edca92deb92952ede4ebdc7cd7992104769a04de8705027a31337b88e8de93f6624e8f10b9f9924e8ce5a9d841c9260753fa1492cddffe6b4400c5719d76fb5b01b234f32ffe64f04d0a00676fbfc58dbe17b4d552fdf0c5b802c0cadf723f73f86e8ffc493992d23e8c0b84f7220a983ddda21170b1f730c736e75549af6ecb8dc94fb25c26bcddee420c83e8a45ae4c345816b7163cfe016dccfe47c66979d610a7f9b4a7b1ba5e230344c9f46ba076f2fcea68aada8fa09bf0b5cbb3850ddedd80a30e1f7a639ac69e5595c6a4083aa959831ebbeb84c018068723192f58baecddd18c857e212d4c7e7215b6c954728183fbaa07720b97189af6a7729c30d28db33a889f225d027d164f254535770c504c506aee4ec4676771f69f935ba08a1c6c85"), + tc("f640aa22bc2f56fd723f1f6e83133fa95943ced744d1bd2c8d0a84ecddccb20c", "d1890b4704e169c28e44ddf62a1091450404910539fc2daeb26e8acf4533b024e5215c2d02820dd8fb2cfc1743955cbacff0f8f35dfbb5e3f942f36247f68211d518f3f601aae12a1cdc000bab43d4c973f287e80741dd1fcf6c34f2e6b4b6c313d01c4ff3cbf9166f26946f18ef2d58271ba9233f09a6b77bfd4f48b36eb3d73d1133c4f842a7dc3907f680b0b773242c11e3dd973a44327ea7cea9c0f8e07d682b6651e506b587559fe01ed721000baf570a16fbdd9ea29fa3def4be912058321a8b720c5c102e48a6e7ed6f8838d400dd57d06eedbcd15323f86d855c94b21e41b14ec9e1bbc8019211fd88138c91f9abbd9bb3914d26c1ddc21673d2d51263b39d66e741d924cf2b192c5d2c1a140126a3d64a2c77be6c2c6ebe8599978ae90bd36cbb9af64d078910c4094ab3bf399c34f2ab8ef843e9fe1bf88bf443ba21e4377e5f49c07fd9653b526e14562237f02d11b904bca6ac31ae721a43e3c4910a24af6f4d80c031c109fc0fe49f15274bca92bda04c3b4196c192f6ce489c63a806acfc895ab52cad657c1783b528e12d0ed856e1f8fc91f2aafdfa0a92498d68530772ee73b359fcf1418d1096c46b34dcf90e5b468bbb2970becbd70089cfb039d64cc50fff5eef26384d34f24515a6558b06a1fdd88f1050c5bd78cc6ed83d4c2b0e882aebcf84afb0430d0bf09f2fb42b8b4589158093a7709aae75a790910e211ee1333ffb6fd80778da3bf73858978e9dd647978841b18001dbaaea43ca0c0a03dbb9bcf30ce76a6f4b2cf2a9b6531b3e4051e7e05090cd421bc66c4731e7122ad129fc42dedc83bb460e3f889992fbd3ca072686e56b72c720fbc98d723ef7f247286f77ccddc728738e941b1a74d4f16671c21fdd5643a115ddbcb88ee7ec67ea66fd2bce718df6e085d4b5fc71a72696636a8f7b3a68afa51a896771faaa7f1f827430ac5e8089dbc0d4175e1b22a057bc5f1724eadc1a41e78fa3acaa8b97e5f2e19ef9d59ae12b04e7f0e8a621e098a66910e2a5ed2102b824cd3ea044a854f1cd0b33e61e7f737414b2953549f25dd34d19aa1981de7cd5649ff6c6364a4f25312ef62395a747ab88aad722c05aec40deea8eee5e779ef458a68840bc6bd5d29ad40f98b3ae010b6213372abb7bb8b8"), + tc("02932ccfb76d27188139a4822e3746a13723dd439038f69cb12bd066ed3d88e9", "d9257004993c7ae50d20f534b42b4ec39bf358393b9fb5c8e37f87ac7361354554be596f40e67b2ed499887e26dc435c4331cde3bf1a118f60fd821477ffa3b92f6469568cd2cda6fc0a2b13906ae459cf5d5417de2ce104d0b6499d3683beb40715582ce70ded5c5f8461edeaa38bfa31979661a2dc96d926307128f77700f2c7d38b8a9d6c6f70a3973005350f938b9b4a64e228cc3b3f9c4fd446fc650aa6377152d7f46903d8ec8e9340710e28475cf21e641737e7d7da3cdf18d01c8f37131e727c7274b2db759c5586fb84ec36a5ce2d820e553de6426b8b96111295c19dba8d17a2b7047fcdf662a59c2af27a9abee37a7f8afc06944346e343e779ebd895119d7460e7da998962fb1100e950a8d7fe214360ad263b8d070f2b7dc91c9d77c6692da0f472a0a4646a1edb069ccd9ce4ca6fc24713d650153b9a253db5136a3710198e60934dd2053315d313e27dd2c6ffd2ecf028625f0e937afd8c2ffb1f646e51a768204d83a649798a51b8e87fcdda7bddf58ed943bb7e29c7d7c5dff024545f6a689aa0e727fecd80b561011a731acdd9b3f283123098ba66b6b9fce6123b35b6f3a20ef1055be9e257cde97e5af41eb796438727a62aa665cc9b771c4a2052efac61bc91bcdf573f7263107d44dfeb125e66d1f3dded3bd63845af3bf4185a9a24a7f4b777c32496a6107b7ac940af24be983f6a758e5064f8717eabbb86e6aada7da75f72abed59a42de82b1aa2642f5ef1b2e7304f642eb2aafbf00c1183d9b5fb83893edd48e9034dc7a7ab66f3f392f9666c00ecc9743ac4f777edcf47b6818a34b7392b4ab8e38891fab4018dfb99af06369115d60c4fa073240d9c2605e020a42db2e1e0ae4ade3a04b825927a05d4fe3f50c1c7add7a496da7e95825c064315196c2007524d1e1ef2c51303dd1203ab66d9fe89de553cb4b95a6c5d629b89b7116463ee10ab3cf2c2615f95741ca226f9e9a20e99303888f725c77476533b4c393763170f18f292c89a22df68eabdb81aef11cc2ac329c174bdae5d3cc0bdcea6eb225e00c15021432264ba7c5eb49a49231d1f7a13afc238bd4efc04da3c15ae0bb7d693a0150e6a5ad9b1e11a490b3ce90c027442501684c528fe260881162d9e5ef84d6e4f73f222e779e58b71d5176d9a27f65e49d83efbb2d7810e5b06619d8cef7e7b51cdfb6e8138e4cf5674242f947eac13fad08d68a8a11aac9754a5316c93738ba7f3c3bd2e827a"), + tc("9b5668b8253457df9296876436d9d40f031af59539436f13f06c646932df109d", "09cf132ab943654184a3fc8d0368b421fdac4ef1081a29f3691a527fbdbd74c6372233b38dc432ba610f9c54fa68699f9ec2a6bfb0b2cf7aa6d10ae2207a7554163aa14fb6a9cfa023ce63546b7b02af03370a899ea73b27bdb5c380613c0ea670c659803d5f6f75c0ce62b311dc157de843a93905e7a28a2557684ae3db4fd009412a107098f881dc06622ed9e3df2c8fe69fe50bfae158b87ca761fe1e221856610bca301ee68958c25bf58d3be203af928a919ace53c3d99f55ab00dfc8a1a28ab83e78617f194efc46daa77573d02ffcaa1f9d96fd8da8dead3d4e75ee5e65fc360b2a567cd34d52bc9aab8b71160312e8e21aa506105d6644e6863e7d5e75ed47c1760743d113a4526553b303341fcee5fdd005d35929f2afa760bb8314d0c0eac960fda3263a1e6688098251618c90332005c757af958714571b77bf2a69030d99f71d1a5259658be565fbd098070122c43edfd400999c90a98ace95315faee4f1c0441c9315d527c7ff1a8171415d2a076e2c513d40b03cfe5513c0e74ea3a03ad4958c00a3e1916d34273e301d517b0a5e770393880d9afb4d0eb44d3140ff7d827c48f67c6d92b865d8fe7c7e45aa50dfd8525f530de1188f3af98b3dc625d335e191fbad5ee9df09c97b43f203bfed798f7d171bf00c82ea0ed17d5890c3d6b9ef6fdc04c5ffb645e3050406fe2fb6bdbe2ffe083e751dc19f2f06445397019b61cf1f505875520f09c6b719e642b81b2fbfbdcc072b7981a666d51e7065e21244679dfe056bc9d42b8851f9d2905fd838707b279a8106eb894b60a72e4851c344119ceeb5bbe110b1c5b21c9a5f7545283002d4b1c115308b1cb20f6d9cb869ed0459c569662aa5729a7d7178de7d69e4885ca77c011e785ecf0e2ecbb8f5245579dbef7a5eb30307e0b40b817bd1112d28eefd548374bc867e0c34d2236952b9f859903e87e977190ff94f5d815699154f96e5449ddc1a14e4196c83e4fb02f5f58c30eeed691de47c81f4883bd84f6ed711fed0c198bfa2b8296182d7225e8989acf55d3612a93e77d24f7b6eef813fd514ce2183571602976391f5ac4e86da59a64233230fb67ff0bb9ec40667f54ccb235d04b3fe1d9a47e163cb19c316046fc1353a6642219e9cecf6e3151c0f4fae1177b67f19b4964df96bb6af37012b8ea9e5ee24c57a010c872b15c8a65d0266589f6da20e38d9be2e2c215c69f558084e48eeb61f4eaae83c57f3f1cd08d05783fb9f5ab1cda5e4b86291fffb14fe9b497ec73292bbd295c5657d71cc74d38a2f3db73ba972986b7169f76ba376376f4747ed65f2f1a5e67889af38a380"), + tc("855dee08ef1aca391266889139c4cc2cd5818efcc3c2048d0a0a55c479775221", "670b79c6030da4369146a7f14e6bd393df729b60369d3d55461e5187d4f8e83114b227524ee955f64a2c607da850235a1b49fec6eb51edc21941dcc67bd07412cfe058af4505c325d94936ee36de4133773acf669515daf4ed87508589cf651784a8c3820a88fbf7cb60e20a035ecd06405b50f6a13a36e174925b4e334e197e1e5fe86836acbc1ac681cfd42b2f11f74587660de867ebba7222c3e89b42957ac4508f09b2de9c3b57c883138f0d442a050d8aa3023ea920140f70237ab214be9fa0c452c54b5020077ecc1f5ddf5d959a5aaeaacb381355fd7b493a1a51ce83e6eb52cc61214a02fafb01593d5730c7058bf9106b8314f1005236e5beceb3805bac8fc4308b2007823bbdb3dc25042d6ab7745941b055a7648f99ffdacaca9801094ea86dbc9d2ef2b61a97ad5fb9ff754190a529ce65fe46afda3c0eae0ec98ed5b089148be358a8aba6b2f014a5b74547fdc00501ddb1472ef4ad592efa8031b0a4376dccea8714575bf055b1027824f463e8af110ccb521b2f1363c0f13256b768645a0377541cf2f73da03af0c026e59570d6007bafc297a41deeeb32e79364f9efff6f81b69fdb99dbae4ddba46016f86be1157693c63cbcfe24c7b6dbc2260798a6c397bd91d4abe0d5c23b43769b0ba28b6b954e66035bdc52de03b33ca004b670ea6e341b01adbc40de2f406bfc469122ee30192138868326bb642834f9e78f51976c3e6ea0efa570926f47612cd0702ea82279d60e062832d31785933e3beb86d187554a1675a61fb0b865092e518ad329f38a70e7dbd6025c9925fb0332b5ed97e77134b8684f6f167ea639fe6545fae58b611922855b44e520601dd0758319e9491a1229dc49f9f6f9eeadd9ce88ce91e3bd3d13f64ed7745280f4828d3a00d66f3966e0eedf930f563d8e48ed94be42ce5f48e92e3f2d801ffc464692c10e7300ab0f85d1d44f353c742e20de3706bba4b24ac0048ea6e0b40454b06fa95e5dc2951cb4b0a7d1c15a3c12a3889c7191814161c5181880223fe3070404e61d9d382aae8f236165fff3121d1b4bbb5b4d9b01282e2663de39c642839b34e853badd9e2e2c82fe90fe755c8677de6a405d1a23492d84aa272ad94c180300d737758e00da835da7ba9efa3ea0c16df2fd472c1b57e0d5ca40d43a3459a06a3360217adf41645fec31f104ca6166416e36597c35dcf0fa43298b7fca4f5045aff900298339ff5dbcc1408d703cee178c6b194e1015df362b038afd349610819fab289ac11cbb3f19ffc9fa0203c231711fbcc48922422059407ea225f8509761d3c53d83437b507814e61206558c8a27e7d55400c64c22aae33c817324abdd7114abfc01cfb1afc16296ff6b3dbc8218514b83102bc84272d41c05eaeadcf4a0fd9983fbb79627ba8f905e4cd345c874"), + tc("b98f79e84c65bce8806e756de95083a75df3d597807e025403555bad5f698742", "c595336827259b050dc783f22ff7b033ecc98f9c7ce026809cf388fbcaa29a9d3572547ccc0d5f8d26730617933f809b9559ab20339fa90a525c46a299ef14c9045e6275e22c34093e9bcdb1230198433672eda8fda5b16ea9313242ed777e86f4afffb4bb8a9cc0a902ade774ae688095d271ce7df975f1404fa5dc9acb50d66d2f93d77e262586caac610ab990974845f75820319fe8c507c759da479afc5557c32618f65ef7ef36e69f5c893435a592c3d3b3b8a8110811362b22f2d7a660a767d88d6ac9656f0d83f1d0395d477ae9df5016d982bd6cb972d733f2e010cadaac4dd20f8b4b798c12c5fcccb7c7a166e3bd451ce0b83f9feafd67af647adeb7eef023cf2c0cba8c379fbc4a5a3f58473a374e09ff096091dec2e5b35a4283c7dd96c46efe4f2768ace5b43a08dbdeaf95be5c2db0540ddf80fc895dce343ea93ed2ed748fc32435e7b28ed8474ede0ad449b32739f7d74a37c928070d0c1c30053adecaebc88f56c013ad5439ad2cf6a3876364a6b68e0d1fc20906670ebe4e85cef072ff67da097e3b6a88f78dc10f95520762ee86e8b26363245555da07649300893ff36854bcb875aa1924b7a99169e965803b74cbea05f7c486fbea2355ca033a77c2f4164a06ede30dc6244fdb3e8daf9e73f83544b79bab20851a640dc83c43c8980a2729ec01f409da9af6e9b5b7a6286ae8f225af90b2e5438892115e6de72df2848376490403a543765ecfe2cbb46ea7f0999cb3993ce099752e0999d84c2bbf91a9fbd39cb721f4c6266fee4df896eb296caeb2b3483b025e4be27deb57a3a706bff59829bad9f11eb75a1d010ef1ae926d5f5cadad5cb3ca7160a66846ab4f12f036933c90e115702e11b2657031eb28bd1e2793d0d0bef4234c2b6166a038cf67dd60cdcb86fb323788aa673f2ce5adc8fa8aae911d7363ff833abf619847e78f137fd0959b983e764a59fed29cee31f2f47715faea7f91483a6e61dd7ecec557cf24bb64cfcc0708db55da2426d4330438c42a4fa7d9dc4a1c625823df1d701bc3bc3aac584843de330206c45df0184ffae4e347fce6860c7e0c8fd54bed94ce321d62325ae12f932871a4724677bdde95e542ad66cdf491f5708fbeae6a6846d7068f8827400c188d6915af83542a51a1e0bacdd07e25064e2915ea78c8ca8327e2375eea19c565af6b837ca5044acfc4df68ee324d5bb8ef4d974368ea9b1369b567e6d5a44573da1249c84a789442ce16604a94788631e8906398ea3c2b8ea304b11e522ba82401fa2338ce925d31ae37bb3a67160dff3db6f7ed0c2df645bfaa5e276f2cbfc4ca288f2c3b246c67315235c0546bcfd3e4ddc2d6a2fa02ab780eb0fb3491239a2fa1b5ec90a0203ded372d5f594847d279396730a9d8ce5cc92ae34f7c75a07bb4873a5c5ee78268ddd0d8a351794fbd6c801551b39fad39e6cd2cac30d4538e90ee24784ba1ac0ba9cbc287151c7d8ee7ff94524fad65b3dc45265ef3b708ca2bcc5d"), + tc("49c978a31ad0ea1a2ce70f0c23069ea5121ac5260ffc459657b19d0c891bf98d", "a867330d0c93083fbb159efeef907aa53ace3995acfe7587b404af8c8334e32f9d35976d0466659f264826109b23a0df2056a5479d0c0c5ea5166ca231525da90b388c43259ba1696fd392fa10d146258040040b6ac2c3e91d1f224e2a2a0ab323b4cde6ee570b4dceda7958d69a2fb85f0d9daefb3715e5825298b7d3d8f09268413a11b30a24358755f7ed9df6f2fa9ae7a219e65bea978fa1187806386eba03af491a1d5708af2409b82db03a19d665ec58119ac537cbdc75ff3ed1e0d0a26d291391de6584bcc0d55b9931537ebe21f42afef05ad755db1af60988daed8cb087733421679f8b2d381c659c1325d47688f2771a03bfd31cf8d3c20fd09f603be7a13d1ba8d37cf492c93724a728bfdbcb6d3e8f0d770b5c260cfa3515aaf7c25faec42b2373970d11eb2a18c863d0d7121bb2dda1b223b883c468281efff0170ffa48552f263c308658d969866781f041da0ab149eb2020892a114a4a7758adae9febb4aa76604a65b87a2a5d0a45a8096391b3d1c09fc95746c449baeb2f807afc8e4f5efab2488ec19805691162ac8a3eaca1d25f0f4eb95ecf48c134584125aa0fafd5589847e656becadebfbbd258efcc6f749e15ba09dbf04b93877fa04ec2b6c3234fc49872c318b302252471705be669022765fd01d157c4aa54173399548bde8d8971a8cf0db652814313b71bc062db092fa5a5caf1809defa1215b42b800072b2990e6fa31f69d52076ad293d966cbe4f89c6d4e45afb76f23a5670e5b36fd60d784a736d0201cd2a119107f278e5474d87a97f53b12a8ab1d0191d19ec4103c472d1e7abe7351258b26c445397aa5a3ba924238b275e7d53b20253695866ee8ae02f9dd8fa8bf4b76e549d9322baee58d70bdfe1af34ebcbfd80aed8e40273727b7b919a28862171cdae66ec885eef41e5d237b9c308772db7cad7673646a7cc7cd78800aa12395e6ddd5004f0e4d4834b66d7ee7fefbe4e0947cc2fe0993c3f506ead51c9e9b6648228f03230f61ad23a708b11dcd328ab9081d102455dbf11a932f90946046d72d9102702114b8965f328d6f57dc0ece0b1aef7620640b4f43615a1123b6c3b58438b5760ef0bee130c5655b756dab1894fec4736420180041698dcdeecb5ee98b043a870c9949b1141d5a0920a1efa60a30d8cb33db0cc3364c657ee7e3e582a2a49610e8c08b9ed733d4f584ef6b9fc43f62cfe9f69bd64c02b1d20efe2ea9ebd6b6dca5f6d9de62a443d67c60cce67c47b4edaf24805dad5ac498bf1634646d0c34fc3b7c4661ee8029cd4fa237531cff681213012993f91106084de20241cde059bb37a26cad85533e4c5e8a4c4e6f952162660c0c55d8e266f0abdd4224487d06f7da1f26b84d6d30101b535a1ce76355ae1d34da2e71e244460848212d9076ff79d8a282359b8961c27a157ed6aa5e9990d56b4ed7113066bd560719a431e3822ef24c0630110ab448ef269f4ffafdce95fa57adf3229e5ea639cf4649e598e70f517f8ca03c9afaf4c752f7d14f96763f7a0eb4e5beabe5fd17fa8ec15271a509b1bade00d840073b52de95c688f7aaaf25751055c8827d1c6b5bb2c9140edca4"), + tc("92449ff6c4031ed17a8312f892377a3cc24b59aa615640cfc2c9fefaf83a0eff", "2eb31026155b7bc3672f060d8407b1622f6162cfe40e0286cf9bd73b185003827eb75ca080f0fa81d9e5893f462124f679ebad807df6ad87c809ff552ddfef22c11c4aefe1745a457462d346a3e0419347274f0a39f09ac211d79bbc13c799bc26e954bef11fc7d9ebf0a0283b59498f6a271afc5415f656f1c0a0e311edd0bcb49853f0fb52c849848406a7df6d7f3f32def50d8073ad2b1a197991d7d5337ecd5c1472e58dfe42859fffaa4b858c34b305b8f921afb319ab0c0a4d03024bc2afee1bd4b0f97848bbee535460144e301334b90ccceda137a7e44c44cb538e9a3d10961ecff2bbf16beb1a40240fa63eb02faf248191dac569f44aa10b9e391416a41a1af9b0379d2e52eb6c2c1a11018ab951d030fe5fb6317538f4dda3d4b3871afe479c4ccb0422301bcaec7c0d6a12e1a4db17de2539492c3c003c3c89c6587ce25c7c7d697a11dcb41764dcde7372e6eedab38b4004ec0805e1fedab9ed2550d976c223b0b0fb39fb006dd4aafe042b0a771e098baafa72aa488a40a16bce6272f64023e863262bb4d0a137f61f9e711db096dd93254c5e4382d3f7f1914e9d18f86637e5e4a7b7fcea80edca63b3acdc7c692770d8ade9ab0ffa87a2c7dd33317db038c5709bce686fe1be395b3292033858a2fa3c8144d4a74b53cfa8222ecb7e0d788f4ebfd75d880daabf66d888f3d7cf5d1e3dacad33f3c6e05eb0ffe7e8cb1dbe1403530f1aa44f7028580c9084dd6dfcd7788b94050b5fe63ef079003a51fa43e726897e82ebe160f4f48c9855d59bb94b8beb6b5492a66b38cfdf850b6f1cb368598443f2ea9653dd5e6a3ed60881d47956cd24f1b0567edacfe3f87a67e1d90ff6459666043b4f550d9480f4d17ff0b40b71364366b17d958db40f3b88189d2367572ab87a14b160344bec8c69bc0374ebfc62fa8e090296e5cb43bb253d04f24e21cc55849be4cb7e3ee80aa0da1f8c71a55e1d98a85049cca30f4000506e4f36f7096d4f88703a6b772690602019e17bea5965b68ca1d78cb157e640cce3afe00a25b6172eca4cb9902d3f39bda25d57e46ee01a794da2fa309c083cb103224964f8914e0bb39f67bc50b4103e41dc29c95041d5835178fbeea6bd35acdf51a6f5444cbfe5400439dc70cdff870e30ea1239faa2ecfebacc73b2c104e99594ab81894f4eba464f0096f4368d8662fa12b46cffd7eb766072bc145e170bf6459429bea5845bc4f088b98d9f1ec7b31b054fbf04ba5b7d44141cf26b24f1d80e39b2fc844e2c3cdf5c752f65057c9d4e982172ce094907721da4967772533632632d7335342386628438dba8cacb60139a7a7d7ff20f4e20e405cd3a029c3af103a91bf2046c372c20e99e3b5971cafd3e2ca4cc7e0e6ecc272bdc50fbd3f0184e58e6b26d329a9380049b3298ce33cf15cd7560e3d3e0c53cb029337236f9d848762d371d3279a8b1192d83cf1ff8c4f12dbeafa383b47cd3f172b82d4d942d9976a4cf2e3e0f69e13f1d8ede152938b978d2b1cbaf27088a10935eda68b2e31448b09e586e9fcac11c6cb056fc92512210c3a828b549a1cfd3516c4a5b4a59ffaf22d3ba324da5e92c607a45c189d7482f9ce1fb005b314d6d8ac0cf523cb21b3d86cb21d451400695d1341345dbc9e6cbf02f565e8b02f7b2151131514ee7d3195ccc262eb615a6c6"), + tc("3a045f1db785b36e84069ccd5ee242a609e2c8ff5c13a73a04234f78ef7ecbc2", "218a31573465b8a543e293e5a591113e9cccd2095f519d2b0d7a195a625950ae2fd52ad7d5e5c984e5bca89f7fbaebaaf4686db96826e8e0cdef1b787cd08129f7ac7bb0e1635f7f52c5b6d6497a0db6125bf0b2fdc06c8f72fe16f4d75d5ef9620786c85d0c128aecdd362390039e2548d1dcaa008b0fd870be34e40e66c0aefca28bc631b8fd8031fe7a5f39e81d19c0cca18f1aecbb8ae0c9a4d10b85393cb58412b565a29999cd039883eb095547e8cbf589b086444f0ec302236e6efc508c3c08873cfe13d565f5508790fdac239158ae65a23819f365ecbf4586eaaf39a5b73ce6afef6533d56d20758720f22e27e2a4be08659b5b749ac209910eaf590e5d1276b8ce3bcc49331fe73813417c0b1e6efbb2418c82724607032433a0d720c381474eba5749305d8fee1230ee0bef48286e602c593b9c5434cd5e4daa36ed48c47bb9b540f077d21feff369179c443665ccb98ba0d5a7b8932a8dd7d5dc020f6261e24fdea9c6611a096bbffc44aad997c7bb3ea32305a6ab41e33c55f027d3a990520017cb8cc95094d225c98fe7ffc21c4480967376ec8a22b2216fe467c49e50ed8ccc42fe9bb3c5210b086a9ff194074842affffda8f4734e354c971ddaa48f38fba1b7d5cd74f7433ef5a45d0f6cf1d99080c2a7c0dea6e8adf51bd59aaa502e75c96bf6d53974da31af027843f3afcc34ce1ecb81b614e9d2c382d8b0b6007cb4c54ec507feebed9992df8d1251c74e79b570ee4c268d8e03cd8573005f17dce24c6a52c076995e72ab5f72f290682081932e3357f4cf0659f5ac6a0d324c9f52811bb52c9cd0cc858178b9283aa53c523045004b2b55bc273a49cac17b95e36f8d39c64877530141c668da715c3e1b2e2172422728c1f7c1fdd3476d13487f193cb18c1fcef43cce594b548f164a24d2e0c15dd4f65614c0f0bed2597b453a9349ec79f154e52e9fb74eb55161466617943cc9189e681a3ca51150ce23b3009ae991649c882629f76ffa9161baa1d02042baf0d816bacc3d3a5c90279f3994d4a5eed4da6495cdf9560e900d7a7e582ccb92e543c3e42abcbfc2a8d53e9141942e3b58115b6b64aa654f9c8ea1b856588d464b9a6a642d0fcf4430b88637206758af500daa35c7b652a844edbbfd908aa93b7929248280dbf48cf1cbabe15aca02fe6d72bfe7fbac98a0032a32346f40b2eceb6de2be6e580f89a86e4eacaf0e952e508537566577dd6de6bef2fc93559b96e64a2a9f9334c3c26d0bbd849788f328429d3a8b26647c96b3d553f380619ad4f799bbbd56aff22ce8b61fe7c18ed88ebb5dbc4796bce01606d1eb762fb11fdbbd3cc6c55aeb68bd93e9f355cf7b07cfd05c2502f0ba792c7703a030c52cbd545f394108140cb9b5a149b616fb6a45dba59b5118b66cb0949a3db9e39ede0abc2e2e4f76cbfc32d7d98d36d23e757f3c5b3cd3b37916b2cc32e4fb4ce191c03edd7ab4f061c5f1075e59328eb696e7d000c9149c93126aad94e30884c976172e75bf25967785ebb5afa68409dae873a612a744a1524c2b96843de61bb959dbc7dc782b42c39171f60c420497db5f77bd5f84b52cc3924605f534005f8068d24c47ef82ed7cd4e5216501c3304bda3659906e8afb98c74f865dab33a700a6c8839e405bc6cee1c361a72fb6baf4b1183fc800bdeedbef141a597253ad54fe11d6db361865dd58825116b534ae08b07586371a3791fc62710c4356e2be12700ddfc7f3e309f5ecac4754a4a2cee266a37d67a3f8cf97c45d37"), + tc("103279a389e9d96ece9d7e2c7474bdecb7affa38a0964c9a032ce5d76f73306d", "4fa3df1dea75ad4b9c379206a95fed930000482e5b683fd2b17dc8e7d5c4bc1b73186ccc13c9ff2dd09fc1d4f68034d120e84ca73a00b71a3b46d1efc6ff88cf2eda65810b098cc5e651d9cf064e87076d5a871849f3b405d3d58ef5b1f10520a9fb4fc84a81a87b13dbfbf9d8674943e28c257e46d8ad7be1785f1dc7c9b1bd574ad1dda48f0255c853d2490bd3d63da22a8369cfd02594999a2ef443308fb8298266a11efa177102c75dc674e89fc9dcc1a0d3c863bc26141102175d2678eb6e13d90bbd9a5eb89ae8c0cb47d7f340d3d32042a2762bc9bf2b40eb40e87fb42610fe7e357051f01494704fbff73321b47301a0799b7ee3fe5e62200f397a61ed4509a62f7106ed0efb0abd6ae9e4a1fe9b02c092dcdc75015cf602f3b9a8988b609e6c0d1c5c3e219ff57875c2ef01615f89447ea602dfc94eec17a398c014bd346691fe209a002771dc8164422cd166afb457a8b3071282178a3ebd201d9b07b27e711e7ee7d33aa5210ed4e4e92486775d14a6ced092e34a7ac82670939948fec149f9c018fcaad3fc597d315713f44fc5e1725f448ecaed40e8d841bd02f1e81c019b08f99412e360c0bd378391c67d964b47f50c26f0a483ed664023616b0fc9afe43620dbe9ccfe070ef295c049eac754c2123130c6b2c0232f6403aa7f0dc35a5999bf95d34ad612234c6289277adb60e4f72ec2df570f05395b3be8a0a3c78b732821aa08927c524e15d65f66a3db8c1c96fb70bc0686aac310051f469fc5ef880c0f66947c1c328f97684ea24cbe63baed8d114f40507c2901034e6ab3893f366d53f1cfca309309218cabceca4722fa9ccbc7249b87c12ff8397f40487eb00082e7f551d27e301c3bc7b5389f7042534bf7e692dfea4da24f7c34b8d2ff145f54b517fc97134ec5ac2cb925c508d7a6bd01fe7b764648274972bf08560d30802e0eb7edcc57af4797bbf92e8688268606b0f1bc901fcc22136281665ec16393fa9601c4fbdb18cd1d1ee382bc07973903e91ffa87399d1141d49f4f0c064acf3ac9897891df10bca0116f2c3fef180fe6a8e937c478f2ef293ae9186dcb1f76b6e48101df64e57ea7c64c5c0025e221c8f5cba5cc92d9cec628140996b26d17f439b780f59a999301122f82d0495f8ab5ae1ea5790f45e992dfe00d5f82a7ff1354aefdcefc0d2d1731d22fa2b75afd4fda25ab194055fa9628381055247c8c7587d22e73c60136c4282452d47ae03aa035febc26fccd42a1cb79cf866db6418a49fd8261e877ddbb839cc39514ddb87a8a40d795532626fea4a4c35d13e028f9ed1bc09b06be999b8ddd2258aa0596bcbbf72af67e10bedd58d599b8d577a583d676bf5561f80ce5e9528729a92df578fe75dbc70474b75747a8d55de70e57bdd62d4344dc2115ed4dd62f1fc98bfa1e7421fc0700025c46d0ed1bef35c3b778563211b9fa9e8ba4bbcbf01c2fb626ab7ef325ce9f468df2cacdb178d36557cd85d542c067c289e926c1ea2f20abd329e984168bb6def1ddccf214dcb6a53afd462f0e7e7a19e8c88f049244125a6d7dd41e58bc9b2ff7fa2478df76af73090cb1ab59e388ba20e2c297c967737a1af61793b68ecd7439444c48e28e2d09c48fada5e0d1d15e5b340a52f8b3b854cca479f0a598445e14f53b3ba36891050c79673df3e2b5825c955a29e5c9a22f3991d0aa785718cfea1d2385f8e47e4a75acbc7988d0558d541d71c4e6c5f1cb15b60cea0c34a67bbce105d7a896025e0254de7d7af724c9027d44b8642192a08ab8e1ef3046dda6014df7f4c9e63c635e48ab2e70b640d480998ec9357e665f99d76fe5529ef23c1bdfe017c3a66cd4eb2ddb42ef85ea0cd65534"), + tc("13c0a87bcb0cd8c07eb745257c51f236c36c90a27a2f6637a86f39495e15b11f", "bc259972ad109bde603999728ef0bbf23a911829c1c910f0517ad7584aab80b27d4a59523db11ef7b86d73e480f82699e44e2e281172d41cefe2a15aae50e13918efb63de0785ba415c7da3746a669c7f6adac8c7ed04fb5f8ec70207615c5b6157f3bd9ac31478f5005f0e8de1139fdf40d2e01dbf54973f7225ca545ad7b8fac8233cc579d4e06395bae791a5e49838a806f69b57858612063935284f7aa2e3769104f13f0d3c531a3c766b1f008db437a2bbb041691bb9377012a9a7359ba410836ef7ba949e5f56f70e83c99df961cc3f49d4133bec543ddaa14dabaa54fb6e5f5263b83df6863e84179722577101cf3f67c08c753c5b079de795d2922fa21ab23e2437a64c62af634985266039f7cbf1e1d762df49f12a4ff7eb9846e8e5dcee559084f0ad175e0c6fca984b168b7512541b7560b68c8afd02fece115f0e9327d3d6d57bc28c8f914e9a5b31d822b48e0e4865f652e9313f0ade5665229631923a497d65e701778739c5842a08a09fb25683c38bb17eb95bab4b358ef5647bb29d3bf86e15868e02da7e0229e8213071a24b4cd373266563e36464c8b245de4a62be1af9c7022f6c3894de6ad8b9b17a6df84af65466c319ba43a5ebdbf24833cbcf17410e79637ad5b771b21576113b851bb73af7ba9fa19e5cd7a5369068a51f5d6a928a8306829b810f59cddfb42c3b58f6b0926877dd35c4173628bb554c7036779c000e80f93d26c8560de78057ea74eb12230c8310d577473d94c2df14577b4361b307d8bb6371ba7b3f8641488b1490b24da9cef1ffc732cac6f9fcd69728c1b7b4e72e8fc5c42b6687f32fcf7b3a90a460e302f3a4e33b57b29803c611ad93be3e6c33a8cefd2a5297c28249fb64ce4c1a30dc1f8670482cf6e220a3e17cc95adb289192ffcf0155f071afe473d5bbd4204fb4308cd5ce0f13088a2587f10362d86465c3aaaa730cb82933f56912be62f78bd6a413bd9842d71f125ec8a07442e1bb6ae8b9aada6bc47cc2ff79b680f832685184ffce05ef128d9d6462cf7728f2e582f8ba77eff16085c262f945e3fb06b238b4a5345d1f83373de40f95e0ae3c5f62ef4bec3663688294f64d15310b6ccf4aab03a358b0071114bb368ff489a0caf7b222c013dd5d9ac39c736fbc2f7eab5b4f5da461c8e65d959140bdffd2a871858acd3f9ddaece497b44565560fa96ca9b761764c6ba9d0888cf522410b605567d1fec21264ad40f1caf60f79452ca4f12616d6ad8ae73bb57112f6f77901ff444152bc287a90fd2c77187d50db1f64801b8235fa410563c6eb4e5f3d3d285c1bb2203c618d1f4df274f6c47a0cc6a892217c608cb3e9da3e15a5e059b6c26cd5357f9ae1ca6710e2ddc79b5475b42e8df2d9d6807692135b922611dd41a750b44be0e4feedd632659bf0dad6eebe9fa9236e84aaaf278f69da65428283b66e433387b3a4fad8032175e7e9c565c95dc94a53e8c52b3bd25a66ae5c6975f32c45276d6dc4cbde155b2b7bdb7e238d93f01b18bffbc301e0a8a7036a70c4a656c66b9a1a85b51f690f271b7ee68ba6367b4c512209fc24cd286f0ced9f62572e22c5c6ea48bb92137f7b819cf54f62e4cd1f851f11d08cbd09ac24a6bd3fddf65abf05cb8deaf2b94c7a3c8cb9913bd6fa9b68e22aaef51729011da21215571b39920fdd470cd2b4a0bd08c405578377c889418a87dbf14a8e4fde203a2ac855f18db0f9d910ca41aed5ac5ec7d08ae7f6abcda0646997b63343b35008ead44cf3a8edf0eb104bac1ec3ba1dca808530332d9c249c6271d25ce33b536cba3d2f8d6c8b6a0dfc830cc426fe13d69acbc24bace7cbe27318acba7fb54bda4104d07561b5bf0d8f0670fe60e6389b2d6e96924ec4e4bd827473668d4f28412123c31cf49912e73ab8f40cdbe3032be127f36af2dd75ab2d3de8d2e017470a7aa3cbbcae318b20d"), + tc("5c0910b7d5b5f188651d42f77a33c61b870ed2bb0fa602827e1406e78f6bbddb", "824a155d7798e12962f4464ead9af132a226da4da6e899629efb938ea29a86937fb144866d8c7b2df9988604ced96892da482d4d0deb4658844f41835f0d9c20c796929c83cfb49113f5d95a7b66d9116a4697bfe335bc83629678cd5deedb7c9f8f11dda079bdce8c2719b6cd400c02a6a5654f19b6d568473d37174f62ab6684c9c919c1cfaf35116ff7ae4f5b0f0bd243c74384c2c9392f1189b2ad448b9d7946c2ccad04d2f20cb8cf1f9590d1504b1e1b1d74a04424bd27d0117676b8c823022858a4c12aadf21fa45c99fad60120b24fe04ba3036b80ac5f46e983032b6d78830f51e65a62310b9058a4dd3f3f8f90ed5311e1f47987e0d410f78b2a76580fbfdda0f944bbe466c8a8170b5c77e8132ccd7d5e9946a5fedc73ab9465e30beee9cfad68b70a2176393ad8a173cd9afe8f9b33023e852876824b720365e688b3fb4ba925040385559fef7bdcfa7dd18bbc31b24936557edd679b502864588cd4bc270099a30dc3b3ef7a392f3e33e21a53fb83756ee371a1986930c4a34ae1301ec44e189e23adb7eaaa121c6d787007a44e998d112484019f7d7d8e16985b039c8ce01ec8ccefb647f07f9fac114babc3d831afd6f08597222a4da8c4f6cb22c673c0437c16fcce7eeb64e7050af9bf41e95d3a0112fc049d405df0e322422072a16df248e10aa22ee5f198c67c59eb8198421da855dbf2c78b36352b1373f7f66b5a5bdcffd44c76b7cdf56d894187de7bfb05c0535c3d2aa491f37d35c3177e0f312a9fc7f1c6668ae0b6b4b6c195eb4aea219f147a9799a9c84449d7477fceeaa0abb836150487abee7a1902540b905e998a532add68b2d15a1fbb63ddecc54c8c206ee655353c7d1db411bd33419f6f905bea567f8d67b5461f15acfb8f8505ccdc29cd708be679238ce30ea3d1aa183befc35938d755c50963ff9c89ceb28764a7ba8e72a2d22d168172dc5d91f4ad5980b12474a96db75e1f7a9ef9cf1a0318d18b01b108cd165a3734d671d436708ba030c1e860eee860b40e61ee5b72a4a98aea24ac343bb6d4095f79fd9f1c8d64057b6672821b96f954df23cf294f516bdc1045170e162bf58e75db11288b466a4b83125f1b54286f14ba0b55267397d9605a57f16ec357853df6f3c8943a73f7d20659ee01b3cf70bafe432089bfd9dce1e31d441c174f42d3bc04084705d0da308c21f30875d68f8611b62f49af1b937319346dcddee9632f5c7044107c3c5b49cd59169008d497c4bd9cf2123ff7636a5b20501ba368ac6d18a32bf798419bf93bf7cd7b1e998471d4c85a2bc50e316e89d17acf327469f1f45a68fde54714cffa976d488989f65fb4e8f9f85d43010f982453223bd060babefdeb9dc10dbd28cff842366dd55170c33eaaafe90428d9e632785621c5f836d118c4bb97bd2dfc2a734c64f614a9fd3179512720c9511a1df29ea9792226fa5d3b74d0d29d4741edd5821904f52aa7961d303b51e54a6ccb80bb851ae57db1f97b656772897190714a5df7450d7eefb982188131d81c6d273021a2aaac777b0083f0cc363e619a1faed4cc2cc7f28e24f682c9f9eed99e09e19a8200e68ab9dcae56aa92c99823015f82c8f90a40df0f729888c8320a7f0b1e2f6155c82aad1b4aca0870deb69f04cd137b556b9919bd920db20a1c1dcfc45236e6e90fa3054a8e29211bf76b13800f3ac4657449c89fb76d32892024e6fe7664bd23038457fa00b1eb88334f4c8ad05691c8e3100f2889c0e96a327c9b36dbe6d9cbfbf9de93adc8c17864cee6d30ebf0bdf8f6b9d65d2d6471d26334089b40c9b0edcc3d0da4710e76b28b392f7c3bad6f34c776a72ab211b720ccbe60e844cec4832ecb8014bb236e0af9a39912ce12a5386d409f5117c01478d75296edab33e1c5273718709487ad0ab5b180a2d065c31dbc1e0737d9a800260c71a0244f19528e79072d8b8322225c6c6ae8471068bfeb61c578beb30f9ab6a173d70373d5f28e7c353fe723316143b2a6edf78eabd60124138b607d9a09d6ace70882d4303eef2c2"), + tc("3765ac7f690548081ff4ba51b5dee24a9845f9128459d3e634801b9c38761f90", "fd76a3e9e0e303cfc3c2a84c43d19b6069167f7c7094bb379ec929aaa4f19e2e1a77e312c0cb08c544c95ebbabb5988f451b01cf9214b37c341a527ca584840111236757108ff138e364d93bb76629c095117fce458ec03b87bf19f8c9936b3e1c4c79f9375815a658638e93c5102c66563f3a372e6348c6c1b9dfa6a4e427f418cc65ef67ee5241a85f7896b1a0e941453916007c67b47194352636df36e2230f4502b2569aa8afc79c71b8b675805375e1ffd2764aa8b8d58ea9c9b580d489b09efeb1d707719f3aa99c447c112cc77b3f833989bd2df98f32f068b9c0f7eef240719817b11ed583c82cf3bbda268d60b03fee05797e3ebf65c153ac84e899e3da3432880f18b09194a1d170dc23a1d17f53de6f3109ca8a88e1390f0d49902a48520b9283c06746a55b041f4a7ddc0727a08dd5717fba5584670cd0afc7a9597749c718b8fd11222ec41506505ed3db5b8288fd234e0a9237d6715f8996980d985587a8645357b0f53c480f72c8065f5188a0e132ad315f3e7c12a854e93d1cc2fe9252fbe68b3befbbeace38531e5bf5f2c671fec8ea58f5e31a4bb05b8a6ea87ea7bccab50ca65fb2a2f312f183b5b1504ed04739ba854ab436b8fc2e50c3434fdfd50682fd79b19cbbce2c76ec1ffe0ac9e54c2f9bf51497ab95135b877328868d6ac2b7f764fec866cbb6a53f3461221240f1bd6d99c38df6121e3751cecb1e0ae1a02fe054059b78dd96fa0cb3d1952a80b8ab550eaa82d7524c02778416e035561d31ae99f2ea52ccd10ea09ecd6ef244576d18e3c12110e6d224c3d296a8be95f47f5a3150b32f67b2c18f5bace76dbcf9f4a7f58a818757ed06eb3e5c987b58ab40016be4666cba843ba4a09c579b2274130a8bdd7dc37557cca365302aed9ccf465f3f970d9799646cfb9b2b69218045f6ddf5180d4e5d36a0f63fea6b71e60c200f3418a8fd35c4f2684c81e3d715362061de34b4ab7a118249d4e74ef7c6898073014120ff2d80eeb2664e47c2349c44905396f0b9cd0ec343d61ec4c6657af7964ccf384e83ae0df1736435f90b766528efc69d9ab204d2d8a6d79d23886de971852cca9283ef87125c28442eb082be6fe68f45f1b3c440c3f93bac49ca855604134ceb4e968654b4c00d1e7a81959435fa36d0dc43400a8ddbf26eac8714223b4d55af42e75d3ed387e52e5222b3646bf018c79486789a672bb826841745f4769a6be8f467ab2294cdeca2d974e8d3fee417ab6d02cf16e9ec3fb22005a632cebd64b5817286807405b9d78050e61b5d975989282d7dbdf2bb81ad2e15c07f9af22de7b80977e3442c1f119312a20b461261e8b681ca612e7c7a67a2076781600f057a6eb3f8a0654fb982771b6c661954b66d96086afd110b0239e0bf9688004018e418ca2e3df99df01405508b8d69cd6a75a7fbdb52d378c2bf798045d02a9db7b1d9d275fbcec5cdab923c57420aa26c31c0b632d23ea2db8c193e55a1df6ce8fe610bd91c4ba81a41c80cab69744b8c88554f1fe8cc14a0c29467d7ab4840ac9e861f8a24c953d68caa583291f1ba8603eed2c843001402aa2134a067eb6ee2830c4972530a5e4e2628e63266a51bb3537ee48ebb6de869997943b08511adc9647e8224d08daa3c009f4d65f0c9a1af4e8b225a30daba352fd5d1f6c36683aa4f44602e32bf04484a7ce88709aafce1f93eae095fcb89a1e32dc92c4150592ecfe99aa59938a211fb13733f30ed476bac2db34f5f0da67a609c49cf3bcfbb90e1a7974b33d0252c5dc18e5c67bf5c0133449936e387b3c35823c9eeb2e5bf6337665ad41eba1346e74362d3e5fd8636ba54c4adb75448d7650fbb542684fd924f8234896569aa466622a53c042ca843ed6bb603f2d7b7058dfda13983ed913ed565c2e1078f6731c76ec796cb41297cbcf3bcf442261d2293b813703ae9926b22f55e33af4049063ea443d230af25597ce20827952749d020fff07a58aed951fd9ca9b2acb15346d513b3490e2c1294605d91ec67cc73f76a38ffbd86d79c55c86515164a8beaccb386e75ea181c76233c18549d41d5ef5f4da25809dc71b5c3f83a1dc31d4d117a5751d77380b5419a2a4a8cfd2391dd97f88c7c8f39a22ea0ea3"), + tc("cf1a67e45a7c51e5d0741bf813982721916a47469820981b852dcf0ea2819a89", "af033772478b6c7a405906811d787acda7b775c28ebffa06aeab37ee363476239a4fa0af7afcedadeb2ded7ca3997c82a54d6f0d1016c4bc2d182f6116232686f7b5baec00759e7b2acf0a2f9dec55f3e3c702f21d760a853283c75b1edd75442e30da1de74197913bcaab1a619e44c65f410e9b8f235d68d1ef0b7853ac836259c76b4a84f402c2e90f5879761351dd281e19219e274ac8d76589153fb5e687cc0de9b543f763e632a465be81f2fade0feeb96d4102bcfa276d7c9d06945cfea6c82cafed680bdd37f6ebe9b8013bc2e7dec52a87a1c539d8aa5ad525f9405b1db1ef942d4e98f40a01c3e5e555c92d18f36ef4dd8cbb5aa7207feffaf28a5762ddafab4c841063c64f0fbc32aaa69cc130c9a2add78c795f5e417b57263909526d2a741276e44d05a3c175b51fba17fdaf7d83effce3487159cd92f40a19c567da4e667a008c5ae8e6e04f8526642cb4a2f4cf0a07af44a65d4f1d6701d6e33bad2b1ae2bc18ef4522f64f285652a1155487b8a49e9b0eb996567fdefe07b8ef97da55c493448fb0eed532088adbd355e4966ed5996ef88cfb942543d5065f8826f85618daaba9f1ceae1483083cfd5272be07faa6270d51b7df74258ff62c20a8b692a4fdf0f6955c1ffbb48c5bd5026bb257972a81849db7b4eecdac9531c2ef50f915119086d8e3b52191b765d6e38dda2b125deb8ba9bb841460b9755d1fe38c1765b72eff98b93dfa87e0cd4787b74ae335a9a61b934e258ecb764284f0715de438e5f2f59639021ef2b21fbf49d53b635f683e3711a2af1d1e2555f5ae93158bb5f6670ccc0c99d23ccd4d228f2a6374bc7aa2c321f03a8abb4d9db673a63ea1ce5d5ba5133af9ebdbccef97016b813a9765b9ac1eb51a93d7760b36751c766283ab13e773a128559155455131b8466701f6c16acc822612c1be81e14f5d9bdf8b9dcbaa7f3e10c35634004daa506a26878ae7510ddac3fc9f8df0f52e0be10513d641fda741fd265f9e0e3dedeee45513d188445890c384dda51ba4cd1ac63137550b595fb2c3aba5b60a798c4418c9402333232e1a2661c8977dcce606f4ba89e83a1bcab88ba6f3db5934c227d3ca820063a1dc2eeb0c1822dd2cb89baa12d81bebdffbd054dbc4a063355ed3825e8196ca04d6d12d8c8c521d633e2f214e846853de5d0877d41058ceb77545e773c0b5d5155d4b61b4cb060dba7dfce84d3e25f39ea6b95cdf83110e9c1051d746f0183c01b06e311035a28809f4cdda0784d7903e132de91e0f426d937c5e98ff8123ad11ac3e1133cfa6298be0ab58b5df55d6060d5163d847ee5965fad60a8503c3bb54935856544d57404215fc843d41e5c73f1a8e500f0bab3fba3fe55475f89a967e7e07ed21d6d821a797a9b3e9334168fd6d1f19f312a5bb69422cad506851770c3897df838303b2783f86c8a506ee326ee4d8c492cd1150f4771a533440e74d7e9080193f6d4b44bc26658d8bbb53a132afce1899224bcea0375a5e3e563fc16939bd3ec085d2a5364faf11eda41eeff44409c5100f0ac2ef60ea8ab98c35f9fd65fa61d6a60ab0c35437867221ddb6a87702ed44e115baef16f25cce2b11b8062a8c2a06e34c4000eae881be3671c566b4c2b52be284fa7073364b7e9a6dbba8ae7ebcf6008491c66c7ee49ed426e7a3ee3de255b0b24f88a2e738177cf7aee4ffb5f2ce4054cab2378b0af8a5a2be2fba43e5859436a4f321f8522c7507538b74d0d3e2b1b94e92600d7116ba319d886824908b04803531914ac7a8153937fcf2085968a7b924cfbc49324bf0144be37ff39a235a151873f9bfeb57366bf9c8a0a9dc9c2e847c7b87d644a0bcf091f68e463cbb51660a0dc7c002765f6d15c446a1d84d625bd10cc97b818066618482803ec0c73b66bee4fa71ee72add45d5bb671fce5b278f7f19d909ec6e92019cce4418796ed5b97e3a7d9006d2c3d82287c659a526df519c11b21dafa787bfdd03f4a2ed832efe8c9206b59290cc3d1ef74976e38b05d2b9aa31c18e66ca7fe0cd803eb583f4a6adc0e90a521492efc83f9e51966fa69684edd67f4a111fa894241e9df30fa8d2227d7db2e79facb7a2d9b94d7d5b741cd9224f96155be988e03205d2c47541a001e2a7eeea3be70dec19bcc6125dc9dc0caa94feb638fcb605a46a224fa3bd180f6761d4d9289db2d2e762699583f0b864d9081611de2b1c032ca00c7"), + tc("12af510e5211ca3e65985ed9c938b18c9af7571b5ab066c9c18c2eb513a45e25", "8f1c1e85393b9c2a2b593363dceb818b102d0f57d4e1d31bd33c034dfb1f17e2a9db960d70d2fa0aaebb7b56c809ea067e93c449f4026a258ac04b6ce809a734d15b7fe13e4faf8f2614db9c3a42435fbf158e3e6cb30aa727272a4e323df21720ad65d72b18a4a6baff9dde47cabb343db282175d85ff51b34a197752138af3a017d19939df728a09c8f28cdef4ce5ff540a6c9968f4635d8d3281f1a5126fc3a7ed368dffd41b5aadfaafce5fc069056854a78054917d39a34945fb5e608febac88f0a5360a524d9c887241d2953bd36e46db75a458f4df4940ed71110334407dd0008a6dcae7d13fa5cbfcefd6667bac657dcbe1dfbab483daec686ab870eb60225689633f71f0d3c4c870bfe6090298b372b73d5aca2225ad724200ce2400a77d4788ee6d4b560332a020da035da3d520aa9cc52229329a81847acf680b3afeb2c1bc84b06800af381cdb9aa0a687c50d03029a0a6b5641a8d3a8e40fd9318d3c5add51a4abe661bc3c5c47d38507a8aa7d6275df3082633637e5700787714fc977698edd2218ffd47a81d428b0fd61be651478df0010dddf9363e93d8b37b3d8e1b122b76e15985d9f4eccf55936d918f0cbe39734a1c41b33b3efd34d845de5526c183d918786c8ed3faf1deb92e85cd6fc30421d209d6cca47e9db5341251e253e932c8f08628d2115e565c2d581f8c8955d71936dd505e8558dea1409e84ea31ba5355d7e2d011f7df83acb9ce0c6defa51b16185a66d3d6d1d1d7142168d9b8a0832fd2ecadad64cabee8c79328b9513328fd4c7006222685af6c39ab5f081edd49154b5a25375eeba3f663f7799aedc40fd74748be956ec66f77ec97457d1c08c4a03ba324365499d8d0afe4a45e7563bc0e4e33af704e7cbd8f85ce14fe1f84e1bbb871d361d46e0eb0b044d4af33b8672b68483e7187f1f89bde3da29c405029dd3da3b2eb8c248e3235cd8d2ec17271ba5563a09f5daaa5e9d118b1e2aa61d232f4ef928de1a10c786ce3270b889b6d859f35ee00da10fbbcde01a9cd472717e1bb35daac57531d44c0b62bf86063bf355e0c08e3baf6499fa0ba8ea96fc338507b675d1a31008f97987b2cc366e715f43446f9de4dc8c0aa7d81bb3b2e7ba7236d317b320c934ad80a7b17f0f591fd9dbbdb1800bc377b6b04dff1d268f3f6191d3a37015624a8b4efddc1f6882d6679fb0fd92bfc9526272cd6a2cf768e6dbf3fd1732f6b46cd4cde5d07b7aab467fb8716e41ca9b8ea9139d09701736b038d25013666a8dbe63e0b60f93f6e77bd27055984730a6cc5791c6b5585a1a51f787eaa42b3db7ce6159471dc5454b7313aba20f2df104bf98862340fe0a7043a555ffd07dd1112f9c1627d541a896bc66e78a01af4ac308003cf970a5c11d2880e395bd92d455960446a67ccdcee71cb6e76f9887cebdd1d5a60131d2bae7531e14f3e04a4eaf5bd366d4e1710293189131d2b9d8f28b8919b27f54a01fb804c2d1a66b49366c59465ede6d6281ba5656e0bc14578ed3ae2189b1dc73db53a217f534e27543004af45451e6a8fb964b3fa2c8af0144a3b22ca3b0d5f382417139dd8b1e62469aceb1781acb4986d9725bdcb8bd821a2c4490a6e75fa4e0540533c39a9dcc8697c169d535ec71bf1db04a6446ccc07cf34ea440b4d45cd8d664a8a35aae7a16ff8ac4d2360d400ea19e9f000e60b92229c51bef3090ffe9c5461fe79d395eecf4c11aa546ca00cda99b5cd5ce4f41e0f00be6c2ae3c4eec7df60d7e760cd3076ecf5fa61dac163dfe102e3917f692b36cc1bcee79ff8fdcf8e18ac90d4ab0069cadc2ee6e4a92e52970e63152e77fb4a047d20e33526a4def63ab812a17f09065e60aabb7a2b898cfa10a35b7b7ca9fab1583f357894ccd8aaab08c6d9f6fdf1b8eee4d938068d35b3eb0b1aeac3d676ca5034448c6b07df0c9e105890912693a84c981c636e7fc8d31b19fc2deb423f13e422594bf34f74db5ab0806f9f2e7c0f224f37e6c43cb192593c851a7cc1902741b55b86365bebbbf1188005105f492d43e0a9a07c7af5c605a3c0569bb4052697775a9905c7a0233bcb9699e6bb0982ea7dc252404451ce6dfd1a47c298c5abffa15041ca28157fcb4f484772767c025d0c4a8e89a1f59517d72693a45d13998816698ec21c640eb8e9f75d401b8118aa3a79f65e2592b50611113cf405002d6162540a0ba003d409c1d58e81d2f4a7175747eaec68cb60a854580b7e9930524cda26e9b74c488f98ba2bb08b06114b8d1f24b51f5b7abf7e456178ada8c77d9ca1a932f85df4db00ca44c483"), + tc("91b0494b3658b10dfed194a743c78cebd7ac31a18bfcef300bea5fd268719eb7", "a8b748dd6959fa8770ad6673b8e70e35748dfd0c3bdedaaa467ce4e502eb866d732683cb64e78c523208cb67dd9a6f3d3247f5b413b1025a33b28c63b42802cf6d7426db5c7a265fd820610a14453c2800b1f5d77671b15bc792c0b0dd64aad3532721fda0ad87ad7a57d199dcf5b2ff0cc84a7f744ea7369e6f4d8f0f77daaa7da0a8255e32c473d981fdd4e9c855282ba0d58897ef2329655dfaf87537cffc25a22aa0874ee8b54a370390601a5492e7634fef8e24abe3203550ee8788926f1bcb8b0df70a0630357d991bb8f45df6a03a17c000e97840feceadf945165cac4941cf363db49891c86a54fa81b486a563492aa01f769a01bbfc7e8697b12b28cd1fcf608b0da5ec7f5b65369364cb752c83d6891b7d870d52b3b65c3696043474f91a0345ccb7af34cd5b308b1dbf2332099f671a8cf4d809e4605628b10b275fc5bad4caf299bb5801124bc86359ac83b33bd84dcf6d6db41917a9a217cc682aec1859a427a32bc1b11ffeef825df451c7cdc033e9148779f753cb4c05674ede8edceac80cd23df372464f039000fa3aa47d5a99e14894caa2ff5b90057d3f411b1459168a85c284d5c2814811baa7700e5ed96e4517464ff1757811bf3691ce33d02445980bcc40e24e1f9dc6f9df45342ca4e14e013c1fa06676d582555ed55d069fd2db267c92db577a556e6103e69d1d73335183e26ed88a5efd87ebddcaa5927b97f094209dbc7fb337dc0f7c98a975a74169600f3f1a1c7433ecfc74b319ba3dd8ef69fd1cc6ce83d1b4a0124969f6cd24b7e3d3418a80c735b4f7add091882673f01ff20e38461a2b389dca81e5e6ca2213af194895139cdb02130c1fe043e58783c130c36bb88a9af656e406dca114e96f2fc9f00eaaf5ca9911708a4a2e6cfeda2503c49b644dd2562637134cfa33c50c62199e99a8a2ecccfcc3c6a1fa62bc1ad9701b7c9ce14824451059414ed52ad36a69b3e0a4fb543f40099c437e89fb12f6aed816c9af7778d91adc6218a113ad9c9c0caa4e602806c4339eb456da4c092180fc8a91fb9fe305c95cc4ae5ccb06ddc64f4127906be28f7014a9ee2ef9f9f075982f7273b1469df14bb92cf29bbdb487b8ddf37f5e10da60c70293448b7e8c5d92148fb466a28a16f2ea8a3fb12c8e8678bf4d139aa2db2f171122e59705ea3409fdb324fcaf27909a66ed1cde7b92b949e9073ade4408e9d5537d3b1436853a0294b77564b70b1e43ab13327d617e86971d009c7dc1be0337b185701a0521daf4940bf5a151c6d2efd077a3048eb7b37acea084e3a3aceb4f2f44e4b42e9112fe6727f5b65afdf28252f341dabc9fc8c8d5dd2fa6f263f95c00665854217e915d842ccb2f33310124e5b5792bf0863e2c1084dfa511387aeb1ac404d32adb5c0306efec84688a956c7041c724e8741ef49de2772fc3e174e02f81b7d970e5012913dccd2cb0925ce51d725e8900ba589a970f19b558cdc57b30a7db60fec91ad0005b0defc05dfaaa2613095f53a478459b4e89170fcbb57f60b2801ef87f0665235c004b8f61a166dd5885da1b04768c08f9eedeab78a1d7bb49294ffcd134522076c3e0b0a2a8fbcae3d001f4e1df0de1669e533f4c9d95cc2ae0c42b67a628d0871b83cc599a8ac4828fda49ec2f9c057cc995e79ee5fc51905bbedc756a7486e79537b559b49ea57e87406c20ef94fa73025fe16ab588591d8e89c79c3f914f97ab1542dc6f86435f132405ff49463f6b836f8351be8c1e21dd14b619b5193bcd04470edc4b863174e4f1b20eda5c3de3253241d61cc17f4fe1dd6ab0a7cd102258f6366402b44da9d7c415abea13411a89e77906ded43075135787820692a3785d09948c5e063551f51a0b8fe7ba9a070a6e0648b07c9cb99bea7593393692ea6a365d0b2576aa030bb4cd288c42500c4297cb073b0f7370dfb7aadbd8569e51ed6a9e74ab58e647879f7a537dd1b3e4816ae90f6332a219439aba6c9d7722a30c932a2542f257111450fc270971d2e6c1b9ebb34b786d215a01e89d536558d904422d8e5a5b3e2b2c54da191b947c48ee0ed296355be52ed192da910cd47d8b4fe7a29a23ba2fa23b8c6cbf4ca572b9a72ea9688ca0dfb2f74ae6d78c2d96748e587d2d1f5d3c99ab0d98df949456e067aa02b3b43d2916f051d636f28fcf971950e9d9c1c0c94b894eee76e9ae942283b516c0e4a10527d0f120e15caedd19546acbf1ab85caba764c74dec2ad190570d7d44b5dd637974e8ba26b60f0a408b7c56db73395c1f1e7606016b661119ca4689b12af8f608a1810703cb489beaa12cd72cf37be43ffcf2f7f8c21a8311a527831bfe5b7cf5e9839ba138187babd9adca84c35b9aafaba6bcf7ed0d22da87fb6eb8145591c9b3652a4eac256"), + tc("8bde8ef3c0648c97e1423a987680a99c85ae871fce8863293b073e41108e002b", "5b4d2985e61aa7145b5e760435ff0d8e12b546ead825400fa6677b5e44435593a38ae2fe152d8b55879a6fb05d72da1df4f5ccad61712b8e76bd69a9d3ef09da0b2ffe0a00fbcffc83263d48e3a263a97582d20d9195b23875176ffb45cb8a942c4860e3ce6c26437826ddce61d4da4fda3878187d551c3baab37ed66eb10d71a18ea6e48b96fde7cc1cecb821e7ae5be07e34c6f5c20604424591a4eac3728ddc786e23b07006441baa56e174ab4c5cb417008a3d6656abd07bb71787cc390f4e9004476d0dd9e9cb33dac68a1c6c0ce45514db4461f7992714b9dd28194856813c9f24c1dc4acffd0065b9d50c2662b41633472b25c358dab53f85198d76c93aa20c7483a1db21c373627538db87035eed0e78baf5205a2be0cab925a0fbcf141543b04fac7aaaefc3a53f64ab3a71004684a3507535d2530d8febb4e7909a3737f9a4964716108b19c491089a01249e4571742ff85872e9fc8e8e7569ac15655ba084ef6abc582f6e91fe2309d5e4e3fadc554dcd87e118262dc408398af5ddadaef2cafa99f6e033b3a422521f43df2d720693124d6da2baf5fe2b37e775ae3adc4610807c0e9b43abf8fa71ab977cf9d3edb07c5644f8556d849b2c40f523eae927bccccf9fd7b76892ba4fce9de71f71fa7006e774f7eefc201a5bbc694008f8076ede5b85ea3fff1867c8e215e192eaa9cc8883caadd68be57dfac989b25d92ab8d53709490c6766ae8c21720e6a105888c2316bb301d392e8eca464c8694300455571fdab6ce25bfe3efcdf08c7cef99860926aa8cfce6803df0a7d0eea883c2a54862e0b40135c3b3fb9d0eb218e5e1957e45fa64e2f7f8f2e294edf69f26a8d65372a2cdbd792b7514effee4a38ff2a78774ff8470ff3af1a1b21d054b978a3f8e16d5d76199dddea87fda4c290b6789067362fa6e6c37ce6660ed905ebd3161b9d05fcec8e132956885518ebabf8525bb3c356301048151bf6daa28ef6f125f165a79b8ee314a7fe20f25a7e2ac8ca3655096b1e77c1005582d82f28ffa108b38d1c2510c40f06a5f90195b8b2367313523981f2c7af63f536dd3dc191a5fb0984a7ad0cf994dbfb8a5c8991dca74bc1848c6cbcae6eaade0abc4a45ef03575e82a2c3e0bf5efc19fc9f2eb36c07e42693004397459d555f0d6f267504eb35ab798856300d96025a1a017433cbf492f7f3028381cfd1375c3d2c5b71ce285fbd1cb22987f5a2146043d2c1f26e6e937849935b6d6cd89601894af6a0a7c94fcc100e8f167a7547d7c72a3b425fdc0eaff68cd3674f9c07b4539bee7e885749008cf538dfc3657f3708d778a8f39e5d7b13ad8ecf84dd4c0acb65a4134c71dd590aad97d6eefd015fde1eb5315038b483628ccaebd095b1b4203b66095731e6292bd806c6dde417ce9b564e2a7c9f69893f0b01fa8aaf3712258a3bb070cc502063b09c303eea0a646a204e1ec0b31abd6ed56f4fe3cd7f2694eba27caa4f581f31254c420b9a51b4339fdc813a446d29c61cf817467f40f6f55eb07c0c7a86182a1472b0bdb9beb874b2572a2a1ea65242ac5ee41025dbf0d4bc338f09146ce59ed656f93c8d87be9f0d73909291ad1336103e05fde9e9bcc77ed75220a2b6339ff6cd8a388153f55c9ca03be9db5da242b9e9f299e23217bc937adc6fbba90980a29b619988360301f7d8ddcf7b8fad0651f31231b816620a295d1d6c6c90643e2f2593a0dcfb700c017f0a88e03d3d6871ebe420b7f435a58407c667ea684d5906c91d699d0f3868294432fd7fecbec527d213ff7d94012a52c9f818cc19b3ff9f3d834dfbab4b1a6b069375d5c3ca6c1704ac5cb391a3f4da4ee3588551633b6b01476f9b13f1b36baaa3bb6b5efea0d9d150e3325357151071125847038db3ea6eca114ac55f5db1fce8089e18d478d7ae82d173599a02dd670b46794e9e2ce91baf90dba5afefcd1babbb8ea210640e14ea8e085a404dbd9fe15dc3cd0bc3a59bf997bc6a10dd4ec8970fb396505fbc17fa5d61d90c989b47fb367f7b2eb17702fd039fe16ec8745912003aaaeda09cb644449dd85ef010488fb0fcde2d804a0abb59f2c4f594b3466b626205703507701bd4fb25af83343aff8a09a004a559f750cdf6b2adb2cab16e2aed2bdc0481adde1a8a79513e94e6f959f4e4265cf2abadbbf173a897c50fdc2a219d260afd0c3475148c645e65e343d4085b76cf14ecc839e4f7c725130777535acb154f74b6738fdc82327f0be5794d630cc81b87d5a19637468b191cc2ae6b67fd5c3990faeed6a05f482e5dac91daedecba379fa68bfd85a218275f44a8011a74de303007e9f5e955df052d67465d4a1c1791b88c71dbbb6d14f3cfd49f414adc26cf8fbb797c7f4d65ab1b260c43d0da063837877883e87859f99a36ff2185289fb3b265ca1d017c731d6fa6437f954e55c4e7b90155156955d2495c3305ef196f9bd62204bf18dc1f3e8b6be2c38956b169b75"), + tc("0661dfa666905518d9a67d282e23687bc39a92c274071c815798b72e7b29a050", "523de8b1f4cbb65e81ff0b6ccd6eb8ef0a0f0a691acaf4a77f25acd2d66ad4b3efd25be70308853c094412a518a32020e3020a9f6ab32f0cd60ec0d7a194917d6c457b168a54a4b46f7b0d0c71bd61cd202f4c718776a701e0770b0efa05418770f98e4e79cd066366fb3300e8be359a98b82b764bc2fbbf59c7e8f94a157b01c6c7577b1428138cd422bc47330f8ed99f4c0aab0d984287445539839389ee08d6345108af26aded0ec1d7be774cfb8c5205dfe07cf6caf8c1afe37c7a2e4fe6013b93eb2463de4e0971c7178d6a76b16a0e8960c984ce8bbe71b3b466edf0445b835f09414d01f14c7b6167ff78ff118127bbd5f812c27facd57b3b120e2bcfe87315c7a92b82ef5d50ca14a7174d1bea7e056523e055a6ae42ea3765094e5544e5ed003c989c2f98f38a17e3dda74dbaf9c669a319638a2698b0e4a611480d8ad3cf016792ecd1034925f42b9811a7214d623d047abca31997ddeb03275f80dd21f40ddc80616e7ad3d481e8ebc0a1a6a398e16a78369215541ed10b75671adeb1aae6e11142a1cf665fc1b7332dfbb0e10c21a2b48f78e57319ac9c58dfa8b1c2548e2979ef1accfeb215afcd6c2c1b46fe97dd491758378330effc7283661d2cb84fa05281e9e517408508d24d042e7b9bcd34db87ce972e4cbcdb98615fb93093369dfedc782f44bcd03e81cf93051318b2401ff29f753a264bda65af199e3fcbb8b5d39c838a67d6c7a3db046dc56c323ddbb5340cbc229e47cff8c9d29b7a49ac0ec8c1440ae498c7d150ef91c29bea7df3efcc2871a13a1d72d139cb4603d9fffe85f6ddd544850ef63c3944fb35dbc00d4308ceaa6394b6e23f650d323f8f7ef50ddb68f1486eabf989bf44451f620ec9485c0b52d1415d3c909a2cfbe9d77db19d069d33baee4d77292e63fcbf65c1eba24bffddefe95211ef0aaf8abfda9f94445e582976f986f5382cb669506af2b4a5a0c43000a3c72c5ca4aacdc9d3d39fc5c492a393b6c341b86dacbbf6ba8b465100cc683edb2d9b9f83edf9c6a32645f51cc79adc22a52a007baaca618be35e356d1fd1cfbda73f1ed09253039def609450fd2d5943b9cd49cbd52a318ee3510d7cf3fd8fb388ac6cb9c6eefef3d3cad8501b91cc04a888d33e16d6a4c9666f5f5f3b257193f2b46dedde11842909d8c48ade57775b0b272e2dc9cef1a083eb2ce58f4d1f211922fd6aded1b82fe6f5b11251cd396e5a3666ed9626036e4e356231c146bba0a91afd3648eb7bfe0b9c14f15af2f92309826f468945cad0ac422de3d6a773b76178422107ce0270e7f580b5cceba82ca0184aafa8341141e65e39859885768fbc5ce63b965a0604b659e71d9da2c7a43646088d8071d76926163aafc69e25355bb0a222b7b2da9f0a20c021adc462e905a9c3bf31c16d87fbec3f014f3957a720f1432e1741553092052fb58a198640479abcaa51b104cc93e2636e1460643ea812bd44e819c2166eb6b349ba5bdebad59078910b5c22a56f004b8d9e4b1224d8d204b48abe7355548a402736c5cb110f3a1476ed631ff168f4f3efd89b38de4751536548647523d334fad7cc2d142973f2db3c1fe08fc5cf83f9f2bd2daa524b37864816af29ee05951fa09d1c51d9d14ee4f72fd7bbf18b1a724ff5a0958a063947c430142ad2356e4400aaeca442e163372a8f1cd36e2db988e7781165e5d4e7074ace40858e8370e883694af09977704347fb735c8717c42bc4eeeb2aaa50dfe637c640909ce379bfb9e2608f88751377038d1669f248178ad580a908d7a1b8dcc7e53e01801f1e485b5893f103f03e0f53b2b1440be95644d85aa7f6eb7edfbb46652196695ea23c08573397b111ff909025e20c5201293b4d223bf7aa01de7cb28b94714370434b9588097e2401b62c7a0def1fbf89809e810749fd3ce9ec3c07ce4bf4c43dc966429b2beb4d711fc6c448a12097b36f1e6817eaf4937a983f85d9cf3e62cc1b2ac6ae1ec9eaa8cd8ee2c3322239cfe5db3d4e8786282e630a7d259c2fefeca03031c960a66a71e436a3ed6f2f3cfab4bd77c660d14205abf606fe561a346f7d849b69475ac9f6822d80b9a2e56d5d495e4b309b0ea963c9fc5c7ef94b217ee5337989afbc7107d233a8b362ac27c4f69df9e191cd65ae97d6eb9e5484eb6f10349575e4cae51452380151f902415ac9cf42c824eb23c9541d2da1c26db85f53cdafb06a12b8393cd580a8e494edb6710c720dcae30832967e33e6303a92b1df0841d7724284ffd2e00b95c6d623b168d21ac1bd3c675eda33182a2c22370998de1e5eb905372cc6ef32d5b765f5c94870df4842d011603be4cdb1c227e41eb2f2e8542cd325884fedc9c5c7bb07a92d20d64b836215c59f162a3da8bb67d6fc13fef97cab6ecb8a29e431a6519a6261c4521ccb90e6e609869e6fe398404ae047f64ec4263566defee66329dd40ac985eb8a08d26529a544891b6f57cc235c63c09057ab6b6ed720ef41a3c9ae65768b43f6dcf4962a103dd93c213171dc2c9194e43265c689b49331450281a3febc618d1aa4d65a135137051fd46b568ce294c89"), + tc("150904206aff7f668da603bfd7a3598fc822205390366ed856aeda360ea3ad3e", "24b1190170418126dd7a5a81fc080a9804311b48c4e4da680727b874318d17bdd34be726daa760a40208bb6f512e5aa889b135d277cffe6af802954bc918e4d599ee185d598701c2fa3c28f7edb355d74a8d2f1a9eff8b6c2abed776be8bacdcfc6c98c5e73c24fa0967b6f2f79051e648ecd953ab5430b8c7d96352e600a6fc04fd42250d24e4a5969bae7712c6c1bf8b95481a0f8186298ed2a70e16ff8209c0511ad4e2ea17f539789f9a1d47ee291e157806cf680067d37231032b032d5efb1552925497e57d088ece2b3869684357c466db454d4fd9ee9bcfc5dfd7ad3620b3fb244f717a48facf0157f87cbba94b91c0c39002f78351e3ea6d411ca587b91a0cba4ce21a8932ec9f0f6e8fed5de7ddd03a3b90b6c6d828f7831aae1f879fcaff77b76fba1c0013cddb19e93d878b3428fa91f1a5179f7240a5b47b146fa34e3997730b601dd56f587622114813b57239a289dba2348b2bd8207fcdb1ce0032984d9389d731942edc5a44d872eb86038e281f49c868dba278d1c94ad1ac376d57085ebe875cba7ad071d5cb3843ddadc6ecf8af7d6c546938957668e4e8dc05700900ff2a0a2e8dba726ccea443f8681644a377d7fb39fd159f72b8b6edabab76e4af39fa065c9b947f53702a3797552e92d313540a50e73981c4340e88722ce19797dd5305e561156bccfad147685fef2b76f4cf8083515ce8f0c60a52699f08e6ab24c1df612d93d7ca26ccb2d5156bace4da28e8f1e9992bb8480d301046babf55954fd5f29c074806482facec432da10bb7556287f2f108b114904c122de43e6a77048af3b47920515d1b03e280c1e7c05bc71089ec769f7fa04e0c4984d186f8c8ee305b1fb21d03f025c95c8ce9c50ad6fb527ef505bd203acb5b12d4cfe08f4579971590bd43755a85a03bd296885128ae64333dc30301dd7c9b8e035e22f75f411050db6a17265dcf3f3dcdf8fe0b728a680b60eead7fbe141f6d9298edbdd07d52decde153aafda6698f415eaf8622861e54a2b279e3b1c0eafd5a589e7121df6e51e4d7698566bcd5021013180d77a1908c17169c9bba1bcacd5aa4799df84593013e21c303acb670de66b5cf08fad04769cd05d69e3adcd5b0002c75618a6a1f440918b04df7b2de363639152cb6a26d77db7f400291e21479d5a7e0facd36ed09f2a2e74f564b8b676d3e3de07712e1f69840bfb48de63141a2455debe2e9fb8186a01921bfc8d644dbe315eea943d370ec648ae74657aa57512b39b6d872212dd13bbc7fdcb481f7201f65348017d0259fa99a69084d0b62e63a7408aab24da4d3ba6f7b187d014a372fa8160175f3b37399eedd7f6fe869a1f0ab8b0a3b835c9d086927da75ad483cdbf83f7571fe1041376b8b57a15dd1da7075ecd040ae2f5bd6d371ed3eddcc13f8d611c22b29fe9f246125b2b868adaeaeed40ba1c3150beb50940875b08e650c00a095ec2abff723f7f1f5b97985b7d1c1d81bfc031f64e452125cdd13732d86a9cb7ee1d89381434d939b515f3f461567b905cce4d6a7a5a0b7f964fd6d5fe90fa726efe18787d26b083bbc8ab5d7ee7f9d79d3556616e0282fc14b80c9cfe02a3bd845b1a3ead1dec61393f0049b85ea64a7bbc0585a0e24c53d774e5a085fc217ef6f0370a794a96e6bf572a4c3195e916c9a51814989dc4ae65d090976bab9709fbb51b004694ab9c0833637fe100183ebecfa6f78d26014a00dbb204471a6a379d9e4294a5da78f38ef2b0a474e92ae6d811e112ffb224c6ee2f5b2a3a5ed5b529efd2b9106d878b386ac974cd5da99113f11700783226315ae8b9cc9c41423e47b51d10b51ad503ba968a7046753d3f5fb0cb601f4a8d594b12e283843911710b8977412c3b64e8611728ad4684d7e242a1d85ffa5d01e7f3dadbc1b1691030c8aa51095cd2baf36e5b45d1fb7da28973a98fb3bce835932a475edaa12c232bbae4f793324f82bbb4e19cc9e4482393438f94006246c3a81ab51be7721311ebf1927488f1bcbbb26792d6180d0ce59b69e397a6d60db83c321bf971f5d804512d554fc3453aec14f6b0b5e82b8a87bc8eafdf7cad1eb07e164df61b6d0deaaed33d4b56b175184730102e77cf81823ee82bfe945e3b7923818157c579a774e8d05fd77fe84ab36a3d266be1f094048b8e417ff0556c95c20c0cc1f3963965e5c37df5b4d88653f831bb222f399d867ed77bf8c89bf6160449a34ea78ab7d9928001fd8dde94f30c8cbdee4d06fba150852ec255c69c590d07b5a803707e11a0ef689ceea9dc0ef8809af685e48b79b4337c756b0e567d3bec43b4431e86b79d6522c56dc31802b76893d548f8f851d7696d8fd8dd15f60253163f8335b9897c477e6d4c743be22b1deaff90693337ba8032978e97bf73bd3fa1b53a46a779b13cbba453346234c3a68f369afcdda203c058e3f3a21adf8b7c872c8acdecc0c292dcc43638d1326b5d2e1870537777ec8a47e69eb54a30f9b1ae214ed0a14506ec218b05cc44086f653048d7e06949d6017bdcc7713983b6ea9dd7d79365431dfeca3af4f6cf1efa18ad23b716814f614ed2a22a5d8b377f442f2c402de05cc8c2dbaa5076133e0905ff843d64545a7409711e1563071c5d48e2ae765afdbaa84c2499fac8f1848db65b745776a49211d0"), + tc("81c07cb8e6f763fc3fb0b4edeed131ebf9ce117d838f48fbb894586da17ca30a", "b8b27ef941792bd6819747432cb844d11d3275e235d6a04842d0ea1be5a7270e3d32924d2f74deb9ac5a8691617a1cbc1046a5ab8f89a6e5a84e7cf3b319b0efe21928aa00df31fed34e2fce5076100fd384273dc5b3ed56079adc948e47919e8ce5dce44726f9368854d5eb7ea9df4445f4e9a3ea55652404b71e16cff45e407c8e86a56b4d2328af7dc9a11c7490d02ddad8bc81316b0c70c2e0bdf3c4bfdff2c661399531e134a599f9c2f8b5397285ef5447903c0b43a5add96cd5c688929d0c05b57b5c46328a4b0d8501dcccd6b0d22f1cbb40f90fb3d28338ef4e1ced2dd56cb569ee63a14a5420176b2f1f8fa1b39296ad88b52e7d3d13ae066668e60835a04ae815d481428a5d39df18e67c4fe0f3277cd74273b5f78f9fcb93d70eccf9764798827bf4d6cfa8eeb685a769cef964fda3e1e13c96660eb0e1851e30f1ec4b9d915c6b620204a7f0988ad3990814a095dceb3eba9b7023f2337b09b3268df41cb5c721df9a28518583b1011763abe62377ccaa5b3277475a9cf72e36b07bbe8f2e2b0d2fe26e8b81a5b04ad6bd0cb8c80cc359c4eb38ea250577d2d06d14cccd761e0e51c83078517a500a810cf0840a9fe52f415f96ea1d1644fe3e81823bc2196051da581931646193666e00eb8003a1db1f95e0d4165d084d5891153b6f27b157ced6675adfc53777843b1f33a4337a2da2395bfa280be711f3cba2a961b6011edca35c57dc20d926982e2074243d644fe60c76ae70ab1e2d904c3ba68d8dc7b5813565f1461e52d46253deb187f609592b1e92e062a8aed576ca3381b7625c2e84b3ef93a338b97d699377e99619a116eef9ebf05a7f3eaa96f32b6b491a2aa785303ae03552311b113b6e08f2e18f60d7198035d2c8a600d9c572251b542adb198c3b5b5978e09393274d09797bf2acc0cbe6c9e501a3c67a3c54b8bcc169a68a3889ff5c02059762771ea9b87b7484b4604b20e1d324f48b0c807dcd885907003187696a0bbdbe5fa0b0b373450fc440f698967baab67b621d8346cc43462b171d0e2e710d696ec05dfd51578d6244375ac6ed69568223f17a67bdb18c86aa9a982b7269fd04e278b9ef1f379686cd5b6f5eead91bf7d8547ee868d71b6e07e907e8883018b26380a16e779a3a86483ccd678473c31838a04e1aef78793788330d162d1adbf9e0cb1551fc14ecb87356c62ee17f95d38a634917dd3a63c07cd59324198d3fe6eb4abd928d78d8cfd9d05b74f44f691c8cc3533fac926ecb0ffa989f504a9dc79cf86f39525a8dd3eecc4fd4842fae92b02cff25dde9b08014c371f41df226235253b9b36e1ea8c7e8f081dc0e958991515eea0855ce7bdf3f2efb01831632c55e065a2ec4d16048407396b2ddc233fa0422f833946ac32ce14320820051d5a68a7695854167264a6415bc078dc37c717c83ed7879d776fd8599a71f999dd5aa2a53e9b6a38cc177058f7a147b6e043b2b5ae5010d017d327805c858613081ab4943981cb7457426b7a857d2fefa063dfb655997036437d18d8203f68d89ff5660d310cb99f5ca378a9fb80fd84852a0591f0e41311100d168ac7867e54d5c6938d72723f22016c962ebaa67368a173caca26557b276a29ed4697afb6ae9be48d7c411a9177985ce34c0c1bc1ff87d476876705d3157a789b4f0a7db6c272ef4af614bd421029cf74fd17ff1b82db190ca13f02e60a6f2b261aa4fdff74e2deab18222c674ffed711ef887f7e4e605e3241b751c581e96e8b918974e90022f086e0f92b63fd7963dc1d0f5cafa1eb9df69c32b9d1bde99e89193b974d41453efd36783f92bf2cbe1ef6dc56ea20ee9ac1f3a7435dcc21a2066c63b746343e565182a941cd22998aa3413088ad2801120f562f64b7af55d3c6621ba1301eeed652661bc22184bdb9a9aeab67cb18d85612e7d8f7aa2f7d2a6b3c4545b40517a4a74cb0fbd4610af8687bd6929ddef85c0f74c66dbf64a83be1077ecab0c5ec92991c6b8652bda8e6c088b0eba61c9006158b48f88d8257ad30f7aa50fa0edfa025fbb7028bb978396714bcdc2622290f907b84247b70b71aaf608315ddfc78c9ae4297195eb956a7990891d5c83da71d45c8ec9379eeee0f8f01a29cbb58b41ec75d905c77ae533c5122b907b6d2c6e4f423f6914c58d010bd4f26bef3823587c95c7bb8670ca7512e8d7223619ccd9b88c6158b1500b9223ae31be27d509ddee72bb1c298e308c46cddb93e500a0b54b41813bba528ec1f8fd6e070f73e0289af534670c8d2ddc98e95fee6b39f3f3681012b5be028c3cfe7b2da5d7eff3ff352b6450eb4710ee54899d9a09b0f2d10a8bb177bc30ead38cd29933a715608e625d35d919abccbb58f7c23a047fecf9a9226930438e30f262187c7fc7aa872c3651c632479c2687777e152258972fa4faa67633862bd2423248430d1f444565ac1a6a39d91536e003667d88df89d9d1cf909451c4fce3556b25101e887fa8ed5aa8d6fc2da14cf379a54e3da7987ff2c811da0de48625bc8c4dc345c4d195ecede0d18ac3709b4b6a8aea78a979a6462cc0f19443096e392b3e142690163ca28f01bd606958f6a511af5b191f0f37373fb451e28eebe271e631ce1faf09566d8c6e1d4bb38968785829667e37538712306a69ce67dd502f01cad3560ab696fc2a5144b553fc44b5f1bae4640b1347a99ce98e224f8810983d64154a7ff5ccabc138e4c42257dc2b5e5691fce4bf4a88965aa3460871a6eaa3"), + tc("9b98af5ab3c3253b89fb51dcdd237c9e983198590948ff868a8aabe8c4dfe474", "fbb36a6d35fa8c0174bf87fd3c232d1f6059b8c43cbd7106aea406ca2efdcabecf3b05db8ae53cf98e866dfc5ee714ea3c555802f7515598560bb35e0f0b26d5f51b81fdce914e3651c21951cc45db3b480821e41654368b3259e953501621fa549114d80a534fee583ad61d681c4fe402925d3bc72a7a63d042cf051ea2fbd2b110017d31218fa0bda84286465740d549f7035492b2c0a282544cc536b8e4eb8695437d6038b13f9125f8c4799ceaf85339530e478ae2ecd4ff909bab849a18cdd775b6758808141b94f1a243bca4981c9d4b7a29ce53a2c17c3e62e657bf51b6163402d6c33f17fcea979e40b0d6adaf572a79030aac63e02e99310b133bc007aac9fc8564200a9bbba0f5b1228b7d9e869dd824319568ceeed7a9c87e5a5c20a6f65879150d8d279f1376960e80a6e1cc929b30406765bae97b22d08e0225bc320eb5004db4f4648e6b8f8f1ff13182d1b20838caf69b054e72df07e3e5c53c669281e227a13ff0f6350578f2e257d9a2dbbe673f8d4666f3fe6b46e007a08d4c79fccd4c8244def3da4b8619b2389fc79d5c15432b672121ff14b58742dcd565ec5eac780bb8832da16c3307b8e63d41500e618c8fa9ee077f4a98236809caa3f3dffcea6eec7dc376226c80c5b1df3ec161c9993def3903d542ebec844c2e7e69fc215bc59fe86d5e98f98e3b8237d54cb2c28b414700e1e977b145c768301e78b9310119beadf695a05c509d95863c4236a3b99c1997ec739ef4f3d74dad5fdc66e494eac463844874d6a9adba891c617a9f98d830fb0e24cc09b70114b32cf242b7c110715f2958219596e0aaccab5dca7aeb2c1649358dd95817eb35cef4df997242ecf387d6a06149e6b4661203627516812d7472b4d3f05f1aa200d5990201e97da0f3d3acc81377752c7b9455b591ab6b46afe8dbde732a9664a42175c20989b5a307209f64b0f2aa00bd297f8eb72eca7255b99f07298b1c59e3d413327ef994ebccaed1d986500ad2de5915908dbeafa00498fa374eed2537e0455ec5060f8cbca77029cf2f21b0d2ef29950222947d3b926bcdaf61615f73f0195ebf189cbfbd25075e6c70640a81e64c36b54232cd76ef24aecd8e619f652affaa27a39fec7ef7df3b6c9d03b51c0975ecc0ce4c09ca29f56380f0b2b293321eca37a6675f43cc7d7a4a196d3d11aca35a54eca60db965ee1db7095d481f1f276f6ab4c0b37398c76ed4a5cf2bb7b9c401e2686ff8678ac6c1d141c3a40d41c44b3f2933dd4b258edb45db5675984e6880c3927dd5087f94e122283fb76c0f622b671477b2c535193d1f4330ec09cda03a1e4ccd1a06447e078ab7a37e34e54c2e0d911593ff522ed94b5ffc14e62fd21d00d7e5bcbec5190d5a40615ff64740a1d31d0cdd07e39bbb4f615a4a0345af40d0647f354f96ff2af3efbe25b9e38e1c5550968a18be6da252a5bc861cfe2d9ce84f56b0071cbad937c947c58d0301e51dd1c3146f7810d795204bc0c7fb71f96224be409ff732e14db1c2cdaba7cc746c4feed829a7077f48c7f466d1ec0c89e3e247d15a4a63114ea1e6fd855c98cd7870cefc75ecc8114f1135a1540de6a8e1c05c9d6dc554a6b08e86c8158eb3d844c5348a61d54a6559b0b7f4bc592558200a289cccf39aa308782370b2981cb5ee050710e012d4f58a9d1e2e0b9dd5a3532455fbcc1dbabdf041f0e7b9dbac805a503832df59b29d0d1dd6e75e735df9082b458dd10920c2cb72ca4ae28b63815785656a7bd897f1436393780dac6004f68a745b235913c536dbd5c23eacc6bae635c949efdd2941d9f1a93ed944180427ff57d0ae76a5d584430fa031d8969883e09e853e6a29921d70214bbe2f7477ccc9f16d18ef6911091d2e62a8c00567b831fc79ce5dd65a95304fb7b0c56cd4d7fef15de5fbd19e312ef76c58c67645003bf0a55610f7a60c7d7c5418ee04c45ffbdf3d9c3c792a5e92979d6f8d1998c00a020c0f1974bb867335288f006dd0e973a376f353ac0d97646924c1b35c274e6ad1103a1bdecee53d2cde4686bd6aab9407a568c7e029b86557617c261590384259b3093e0098f11c17de7210113d586b4624c2e052ae6738ef10cccc728f2ae111458e5fa1a93ea3a594e03aa1c65e5471f24f3b8aa126c385062a2fbc2ae2f2951874bbb64e02f3009a575941d4235b06c1fe451a825ce91be59bab85da91645e0e2b8ce374dfb054a40809f26db0aec534ab5cc27e27477f1c1a729f2cc0c11fc51417b71443b657fd79da807bb01cd01b036c6737258e60da376df08089f41056fc454044e0f1d173a7e6f11355851d36626a4c3e89721fe659bf58b72e411b015bf13396b269142c576a5976ab352e1708c1908c43d873964a097721a60485002f4675c5a5f582ed4bc7786eeb2d49232dadd6649bd9cec32d6901ea5cda7ba838817db8c0afc254b94e4aa39c0671ec45cac70a73bdc77e821cd8baa77df6ed0f3f79386de1fd7d7d81ae7e05db6d1d9cce8f42ed4ffb1a7fbec62984dea74c9480945eb916752ec9e0606815f8d5779ceefd2df49775a1ca29465ac9783944a0617279303ab86c2aeeb1ab4ef1fff7465ea6850109d770c990b842a0a5dbb1cc14ea4947a356678fda4ec44fc9d8fa352a546a2485f842a6bfac20deb0458762a72dd4a039d64ad8485d9f1c4d67ebb87b99fa95cd5b38df8e32cb6eff1401d0d2cd20a7dbbc3977b0854eb9f945510125464300c91577b21b6a0a61341379d1f1e15937c4eac435d05be9282f632f84c2ae1290c6de6ca6c5abae90a6c9f37321966cd33d86c6275c018fd2c15aec46c095ac0cd51593cfb646e8a5f8"), + tc("ffd94d687f3fabac9518f20ecb10ff0e2aa1370fba3458a82580358ba301fc4a", "6e1cadfb2a14c5ffb1dd69919c0124ed1b9a414b2bea1e5e422d53b022bdd13a9c88e162972ebb9852330006b13c5b2f2afbe754ab7bacf12479d4558d19ddbb1a6289387b3ac084981df335330d1570850b97203dba5f20cf7ff21775367a8401b6ebe5b822ed16c39383232003abc412b0ce0dd7c7da064e4bb73e8c58f222a1512d5fe6d947316e02f8aa87e7aa7a3aa1c299d92e6414ae3b927db8ff708ac86a09b24e1884743bc34067bb0412453b4a6a6509504b550f53d518e4bcc3d9c1efdb33da2eaccb84c9f1caec81057a8508f423b25db5500e5fc86ab3b5eb10d6d0bf033a716dde55b09fd53451bbea644217ae1ef91fad2b5dcc6515249c96ee7eabfd12f1ef65256bd1cff2087dabf2f69ad1ffb9cf3bc8ca437c7f18b6095bc08d65df99cc7f657c418d8eb109fdc91a13dc20a438941726ef24f9738b6552751a320c4ea9c8d7e8e8592a3b69d30a419c55fb6cb0850989c029aaae66305e2c14530b39eaa86ea3ba2a7decf4b2848b01faa8aa91f2440b7cc4334f63061ce78aa1589befa38b194711697ae3aadcb15c9fbf06743315e2f97f1a8b52236acb444069550c2345f4ed12e5b8e881cdd472e803e5dce63ae485c2713f81bc307f25ac74d39baf7e3bc5e7617465c2b9c309cb0ac0a570a7e46c6116b2242e1c54f456f6589e20b1c0925bf1cd5f9344e01f63b5ba9d4671abbf920c7ed32937a074c33836f0e019dfb6b35d865312c6058dfdaff844c8d58b75071523e79dfbab2ea37479df12c474584f4ff40f00f92c6bada025ce4df8faf0afb2ce75c07773907ca288167d6b011599c3de0fff16c1161d31df1c1dde217cb574ed5a33751759f8ed2b1e6979c5088b940926b9155c9d250b479948c20acb5578dc02c97593f646cc5c558a6a0f3d8d273258887ccff259197cb1a7380622e371fd2eb5376225ec04f9ed1d1f2f08fa2376db5b790e73086f581064ed1c5f47e989e955d77716b50fb64b853388fba01dac2ceae99642341f2da64c56befc4789c051e5eb79b063f2f084db4491c3c5aa7b4bcf7dd7a1d7ced1554fa67dca1f9515746a237547a4a1d22acf649fa1ed3b9bb52bde0c6996620f8cfdb293f8bacad02bce428363d0bb3d391469461d212769048219220a7ed39d1f9157dfea3b4394ca8f5f612d9ac162bf0b961bfbc157e5f863ce659eb235cf98e8444bc8c7880bddcd0b3b389aaa89d5e05f84d0649eebacab4f1c75352e89f0e9d91e4aca264493a50d2f4aed66bd13650d1f18e7199e931c78aeb763e903807499f1cd99af81276b615be8ec709b039584b2b57445b014f6162577f3548329fd288b0800f936fc5ea1a412e3142e609fc8e39988ca53df4d8fb5b5fb5f42c0a01648946ac6864cfb0e92856345b08e5df0d235261e44cfe776456b40aef0ac1a0dfa2fe639486666c05ea196b0c1a9d346435e03965e6139b1ce10129f8a53745f80100a94ae04d996c13ac14cf2713e39dfbb19a936cf3861318bd749b1fb82f40d73d714e406cbeb3d920ea037b7de566455cca51980f0f53a762d5bf8a4dbb55aac0eddb4b1f2aed2aa3d01449d34a57fde4329e7ff3f6bece4456207a4225218ee9f174c2de0ff51ceaf2a07cf84f03d1df316331e3e725c5421356c40ed25d5abf9d24c4570fed618ca41000455dbd759e32e2bf0b6c5e61297c20f752c3042394ce840c70943c451dd5598eb0e4953ce26e833e5af64fc1007c04456d19f87e45636f456b7dc9d31e757622e2739573342de75497ae181aae7a5425756c8e2a7eef918e5c6a968aefe92e8b261bbfe936b19f9e69a3c90094096dae896450e1505ed5828ee2a7f0ea3a28e6ec47c0af711823e7689166ea07eca00ffc493131d65f93a4e1d03e0354afc2115cfb8d23dae8c6f96891031b23226b8bc82f1a73daa5bb740fc8cc36c0975befa0c7895a9bbc261edb7fd384103968f7a18353d5fe56274e4515768e4353046c785267de01e816a2873f97aad3ab4d7234ebfd9832716f43be8245cf0b4408ba0f0f764ce9d24947ab6abdd9879f24fcff10078f5894b0d64f6a8d3ea3dd92a0c38609d3c14fdc0a44064d501926be84bf8034f1d7a8c5f382e6989bffa2109d4fbc56d1f091e8b6fabff04d21bb19656929d19decb8e8291e6ae5537a169874e0fe9890dff11ffd159ad23d749fb9e8b676e2c31313c16d1efa06f4d7bc191280a4ee63049fcef23042b20303aecdd412a526d7a53f760a089fbdf13f361586f0dca76bb928edb41931d11f679619f948a6a9e8dba919327769006303c6ef841438a7255c806242e2e7ff4621bb0f8afa0b4a248ead1a1e946f3e826fbfbbf8013ce5cc814e20fef21fa5db19ec7ff0b06c592247b27e500eb4705e6c37d41d09e83cb0a618008ca1aaae8a215171d817659063c2fa385cfa3c1078d5c2b28ce7312876a276773821be145785dff24bbb24d590678158a61ea49f2be56fdac8ce7f94b05d62f15add351e5930fd4f31b3e7401d5c0ff7fc845b165fb6abafd4788a8b0615fec91092b34b710a68da518631622ba2aae5d19010d307e565a161e64a4319a6b261fb2f6a90533997b1aec32ef89cf1f232696e213dafe4dbeb1cf1d5bbd12e5ff2ebb2809184e37cd9a0e58a4e0af099493e6d8cc98b05a2f040a7e39515038f6ee21fc25f8d459a327b83ec1a28a234237acd52465506942646ac248ec96ebba6e1b092475f7adae4d35e009fd338613c7d4c12e381847310a10e6f02c02392fc32084fbe939689bc6518be27af7842deea8043828e3dffe3bbac4794ca0cc78699722709f2e4b0eae7287deb06a27b462423ec3f0df227acf589043292685f2c0e73203e8588b62554ff19d6260c7fe48df301509d33be0d8b31d3f658c921ef7f55449ff3887d91bfb894116df57206098e8c5835b"), + tc("e82256801bbd45241fb49bd3177430a3b16386ef7a10399ff2372633f03cf346", "d7751aac0b43c4ae8c2ce366256f95e3570cd6ad643c076f05652214b6ca4dc134e64077cb1b468cc19500ba1d3fb0d3c5bafa267dcee972eb4ba3b70af32a00e481416ba6917da4771db3df2df38862d9adc8150007e6c369f3eaebe29d50e9a5a687793b3a41487d2096625a2d0ad46ee44021209e0f752615ef13232c3eb9597b0f75bf175250e72640f245db41b6e18dd067dc628e364a92fc96ccbaaba0e924c8fa4e2e0bd8d5eb1f901e153df1284f80b6b85916e2bcb3f0ac0f357c58ca356481c3e9020331ea63e87684032489658867f7bc88bd98fdaf081ae746a52714ad1bd830c1d65a9ea5e8f0b65385caad9e2ba9495223bea661e69a5ba035235063f275c5c655fa0d165533fbfe70a48de545ded7beb72fd905fefaebd683f85344103df0b8da663348005492d8be2ae95c30fc629c4d027d503d686b5a34bfb6856f24e8fd9e56eb589b79c28f727d4249c6a8cc5072bb19229d06f2a5f7b0223364b4a0cc55e53edb771a2edcd7f278ed5286721cdff0696616566642ca04513b58d62371d8c533fadb90e43d78e488900e556c865e5cee9c652f94440506713e4833cc6e119d5bafb0fcdecf257da9c7dbae0e9825d4459532a2d71b226adf8849d691d3270b73d95e1d393f6d323f0e39a443bb5db50550959123e59035413fe4b15f32f3adcb425801d881a24b241b1d95e8beb2759411ba7f34db03c88e55716a64bba0a2f41f50008a57c31db3a6ea1be64691b388e93c3c4cffc97ae4ef455a1bdd013d44f2bcbf4a45c0c4a6021c220270707c36435835cadd611386b5ff310e449778234603fd77a3dc176bb965a0eab2142c1f489087d4bac3d7aa03da557d23c943f6588d64c3e14769898cd45a6ffef50ba45391460e243a904b01a8b3d80814add1e6af635e6b7db457a47bbf707825fc72929dd1f8884904fb5d4f8c1a331b443b01b3ec8978927c1602adc6a9455b332ad3d5074b7a400d22367a057eebd3ff91dde2fc3e367f5780a67f5e22b8450d2b738ceaa99ad29e0032d17d087725dacdda2905cc79fb94bd835584620ad770f91f9626484bdb6535b08e130406a397ca732175e3a9b1d26a833da2cef70596d8de6d2c544ac001aefa0c5709220af34994f32a20cd982c67ec3c3acdf4de6edb4ed461f3b05c2e0cdb38c606376f0af4687b9cff71ebfedc3b3d4e253ca4fbe89f4b19b820e9ba1d4fd4ac40d05bbd815093fa00cd5338508c71d5a5f73f3de10e58a3ab8d555e5b32854943b924939792bb160fbcf1d82ad6e1da7e3f86ee46274bc775c42d2f541c4ff0764ee1d9d635d71d849c1db917377218f49a118454b4dfdc0cdca20e4397137d6c5744f387fa3eadb245b0757c50bca803a9f0fb764a761c77599027ff8822b2508597f46a41a03971ad32e59a2668aad2e9798306800cbefa0408ed528e58b1d5f312c4eae7f70e7dacdef0b2234f5b96bac97817011dbd8822006b1fe5654b5a3e795977242c25f56cf3ad61231ed88e390b4a0199a5d06f0b3b9d9e918bf66bf376431f62d40721805945924938d8c47374ebe697feab839e6c26b6f3984fcd149c173e2fd85d9a45e5350c554dda978efe22c81b8978b651ee8775ed85d688abc798e7d42d256cb36c09788d387699cb44970ac4a056147be9bf5480a952724ee13368bdabe812ec8b16e4e168013f0dfd1747267de2b19ee490eeb8dd4793a00c7ed8f779b90f2c004b30a607ac0dcae6be6d1dd6dbf2dbbbf0bcb5394904fc7c4351bde6125115ba1faab76adfd0a4112d962117c6c304b8b552a9dcad1b9f7ea9595dc1a432ba8534c61dc7869edabb46f99d5fbf13b257ca2803c73617cc32a3d39c2b8e5523326ee49755b3b49db11471925d71c66f4e92e9c4b6832b236539dd886835253b9681729f493d304518eaeaf837cfaea9798a6d4f8736f06ff37586d190783763a660465df397883616a5cd1134aa8b63385da8bde87435f6ef79f069cd676eb7d141b1fe4d510dde942f503f01bad2fe5fe3dfa8483e8870e2a0c3f45e6e8b49e0bc7b1f473e5c382085aaa61f663b3a0f815b485ffdf9e76d18baa643293a736ec6d8487d76f72ba9d210ce2c9091063295671b364cac70a6b1c7b859f28075a8e4062bbca72eec1e3990f011251adb36b92f5e140f6eb7034aecb10a865c774f2b297f088780c6c56bbd5ee56020abb2ba1fe7b253fb799de90261ab0b87306bae0877053aa5bd6289b076914eaa987d631e5d11dc67e85b81f593d96cb6d99e62e5efb5492bdba4555540bd64dd1f343352aba273395e0155b37ef2172c164ecfd2e4cf886c22d49754a7421fe4a12689ab6f28718611f2a48ad1cbc0117d2a668c9b2f543defe73595f9b2770108dd2619cfaeb0c40e65d65784748f5a5ba244836473a42f64e71bfd2f4196cd5447b7cf1c8a4600e8cdaf51005fff2c0b43458966d79ce039e7ab1a1f485887909ed242bdcdfd4a26c294506478e16e71e7145f109a48e573ce658f62efb6f667b486427e7c92348647dfb1eb23ca6e47b57d8f69126f89f2efabd876236759414e775f6bb98aec332b0db289dd5b2b7ba3dbbca2825b0303b44c3b90ba6d029a5b77556610aed32ff21d13bf38667cb31571f5fd2ae90dce953d8dad9c16a3ec34f0c3085a44c0213df11539efb80a013c31c6d72cf62abca1be594f7d9a47833a863fcf581014a83e73d7e66178c0a007aa21b2314ed0b6e16f4149145c51ad8cbb6897a6e94ec091d9b8a11b4436213e7ea0d34d34953acf4972a6e08b137f0a1168324bcb9d9bd4f03fe0caea5588c8c0a93722fa4a9673ea0b3175c370dcc23c287276da6d050de89b45a4bf2f0056b790634a77376ef02ed351cd5d33c54ff150daebfce8d42ef3ccfc9021f83da1186166ff2d526230d7efb28958d66e2de41b5d43c3dc035025b0d1a7c8627b92845f0e388ed29900075e84a6e20d9f7ce04688529f9452bbdfb68c912b0fecc2ec9c9283d65e22bc658fa3deb3ebf0b5679e"), + tc("6ef35b31742b443a0368e9bd630767a56543b34096329ca41215c04869afb8ac", "cff54596c315403090b3df1253c9779b89e866f1421e13f7646e3cc16d0978c41b959499c249d229dced6e267cb4cb6699e0478be8de4e13be4b58d6b63083e9734573093fd33ff2fdef48754c09e891c41ddb62c2b4208f779c44bd445d852e2be9da7fca6c5d7c015f319e4baf9f9b8c59710816a978f670ebdbf5ef455748204b3cf1501882530f7f95357b7c0c66b354ab947fadd41974101602c926928d8a2b3ae14597de327de07b724f9605bbb70a193f4bf6db0f1250bbe0732ce9cda13f2203ee04164438b8893238a1863c19f223266a047792e138b8ce77a96330386c86067c7f1097c1a4437accb9dfecc965d576955426f066d1ce72bab6aa25b944eb7cd9859988c9fc38c77398839e5c3a6a94e9190335fa8a20f240b675a5e1f2544f957659b557f2c9393aa799abafd4d0a3169627a7985131c7c2bdac9cce99c8c744373c19582a14bedb713b2068f101fad706004c63da2f5675347a095393bde6e5ebacd2f88b0aec6999fa0a1673d8f530c5a43e65a7bb24202a7ddb5be1b2303bdeb0065d3d02b8a3a7b7463ade755648197f991db4e73379948d01d66f88e605285a14151c6a0326432c4005a852b6fe7410e7a774ba17d86f8bed48d83a777fccfa08ff6c0f4af9b44d20022dd711b1f9fa88a338ae412868deefd9ad161a910cd55f3375ca699f8f3f2c3276e30c2eecce99bd8f254cfca747fc398d96c3c9b5621ecf8dc777d4075344fdd0e99291556c9ca586cb5f06f1cca32c1194ff82f20792440adf15c176d01a4c4ff04a02d2d80a476a5bbbb1eb3b56f1dc76c4b0602c50b62a6db0e160c43df6c87882966bce7c33cce79bca7cd265014858acd6f0606e2d25b9a49409e41b2b17deb24ef5ebe34cc713d5cecfd5eb560a8adbb45699b2ea19876ec9f216beb541446004898ee0fb56033abee5dafe1b2a196f11e741e16445b1e882c2ce6e5f433c6bbf258ac9afc81453d4f95d9c3756088e4288fbfdb81a72e1f2e5a4adb99eb606e6b5aa484e578d97eb10bec972d27aef2444f340634ead3aa681c0a43f04735ed744525e18c3ec9dd22213f8f99ea6334127bfc0d703677e15a413b1f28806234ad5106d1c93a0a54467336bc02ad71291605a87bf26c95a382852c48e017f082f7289c57e3e9243658e9c10523637fedb5b3687305031621a6a62f65072d572e35325f3f47bb3683e437c1f648250046f462ff6143a372aec2a80a999823adb2ba65864a7c8055a1f38e8a455313fa4f091087609c84899641b944a2d764f5aff9359944b33d0ad0005c08bac003ba14d5086a88d3c12fbff8ca5d5195068bc84ef6682ac2e279c0082dd0abf42333e7e52cf8bd1253bc266f412d050c9cd2e2dd3a814c592193203547a6fd0ce704360fd9844fa1fb789181175d089db4db8659c5e2bc991f213442285879415618593ea76fbcf34067e7ee0b5bab78d26b2a4964bfadd9fcf4770edc62c0747800ce582be6c3d44dcf6b448988103c4ff81c5224838f72168985b241e88d2ce3b05864b3daabfe8063330765bd1093783ae2a2a104d3258cf0282e5165857fce961fad5627f508c4012a45f61602d076cb2108efb8d963f266edcc172f0729f3b4391b192868c90b58ef83cfd713a27cc8494ba5ab9ec88b33be6f5dd03c15e1c2a84c1bb9a13ff4967fb20884fcfd9f107e13405307a1e524ab8804334108610b12bc3ecb2e7b6c02085a593208027a4e525be7d4a156b975625db2d87097d0ea36917920c47d90dacbfd5978dc62e11912017e28d4328b4cea1c5d4bb8f176aa793bc8248f3f4f80f52156783c47148ce93f60e33fbcc7052a8cd490bf585a67b27930776c7b6a734d7e771b7de929cf24c03b77a27af2f86a75af59d6be581e3d1bbfe1f6ef070c73fa4bf580e87e786109e57a0cc1d027cee3030d79eb67f4bf71bfb3a520fd6829eaee6f5acbc45c40d2d19bfffc7310eb082d67e4ceb2b12bb77c334ac9b24a559a679474a86559f5294330b155b9f8c9d845856c37f7560fcd926ed0841c8d077249bbccb28bc418ddc7d189fc0aee67074e95f81619b8727500f163107ee17f06fdaaa1e6106fe558cd0707ad85884c51eb5ca108644c908ede0433db745930428b56406e063cdddb1a5c835444a0733ba0497fe2c0cc2739c373e8f74b24bc1b3a09c28654e74c4842c5b1776009784a405b9f81ea961633df7be908593b1ef824e43acb9eb4c3972a662462c42ee1ce406ed55dfe6245e9ce124fe7c038deaa15e487cbc1fb9b0feec1536aa90f2ec9f0c979f369ff50fb99a14d3c0aa9f94e668a9801d514de6901dfa189196d0fdb1e4313d55a3ed19e8768ed8450b387b8404bb9ad9db1fa48fb43a2f056168f2369655d8383cdeffccddb921f09421340241682cf1ad233e488da9584895bbefec3602693ebb7eca32be773c9142569acfc188a97d46801f613dec226e82f29ef07c1e0197f4462b4f1f22ede13e1242ff5101693112a410aa59d4931d2e98992172aa4df8180d571fea9ef94515910d6ce14d81ed8f060e8e6cfccd1678a827736009dbc38de3d63d2bf7ab78b0c9cf3524379527a7fe4a034e36dfafb018b54f08ef3dd41904fbb2782d63eb7b755a6d306bcb004f0a8cee3182b99ed6a01df198a0027de42938064d32e45d37a613faee1f1075f699125675b18908c23ab0be248a5a1ae8a01b1bf76ce71371afa56cb3462a1c52469a460a357e4c7874224c37c1366db9fdd3a6f1f08a6718ca45bbd3b1850e74e27e76571dc89e5396efd5c28aa37a61dd3f82fc4257c080aad25acb8b336e5d093955dc6847af1aa4bbae31a0e64ba5c021053e851e05e1fd398b5eca2149219892ec0a644df2277b77bda83572a7906b168a9ab17370ab9e24b57667f9de503ff0b1f9b798dda99996356cf60555dc63ee40b23c77fce438790c8f25003003320aba731a2f233725ff7d62a49e76bceb7ec76f58558c025672d7144e5943408f4daf60e4d991990b510f548551f2abac996e603f559051c4a82c8181e967eee72e05ae3508e181db54c8eea4c05d2c9440521eb19308b3d5f0b5485902a6b01a5ae04f0a4d5712c2838c815098"), + tc("348cc295d0f0fd7071771fe479bcad42315e1c345001c65d01fbdf6f3782dbdf", "94133b8e14785fc19565a3af0aef60462bb7f8045e2edf4a836718561aeefbd0382434d319211c670fc699d25f02ec608cfed9786b7da70e5595ac816688455cbcb4449536e8ad20863d76937796cb0c599918a21a0133b6c625b2000bed8884fc86356fd742af78bb55d652ce4892e7c296853a40af29116cc98d38874ca0f8bb5f6ec8c04dd7f3f9681858e0cd54d8f9b50a830c31a9781e70f63e5922b08617802104044ec2ed28566e39534fa8859573f024ca381d87bd6e04607b9440d3b7d4ef0fd172a6b8fdfa2235d46baae0778467bfed91d33c8dac9db5e7eb6c69ffcc2e9f2d6b5f53ecc800306103d86ccb1e3483ea2ac349eba3833174d5cf058b8ca82855a938a6d38272851b1977319ad78a96a59b7d6300836b0b219974caf8776bab7abf4665757f9cc941299a96998d3a910dd41faf26d4ab54408fdb920fde1d614a08c2f02716a17ac3a98554d9271f644d0d74c0d4a75c663ab8fdcbcbb75b250096c156366ee1a3dfdc6b28299b38c0c14bb685a89acf6c41acabf6b3c6745d08f73811009e9e5ea868d469a0c8cc35d83e99b2da57d202324edf0558e7632de782740cea7d6c4db9590f675ced9e6c27c8d39884990d7b685c89f70e55e084ac8c1f366a2b8ea89370e7f7d724eb9170729e20bb18c7026bfda8be4a9813172b76f72b7ff8fdd31f3ad47282979dfa8704928042c24ae398050a4f4384517901a3a2d4230c56a0cf3bca928ab211bcd2bcd4ef0b73daf60240f39ac01a7ab29dc80896f6118a56db957662c2058f045824da79da35efdba1f8d71094154643a570dea95f30cd28f28aac3f3204132571cf9cb688d27adc77662ae925b75f3fa3854fb849c4450d4cb6169788e2aa6af63a30c3654b81d08aa118773b87d0f76b7ac443246658662bef132ed13218498f0e4646ec7782f63b0169da7890c682a2d046e13c096fe521e4e42851617dfeaf85cb366c6da7ce656553264bf673906872ce8ef4452c0993b2f5191f2e83117e2e8d66d3fd8333f0debbc9d2b1add31ed807bf3dd2dc056a2590f8fb1caa31d110f389dfb045c26706802acb2b4e7a2c1e5971d07920a58fdf2e30a48756a2be92fc4729e5a779d61e82477603cec73088c12314cc89c99c0ac91e41f72e830956f351d560136fa2412f5a47f627f5724a73fd4c584c9ab62f028342278e96b7f3f1edd270ca45bc8b5673971007fc557e9c3f1cf7a65c80426502213ee2d5f32aa726c336894e35ae3fa3dc452f1e13f94d6aa2ba5ae7975c14c3c2804fe71998ac481241c66fb81ad62157e4058884931c93da6c377fac5dfad4c6faa505461bab4fa85266922b2827bf3b9a431eaf25c5accf839f78a0029e0dbaba8a2d52b7e0203263b6c265b03c97600c7539129c3eba3b55ca8820327920559d86878136e40460c45f5e3ed4d0a84e81bc2af68473ae7c9eeca62e5591729deae74c4acc974bb998bf6fe01cd2a781f10146cacac66a492905973b06c3917c72268ff89e84736ae386eca2913cefea3d55c6ccb392d1b1c1627269c060962abeeea388791f5bd4eaeb1fe0338bf18fc3401be0b7ab55c28fbcdada3ad2b39e7bff28f4f8f31173390eefdc9927a066a554e8175b92efb8808c92527489194c6192d7f1cda78aceeac77e67ba502f794e6da3dadb8c12e5ec436a1ae0af571f34a9b5edcc64a8ed34354e36eb67e8d616317075160f725202b832c7acd649a86ff7b135960a3f295786976dac96637d949ecdda43e7014c451ee50ec752bb20c8defb199207c6b2bb0dcd1fb212e6db26476bb3e90f5ed2b9cbb3f1c243dadcc622cb503d08ff4b266a742c5c61a4fff23de25cad7c3d13c2ab50ad2ae77bff6e1b18e593283a0bb4afd6111c9b5d5d062f77eb2ce8071561a796cedd4c4f819373685b4c3d3d14be5bc20a72eb9c6c2c1765ba887608157354d0a5bc1f4158d23ecf30ae3b3bef24bcf8530f13b7fea54722b57a761169cc827634a84314f697b090425bdf9ebdf88a6b53515e01d3194a8dec7b8e15babaff42e0e0cbd1df286a25c1fe3934201ee19447eb1e29bf9ddcd14ec9508ac213ce17c6a5ba3bcd207d1e41acc4fcf9ef40eea8943dffa282200f37724413a438badc59a13a07caf163350f2663d2b381c14363e2372e30faf1381a8936b61df5370562bdbaa1102b6589db74c635c791942a5ec148f66dab803f469f6c39dc225fba4ec87bde2d5ea31eb517f3a43835d4b88c556e84d63b26894bdadfc4624ecb432720d164bfa5f9d2a685728bdf1195910a94c304369de61c34bbe32fded451534276de8e3697490af835fbd8153b807c8a45c1e7057333f99e820032f8051fe8fd6075027041db4baf7693ba2200a671b37679b7dd088122f48925c64e4c3e999be84348445898c3a1690a265986ff558d8c6e0f2fd142e1a93f4eb307e4cf2047211e005a42c92a431d111d48efe4762dae025b183a2dbc57c45d98484dd85339eec68e4a6ceb3157161217da7df44029c1d042e4fc302079b222043323079bb6851fa732a9a2a91a6d94c1ea5ecaaf91312953ea0d246ccb56c9c234ea53031c36d3550814aa8cb39d77f77ae842cdffde17d915ae29b8f118a46edb08ec5e426e30dc4dcd0b1457371ee18c7853b93936b8eb4b4334cdf6413ace28d68b1e6ef72050c121289dcc107d5e859730f97aee4407048e4db2b71c91c30b63f01c2d02845f3192e861853942e6df816067f4c2a8ffc475fa7192245a5463c9793f4731dc4a4ac3ca203a270bbfc9bd841a224d347f8d30ef84afdf9a70969330e0776670edf1347c716ad931ee7b218bef3b62c3ec4fcdb6b41b9d42509bf4e844e7c85ed2cb6d40e0bd125963458f8e060c1dd7d398615357d7c2662eb4139d952d67573c2ddc83048a7da7b9d669cc5002bdbce183889aaf434a6182e32f051dcfd93692b9c096b57e6bab5d22e72afa2afeb53b4870b52f5f3aebce58addc005db10580fbee6cb9c598260f364962917a7323af20eaf3f511e2346b1962ed247aeb7de64e68d6c5c1c14a92f606b19bbde4a68494bfc06d76df422ec182fca8c0d1e300896455dfbd49c29c858c64e80a17c80b51961c0ed312f071b0d62d7625dd3504a2ce214aaea51ac81ced4479fd9da06c7cb02021f4f347e1f20d62c78a50a30a82f288407966d69d837f881fce431ef0"), + tc("a4faa1736367886749614a6445b4b9a78a4ab8dfaaac465bb3a0dc49e344d036", "f5080d4c59e804bf8f34b334cabbcc7d32011bde3677f4b9069416ac204114cd9da7a0ed0f4b4d8344416336eec15553ef526b6dec267b1242657dd0b508af81fecf9cff9c82a6a7a9539814dd7e097615ef15373836b5d2f765cc8d5f82e90449f13aa741d5ee2fe63898e55acd85116846807606fe1e2e29f98f9940b067d0d1df01f080211b2ee4b0a30803782a7bc2eafdc5ebdba91eb05f7d7dc8e34bf6d44fec05824f53418f235fb64e899ee147bcb403c8855e94af378d182d79c3eaf977cb4e9d4a16d990a6c388ceb567b97785e6f2bc6745102b99ae765e960b6b32baf01e2379cd6ecb74d3e1a56552f5976dfe5c742bc92be596ca742ffc3d0fa032ac29f9f7c1a5c43bcca62df7d9de35d0c7c179db2e1aa255cedcca55064c2049fee1af2ce5ef696ed4bc46b7c55bdd51f2d44c8713fb2475c0b85246ac0103cc3863b7eb026ae076a600313f6fb40a4df62a2af81b7e917951ea870ecb31b3401928b5046d9a1e62d14b30fdebaf262868517318fe17ec3c0d52524f44120ed8ed3ba70c643300cd0bc70da72c964a88f52c3a91ec20bfeb5caefcd4d9c7685d8407476b5f34676c5ebd1e88a6cff1c625322f8cd59b9ed60cefb21f9491b95e72791f7ac7eaa3c16159fe9df7a989add6c2282c47585e11397eda9f47df2b40166e03bcdd6186b46c6835118268ddbef19a28bbade1bde0228ffd7e8b3c3c598d89e24b8cdee79c940254de26cc6814ba2722e42f7571600b7325e1ff300251d52a895b8ccbd049b2953b8d231445f68f7c26ec25a4b8695c8ac116f736be939edd762c9b4743e463c9b9b2f88e0bc0ce78781cddc3bca825acd463c7cac2aa6c430bbe820ea94af9a40b1b5c006e9641a2ffa6e427379e1ad49c81b98320b3431ff0030dc683d61026438bc6a6d34b2c73704d9f62eaeb13abb3e4b0562b4e0482cd6b2d7aebc0367ea29a88f4a76f3d76fa1197e1dca92c8216c84c1af9b8c78c9e3a7799a4a79a783033b0f5547e8e75e69cf3615ab04ef989fe1a463b1672c571d50ab56972896e8a50c242f22c7f6e27ca4ca793f627e79608680f5421b28bdd2589f05e65430df774ee873fcd1234064f7a33cf5a1fa4e368137ff9c1597f1fa0fa36493f20538077669eadfd3b06f788c912c715fb5d334db6bed133a8fdc40f5496e66ad63881f0ba3727416715865253dc5290327b515bf68da188dd5b4b0eac7ca712cafa8fcae0c5503fe58a219182f1c30da6d0c19cfee897b7d837c97996a35f4ca8cf0537a01d17e7de0cc9c129e4da0adaf1fda85030df9127be628263b0624f372c47c3ac87eb945a57f5c732beee81a7403001798992f3dc944114ff3d54c4666ac5ac8c98d0d5596cbdeb420665f5edaae747d54cf7edd37b162e372249d135938cf17d174d12d88279cb4c32bd6f018c766da6983d4ea51d6bd8ff0a9b34e9a93bbda70cf1b4b867d60a74811fd98d52faa559b52c755cb70a76c94bd19654cae7017ccd70222bf08c5d7ad1f5e4e6344fdb3abe703452c29a696f39f9826ed8bc510a4a148e5bf8a5dbe6b82d7220164f08011c05ac5159d52ce9d45d758b645bbb248c2d341dbefa1f8602c5d458a64f38f3b04db39089807b6a10e1bb52770b92ce72e2d3bb0c2241cded35054b84558d1cc099ef7b2296951951d5b6a22f93bf962ac5ef8fb55ec6cc2b316428edf12078ed1b66d525d022819cbd489e1bedb02ffbd507d55f9b5d4e22f6396ea233453754688d20151a09c70044b8a5a9ac033c3c3b847ad833d5c05b33407666ee82f9581df9034ee15a9ca67d52f1d9b634b84c1b8ba9e515f1f060a5ac5cbae2de75f94e112f7198e239df08d3103f065627438995026df511c6e5bfdeee5667d511d4181850c7c5d179107c1b86d24d5532a88a4149a2810dcae73731b0e1247281a6fd31613df6891b4c17b7a6a9ad9b77468254b93f85958aa0f01cefc10b25169dc46e035d3f24557b4bf0e7d60174219108d916ffdc55e25bffd9809efd058e12c14f39c69d8fb73d3ec6458f47f2f8db901ba76c86550b11b54d0641d4db3eb000057dd00f2e511fb7a47e959a4402a3ac5462234b40b184020fcf7a0396c4d00a987c8741a4537bc17102a5c42afeab9f71ea66ed4cbc7b5ee682ff04f56f4ba1ea0bb326c4089930f9e3f3ffa3e06637cce32113881a06cc3a13837448145c2bd01307a580fdbc385d8f46fb92ffedbc8918d269dd1871164d4b3e2023441ec8b99c82a5f09821cddf6b38c9acc3bf3a38d5628016159588c33eaa29d9463a537c000a16ad8c177dc4cf716e625f46fc4ca8c19fbd8ef320f1d680639195c8b195b0a02738e0665f4190d6287e589cd6dd45b9e8cc23b08e1681bfc6f66b88de6b091e825ea4bbfbd697e10bc407570ae4f2a3ebe569554639c2b8e051656cc30c837f5a92260ead1d552b45801b6d28134166796c87f900225cfdc3cc49d72dfbc18d8d95b1e160ed3cafd5c3467d48aff87402cbcb1e1420e3fcb588aa19c8f42753b59db6fb6a9fdba127ca806dba7dd97f2488fc2e438eef57a4cc85b88dcfde76ae1ff61225a1ca8bf4a14f729950322ea681b16d6492902506702dc8f348e4d3ae7fb55fac1231fde82091b34f1791b6ae37587b10325f6ff5e23b855845b86eae90785b9d10d90a16644d01bb626f343b908a9591f4069b21822ca4ecf985c1e710475f33df9af4764cfb0ffe649063775338f15bea7cff29f164678160960a80ed148c9b7faa58e9139911d3dd9536f69646f718f083dc9029d6294fc4c607688aa75af350ac2c0b001a157d023d73d86ed8133809fcb9592d12089cbd7a1bb6bba882fe227c09a53ff088907cb4bc2fb4b7f62d41d3d397c4fe0ad12bb3964370e21712951c679814d506e738c0201e42181d231136a435ae0397b61ccbc5e8bbebf8ea77c8bc48bd6211f29248f9d498d818e2b544d28a5e60ba727f32ef4ba2707962230c900076fb764d0ed5ce078c9db14de894bbb836c6de9e83202ae89f9a8d8cb0341e1c81b5fa8b16731b8e231e969c0f1ef95336d4e73ead6da23de3ad1eb608acce4d4d93996dd76ec1f5f2c576f6b3b76e07bd8a810ff5d88b00ffe48c42700b61cc499336e7fb57ad72ff44fc631c7222c9a3d1abf6e77b5ed7fe2f7228fed6c849bf7142c4103989a80f7c15642ae61650cdca7e854eb25e9e72f4c3e3768e6ccc8bfd556b56d3507edde9e5c331ddea75568b07813d20e8f4c9547838ed28448f2e67158acf0c00b131473847816c5e2dc215"), + tc("ff09ec4542b83f530e100cc8493a8bdb566e902a7a35246d202e23724bf07bb0", "34a8b8956bf5adfa5ef8f10b673f6e53bc3fef8dd1f9428078c256a8fa1d8da1e724ba90b65bd4394eacac5469c520bac2ced1640ba26efef44f50baa72a9e7cc8bff69eb8719aa1d88a2450f5c3b4fa54a2977ced1a904c09356f483301add61006524ff814ca0b1fd50d3f3a30492ba70c2921e3b9da58cf8f5bee32ac15e39371fd9d784256054a3d145563c62e7258fff3a16524d38767a75fc27fc3d0a9f0f585a0046d6369dc6dd17fc65b53380184b4d9eeaff245fce1aea0d4e8407d2ebff6ec0fae8b654747eecce13bea89d4879e0c92e1f47ead8e5cd7f56b04c62c2b7cc13c5cc2d9e6a87d77de73e54dcb9829001a3a9813dc1958e4fa10bbe4027822f8923af17d0b04bde97b9810b31e26ff74f9ae95e50c6ea25a49ee557559c2c9c83d01737b6cd7216c46859d2833f04def7a7efc849f8b13f627e20fcf0cd3e0b1cc7d0b22b4ebd576cbc2a397828eaed2e4afe51104507c0b3176a74a0261a159525e0883f4514f1856d1bca3ceb1084441e99facdcc5b7edbc5a4c803f6448119745c23e5d795a0aa96f31a48f431960b3c41c9c6b13a22826743839e571f40bf5c965eeedb293dd5ddd22442071895bd9c34d8a344907e17af7ef48008801066aa8f434019d95a01ce34c30dc116d6a5d9f6d43bc2cae327b613e61299e75d580c7323eceaf494d54825553c98514d841568f5bce9c97194ec36a16f6b10d9f1808ecc1da80be4d37735adb6d1b83a157edec266612e65e57ee419abfc35e342a6772b5ffe8ab1d8095bc92bf9982a7a32e8505d8f2bdddd626d09f208930427f14111b91ad66ebf710d9b1eafb22fc34b2616458283e9fe1218a2886a7b73742342962bfe5dfc2f829a77226fa8bb6165efc66873d618d8b6acece0412515c5a032e44685f7354f2df2ca1f903ecf1a53894a9a87e07fb36759ff666560605ca55488a7b3bfcfebf6a0d616417c93835ece08fb69450e041a7c23981520ce03d5194dce2ac492f2e4892b8028a7eafbb913930942fb26643394bfdd67b9381b1581f9f62993708a3c461ffa43f11af679b68f62f2553500ed3d7d04990a6fc1017d8d7aff9b6dd58b22f168161e71ead31183eb338c75c0bfa00a701a19f72b73c5a772090a86bec03e4f9f2554948d94e150310c5aaeee46d1aee5b837695fb49368e59d176e0199cddea6085c897ae2829b10c592553d54ec9a0a7b8b78809a51b9dead66d0879e8d716b0519801ad71fca62d848313d40f8224861e58f0d107ef855a6fe9315df8ee624c04c56473c61c4006e9372cb22b4cc3dde10e60ed03d6d6fc928efefe11ba7cec15351fc0b0a5597deabc58b91040b70bbdaf8e1615ea5246ca993bbf653efec3faff587467a18a4f3868c1109fb355338e54eff58a1c90e2bbdadb0220f22511c5d8cba90ac474fccb0f9dab7b66109bafba8e82382cbb851c2f0869b3bff09a90751b591bdcb617c6871fd8e22bf554f3af8097e9443d0d85f9a7ac3432dc8b3e3f0c8a570e2f97dfb0e4445152e4dd0b0ef1e656dd7b7a50b303b6bbc73439f873737b47af21af435c9703f704ddc153b26788fe932cfcf00e87e33eb195a38c58128a435c81a0dfb42edbb0b9eb27f64c0ffe8357fd995e8ac70bf0c7fbb2622ab5477ca8f65ef1ccbfdde0ddf2d610f7913d7ff69279dd33eaf4a49e3a5326e57ae3e8dfb752e989c99d170389c01c9adbda22846868cbad5f62b974bfc6a5a4a88eb6e04a28752de094391576fc17275d876a5d3b470845a7a064e00be791b20daddbe58812ceb296884fd7246ff67411aab73d049fc3250da9111d1b3f192bd8bc650fc4ac9392eb18840b36d8460a80d7967b20755bfcf8251836961f98b5e717d7f078d3a2cbb09bfeda034274b93e8092d37f060cee0659269a09a11cac907c0ec027da78a723a207b217735b15998ecfb3c43d87aef6671b54a76666933dd98d5db4c7a738c43173e77b2573c7618239f4b664031477029666fb8d3bc203f1190dcc27ba0608b276b51f7cfbbc70f5d4b1443dbe37dcea0354dd288dd4d851f182283fbad31d4a8e5734a28c49094de61af6674960dabb2e6332fd5a9217a92e5cb101223d98a4bd50e56824c7da09af1668b1e98beefa8cc9da8f5b6489ff1f795b6f2e3da96526ec0233604a5c300ca991d5090a358ad21b01e918e5fb9fc0070d3ec1a8647a1f40fd9a662db16aaf39b8fc351d2fb077ba37800258202449db1f1a95fa9def31e48cbbcc594f6ad63cca4fc3eb3bb5728cbfe4026c4a7aba1fed992661e58edd334b555dc93d34bf346410122cac14f774b3990e68508dd21ef27a83d6bb6ce21eefdbaaa2ca63443000ebfafdfc18c0c96d7ff7eb47448fc946a1a096b0008ead107520e2536cff86ceddc93f5f8f727825575c7fd7d528477e14549c91fd86fbeea832d1f69d21d05f220d81af9c241766cad8aa6154dd6661789ebd44b67915f2f55b38eab1074e48b1137cfb1c440f1bef438ac1db4180b4d50cf735d39af8201e9236dc6056d2974b951ab4174751969fa0b33705fb605d2e926a764da3ca2992cc930854075c0da0e5d1b7bcc8b66cb937fd6b92a2776c88c02b391d353759a04b5aba5034905aad61d0c330c9b00969d1fd1dd99284d2313e90a5b8b0803bcc19d702e131b4133bb91ced8aab62b0106cbe3d0ba597c69cbbabe0cf669fe446aad7df1e4e6827e280fe8f2ee91c99b1db44d10a6576a535b4fbc6c76269d1b0252cf482b921889aacba14b4c0a2eb5907611290ce04b29ac5436edb8d5d77a6e8ac5c672d67e107b6a866c4bae36e82e22fe6fc5b31538484402a30a5221fa2357c3d17a1317ef8e5acf7329c670b67d38f1ce60a05dd991ba855c598f9c32e518b95b6aeb84587103815019e39753d43a71a8c825895cf2569786358b78a74a5b47b7dfc3292146badbe0867a353a7285487badde28e9d78f7e8b2fe0e7cf78330d4bada0d606d6d40fe9296eb500cc906545d72596c8c98188673f410576d154e3bcb1f8887e5613c3cf279be307c0224dadf58a91e93211f1173c461ef42f351c7dba499d6974245e1d88ba22830b07f6d735c5a74de475c7f08a940460512eee2caa46705580868d639e46b3fde805aa1343cec3805ceb7e624539fb2b362382f5bf0d55e6f3b90d2125162ae7babdf96fee54b29bfa9effd80a508e80b478ef67997af166b5417620ba5886d631df501eeb242cc40d05387c9730bbe8cb070b2210a8775dd25a5b76755380ba7e2ea02142e43a3fffb6f1bed559caa68b09dfc8dfc1864edaff705bb1e30fb58c3a27c5b1c425b22d0c1073726a2caae5cfa631cc2edac96c10fb25329f0e6d15e4d52694f8670d3d054e9fbb2"), + tc("4829078ecb51147b8976b9c3cb281c7a881178918079b86eb947e933a1e56bf2", "b7202c31c33e5579599d14d12511cd700e5a077679266b94b44586b363677692a4f2838f50d437c909e5a41ba747a6f2c72be85149d53a834f7e66b6dc74927250b543121b6b4780542ab511adf2011380f0d7133bf82b38a2e377b5c7755e49bb1abc16ee0a4a772f5653bbfe06be7773290aa4a9a059b98647c188bba609d31af594d0594b375963f249296fc6974d2520cfe9ebab8c4154f1b9a7eee66968b853f19f74d3125b8283e5f167395ee1077263eef91cf573f1a258384d369c0a1b8f4e12c05f942b330d43a168290d82325eb2b77d49f25e028aa37751a28c405a6ab190b744c5f998d7cabae18ce184d96ba9be24281556387d710e66fd7780eb1a0d91a3f6396ccaa54a7b2b223abc1902a660a44ed1fc2606aa054e1c0fd9124eebcc0993922b79ab6dc225826cc3fbb0b99046529ab3cfc79eab58ea68a153f0c14e5735cb4296fbfcb23acd158adc1b049bbf854054a7e08bfe0b7d4c8896670a90bc1a3a9d4a2f74d1fb5e143a4d22ede79c738b41b91b93ab34a91c8e327bf17a1e86884544b70557d4ec184f82ea5b5c5682e33af7cc38c5f91bf5d024cfffd145a5aea6fd7b6b0456af14bdcf60734cc93c1387a48dfb97b91bb23d7f68eb783eb0cdbd0bbe4104ab212147c9caa9dd923e883c3e24b53aebcc3f7fa5aa5c887078628efd7e82543f8600fdf89a3312ad6902271321b77c1f8dff1b92250c59e037411c6791c7e930d686e7bf359ec480c6d0bd05d2009f2d339d2089d5202c65b8fe737ebea6cb6d9271b0bbd3ecd6328aa27ec7722cbb3e700662ee6afebdc4ff5e9b00949ec3da530aa271a78ca7328217bdb5600448d9f825836e04892cef50a95f8ceaaa5c28f2b9b185536ff2ddf380808ea351bbd7a068bd43bfe8eea7a3292c65130915e828204c0ec1939f1e3088058cce4b2921530e3a2c8cbd3fca2ee39504b72ee72de1203164ab0af78b66600b27a067cd62d7dfac4815b23a712870aa4f2659157037285386beaf2649741da70fb7e031b4e1f37768a0c349c7b8c90a3da6138447db7cda00c9fb01647e3239a680ffb6c8d4d536dea7131480fa95c597bff67ab5bae2578954be01be5f3892688a98b0ad0a21b0af40e562175164018d32d6a886dccb256ed7c4148eeb963042bca34b689c44bc485f286a65d1ec44e5a0c90f5a1e8d6a29dc30810917b8a3a6deb0362d5967f75802e8174725b734b61787437aa6ff8a4fea6c2390c477dda085a62c6477e994913502b862bae548798a17716a48fcecf315487844a1d98eaf122521173807cb72308cdfc6cfa2f1b9324a305869e47ce15cd18cb38ce83ed3b41a71205be49cab4b93142d8e20a4c4365e4b0e72b48e5440c8e8f5a8abbe22e5c24d2ffa0e062434fb17119028299ad5ae732f0fa39127e533d591a27c7a0107a9442b7c2211c2f7fdade86bf032ce72e94e71d6072466e7c50c62e01c35af9bf5de708a309c5694c84ed47ab397c409057219b2b81d9bca7526765c11701e6993378bed05f5e5ba5f9c5fc3e3e6097d39c86b2995a9fe75dbdd0cbd3a9d96c335c8f30fb00419622f8d6119246f1b94ee8524a275738a2b312d036ad0a8ab72ac50f76c8642b00f354b00e0311ac1c11c1332372e4ad1a982027230536e7a3d451bb1e55e509ad026188668be90b12cdcb64ab4c19e46bd19983f4e1d26f85258504af4daaa3c188fc2acaf6ade8fefa7a42aba19dc7d1f2269fddfb15ed2c659725829e0736947dacc67d83b0ff4f1dfdb8b998efd4e50d8e505583814e049a31923febe479ea88816a7fd044a52339b761f9e069c7585d9ed7f2397ad4c208ab06c5fe4bc6ff9b367d109dd3fbbbb48f319053c8bcf643e1b8346ea42920e9cda202ea0a74e44305f8ef7b18cfa5c921c9e5e546276d3ead2568a9df7b7883d4c20b0f38bed538d9b41aba491a4ba3e4c4787fbb6f75b18dc6c356a95debd8985a028c1d0c56ab5d06406e547cb4fe88bea7d0f8b3d244f0d57652a18d122fbd1c6df0002f3bfa071971399c749a05569f972ac0c79cf6532507740c3b906e1c56641942e215fe60002acb2f330c99800b84100982123211a1bb9c05845c29ed03e3549861787868cdf9f20c9dc3dd9c638a9d5ec0238ab05bb2c31e475f008dd3e5c9adcb0968d022b99f1c848339ed38601fd594f2be5438440f6371ac954573012d626f9cfffb06af4dc2f0c522e5258968a6eb28822584835cb64dcb1a1593029588594665be0f56848e24f6ba914d3ac6df6ee019a4371d8d7cfc74733f29784ef56e0183639dbd8fe001c63e85fc3206b299f1ec1036dddc01851d189b5ee270ee5293b77abf4a4bc6f986814fc0ed65011754f4030ddca7dc67d6051246136951cfcb6aafe145f5c14df5390b105ff1ef9d35d31ecbb05b2d88140a7c662ca6f58e2682cb21f1da837191220e3b2dbf92546d576e27709c67fb3ea8ab8b53f394c82834dd9fcb317a1b51d1723bbda7ab5315778b27afa89d191f1a47f8a0e0c15e33f0bdf577f92efe3eaed6546a9487233cf9c77550d482da1b99fb3b721ab7ef7826550fc4bf953426b69cca80549569e1ec6e523822060b4cb8a88bcae1b9f480e073a90e4cdbc8e60994b6b953ed153c9da146f4ac0d2030105bdee296dc6bfdbebf75e6353add84330e8011f9483f83a39f8d06a50961a07557d4ed83dbfd9e5cc434f0eaa70d69de7d034e359a412864d8be292f588d607294a57cc3c5065781e111439a2033dd64b94b7155aa565368045214c588d3ebe04a04b7f7df5630a460bbfbc39dedde14d590b2c79664504ef027f4ac22712f8ee8b4399f01a7418a8ab9cddc4da649f5203a9ca15eb758059b911b7431ec07ac541d4d2acbb3d67838f4be54a387757f1d19d9cdcd7ca1e5e6edd25046ba117844d98153008401a83989c6788274852488d9c3a90a3c0936dc09087a4dd6c08eb2813ccdb49f1ea8680f66d580b10ebb30901c832d833ced19b52b419cacc1e01a61d16913396cbcbd31efaefcf1fda31072af08c1f1213658c4c6dd7640cea2c2c4111a4e2e39b806cdddd6c6083ce7876bd196400ba07fbb9f6beb7656281d9650317d8f2edeb962e62e31fe73d53759a357da4b8571337e3c7588b998628f567909cfbec3d6bd818214fc7f0fe7225e0f8ab64bb4b0d74aa79d9945e56177f0e81793552b5f2d78289309e132d16fc7424155195e1f5db8cc327b650f2e3f1af987af84149052f27a1da66251b08a562e8be10e92f976cea41cda5317035dd78b327d9faaa0b3c897ce1106b0746606fc4b6ac5f358d2f2e10f7938e5fef61e540181bdced5a74d3aead4207e18305a4ee2e999435a75914f21202fe3f39c39e5face4e391910b937fb5abe043d633410e47f99cde9db85015adcbdf48f45190193acf6e6251e99b65a56868cd248f6935aa9c6720c2979fb5a5d54b45fe84fce1"), + tc("610b553621f2a760f00e8902c4012ee240704d5e41ce73f613fddbf5d3e6b2e3", "cad9ef077a98e3f2e5706ebec4960d5a5b78b57cc8017db1112f2c537076350c807d5cb1421c235ad2f8abf7ce905c26b18566d8fd6af44908269785e789caafa1002bea64ed46b1abe2a8812ffdf400b18a39b731040a8c703d837e463f29d4218cc4ac0e38945f5c79263d2ea86ae52811724424644ee274c55fb6a050782807f3df60547ba058465372c8a664e833b5176ced42c40177cd3483e636ae6f0b9c7aab5dde5309e5f377af74314680a72dc1088afb861e6d564db6db6e6d3e66da3f3d539616408d7b0727cdb924bba38c3bd5ebb823e7f441d2567fb36d31313c5834154abe1623723449fa8324c85a149ed1eb8687be0e8a6c07527bc0d9282651cf36d5596e1e07489131ac3c71b75513ac3acee7cf9098d1d5c710d31a80b2a6037b37228810b885160b764223d4b016480cc9835a695dec0013e9e064c0834b580c254c6f04689d0ff80c6999882ce2ae5b4d8f01cdc5c2728890c7c795b10c5b7e01ad2054e750783d4dafa677cef7ca6141b0c5fc6e5fc38b0d1ae088b16a02c31ffe242afa4264c21d4aadee3fe6b3f43afd38fc554bf168c4331a388508adb66a12b85af205e6f2a6000c7f222b5e3e326ae9e572956330575a07713ee32f427878a62146810b85483f10363722279af71290e58fd7da3c79c8fa0378648423e18f2bc4649e8ebac7e28ab3f8d1ab64750791fa9158b0c9261b2da1a8174f5002aafaca122eb2857851de26278f594b59646b3b2f6b51a4011ded6188ee52d64311db7e50dd8197ea0187ef00f0411d501e5d1be946e31603242757b28f3d05cf1744d62ced24366566dd16a3f8f3dd4d52d7fc891a31e83ee8038dd78afea84ec8896958397aa7aba0056db3b16a04a2023af67a1948dcbad4a0a5376475a320371194919c939f4e63971bcaddc344799aaa151434d2a5c402d02d4ccc29ecb1bc40b297d9bf72aa09ca9dd2cc5268f2c3f49e10fbe4733ee83647b9efc1f59bb9f7d8e2bddfce717cb1afe7df9ffe8b4a7bc79371b74f1a038c9c4fb8b7b1eefd38988bd591dd2817e19e9d6c52b522e90dcd89c70d3eed91b1286c3891d546f4e198bfb385554e882ade07aaaa4559ad517c5cd7cea7cc659beaf8214716ed9d750d382389ee8ab7c3c8aac48968220027ede85f939098fd679d97865926f120d8a9a360429cf00a7c1def836a9933655abcb384e9f630b4163f204238e9a38ed949af36f5204b74dcc45266f89af90e6db2c948e7d773d72e653d35c848c77936e6e689f75dd78ec2754e02fbe119c572885bf090b04a4390bc7d5983809e5ac7e4a16f6ad023c8b28e6b01591b0fab80dff9e1300de11be3168ba2f1691e6cb7c416c3201fae3f171975bacdffa520571f518fd8477553be92539a0ecc5ffc161043139cbfa35673f71949ffbda64693a032cde6a34432cc7736f1856b4daf68d39369327744355afb6a4e89b82e5bbb1d53b5ecd1ebbca25f6b51f1b0076c34068676b2b879c13f64d91b277fc82ead7420f2497b86c9feb5341aa553c7916ebe6620de18eba7f453883a6e8a037c314ec159869132e1856943ca6da2411830d6af8354346acb06578ce89e277e2619596330476ee1b8a1ecd28e201c065bc30ece1feee5e7ae182efa8d0310eb731c30ca7184d6e9f3c9cf08ef2b2e63ccb369a36ad63207dde3ccb04de341b5d093a0a140aa0a8dbe811f03114b235f07499e622ebec39521fac671183f0798ac50919d260941415832b1c5686d7c27a7a5d0e6222a991e341c9a8b10a76e01e79b1e7a0c18227a39079d50f5ee94b18b62c51ac36650ed3adfe142ba5a34a839449599355d4ac9542b146156be4fc4dbfdd10cb8dd15d0c51ab23ab19c16500f770693c94ef61df26da43a09b00d4eb7206e9325cd66780ab47a7e95b50df423c40767229df1dd41a808fa70da64771fb91b5f6f76872759a8cb77fda87e2f2d7f030b9cdb456005db9a88d0f27d4796803bd570440fd0b7e99c3d08fd8e603c92098aa4150d5a87c2917cd750bf0077e9d71c43f5bd3dab919c23e16bb90537e7fe694a847ee843df4c8262d44c39bce4d9c49069b213f1074453ec66ea054833fbdb72b81161dba424390413dbcf96cbd56a35fce6510ddc2bd9296f7cd2f7474f86fe24cd61c9665cee251420c0a0d1f56f9fb06c83ffdfcf034a5c27b8c68935c4b2abd2fb92d570f1fb610b3ff4f66a3110a7a0b89c07191bd5928a39d579191633b96f1123e5535953f0d25d28675f5ddee31ff2b6e13ff21a2db1990f4ea8d0ab5d4a73af9a41d5ff8b1467a11cd297ec416f448c5b0d77df8d02e6880a7dca283054beaf1fb986acbf838b68a32c6a4dfd097c9fc998fa13e382399677cc947af6e4ca5b37bdbde394b89582c04bd05dc7c04038c626c5b72788189fc01799a4ca03a1e65c744f6c4893b19d2749709b23af92592044224aba18e1052820250d0c35cf8d76e51c73c94386c6e0f147c770ec379976b574ef2ec6d68c39ff2c287fe5149f71497e1abf07176b024e408479bb7b56b373367d40a5bd08aa38bcfccc705531d2984cd386f0f47f0864e00d758eec1ba0ed0971a0a2a84eec663f198326aa62f931daabe381dea692a7c43425d54acd99e2ff5b7464f5bb794d2009035ca9ff721b1b4494cd8cc5cca9957660f29b77ddf173d78ac56b36aac5e28374c38253b22e16bdb93e4cbdf9f0e854ea1d79983f8f9cf6d25ddaa7648055354a8cea372ba54eefca4cd040d3eadd23b89ab64c545b7462d2d6d2d249a919703bccbea471251d36a1a27bf0fd653f5f1d8490586d9568aab141383b186630a270bc7d2a8a299b4c90bb22fdf08c30b2745dbe6d6ae7dec9bfd112f5611a83f9deff408346dd62f75de00550f6e56b3f9f9b80d7027a0c7bf6306aca159972f2ae43346985a4601b481722eabf3786696f9623e1366f93dc585758fe8f245e82211ef984bd31dcc9fc7d7cecd099dac9e45cbe655dacd687a848f53353d7bc1d68103d4c8c740c91b69517b164f47ed718e52687757a51f086a4ddef8ea4c858580d6eb8064a044534e277a7dd5975af13ef5c43066ea26902e7a36df395f2495ed4311c3736e91450650843a1828feeb42e6bd5a05d0fe4af8d7a543463038bcc3d31a589108038dcffc8c7a3cb86c57ced3a1e3e22357cbaca328ebe1dbd16c1b882fb009a697c1efeb3efca007753ddc51f6e7da8900b3a37312ebbf41c0a7ecf23c39213fc8a23c2c5ee6d783a1b6520b07ff747a143f6a8dc512209c37adbed276de13c5ca27ff7a595545c024b11f5eefedcbf61d92ec0a107bd1ca00934618927da450c940f6dab1c180b29e72c0e8709627e51e91632c364fe45fbdae43ab876d33000fb1ab437d499b33250d75a1d23192a55f813859cb693773ddde616e5570e753128572bc119a46951c86f99b3f931416c0347d95d5a6be502c3c8f696ee026c3e66e4f0a3847aa49815cf40c902ba79ec7c4e3d82ee140c8c09f73ebaac0b5ca11723b08793fe17dff3efcac0c576d8f5d9699b741861e6e2e9770a"), + tc("f1c97945e0468c87f228ccad6f4208cf334086e1d52648b8d9ca28072bb38f1e", "192632fdde8f5d5c75b31a51a941b69d80e5b6a5a05fcbebd9b8be912ebede8935cd45c63aaacb845e8433c6c3a2b4a2a0822001018611ce54e8b1045d0d4ab45fe08714754fc6b8c71004335f3615534efa0c19eeb06dea3612dae67f40d2ee6afdfb119e1d87f234f380b8d7e2f44f5cf28fae6998ec8dd6381033ad3709eaead1ebc3a51a3bb534ddfc1697bc49d9d9f0c2e06edac9b66c492500bd80c25c383c2bcc082eb6e6da4f1c2c4149cd3bf84957b83612b3351d270754090502044e2e81d8236412a6905f96528cb0c43760c32809527a85729a3c3b151f0edf53730368b6e844e1e09524cda222e7284ddcf37e01e4b6ae1fb422907136434406059e69c8db71a43c507c72567e66d5ed6f17ab757cc1f5bd57f246a308e5a93510ad80a565751274ffaa4679b131343223eefacaebdb523bd3ed313aa20899ff15cca0e708f3622054981bb567000191807ed9dc2ffeb7928df868df13a4cdfb8e909fde7867999da248e5bdd361b3c1022cfc46fde1dfec94e96d609723e3c7f4d23a8c477b319650c997ee7a206088a5cb959d8b2799ca597b1193e43a75672395b8c205e3ca177df6976e9f87853a189f33f766a452f111e99a3fb26bc1e3e5786990633f9c8695188bc3f8c8545172fc64e4a9896537b44f13d63e5620c3129ec95cd94d4e267ca7fad4f42334e3a657c742e7ee75699b81856b1de33d4f2bd8402286dcd8afd5985dd38dda6c853ea73b83829b6071af5a69778d8fa0a5a0000df4facd723c6c9c9f50d612146dc229f071deed3258e4ef0aa37693a7c2dce6152469974694005e8d82cf0ca0f9f03436967ced5ca8278f065c9b5241d2d81adc1aedbdef5af4495a797975635c71180b6b3f802be08f2ab4ec7be3d159df01d10b33e5f766cc3c9e2637e6aa39f46fdcecc37dc04b45b81c6e113bad692a037ec392b075c05a7b6172be9cf0d154cce3ca84f5d78e9580bc31bf9e43fb6c10c6d8a7917c2028cb89f08d205e33a9d5a39a0ef1e61990dcbb64c06361ffe9b161a595be165f333a2e148522d02eab28afce3782403fbfd88c14e53cb09ec3f0a0f5038c0973590647958c64e34690ce0ff13396aa45329795d50901c5e984e289e2fc7e91c40df0d44422700202ab3bde481fcecc3de99df9609111c3f57a8fc39312c52ab761ca376f3ecfc7a9514192e98f7c89f43fa795931cf0970142d407cfdd8a4a5f27db8f6e1de352da5820ab4a88f8ac4b5c199e6e46fd3777d068fd97c5669031d58a83129bc095bffcf05f831da8114b1898bf7649cdf5465923f13b82098d6b033808d2bea2c3037e9ef25ebaaaf058ab69e6fa69d663a5070beabc933f5d12a1346b1b7cdf46e1638ce163060f185d5bd88ab73704da4f3854cdd3156a7f938441b13c616e6ad180d9b770a6a7a4ac6fa3798acebd24c8e0d3458f5a82791b50ed17eb0ef05c8eb80ed592f173f039956c67827684227063a402a37c009a5f5a075cf5c309eefc8da517054bba094ae7b0a1126a791ba8e94e44b565c12618510b9e936e1c15eda1522f0731424330eaaa3a9ffe94727f9caf8eb404b01b4e1071be6bf7e2e58513c193ed9ce22c870572fc3976ef10a2255c5980062f8bb9877b5048cd01b4029ff1c7d266b42dad9d02f8c89c3bd7813c02de27de1f3bdcf80f6dc4ff1c80f30d10375bc1684be8e9c818874c38575b511a2d80f9fb6539bb003da871dff26214de6e806c31af9681a075ba9ac4a4e07f7004480e4e3d8cc06a5398171998f03bde219189c0627b8a4a4f8ae174c5de721e29d7b9874f22349acc6b35e76016a749273feaae94116c4e6c9ffe62b87189e61051eb9cfaad72f2fe368258c4ed2d36342c81b78351348e0bdecf63a09fd24df89c93eb181decbe3f937b537a1be0221f7c0e257b82d679cdd6b26a099b2e9bc1e5f9d54f979409a7bc4f4b375f5a37991e046e9b33c01e6019fe93f6d80528195d9281e61794d87776914cdf1e4615dc15eb0007e39797d14fa7dac2e92c12353b2512a3099761f7737aad3a5ad7f764c40e8878ab9903b8fad5113a84603326fb237e8f2b1123f678ca09117ac36b505ca6bcab8c4f75d5a36bf8e856e04bdae19998a2b98fd3d68afcd1dc5e33a5c26e4e8506d05ef879b98fd23e74989d4b8df69c33217fde5e403d85c02bc9c33a8556f0b02d92abbe3a5edbdd6b34581da37e77bc816f50b6dd743e594e3548dac57aac1740fd08f416f113d6bd80e127cdf87e681ef223cb709f1ae4c70c81363c7e83f95f95784e2944ab1c0a5f4f15107f8944bedc669d38d97e2cfcd6862d97cbe3cbe880c397356de41f4cb4a64c63039c833b2e18570fe4b0610db5a9430bcac89fad4062783e22ed9716c9b330aef9b9ccadd78a0b543a5b070fd532d1b005612e68e6222e74a1bf2b1b677b1582196e8a062392269aa3aea91ea0a6bb96459e652c4ec505344058caec530598da6f423622dda7813b59c833eb6e666c5b8c2fa4ffe358b3d69f175f883883e677c159af9444959f0bc345fa81877a1602f9c65172669c33750052dc1aecff644101afa255dc7ef3c2f0f790b61a3062d68e475a75fc6abab80e6dc958c7cd1d87cbea87ba27d186f7a2a52ba446e25e163b18189dac6dc9351d24c7e17b725b38343196a732a9c4c6285dc10f3345d0ce3208a847d510fba7aa4b88fcd812f67310b11d3e7ddc1b0dd30b1f86f6ce350ce26846da8d0ad21adc2f62bc4832988f30a479716c2f90a6bfa0fefe75fcb08ef5aca554781cfbea1c8467aa45854ea026b9ffe1e9cf1db5f3fc48e189c65ff2a743883b99988e2f067ee3cd9f2cc4f841c10ccbd7b52508252ae30f12b7c7619c557385333d469204056829c3a3e3558604da89a214b8a66eadbb71d0736a0a03c44330e66bce1a5fdf4f63fc46f6664fc9c1454290504c4ae2425d8feb9577caad4e326b793ab688ae621c76999564a408becf26c31b5d150cbd78e50d3ad3403e285e96ea57dadf6fe9962347069f6ecbc2f00eb09499c16c4c4453ae16f9a782556ae567f922028c30ff88a2ccc92428c9f5abfb12acfebbdffb0f1f2ff970c3be378d221cfb340d107d92597c778657c8653c76bf9afd185e9ff480746d8831b0cbc34f7988f2b8316cb0ac02e9dee4149056067a912866787d5f552967d10d067d50c67959effd0998a5abab4a1e7f989c3a15a99cbd42e2f03bbee719fe14c5b8d917c60ad002cf75d5ac13719d1a01fa70ba83da1ae53afa8fa937840851ccae05073a65004541125b58c6f8d0bc7d706d704683dd046d5e7360b6669432f87205170624f231d1eea8abf2bcd611e8fd7c2c923d8cef8e1ae92ba3e66aa85a2417066bb7ba68ba1456de9d353a94b146a3631507f0688cc6ca406f7be47ee6cb9488c293c4fb2bee62763f2ccc7f3d7c81774efb79238b1c3902370e5e9d94b41e7d0b8182878712319b8fe856cbc56e1c8530c41931d6c2b5a980a64938988ed84e0706188a56acec67ec4ba0eaf881d177cc1cfeb8e1034fc35774c6cf9a65f30bb01d822e64401d98ffb936fdddd8cf0e70a482f224e0f4f3ccdeaa55a22655b4482ee23f7b5d3ee1036889fe5ff88856c0545b09f62ff8a54aad8e5c1964491d3a"), + tc("c6d1bbf6e19c438a64098a1b0e4dc2f5d2a198c1dc64a729dbdc9b2cb2623b08", "28109f1e97641e7c246ca9dee6fbaa15d985a84292ecaffe025f66a9dcaeb1f0f8cd724896a0bc2e6f7b1763d7c15b61acbbf13fce74782b8a7151527c9bd375d4bf37c15ca39e20c352b2e0b0d8474f904fca4e039dcd6e1e54e872a481f8e1e5ffd3d65257ff814fa0e59b5ececc30fb1ee1b73a87c050b9c0c8db6bab93a24ada3fa480f215e19f63308cec77d1bd5ac1864ae246071203c7499f03eabca4093058d9a98e26f29dd91ab0ac556bf1a6ed7649e5559d6e3c93254e6f4cd7943585d6e1502bbc6cfa4a38223b35eed2bf658448259f827830d897942a3ad0a8a5cc7e3018451e4fc884c5b7cc65f54ee75ebfd7c1d4c88c71a36341327b9772ceb9f8b7b4b725165011b848c2dc917fd5514182e2698f9ea181fe6c9711906d296ab9835db7501e2ebf79ecc4475091e6719c8c06599dd2b88670bdb8c4a28bddf78c97b81bd2fd673d5910e1247c43fdefadd30d92e0d9b6e0b456635f96fa477fe5ecfcfa9f94f5c1d766640c596298502b20cfb3bdfe7f3fd237fcbea947d24d0360776030b0b661a9bdd214764d0e70e60efc696b9bb63e3bf171c62d1bc2b6e53f941b3915f85fb4b9b2e74f391b5dad291fe9f9629e02fc3fc5395ec66fa6fcc5338d353cfcd409a1cf4b6bbea517ef5979d086dde35c08c885c3b9ee5c8c59284e10a5041c9deb7860b112e05e051655dc374271012ece9eac4c840d2c47469074b77f808189a0ea645eee5f5eec17fdcb51d0d1b2d8293b4c8a259375700c3fa8fc48a39cc8b32b22cf7bb714fdc7eacd7190bace15987c7f8674f30f4663f1bb50470d25437948d4353fc2b96e790c19413e9f28ec04a02b37cb0036481803bb6728b6d8814bcdd89dcfe458084b8c8495c4be33b5f02ec69f22465d5fac7d85331e8ed290787336a73238448271a237a34730764c2a4df64774ac6a2512231c54fa7d68fba4e888331fa28d1b4143a20cec2234524c522070a2f3ac336a416c079b58e2ea8b2d71188170b6416b7057b11b999026bd672e0159fa85bc0a1a2657aaa11b6742f121f08dc8cba4cd1624a2a33798d5f7ed096b769b79b810edc81c4e05c17b62d8b2df2140c5dcac0ea9b50908043425cee2ec17c3c3e522d1272969f1f8abebe82e1e1d2ae7b64ba14e00c17a6635498fd7fecc6421c89bad4cb78005eeaae96978bbfa2015b282917accea60b0e0aaa2cf1ed7a6e7ed0c9588d6a6c06e49339823c6180b9a1d0e6114c0de9d89eca817298c6e3be3f53404787638b2f883248eeef055c18fd476e07a7eb5f179c8613cf4df01419c1c6d6309bc53e214bda546f272a372b813ef083bb3905a996274bd0be73c9b79135d8167fa5a058a7f926f9f315f4f823c44ba6f2800e25ed8e1659444d3ce47f1a58e5011660b40392a942e901dc9ab949094d28ac38d1d7a13357f716511d1b12a294baa1fe6da6bf6d81b830dcbaca4f4c09a53331379cb6c42c3ada8d61322de1ffecbb1a7afe3279f6eb63c8f020a51925862330451a0002a6e1755eb264f3c74fb62dc92e9e6ab822c8762cb9f93cdf0659a54a5696ad13ec6cecbaedeba6a41323d7540ad59250f1fffe616dde671d673e37d4a521305e47a645576d6af9a6991b17f3c7119db463a76ec933f05905a1dd621ad15775eb9475eb0cdeb6cd93acc120b396782445637f4f8dbb38a3aa17537d9170daae4e4ddd899b16168a2f9dc4bfdf7a9bee07bb9f7c312efb78d37ae437392daa755b184fc51100c54e82fc1792aa87e58778fbc150edcb8c9f337227b4f6ea5837df8a08719789356281c8fc7ce0663eac9ba76ca0ab589cd3c5a82ea2a1a996d2cfdb245f1908ad74934823b9352ff0939ab228f6f8c59980ccda984c212a0892928b88dfc52c4fc34997a6f0285f451db3a1d5dc4c800b33a6863852c44a2e675a98b38ee8526fb794eee592962a938660e1b7114abcd42924ca4d70045807c50985565efe24f3303987c307de754d8625a5b7339f054a90db37ce0b9f5ba0595425b26712af4435ab8082ae5bc1b211fc6ee44262d26d82a88e662a47688e3de67cf825bc62e1b3e3f625b5481460e0bac451db6b67f40e9cb6b87c77f2ee63792305eb697b1f2f7573180b852615841f6b9dc8e5be40519e9c2c782fd0069b8d3b2aca33853cd29cdb1fec735d266ab25efe2a0fd47476e5cc1173e9717bf402c02843e56bc70d956a3daebb0eb5f02a4021e6259df33b65a363365ab7eaeab3320716842e70a86d3efa3550c59bc0e14b05e38ab9c4e38fe9e7dcf9b3002ef850b7899a0e17539359ad4322d1d53afbf9ec45e1a637e4f0ba10d9d3b244cd90fdbb44f9e7200dca987b1a5e1255c833f3f9d5af3504479d6a900d3190664ab93755478cce9a160ad793d2061fe3bf8c28e601172d2b4685d517083148c1368c4d853b3a4182720d47cc7363d9561ef31f3c108bb9f6642a34ff38846f38464771f494347839ab072e8e9b788cea50330ff7297f9428f97d095f776b9de590e6550dfe2f33273a36d6a201103973032147474335a63e83f04d7dbb940f38919bb45fcf7a4d2ef846eb24fdc4da65de53e2922ac07e177f0fe1e65e82cdaf8f65948eb1eaa031f0371bc49f547eece7c4ce905ae97ee7dbbc78ab347577ae3985121feb81100d2be2e5771c4118f240eaebc3cedc911ec7588359da18908ee3d8302d5e544381b66dbed785dd684cd0db274e648adfcc36ac870db8689dccfea3ca09711b0bfc32c8b7f0676d65d63761422f077c9ccaa2ec80bbdf742629efa5d9c4ce5b3238ecc05e1804a7c044bae3bb4dc3ad6aa3c592f71da7f548148aa11f352d2f0d09b84fb8f5be629c3aa7c68d980294a6a172669be5d987588014bdc1e917463b829ed623b87bc5ff20675df37be0197a5e2f48891e6cadd2ed860ce154354d4854e6d08f0a28e673c7a152ff2feb7634f0085fbcab1fb205ccf3660fa288f5d214212343b5485934a256f043396a039539cda52db6c47a3ab7e2374cb2870e14ca6423312d2a9c48f67352e1ea2732707ca11785d8c27351ca769141d2090e79472645af2729c158479b4d138b1bceaac70ab7dd221bd89eedd8e153215e522c837d7554f45e8877b6544f7071d45e7ec545f7f9bb27db95e9ab2298c8a513d2a2ff8828f243982fdf23e4829d0fd811e9c19e7b2960d8c976e3ee653788c262692210b5d105c73268f92a5b2ecf6c283d85874b5ae3f37bdc520e2f4175fcfb2175c57f46096f9eea1e0c76080c765c75193a9563ed5ad6e0aae0ab0f3f94459ae4b9ab0baaf302e70c08d58c1e1da8308fdca1bab172aeb07d37e79afee3f3067ddad92bd4f51f51ec8e647b212ff0907c9078cddbd4059fdf22350241ca508935442463a6c23ac28b2c2a968a7db22e8e7d4cc9dcb3fb11797439cc0095f45d21e075390ce3483dcd09463a4e977b56ca19ffe4c6fba28874d96ac2c66b657314303aa68839262733e903f074a1463a2ddc8ea016f6802f917614c3cf44dae4b4f6e78f50621b017b6d42265997f596ebfd7c1388bdfef983cc91b9f98c17f85c100cb37dc40c31ffaf2c882a271c7f6ddfa94bf69c7e50cb6d78e489f6ddfd26e34edbdc9a6fea4ffcb7678fa609c8d006933f4becb85283b13c04c6a27a89b0c2cd27125dec2c49646496288ebb162cec4d5a30e3b83a90b54ac61812a908014f9d22ca21f98c12357028971900e57acf8ebf095687e2d"), + tc("093212eafcc72b253f0eb167400f1c95d866236936325b10ce730926781adda4", "06729047c0d4a7b3020865f527f0657ea5397eb58090abd01912c485e73854aa9aa0394cb53d53e3d240d1bd6cbd1e1e353529cb87833b5f476483e684f11748d5ab7a1f18f5b76e5938458486d4d5b1fe1af7ec2a139cf5c4b755165e182f23b9e69ededc39375f0f87b24dab233049db0b34627d53dabad0ac3351fdd94010db51ad3182f1b1276c498296ddd759adc3384ee949e4b2b6412cfe3e8ef769dcaf76be4b2fedce054515ee24f7601eb1553516187954bc0689ef278071ea7b95d58e7ff2116b450728e47571140ab10a327c8e6d333ef044dfa10866a82b9728d3f5f343e1b33064e1e860881df05d78b77b1f363da7d3ee4209e8e2426b6969b5544afd81343faacee915269c6d3b12c22f2a5fa4e7a518c5539228373192f85d8123105b04c2866deb8967e7cec3c0fcceda19fdb84c72597e0edfeec620f8380e3cd5b5ac2053eae404cd6052aae7bc7c666628aac1fc722244a6e80536246c9894c47bdcda71c239e2fd276e6e041df8a930639f7993300d68fda7e2eacb310f49d48049a8b6b1be42fed5bb26179d6e4328e60411afa7bbea122ea9037450d80e5fdf13c93bb7190bf447e3008a34eca6eae08ac71a5446bb568d4df8ca4512b9bbef21648acf2dfec95cd2d2985c97c461da651c1c87dfae35668c008001017a48292b4b0b050c00124446594fb27a418e3549171d151e5794070acaaaa97b88045aeb3bdffea9cb3372950cdd727350eefdf06e7652bcd1910312b35f5d4ef3b8418bbeeb52657a36ccb7979ccabec0c4b2d22014f60f0e847ded96698a81a7f2f3e35932cc5d34d3a5ec366507dcf179309c1f4de6018806284876a96cc9bc54fb5d9e5b3127c74723e0c3af1fffb01e4ede2c59413f1ff56f8346c71e30de5a66fe94878c0d7a217d754c94a7c3acae3ad264a3d2c7df93aa29b4e0a1ab966e36fc8a81cdde9c492e4a09a9c61e63c540a457489c7ce6d9ef3046c77812b97237833da0d8e6da300302210cc009f39beaaa0ed1aa107ff0c06d210ca678acd357709b25242da3de144bb397d9a3cf8441039766407a67bc9297c108e7a11e98b09046b4866c9a912817b6568a04ce10f43bac7bf211daeb90ec1a281437b55f88c6e883ef9a271d8573eacd965360fd8f84dadd72eaa12377a7e1f47fa49bd7c3961a15ef5c311ce62ccb43032cd86c509a100ef8a250ee84b9f7f9730196a86adee92fa93233b2e32c87af90915b23b44cda9186dd51eb9a049c478c2a5b363831ac5fd170dc84312440aa8c83b7fc44254be6f4e81ff2d66f57b12d78905b89600e537754a46cc5b6c5ce2b6b04f8d6f1cb60359539023bafc78aa0e834ba8709433f8c6f0add3d4d743f4b6fb484c10a4267505097082f30ba7db81bdb2476cae452b40f67409536141428b264cdb832d1d611cefb5d4d1326428f9778f929c8c182346042cfa5faff0819667ef4ed29e8d92279a2999545c3335285f072dc952e7c7fc1116ed0c5115c6ce7adc993c23a8b0c96c2884f70817b5c9063c75ff1875a7b080b76b9aa32e6f7e4e8464afa40f7123ac79c45d88daac71d76f795f192c6cebde8c99f10ceb9e90f022e5801a78922f389725bb236afb5dc28cbbf1c9be9617cea56c80c531bb7ac227f6b996c68583b74bff78ef44d185e0d0e5e9d6f34b250dcc4639e3a1a937cdb96e78a217e03ac900834b4abaa495c9bd9dbc2c4e85463cd1c6d1f4d8746ee4b58bad78cbee21ca3e53e6b6d42645fb96c9417a374eb62df7f7d23983b53e1da754487f3c0711712499daeea9e6ab939cbebea79d5b9c7299c06ce53cc8b0c8f8381e04c525cc691443318b70e447e83f485e18ebe4524df0fe0f47ecbba87eefe4f709b7c84274f037e89d3e140dc2bd608f8bcd5f51b3fbf4ef93edbd45aa7ae5326d18b719a5dc6ae91400e2b6c88e699c9f9a95babe33e98397f77fb0fccea59f3bc02a479e6fe1059dfed546644dfbec3f20fbdf4aa53d025c66dab24875021d7934ecbcc0619aabe7f553c9287dd6fb916d2c5b8da77555d91597d895b9f8e0c601ebcd38364c91f28445ce09d445a9e71493fe35ff723149e73dea9eed1e19517b5a735f7abd8d92d45472eef1d2c4eaaad6fdbbe9570950bee94613ae2a934e1a2c7ee899f2861ae17bf8d60fcc1005abec7240c7ef98e7df442e587fe2407cdedaf6c4ad725fe54ac880a47530e08b6452f5d9d044833cd117ee4e1a242fdc07545aa74b559d394d837e55ebf07fd2d6771811df95791e293a52a972b682446a29c4ce3e02f71565d537d12eee21e1ff558c9b74fd4914c6684ab897d9c4fcc4a5f2685b78a8162dc1bb70203f5005acc0257252e0ff9efc43b102e09e9221bf908b83b497a88ed83d0db7c8faca741a28bc5a910048764c5098aa7b419f24ae5e1e2b013405e3976d938e781fbb10ba679e855d2fdcb772ee96ba264208cf4812607ee0be58c307568063789ac783ec937c68607c87cd00f34ebfcfd2635e5c6b2f7d88469fdf8723b81312d3402c1ae85c3c12023880940595175d586298d215661a38a363df4263a51f434362fd5a11bf69b121065bb08ceb2daf2d9f844646916e4a2afbaba77999e7664af9670cbf68e7e4b10834a52ccf02e45a431e06a2760084475a74c4c66f8ff90f34c1d83d70ef8d6a3790a6e3bc9d816f1ab038d5c3140c8f36e66d3976bd831ccfd88c84e7788cf2a3246c8153a27454b0e7a3c59e292927f1dc04ee81e2aa291c06c8bb173b53fbf39ecfd6ec1f57a7478e37199dc39d058283bd046abe25e91422b0b25329d175a7d0bfa21e297c96a5c3f1979af84752a2fb67bcb0511b790124bd96ed13749f1e922c4ace355db866011a1551dcc9548eb46b961feff4f2851055833bc4083ba712e4fb9c30c49928683ad783fc370393a55f904fdef279c0a4e1dc9e1ed7e9058fa5fe363ab1e17a7242d0921809ccae1b9e5f28fa553f7ca3e58fcef7baeb88d03da5e2c06fecd357c12cb04c5ab12dafe4d68ad3c7aa9302c9e2e6e4b86d1c96c55fac6f7b63196a15e8182c1bf0206119d67df749e1428b3fb137ae7c6ebaf75a0fc64163c21c44329cb4457f7d4f36959e8a93462aaf1bffcfde02ec1a2c7e67891dc64c1eaf2a44dd7d8ecfa5f7d596c2c66c18a4d8b07e1909fa87884816f514ec17af286db0fb353befff94f810942b56062a4a84a45324773bd26ed4b5ccf4e10bf715110fdac5c4c05a3ef4a3134bc9c9a7b5774d50dc817eb6a5aa3bac029aeb5505204db40811f69ecf17ebc206746d7faec59b26170ff23ef456235a66cda3b84188495771e53421c725671788633472365d474167a0bf9ba7b212bc6fd91c4210570fa3fedb1ecf6fc77d049830eb1e991951df439dc2116f72e9ae1679b452088ff0d8c76a4486eff15ef9e7d80e529a97b368700a4c6c78847084505c8d9177f08c96fc4baf5be3f4043a21b6fcc200e7f59cfc8bcb71a1e2fb9d9ae85414d92d09bc6e2686eda670eb5e903fc9406ccb0501f8bfa16c0167e2f48899bd14258d59e58aaa8f450387c9d80f66a6f9d15fabec9ce121866b2b6ba09cc93165b8a567f86fe0423813d7a0b9acdc505db6fbf6d5063dc231be1e2d801e80dcf0a82d3d8c98e80e3ade2cdf4d8ebfb9966ca1c68fabf0e3136380e6ad17517607e2a36d41017e60ec0d20d6b17e5504493c94fbda647e3c2195379b98d84c1da0634577dc5fbc3638d9433fdb32f7dc25e52b621db7f3a0194f3ccbbe51be61b55b4e5d27e0bcb2712f6cd3a29ae2c5c8f9a29fe836c02b6e9086aa4d92c57c5aa8996f4561f"), + tc("ad5e6d827897b23852781273209d80a53a215b9176316be60198d8613b2dcc89", "fa8203123566c334559f12f1c37181da32759d0ac6cdee696610445da4484a51a867263ab6043ab52c6c097346d5bd6c83a2519b28d37844ae1dbb55cb8b86743d51881d1bc810f0e4a9e1f589d8e4cecdfe5859ab245a79e0d8805347991f23ecc83a307871c3d1a22d6bdb2a70dda20eeb51496216adab2cac4bc4346053a41a6fac5cd78b5d2c51af83a754f8f38c0ca7eb48241e34b738ff4ce7421471f8a792e36f32c16e067b85562bd752ee5e73c7920df08f1090ad8cd251874d1164e15dc9c131afe318c4caf04dd044ad068ca856eb44f12c057a676cf2f5be9c72c74c4709ecbe90a816ad2af43ee892addedb7089c89da5ecdd20e7bd082ce5485d559bc9c771e49f872339252d66c368c9e94f4e23c85734cd567c4de7ed49f92426a5d38783fbfcdecb7ef62adc5319cc6334357c2d8389da870146326b1bc12e9f70b3c2afad6c4f51f710dd77663eaf37c101700d64afd4404773ab572a244b36c32737fef7e168d41621123078af025f4c25aaab8c9c844e7400cd2d8e43b3bc621db1592d3212e276c9a52ef56b6b4cf39a49673c483805d2450cb2ee045e3b8b5bbcf7fd413e8d34a8716363eec48f76f54b4f2fd08e81c11997c5647173775d2607ede1743e2a22420b3c87eee4d75d14343c8735e2b6a7d276c739fcc75d3d1074c43efc5a624304c4cdbac9f123120406150d265d36c5d4de4441c7fc637fa4b4237a403a5415ea01acb33e5ac6507dbcb0e357245916cfd6b0d0e35e02ca15580d52660872b6c2869c4f1da49b600aa3b3993dc569e35e4af380de7400aea928e9d25510b3bf9aff507cec720068d4721f42c8ecae2a48aee31d0af6f844458c441b93b3508d74cd43a44033b7618aa4d852baa420cdd6b7428a8401e1b53ea2ce33b3f09991e691e53344163b2a8835e0f90e83c7278f9a0d8c64200c502d8dc1ae1b96f9947e60690032920e795950ebf6642cd5421cecd282cc470fa462083e221a900140bd511e616aa7471a7526180771b803a2265021be2876ae04e4e4e86e830452ba79d4306ae45c7fd75716c6131211904f72e1f52d3a1de2081770598628e33e244d93fb971488823da9729169b699922876818d0d91848a3ebaa785b7029915f3d9238a4151910a88e2efe6f41841eb85044443098322b0593826f8cb6b0b76e53120a004e913ce42e448d4ba1f2930849185e7c8e029533b275aa4c1b1ded8d38818efe9535f4d0724a5efbce485cf493ba53f619b56c251465e8a07f3a085627e13d0e24ee6157dd76f4fb2fbad7b58396bae6919069eb0d32b8d57f933e6059fa2ff062bdc7fd86cdebba4e206a4c3cbb2a868d7e09867d316c8801a39dc7c71f2e7fe43eae3c1cfc71e0ccf8ee9465e53f936a9ab608f29c9f2e0dee8e450cd42637785cf84543d39ba6fdab77f1c93d08c5d5d87419540b3a510d04b029888d0f2fa64cf5a62edb4db7f4a8fef45b8d27a22c68952c405984ba94cd2fc0d79db81a70bfaf9237df9b86f3a7e3cd08d4a1ce6b1e25c3bbc5b206e4a018070f0b0ecde81ca834fe21162cce9ed32e1b507fb5d93a625602e123ed1ef25b3c291791c56b4500fb7b12646773b1f59e204193dbaa23c9ae79b293132cd5ec9b60965280ad0824f3e4a56ffcf966f6b2309f3cb78ea7e4cc587c4661a10d1e22ab268091e95e8702fd20db71d73a6b9fa6696a5c0c8a67188ffccb90696d73d8aea1d9a0c0016cd449f9c9dd18da90b25d182f556df1857ee0b3e48c28882b110e20c9c9126ff73df840c0a3f72474a14c91679eb5f5d9a2095a5acf1015598cb5de4294b4a78c7e5818c8f1323234b04e79c5a4385aa429cc3bb41255be8796165ca3b2d0addca422fe8ca3c75892abc86316b2eb93d6e75600668e8afe99dde205efafa472bec72cd312dd81570154490931613e8f0ff9fb0507de56509a3c2e7c97c3bbfcead923a3f2ff11dd8b052d154c220d44e7b2e45bd652b6c885770e0e486325f54fdac28ad7a760efaf7c505164d520955e13527d186c2488a8017a530e02a57f61b5c5f373907714e6df708d47c9923b70082fa641ce2d7b9ca4f3087d8fd88d2ba1533cc6751506a64e06ec92b4abda8de2d75b020b2c0113c15b7fd92ee8494b572209c0b4c614eff2fd89af1fdcf489bb5bbb7768aad64ea49a7dfd796ff7501d75da439028575ed0bf0cedb1fc71e68e3c6cb3da3cbfef8d12ac052cccc1de46bbb43835cebd9a1111e83d6179e6cd10f67733eaa0d9bb166890729a48aba05ec7745409209942be44a278486f38cddb6e9852b0ee4ead981a8e8bcc46ef25a4c07f874a887f29cd61d47c38c9020a0f1f2d4a1bdd3c0b8e2c53c9b4312b54ffbdf69480b2a3707f0cf89638d7b7679aa91869a53664b15ad07efa01e718871ec9410a1486a8dc2b41ff00df7fa069a6e68a33f27a2ea0ba4a3191cdefdf2349be5df727b7354767a299faf5e87ccbeb98c4af036898a80b5c485881645739fbc5a76c3934774f6a078aef28183a0148305f8c0891832402ddeb993e1ca0eabd52d2fd5c5946a94b23b057b85931b698b46f68ad885800ff25ab1b5dbe42661faa902074a3d45ce02fbde7c1c38013b1000d9cf9c2f4226b5ed1adfd18d676d14e7b1422c6321c0fa5171933041abe963db07c06953906c051d7ac1d356bcc0b800a7d5f19dc0edf4d0cbd16a2254208235f972f59e71e410bb6d51a89e12a33979ba6a6f8f5cd5c1cb0329c73fdecbe24d66c6b5d5a1125d162a9b0e982bad23d44ef9924c6e2903fee8355223727e4fbe400e86e4ba8282593695018f50d96b7d1f5f83066f023bb7d0a568d0c396661b876a15a859ab08425ac1cc1563c7a094bf8d9a348b2ebe8cd8826c252123968aa90d9404be82019df5ba764d5e796ca43f5bcb34a1d4f908e7966d89833e5a0e8b00a9c0b385045cff25af35c4f566492769189873ba4380bfef1ad52fa96ee049bacf892dba6dda605c1c00244c7c597543fd2b6aa6f0f044ab4a5219588d5480e759ab44e79c5759f1083f68165d42da6cf04d16ec0a71400c1c6a50fffb06a06ac0fcd23f05ee8431519407c470da99bca4b11ec233ed32c1b1f6e506e34270c5927f8c34cf30833c94a23c0f90abe0fee43d0db8bc56eff9a00c403a4c12f881119db8b7c25905161f2030efdaf80864880f6f93c50b7fb74644bce061c20e9f20dbddcd98e63cdc69e0f48a0497534bf59c06263108171d5e480f0954820634349d5bafe9a4e8d96ae1c29c46c2895227b04a4412993b8b6bbe5d66d5d026635bcefb162a53e484a9d9173d3a2aa60e6024429340b34485fdd68b077b13f4134caf6dd7f38689e4936325b314223c684ed99d50163646a7b725f321c7bb60153f40e3cfece9de2a263f3a1030124832336e12bed3b81d99f3b6ce3acf87bb1d57298bca23bff9034076efa9a362f30b3b9b3e2ee83b455d8a63deff545eb6341910c767ab1fca2960c724915db60926ae6d40f5e87ef28db3a133b69fd673f6bf1088d727ca8d6237d1055bd6b1cda4e1bcc849b6bcff9602aeb73a3f59bd3aac6d0e54e9d5336fa3bc0c0fd22782edbb0b9b35ecfbd99793fd910399d35e0809f16b53597c24e4841fb8e7ec3c4c434b9653e0c1f9d859dc9a365e461db6827c2ad1e07c6130da1b84abf31a76084bd7efb40f302de75d62eed0eb4a2985345c85b80ae532266b39a704d84bb16e9a9faea7b3e7924306f650c2f338298cb38f07c04b1f41736876799c0d17c487bc5e0047e8534ed356c6f30daf21610618d19bf1000bb8860c279b7ec27c6270709c00e3ccf78bc95cd550202275013235bc2b3f0e11791d0f87d125b2b1775f4a866daa90a7ccb1c7d214b19f022825aa0a22db78041250f63b5a683dc9ffe76ab035"), + tc("1d825db86cbd7386c1eea3b536356763c90ecb76fc0448c45a357604c0a0ef90", "08944cb473b828b118a31986db67fc757f238182e790553404b792aa4f0095a6a83291e287cdd16521a3ae8c48f56fbc909dfccfaa7bcc570c2159f26592dcd6b15bc4dd55cc05595ac634b2c3de15360b0f07a03b5957bc9333cc5097919399dd9973ace15e55940178c4c96bb5e0a0a10bae175769548ebce11e0d7d9db29647f197d4b87f7039f5d4e59e016531dbebf55a797ac9a6835032cdf34240a7ee7423e89c09124829cafc5f89431c8afc54fd979e50d48a82b47a53523c84b6004daa323efb708203e5388a6a5110c6ce2e341048a65fdeadeb3837a03420f9faddc3f02a544f1e46d96b07c90c7971a7040a179e8198e90aa019268e00367120d5f3d98a5cce82c885e77144b1aad66ee682847776b04f01f501dcbefe3903080a8058b3b8f1d823d917ecf31fc2d5b0795bf95a55c7093eca7c801dd0bd0dbdbede7d56513128b29fc0b4d25a6240b24c99e017bdff7acafc8f8de9faf5a2944384aece82bea04dccc6d51fc6e6f27aa38f131b7959b13681a09b311d242e6222a1ce5687de5c080508b1db16b6f8290d33a3cc0d0138ac61fd9093825e9d3752889e9f20db9f80f92750eac88b38ac81c0016d40371eab4a87e845e91446b0a07081b84f559cdb95340cb020af22aea1bff2fda12f7a42973ff163a1c6f33db8b8214ae27abdf1c54f5b03e29310fa210125e1296e8af93a2996dbaefbadd4c51c2c3b8a3e2bc9fe060c42ba32768f6992a99599206cd2291ccc5bbd50856f7f8d2d0ae1efb5892c15a799b77482de4553736b162abb06631f1688f6746e7d7a37ee7ef24e6cc901175f04960c01990178f81e957e941deaac8846b3704e24204f43ddb0765c433f3f7d4d201459cd65682b7ddf3d47e95cdb31b96a4cb22907f08ba6e92a4a07703b2dcf150f922c4b7cf181380303fb72547847305999c3c8f9ac877d05d9dc4159deb8a13d36ad1d533a56950e20f906d29d51ddc45bd15c1773991707480e37b827044bdc6473181b760a9036e0d3fa491c2f08c55130d8cdd5ac8e97d0813164af3d28a585f0c2ec7004d498f95c6b62231a632a56c2d0c48fc3a6992d4051957b9ed6d9a86dbccd962a8883cf82caf01da2f51a203d56b6089bc8fd0b1bd414c8063031ed469555e22ef872689c130b1c101034d572fd8cd0eddabec9ef1503d7f728b0941efe2b9512438c7ddb176be2ec2d9ffcd56495a4511428df02819cdda18d1ed5d3b16c6f42aa0ac681a9fab51e8a1a856c15c51a3ec1031427142ea12543014dd4acac640b8a7729e63ab7df1051112cdefd4b988a2258334fa9a7f5b3a87a02074b9f69dd81b83fc74089a91d76aa4041259e80fa255f2084902aeb9e996ac2288ab464bdec47aab26a28a2a8194989755d48fc9a5c9279285f2f1dbb8b8018f3e4e13115d78a879792e45a8f4f24ed4a317440ba63e6929056efc1d2529b75a709d6c0097dc2d97f646f334ebe6195ec5630132fde58e25dbc17dad822d9fa0938a2a2c926b105d108403dc29cf371c3504ff73bce9c7acf9a74c4954ce6a32da96b21cf3211b3e49953dab78c49c3e532a349003c59c62f7d40261cba63a9ea21c89a38aa63ce431c43ae261c4d9999b1caf491fab8e7be6e8c3454f1be8793b2d27141fc107da599a4694c41353d7785c05b5e31440458d17c6db66feb8a9c5c073fb946a67ac0312bb669d9b12fabaa5272ca6631379ef4ed420a4424a5cd08526384c047c33a84d5d7dc0c2153663b54c73dd799a3568c01b818992cdf8143f1dadd6b50cae6eae13ac66f31ffa2b362cc4d2880592b7fee4b9e4cd6aa5e5de27aab9b5dad9f7d39407ae927530cab2b61cd7394a21ef47bfb813b5ea6091458d239664923280ed0d5cca8285bb2281a2f9fb3ffecc8e9147e1e8fac957d90c9e5f513738745a47c2ad0c31fd8986ef3b6388c6e821f166513811d547ab4336b5e04643497fc9f8d6e380ef6478b82b6e2f5f65dd98a63c68c32b94610e1d3b9538f13a7688fbb1ec3448be9bd77bb93a34546172ae8d614f85228988e7feb18c9a0c9827699e8b3cbc69750bdfecda8268f694f4c509befc1a1166f85c829725299d173f867a300987a2d36d1bbbe37be3208fb8efe9152a41a5f0e931b6382ff7f9b18937958fb180e61f2a8c28f36c3c80c3722935aacb81c24aa17fb3e7a1026f7031a7449818ed62ba7705ca27c2d3268f90b6322921683dff800a306cfc186cf2a61b37f35837b217e3b2cecb0843d84eac67431e3d689f01522d4a4c73618b7c2965c9dabb15c0be637d10ceef72271cf39a7b803b41767bc34433c3e6ff449a439ae13da1eafa038cb9f2e1c84f1ce39c05df56fe3d7b82386c4e628b6e27cbc5d575c66ada3510c246bd04db48f4afc2d7352966da2266c2bc9831532f53655d8be42b421ac0d70d8ad1d3587257886dbf93668e907e861ba64f45999badb0f766eadce5238b5ed397f265935194812c03c5769137bac97140525303cf48d65f39004a3f59b1fab09895cee05335d15b9b12265892f4abb92ab1dd2002ed00cf3562cb67dfe1055968e4ab3306bb34bb87d0f64b26848812a2f7b50424a21ff94081a7f70f7b684ab0f092b2b085dcf84ca38414cf7290f607bf79c37ea84253abca8d4184d2dbe2e900200b81479e1ce8b71dcf2bd6e3c557a8e431d627ba669c2ea03068e0f7ea62c29777b22142d7a1d451bd541ef8ebddbba4e3bd8ffcd340e935be7c66efc14a13ea48134f655b0de3180101f09d204c379743a357e6df1268b55a9f7524398ecf3a59849a27b142239059998083e8fa91785e91c4d220b2fb17e3389ebaa384a49d89b5d78136dd2454f06cde9837f096b744d53221127869904ac227cdf30bfea78cc5545583f999b9c42a1184e2fb9ff3ec095b9da0d138205c4eac4c8c480c43153608849f63e161135c79d8b6c9cfe9b8dfd8afab559d8b595ddd43835033b4bbd391e028bb2a60832d9b697ee61408f149744dce71aa11bb2b0436c1e2626ac3a27cda293366b90b9cde2d927855130758d3946b867192dcf3fce9a3b9a5276e8c37b8cb136fc90a6dc22650f95e796a9886efd3f424be63a66dbb1041cb3d4a06f4e7eee89f0b6d15c36f9ea010c66b332011c8888e8e4ab2b3ab5223191e1388613a0fd0f07c1b26d7cc7cdf1ac62a226454d6291b431cc3ef2db2b2442b37defb942117fa247096beae598611b8104f37bebedd8bb8b949a89b5bf8e228eca1d8f16bfec75a02ffbb4eee3a6d4a6087c43634d675311e72a9f3253bb5dd364e07eb4b9c84f586ba267baffaefec79e03b83b18595fe06d7e063ee604ff287004d141c1a43af0ca7c5651d98f633fa875b4743353fb07bde59b6567ae25f7095f1d9edf30570e2f7d7ec194216898d910f9e295a41dfee072cb56f914bb78cc9854129250f9874b63bb3ebe9a1cdc6ebcb0916e1c440354ded6aa818f2811da913912a21d3961ac94a39f0827d3a419616905dc45842c8e69a43004b8ae922c8de1e8cd0668674a7760153213835bc63fae4f8d65614afd74a34d42abad5025b884b34639340b45d49cced423771916e18aa077291923017ca50795f3b7a3f349a3d29923833ce57801c631576e23b838a7767ca1bda92b82ac502db3688ffc83c09a4e40cac31d20d9d32fa6724a80be7091cde9c7a6560cfb326b467caddb9e9b7a491eda283efb0b61b4a1116dd859d5c0897eaa2a3fb2cd82ffb33770bf9e08091363b6b81d23e61c2a647d2be440c5c79ea89690656d9f10b1f07942834e1cb6e2d2df106eb6d6a21fa23819e65028515e88bd279f9317beaffd394ea51f8639371c3a89f11305a4ca35fb0711f5e2c7c3dd1659c790245812113204b4ed8aae9ff09d43c6ddb13f5070d98831b2c7639fb6b9b01c288812ddfa8861db32dc8268c07d30cf969953042b3dad530d9d744c06aabe7a886c0fe57b09b7f42d193fb3e9c06329818251a2f7e6474462c95ded"), + tc("fa1dfbfe0aba58fedf10d6d177818c992125a12ff47f7705a20d359e6161f7b9", "3d85e0037343fa076ae1a5ac3b9b3a299654a9f52e73312113e0ebd78f4b599848023aa97550dc77c91dbfc922bb965508d5cecb139153d6d6faa4f8babfa2250d29ae12bb010d6d3ee71f910773a15283ac916f8ca76d359ac6b654be15cfc8edf2460d0e993d1df5ed9c73e95d357d3cff052b3f4ce358f96a631f0f7be4c1b9d88c7e6c04682ce65cdbca2c4e9cd05d2f51027e5e992add31026d37fd91da8c41a33ee726fadd3d516923c6cf5849e5842291039bc2ea94d93b2a8ac7fe7275fe868928f3fac2beaa44f06375d8b92cde7c403ab9f35b4f0f34eb66a5a31dda32df9dcd77dcb040a6cc127fd109cfd54410d7b3e4e8498b04089400d61f0082ca48a9d1d9565487d708756a9b16947e9c05f94078e91c565a56c9f7205ade6d8e9413d6dd0fc8885fe01ac7faee3605e22725c9a52fc1dae92b110c38eeb87729a9bb2263b6d43ffaa57abe9c8438b31d15cdc8d80dc81372b4d98154937e7eaa0eec0bf9c71fb77e2f7c360b64112ac8d8fab5b574c21fd31600d341a9173a3ed05b0f417c18c58e526a010ec9ffca19461aa9ea5f85416d029689afe7f69836f89d1712004b8c79020d6cff0245c7d4b1c196750d44527ded407595a7b28ee8c95a85c6ced826bb7f61d8ead9e0be20e67d30d5ad6d2d4dcb38f20e41cead521511754901f10831f8ba88679aa42e9c47308bc5a7f3ae07c14a8ab992cf07a26715f564b11538e4c441a9ae54a5a8271b66966c2c9065a4eaed9884360ce60415e953bc31072b4b2e3f7579d6fdb70fc737c70cf55ea5f72743daff866ba482199f8594d6d72e1bd07bb82476ea95b7df9243b0013542ec95af486ab184bbd93e6ecceafecd1a64eca30468129683f150657ad050014901d0d4ef1005c5b86f98f2d7553fb529d7524c3f1b27696fd98359355cdc39de67cf18d8ef58653f3c0499e085a8f9bf555db9a1604a9d640c09c45d2b4a8d81be369349389aa833356338584704f864f04227536b5f95819919c8360111fa2f1c9f4b275a18f724647bdf2a7830bc0b6af386e9b5fdff023116fb4e220e9eace1b6e6b4c68c1c7dc91902366a6cef4c4163616b85db15c3d35733727214b17a47a6d9c5e059cd0174b1e5fa764ac11d1e859b8f092a4dfde12543e5ac4c27e396183a63fc0a76a52469145ce8b01ba733c1bf4ae0d3da35f5b107f3083cf07bfbf0e0e76b3fc7fbf5e515c6b79403523db6d668effcec49dad419669dadd343870c82bf5b7624c06b9f8dbe66e49a35495b0b17c25ac814a0a58e5467dad961349ccc2e64c217b8c32dd28db4b7a4d8fc841bc1adbf9f1e3c1f871ac9e3d03b0e7fe25aa79391de961b87209c6e41d73669c3a8883856403736236317247f332bc4fa1b14f19878e2df7925192ad5c4f7bf431a01a1572da98f2b01d1846760dbfe3e387357d115135f1df6e4b1385da5e591e0ee2295858705a2f16730b0cd7468c08612179e3c449f9977c0ef6688417190d7361bb94c4782d67b038c225ea703b8bc7309761561bbf1a3ae4925cb0e8e33331615ee3f0662641cef3942f9a003140376393c58eaa46f6e994a375369277331795d9ae7055d4dfe87ad41a843202e598e492011833525d2d568b699841e6a82cce59b51d400ed6f896c271194f2c8230e271bd21ad4345cb8cd89badea7fb6188b0167490e1493264b68bd995d56c93e6951ca8c554b1e0af35850377bb87c13bafdc81e3c433ff3b5f13fcb5338b31ee619c59eb160e4e6e5185ed41ecb18791e6aec2dbb898c4f951f7179b3a523a3ee8d75bd375f03a1833e5046727a501178e8eaab4e25a05799837f1c91ffd4ef364ed731f39ac656371cce01da73a85595ab9ae3ce41e799ff94c676a4509d774b720f38c1d924d17b09eaa04835513c96530654dcaf00521b8a74e40774f146b00af65bccbf477b58de528aa2bb793f47d55f4f2893a114eed09f21cd79168c52134fefec9a79659d4ae45f28a56a6e65376188024652c0cbcdeb6dc8aed36604e03dd61d4ace9a8b7c54ebd35a19cec99c788fa02e45e5aa29160c661051b262080841da9f3be61ad421b4cebe7eec77d0a2c2f14658109cde4741a8f0967d3e69d95e6909098a61085ae1237ba8bfcdbfc339deb4a11ad80c118023d82d904a0282ee58d0acc180f9e9a629c083cc70b6bcbaa47adad76bfa0bf0393e9fc86c6d4374b1fff93e9ad8f7643bcc769d2e1b91a571fd04f4602ad08a7caa92319137291883ff0cee2023bd4120577611d687e71fd39bee25d23d00619083171de81a0475bf769c972a0696031719719187fb5c172ea05c7cf5740296683889f0ce5b28665c0efcfbc95a4bf7b9c6cee9e3abef317aad2c829e59533dce70186657c6db44da42820bd1f9191abcd3495aa6d1661fe0735f4ae7d567ef0ccab731036b24390c56096d3b56a1ffdc9cf36f175b587612dd7ecb41386f82ba6f81af6e684bfd6055ee237e5a22a25db0ac2985b8f4e7c553b10283c76abff2cfd415d6d39723bfdf88f2a8c587c5c921d4fd4a8e7298de0e7b2e897faa2513d5ae1c315a593421db07dae696344ef882414e323bd03bf8b97eedd732e44c737ca232e4e6a1d260f367dbbac2500c4619ed7d7170d2482a1a98f99f9d4d6f1832d07e83f58e682d89eea023fb4a3a9438c906aa36411e37356d95689e4ed4d49548da8e8fb59c9c880e776e2ed8707dcf4b57ae1657a5cdf77a4a757886aa59b496182a68b62cb41360956b511526e8955a8165898706258b3f6f9d504eff7c2f573e7db1064f9a16391b11d1d537cf709572eaea023952fab7c70bb4be3872e794930cbe2ccea92cdbbc8586830aff81264cc0a4f3a3ff11a4d1a7d521a8ee737c4ffa47063d8e88d69d381a085d71efea51a3dc173f6f76f2f4e1209a536951fe946e548914bcd7bc4dd506b41b1dc5baab34874362ce0ff03bda19cb5938b813c57e44e0d61db6644501cba3d59b6cfebe20e35e4f670e1e878e3daf70245d9c42a190d575b98f9baf88ef59e5564630d1c0be4e566708c4ca9131813332dfd7f267e37caf7681ec9bf5ac84586fa9a97c6c8882fc24358bf8851688860e838703d3ebe1498eec27d8ceeacc0afb158f5ccf1846525477d3bf069f63fe26ede5ff556387b7d92f9540857d799d77d1d3319c86695b0622f82c57a05952f6dcf114b1d36f5629962df10727b2b3f9a9e583dabc8b561dff53b3e17d72cbcab8ca974018d65c4e3054ba51211c31f81344cff4354f8122b6b2b28192e80999537d783dd5b34a47752575b3baefb04247703271f9ebd89649d0df30bf030c9dfe39e616470f3dd75e0f0595e572e70a93d1cc2ed8cacb4dc2431546b123972649b82d5876e09d6c4d9e1d20cf46c75c4e2ef76c162e5267b0b9c11b6df4f32925042e191b6ed5da0245b68de0b09087e64e177e41a5a6bdeddb4842dd08664e03fa594572982d415a64b70749ed536ea12851177079da7935b85a81f00af0118faa62b879bf5c0410672c5948e545218caf9cea64b60c9c054a128e7867572e022896534d14b97ab668bdc9b50ce34c2690788f47cc311b866d4de1facebc1bcc306efdd6956e58d391304769742a65cba3f81d6c45a79cfc2e94ba44f9c73986bdcd68e9162d9fd52afbaf0154e20e426836a68e6fc5d2273cd11f09da0b3019b7a5288bbf21eb3ed02a66d787402494d51f1fcc372813bddb5017b0bd468a88a50d65ae08076d7c754e64898074f3048c8e3ce1b0954de60dd97e94faddf31b383f7b1365cb83ab0b4d30a7f8953a73435f9160d45c370f39f3db13ee46a37b5ace3057b83993f035e8c1f8cee6ef7c3591277cef69301d54eca4670255837504b709ba3b2dd091d95ef6e6c5591de439f5a882e86e19d1a2d6df56f0dd26e8e954de5f3d1438987ad2ff1a22f4320e96feb55dfacb83a53d1c84b6b25fadeea4970c85e82866aae5e7a7cbe85dda0ab5e7bb0a074302c49609118c8e34aafa5ec7bd1bef5d81b25b5f8f4cc8f264086d3b046a5eeb89a307d044c076c3e4698e60789dadcf265172b75fb5e80b59cff939c06f937856408f709"), + tc("129c36d11930da0bb8e9adc3f49ab1ed4a5c6fd06587e25c752469175bf9fe08", "16f85f578206f4d625a5831cb3b029f09d6f9e13f8e4271fd41a429ceb638a1300e51b403ed6124420b363191f55b32e14d286584b51d3572536a0b7022b63f844f85c457719f88d4b91b1034d2b58e77c3faa34a33db8089284ad13c993c234ca883e0263d4fbe209afa31a445021a2fb4f457633a54d158d94f2342bedb2cb65dd67088f8e017b58bb787ed0400ae30466085eb72f190118bcd6b00a7e1be12d3e86553948859fdb930522ac99399685fa8d2927d4c97637ea57730c1216e00c2bd8e8ba684e1e8202289f8708d5e43f68efbd1dce5db6408889e5e6d32bc6b20c1e40301471ee658a2b89873589a95f1b1923972377e6599dc7a0276d52b54b18245a38cf56b9e4603bb9bfb7f9a68cb63184221345895dfaefb6db77c8fc11a2b0637ee6478db74f2dc771ae293c9d1b496a410ad012133f65eaf4d3737e01b4142b50c739b0766e846e3a372936e8f05d00cc91fc4b01df2e39151dec6c11c3e059aeeb884ad4b146f0ccc66ce48d8959d7ab9920f895fa911d6f127c97f1e8583d5ac8b0bbb959ca80e6f025b4df440747648dab69662edb9f4ee44a6eb7b3a086870b167418c4cadfdf6408e86fc07459d37870f95d1aa11d3caadb3fd65038cfadc758c69e8e77b9f0d08844f942a73b2a16b99fe58f35444c3e5cbff03cc7cb4b95da6ca7d9a70ffd846227cda9bb71a7212cb7f66bd635b2b25116f1a68ccc239fb5c510e876bc602dbc518f068f55024715c2236bea45f42964acc022ecc3653c12399fd65095e0e30614d02d8274c63bc4b88f3d11dfead0e406b1cfbec34065059ddce76327973ef82d5b0a6db9b3014607207e545bb9650518e4be41bf9454ac5fbfc984586f6fc5714293adc8438c897522fe2cc6b1b800bf64381d91abfb2ef2b7703e337ad631d24498fa36fc948f7b7bd668187ce9d94c396fc322e53f341971f8b79d504289da46199d7df841b985d0ebe2f82d9b742a92cd1879cef6a9fe968f3dd0875f4c172afc3a289a86da0f4d03e8e5e27df20eb1c819b2566a6362a3a202ed318851f218d39364c4c47e5b47e16894b645fcc7a84ff4ae8a075a65237559e69f6f634736129a6783b7c13bfbbbc9e3fbf5f8fe52dcacf563ef41c1e876751206e5fb7cf869f3f6bf382c1e73334f1c05e5f6fe81344b7d5cdf58061775cb3457d7476c3085f9fe620cca92679b1a5490d1620f613b7e8ce137c2f39ba5ccbcb7c61bccbd6a024eaa79e58939b7caef19348f745a4640d15226c0532f1ac35740ae23cdf1c5f7113725022a7948615a33468a3f2e8450154e4d9de452d3b9d5368980075858ac06ab9b30fdd432e44edd9dd3c57281bfb5f4e6822d8bacc2e3512834af79e0f8e13f8604c6cc838e2bd356bc3224d351187c89cf0a35069c7891bbf9f2e7d6b5d186ec92ca96a17a75b5315ca5eca94c03767c500b2728e36b7c7c10a39155af28a8c262152065cb68ae92965f0a0289fbc470fdf712cce4a03cd0afa7d9d3da1e0799177d78c5e87ea5ae6aa1fcf4a514812d0ed0587417f706891cab158429a326bfda07f6c3112f7212756e6aed0e548cfd788aedc7cb0d03d1345b2a28c6d3864ec67360ce237f008a225de38b3c3d95047d2ea919e47ded2b2b8139cd37d300a5296550a162cafa5bc0e8014830495487a3c2c4c49282b8e04bb988d744c0c7248ae2003f41ccd44666e06796c60a0e585bb1e10b5cb8d346e58f9843425fa3271b5227d0498aed2906f36d9076b1083bc26fa3f505d908f0b294148c796ee28aab1191b9f46f5a4950485dbe913b9e22365f0064720e9f096884c0b36d18405f35ec928101adcc82e3d56dd578b6e81dd0ef2a98dc9416682883522a5f357c36f292edb5337e0e6b33db1771edd2e98cf440a6116eee24c7eba0bcfdb2ff1e1c852a6927cd122ed05a2d414c51a56944b3012f5f9eb545b838781c964bed1c55f0b4e36f258f4d0ffd1a01d5cad9a2ed53df6a192ee74df63df7f6587eb31ed365f58f1666343c33e42d75322fe5857543cae191b799e312d1ebb8f70db19f831f136ec11f3fdb80b693cd2d2c2a56a3d5a1f48c63dd691e036387d660ee988885f08e4db47a72e57c8b16c583a7ba9c13d6f686e6c7164e9119709bedd892db2ed244e7325c311e3fff4827cde0ac7507dd2d2388bb978c6280ac6e096a12cc8b44c77a5e4b4c79e2cceb84e24ce7e3046867f2b96aa3dc663a2d66d481f47d81ab487b1a7fb04b6b1443e1edd6a2c8fa09015bc17cda3d7a82b9dfa4194094b40353e0b23b1f878c3f3fba505d7017ed16ef0a0372f1443e81d69dbe1fff79943ac8b6fa31aabd090733109917730333b86c093d7227dd54fcb590614d8f195922b120904393974560ccc44e225d1740f5590ad873e3093bbb1eaac54ac77047aff1b8b1439944b5ec784ce8a4b1a116f7d008c3b23f2f7c9ea10b802b3bd0c57d00ba7d5fcac12fc3b033c35ecab30c2563a0143c7b54ebf3b5ee18d800ffea1a04e92a41754b1b0f7190e9aa0f12f3498625f222de621ec311186e2299f7f86b4d95e3024b46a58989723ed88ad57b9b9917647f948420fb0cfb24a865610f0650ba773c258df09d739f73fda76b12e7b56c1774e62ecba24a0e00da25cb890642e49ab6636ce5b2ddb572d6caa0a9231d163a72d5666fbd466724de80f7bc67a47f9ed51367164b9f8762b7b4a0cb4e91fec551427cc9db52855c6457cad53599ae416d2e9434aa4ad72d191e162502d9afd354e8a3bef8b85a7ca1d52735f5c1aee0a7f98e3d1d793148f0163e4f8639c07980c9780b025a8f2f78790186bc382b766d8018a1c3f6fc088e14fbaaccd9e97d67ca25938799798f11edeefaec5f96d1782f1073a14b971fb09fcd37be34d39a6b5e1308f53ff291cd19bbae6abb7997836178c074f7181d26931611e1c86e90ba8ef3781c81ca4c93086dd2f4daabc2f7ef30ced53aafb90f972b75774cf7d3112cd1ca5bff8c1b463fdf46aeccee30a2247e9a2023ec1e3ec21b2250b8e9fe2b4bcca82c84f0be24600589f7b9d6be5bea4d2ae543d738d0f2b7b086c88e92f7e18b323bebc44eaeea26be12fe32753e3d2064e3deb7ec9ea4925e69de6b41e31820d49d6fa8e303de0faf1b1fe6c7c23a4e1dcc23bc626c108d5184141d6914c4dbbb15d0a2f55bc2dd4ecbed4c419b9b00a6444d639522600c8f9204b0de1fce3b6be57e5a9b0e3cde2a45595978fefaa4008cf74b13ce86db5c9af0ff4ce122f60f07069423f5e8c513b5b955d2a5c22b11d4103f9f37afabd606d50f7976c88fe0f01cacd075f4825615d3b9890f9dfebe49ab86e60f390ed2d756594e94827a4946172232979665fa8264b7c0ce1d8bfca203edbd7d394a9dd06b90d428da3f7d58f961d3680608ae82a0469bf3b3eadedd344898240663088b62b6fd38235bc0d391151238963c8061470f07e21d3b7fe3bc6cfc4efc550e2940872b67322353bde6025cd6d65c169b9d66748d73b9de6bf9edc57da1d7aa072bb2c35961df26fc7bc5a4928a4c6a95371b87e03fc52c2a06342959f7a1bbe8961d69ff1704fab283b4a5bd417a80e45d92727e0547a43ab04a59272ee8b3f6c4495414d00b88aa8a10e22a1a6829dc2b3ea2f9d2a544ada933fb44251dc6fb814c82b0e2ab1dad9f8609fe86ebefa6277a6774c81d4963578d56a49ca1a5b80423a6b1d1735ba3e6c0a0cd4aec877d9aaea84245caba678666cb5c2b48ae671befed3ed327188ff20c1a5e44a7e14e4eaa6be3a01c8a658e37f337f3cc8074d65eb3725595441fe7d66e1321385b2181060efcd92fdd5a556b8339890884a54edf0afe4133123f018e9a27051a9bcd04731e9ead9233ef0564e162d546319b13fc430b8d434e2ddb069d983cc12bf178983868d34f39f252a8f1f1e827f83a53bd7c04fd83049dae31e2df2b5efb6ab8538d090189a0a95570e3aede41e938f4af84d195458402829bd27759d3a429153a0a8666011a023ec50cfdf765c55ecf05426b872b95fcf281db662dd8f1689d08ee83bb32241b29765a1be2d8c9e5342789da439e7d94a4e6730741182df1e61e18e5b820d190e46fa461ee4faf5e797290d3458fe4ba805750ad85679a167718fdc1b35b07645d9e82194659fa4d524327600cbf69e19a76944ee7"), + tc("71d02703d9a6a506270d654fa9b1aabc577b75d48227786ccf0023666e6019d3", "8598b32e7ee9469b6d33247aeb8afb12d6bdc3ee4ae5a49683e84c73fb353a7ba6fdd95c497f429e82f07b2b652d7e415e9b93b59e541abf46c88fa54ee068f90764564a11a59f1d04d8b88de186c4217285b72796d5c0575daecc30920f6bfdbea772a7b18339a1e2ccfc79fc8a9839b20a981d5291025f341e5d7acc1a3bb4129091a5062b9e54be7ed8af8196ca7a9ecaf18ccce4a1d718149809d08a8ac86c9a12e76379f634ac1615af03abb9287c4b4a79847b52ed4fa17e1527495362b625155e9f1aa3a3e5d0821240d8360894e19a5620bdbe00a7af6c40b1e513490671bdbe402379f495c528e218028d315f6ef820c383c83630f7564453f0e2d387273854fa05f4da7bc24cab0b71d4092a432cb0854916f71c3b8640ca602547922c9867b002df7382af29e4e1b993d1fa74d950063e527db6ca0b736b8906962412ea1dae218a0c86aacf51362bb8bceb3826bc3249f1c5155f0cb50133d0527b98ba21f757f72a9c52d342de013bb38ea52774a7c012dcc0a58051cda565fdb45a1f065041ba5fe62d0c46cbb1b9ef7d669be319d98834786f5532225d26d184abb38e345c4cda3fe3b85225d6f6c70aeec801bb571c5e44d9298f02f632453c14d1c710cdec5843b7a055817587d790fc589017348dacc18b686ba0ee29d074cffa70017d972449f4d702c051125feca48ad3c5f88431789714c90f48edfc382c28f5d9439b88531b003be7935a845dcf5ee545a6617a3b05eeead89ed5b950edd0919bee243ebf5cef536a0a7c28158bed2f838f494a2e828c580b48d88e216f50492e754275697882c94d1add46fe443b766dabe2f9ada142a5bb1a096d3dbb71fd6b55810ee5b2989745695d4bfa143013f7ea34424833fe09d8966a0b97b62953c4cbcac86cfc83e14ab36a4d9f8e8cd9010713702e1db6c27bd0166777e2bdb5268ca388394986683db83ae0603fa611acad587fdff471718f20f42360fb5c3de8a3b3fec068e9dc496f664fdc0c305ae936c7ae560a0568cc509f99ae62c6284f8bf2a2801c39c79295609b622247b9f1f6e0e77ad89fd5449cd9bbb84a523795f579432c19e92b270b6846d72986ef052dec372b3db6d8449f3602fbcd662e2dcd4edcc59e2a6d15e8a41ca7932343981770121a78d34b7400778072c89de969d0468ac052ae54d6076660c67e47ab93313e9a979d80e05442b4e328e89e798158b747ab237ca577acbaa07cb85f8ae7b1c9325fe8b269765399ead37522cde1f071dc26bbc4d1e7aa8f5f4dd66a861a8f4fa38183b3962b9739341404253c93d7dec6b454ddbac9f942f2f78d6f75897e673b9ecacb1579e298b8b9446b11aa43c6ba7511ed9ca831152d3c3ef0ef390a2ce65f4f5f6e177b50c9af912ba4b4548208571a43d6854a8ed4479d9538456539203d490103dd23bace095d0bf66c17f2411d514084e047970696400e1bc9ce4898ca62fb358b096daf8a3857ac398a07882e2201f8ca0e4d1cf0ca4e47bb268a91dbafa837375384a1bd68f87894af81bbf1e220c2d3c3b2b93a877cf6a7c253cf660b498d3efa4f38b9b401816c0aa51ffd3be9f36aa1d2335f4c079487be16e532bb0fdf8e00d5d8379c0f617f7148009ec57dae98cd41ac01d3d0b568e0085775694c74fc339fb5f5a57c274da07cb4dae54833bb74b0793e93e8f1b5a7f5a092e90459f2c6b7e7a6f488e3c7ae10ac90c358c5bfd603080a544822b4663aec1a42d3f16e335047a1c9ef6939c36cc34dafc9eedb13e1a9559279866a6d71a97ffd317fbbcd8cf80476d59271136edea5a186e7eab7fc28881c08faf7c5416ea4801a1eb9e0c883dbdfd5cf0f0c9a619d91481e413a74f01d86bc762f4b05497e20893cd55c0c570caa3fa00bd537d6373b60d32ca14a36bd2fdb3eac1344e4052efe7d1abcc77480ee433008debaf71c9fb7070005d8365ac112f151b374da141e1fc62b3e40a74f3546bf4f02ea8050b823cf2cd5fd134f10abc2db0b9b27f5c0a44e320eff81a0f7efd4271bc9d574d6a82fa807b05f3892564d8bf54db7b81ac87b633c53f8187436c1df435ba2d16d72353ef7b39cc550cfec1ec2502902aa6bf4d50563301f0b39a9d201fc02b0c9acba147843a5ab0ad15e1fbd9d3880d99779d0fbcd217746aebacc231a1285927f0bc808b2a52771798a9104792dd0e9c938f66929d30390d16f57591dfed8424792c1a0cfea7cd2da90c6c2a80234f3b3500dcec078bdae333816ceaabe9b686ad0bb24f65bfca86222bfc628843bb4d328da1ea28281d9a3d29e0d7eeb9fe24b33284d73486d8d5f37df7450409722e828b879b8f7d17aab002ed2c2fd7f03fa4647f49d4f1b204c0de0b413c613963a0c3ad0026adec9279779a0c7986526f8c9742baa00c088b6826c2449378f47eb6db75cfa013bc19106e18d062b0698ba02054d8719f756284191461fdc5d33cf0d15419abea010c8bad6e10b79cc39334fa8789383ca03732e3de9e454d2944913ed2c74021b7b393b3641448a6233a9eede72b8dd5a463e5a2dd7b44128b09c1d256f6942cd7db74e4893402b6aec1c01170523bbf33015575fac4534700e91153370006d829e4b76216b940f1945fed31378800bae385a54c4d32dd7a960e8b2ed1e34fe8db1cbc7d1e7a86847a69ff5fabd40b0e9191e426843fb79a1dee7dd0bae4c55e58f322a4f6586220b0a5d483e70f998b10656d1f56fe911cfb0d1cf9ce1a24c4b0d4f19a04129dda063fe17cb8f00cf9d516d44ba0d1af9ac23bee55ca307fe69f6d1753ace1b1893dcb455934f73485ca3d8c141b56cc8039ba3cf7f29dc680531facf117bd0f6d10be295426e18167a33d22a83b416dd1b848b558a944b168a2bb11bde0c55f9f9b965745b0e8e367fcdc57030140b87b88613d5ec8136675fb3cd0dbd2035f1a72844eac0c2394f2c4546674bda13163370b1e3d623ff301780f06d1348e99c1386411dd152ff4255e869db6e3187e736f7015ab4b8d72b45a22b0f4f3f05ffa703c3b576b376fec2aa142e68bd1b5285c138afee111987a100bf56dac66217c6cc3aa1f15b49942c9c3c00749e2cd78a0438444165e72dae65b1bce985325e341e2736f11ea5af9c8d00c4dd1c1523a09603374ac32aaef82ba4062a0cd93790c99595b9a82205fb5f968098e219002fb84cd587dd68f9be290bc4752a5d9dfc864ea1bb67878c97147cf547da959ee3ca1c433b72868527744d29bfcd2e323530a6547389978bc2315d5b34be3632b641429cdf9355c48be23ad6591581c706a0cd25793015d9d88887806c2fe8504b0ed19ee74b75627074a7124dc01f0d337f52044bda1b0e057470ed886126a8a8b4bebe21edc21d6e3b9bdf88872777787ec42e9813f753cc776bfde50cdea090e5f6945f242efe0a776dc42de1b442e510ab87b08d97a56399d171bde5f7461517f04b9b50c3c42824c301955eb9287756c30bab6be7531989bfc552272faa237b1099c78f8ef3f78121b61bb2690c1728dd0300d1299439cb99d906c357672112a1ea35f83e9144f51f92dcacfb9285c75c73e2381433cdf39327a8b2d130ca2a73f5a769b00a85855ae967089ac941a5f1b535ea5b3c68d01af72f692b700f6604fbee54511973269984fd1744169a33029fbe76a43647f9a3119063065dcf3456cc75f74c5b9e17dbeb8cdacf9cdb5503687509e4b514bc1f9f90b4e17f167cc426f956602fb42c640133d7daf0227a48dcf22885f58ea87feb44cc4e67209116dc9e6008f2bc7a04045b9a0ef3ebddc3aeaeb776cf63adc4de7f091fea139efc268fe243f1902434eed5a8d47f391bdeb52e4a4918ac40cb56c8a73cc0f89a643e6135361d098d9e4682d2526ff2848b8fa0daa79573bf06fad2d61381ce071bd8a71147b9164e6f8361a94bfa34f1e58ca8d44a806629f811148051f0ce620b3c4ff8f094c9ef5ecd24c0c61090bf4c8c6c99247557361ae4228e8add6831bc67d6ff9c0247bd896e150281598f1097594919070b971c23f4edbf87871ea88a15420302968b01663366dad4be0dcea375111bebacc333723f17c2f36cac75b9a890f34d4bf0be5b943bb3dca5c897be3773818d6e784d733021aa07106f369bca543ebc7e9bbece36ae8335967ddcc28ee49b5869c1a212ec3d4ec5214a90d656bb87e62f7b25eabe97d0066c69963c73628aeaa32a3fcf443b29ef1ee389736cbbdf99fc7e1b77d40f651ae35881d2ff43e7d4a13a56e8dd"), + tc("f29a70d4f4493219453851d4a85eebe73e8f4ee99f8b8b54b98cc14db7cae4a8", "d339611e4d57f858a0b749088d129cf1258c2035ee049d5f47816c0b82d640458c8ffa131afda45bef3105dd60469a70eacd18ee594ccbac487d94085e957f8a82086ecbdfcabb8aceda5120a7e15a12577985def9ced321a1c8d350255cce7d336825484c86f9dd1823565d2624982516a6027a21fba2702571cdc05c514f6b9a7138ab8a441d805fd37bdafeeee30159130ee5f90c90de030f9a904e6d2a03cdbbe241f0430fa813a251d44643c04c4bef5d07273c9237f70530c30cb269d7e7b6e7f10831069eee541d5ef986ccf0e1e870564fe558bc1c219dd870af2870321ab62dec23118b77d16418465b54ef71a68dd17e2a0ad16e31b618191577bcaa5c29c2f36c2b56dc932ef2f8ef641118fa3a78b65b5604e0913a48539c4b26f0b5e73f77de1d6d86ebe0038c7883c2f69c0ff462907dc3b85ccbd87b85b0678139297aa478f03a588c6cd9bacf2d5cb5540c066a5d52889bc370a08ace06953c2398e92e2a6aa11616b40927c42203326b3888cecb70f7425c1038e57d6d3bb19bb643704f83ae6a55fe8b3234dc25175d243ffd7e16bc5cb84e8579e619cf699b54bf98890c0563c424477b5b18e59314999fbc4c13d2866dbedd9836c11a62c4e1e5199bda93d2c90483a4fc3590bf66472bb8b1f499dbf94a56d8a2d27f9607b019228acc75f8da8f3f9a24ea0fbef55cbe6c3b55506ae4fed8499543b0f04bba76f7f164f31dfc7681051ef05b32846933ce4c1261659d6c414bdff597af14d01d73d4f3270db309b8e8b62001ea17b35bb7b7cc7cbbfcdc846a3d19929b7fed5693089a3f81a467a539be1cebccebfb5b384cd025eb2dedf5c4390f607f7c9ee3ef273d7270e5150a241abeb97b678ca3808f318da5e38f6842a2a3c09cdf25c8d093caee7c5adbab436b8da15ae3fe3f0b84ab2b43cf1cf9ae3c0d44770282b6f1cc1cd7e82d195be2a5e5af40735449f6948c7037f6e1733189984b4d1da1f0bbcbab8373c3a9a3c0dae236768883634eb42e574525264403e90a503a7d890530834b4701adc1a9c6b953981b9581721ae37ab01cfc2b9571f2d65d4e35f3381f7f05f2cda160ed6143e25c6d9d2ad6dc9b3f69ed693a325110f49192e169ab5eed6be88a3e0be03d1fa29fc002335979971c16f096bddc4d88eb2300ba514b167d4a9dcb353d7ab0c3db7ad991415da362275d3264360d0568bc5b60b5ebcdaa5bd62edb040c6583a9b997d9838479a508f79e9acaafa63f90660fb0c0d0eecaa72bde7fc7ab4b77d29c210fe6aebc6abeec7f201c8c64cb306aeed1110768b3360b8b2f30f8d1d2c1ee7b5dfcfd5d56515f3f617dec24f56c4fbe6604597edacb6a42f9447bac63c53b54010b5ad6f3a5a8c37f89cd345def631450c2b491e7ad6436f6f923a0cec85a76fb04c0b559a6972284145954ec82cf84b0637e4ba11b3f8bd68dc125eb5693a3ed2cb3a62959cbf41c9409d84f2ac9264e99ff695443ffe689464b349b299ab240d542b29cedea37f2221b5444b92616386a2bc74414395b97ebd3df78c583234883af5c7907cd68ba81e19ae616b4950326e526cdec80443497b1bc42cbc4111573d39ed236323a8eab36ab7dd45d65dbbb9f5f7d657ccf944f00ced4f12817de3d031960bc334ed3f864643737b2641c6f392a4a60527252272e1334bced47637dce95292a8e4273cba03e17e16670225c2e2733d7858e2c7d9da6e14333f2917c6df92f233acb60553bedc68da7a99f18c2d3854fec8c0706980c5ddac1174befad6a7da03dc1df372caed2b7b5625eefe93d6419494c3dd1890113e190719abc2b695f7f2aaccd372ef78181c428f19918bc2e2cc16fd97f1aef7e4fc3297578e0be19b8b2ce6ddfa200e593692efa8b11fb5a55919393b98a2fe31bf7b80701b67430f1b33911af41504b2c3cd000e3eae148014c451d166c47b6fe8266f44e62ed7515c6f30511743143485982c3245332fe19890fc8e7db99791a36e77abfafbd0b65cdd861eeff1f29e142df903e723b2ec3b4a4fff8c5b4f3f4377e3837cf134d742d46ef148c986dd161a939482fdf40d4bb4a4231b7d5648e2dbd8a9d8516c6bda0a370fa53620d5255341830b84c751e31b8e17f3fce027906afcc2ae7e5e104a1109e86fb20897e3109d07e700c13e6ffa7016ead35969245a16be032cada0f2327844dfb3b45d9b3e9e309a98dd927c5e75aed3270714041e35a703c3ea7c10348fe521cfcdfb7c02ea9e123b267c22f091a4b98473330e93a362c358f92519f9f42b0e6d2aafa0538ec32af4a52358efe45b59a9ac05cf4d6d82af2b88bbf327d3ee61a4f341178bad4b228ca86d9ceffb459c64ef12e5c5339471054fe09de0933595c2b1a5cd98581675dfa0b9c04db65ddcf452c49d720c0876be88e9d7232737865c598a5d20862d749d3807a7f052f4fc0e0961ff314df69829b3a3085148a818b0f53840b92ebba0266a42719d49902775b72d15162b1643f80993393a9280a99e72aca1fc8b2f8454e566bc79cd9075fe091f5edb4eb0da5e0dc5c4ed85a8fc3997389d235cb5436e9a8aed1d4604575834c1eb2fcf1378e465f77cf037503639482a285a9d0b476cb9ddffc0b82e64bbd175538c2647382d2d7587225c845d8c14f3874c1456205071c0c0b3965fe30e9ce3b616e4de78b3953a9ec525e43ceeacc0c303c6400cb4d52fdfd142376075d26ac1123ec4cfbf82e77cb04efc09cc95601b14681c90a7b0840efc819613aa06fafe2dab4b036951f82579a02efefd08df0eeefd2146080734961725b6e917739fb80deb9b8bb6dce39e20372ccc079add9030799061ef1f011699445570ef0cb963483099eb17f22226698deb110495c159eb63da750d231e78c4a7c734941e443af7f2b5a3a06cc5334ef76743fcbe8edd3939abd48ce32a03dd59d2553ba408b444d9599c2358443294c406d03d86a23f872992a1c455027aa4d7ea35afce35af0a40a8b58ce2c149c00ed49ddfbf01f1efbad3db59d2214b795c7f78229889da77c4a4faab944ebb033a045156aaeb5b457ba7fe0a970fac195ef5d206e28ff9151310cc91937fc33e61a50755ab1c109e4dd87dc511964f970784bc3019afcf015c6ebadd5c4c32256311e8996fab7dc3945cd87d55fc9c851e863dad371d6e9d69157c5040bd702f828c088c5a8e4e35ed19e8731eb9addc7d31edeb95e2fa57ec514d97fe5914264dd0016bb430fdd7dcd5a161732802963bec1549e5cd5151ce9c8bd46b26f441cb68bbc9dcc08c139dd9adac1a158b5f9e54d2c20f01ae66be788c10067c60e088fdcf828f7be908d863604e19f318389e5ac148237e11be711ff432869c5a9082e5fd9fd24c39e57863d63bd85d5b78c6329c6649f562324398c21ceca17cc78750ae9adab612ca3bd0ab6ffaa9f230637927da9dec3077ab6f425d21ee971a0744732f39ed82f6b06f1e84cd777f1910a9290a66c77264e484693108d973a25908a9701c414920cb0e3b82a74d1ea8304e8594c777290cd32864738c657bdbbd57edf42ec9a2f522ce911c1378275ad43889cc26e9a3818987ff0671a7a94573c013eeebdfcfab5d9bb405d05b66eb641c976a4233169a46338895467f36f9fe7fc1cb3eab830f46f261252aac0492c0699443303949530687533cd09cbff09dcc8f12a49e892a47ab0a2bffe1b7a493bfd66d9e4822598781bb8a94a4d7d63d77c8f1b4112f89ca215decb610f1339184f8cf66bb59169553013276472bbf7753a7eab046f55e720c0b235407748b8cfa07e748808875a896661a54dde939e0d64cb3031a4d48d565e24a56ae117ecfeb27928e42c06614b4327b9261be4ab24169b26f9a7a463a42f0ada305359314e7d48993ebfe46fed1ef6f65f733483b883b72deefd5ae803a0cbce91e5161090da188c475b38379fa2cb191d0c172e966376fc8c7a5ef4263949d75a955ee94e0391e70ce04c61d3f4f49cc5f1774515f01ba6cf83bea9bdfb7f158f6ced3bc33beffacb80ba3767e476f3180c392f17de1f72719466b4e814d7570c9b08eb3f053d728e5efa2c28aeb4fa557a2c809e357f9ccab21f4e9fc3291cee7ccde2edf7b086f844b531c6d6d5744ab2c3fa2795a7c2b26a3fde44ac46738972d374e534d1972322d4dc8b349fdd5d3aee827bc4de6ded715b87f1a26fe09fa12a495919997e07e75e268899d599be0fc699433619d73c63b34a377f6cd511d095da8ed29a9fa86944b3795bb69fb8f51fbc34d07809dec6fc646ba63ba48322717a1ee01ec9b1cadce556b07e943f619a073faec003ecfa657d7c0086631f41909bfddaae09054948934225a3"), + tc("448582e158afc5087486029fc6a0e74d60fc0ce0ad6686c5734d5f8cffd911b3", "93b7161e99fdee38713376a69fc4bccc66c092cf4ac5b71c350089253cd17e7db28a41490a354221017995abbaecb67a2a989d81984f34d021fff364e8d18fa58ef3df090726de7820f598de625f90f10007f62c09f3e79eb21e847d28a8869eb217300cb4014aa83d7d7ac6dbb9acb1e19a2bc76291527c571ef593b509aab454f20bdca4abcd337b5087ba30bf4a87a936f400a13d4c455e60146596577c309ff25c22489d3d51a4bf1ceac5b8e20a70c541922b78eed7f2c3c2de6809d474237ff6b9221b6a20cc7046bd7a292a4a7e1adb16b21da6812422f0a2932494da0da8eafc50260edce61d506f0f9dece624e9b5c6ec555963d5f65bf5f978e441955b0276f156202c67abab3ea79013428ddfd880d861df4e7a1bffcee619d04c0863b2d675de97642b44fe7114c845b51e57306b2d0072dada8673c0c056ecaf4b486653a6dcc00f337f1acab6b9b2d665b1a5bfee45a754711d45c9ac63366c15143f89a5d32de03c244ccf831c95a97d44c2baef5c90d8752e2caccc11316e1b78e0f1c4b5b0a43871c784cf5d977058ef4cca06172d6081996963f3da127076493322d9bf39580f096a44169c9a93dd7c84a6aaf105270db387990ca1be0094bb28ad741dcb4295b16af18c97e15a61aec12e108d61d5f27215dc24dfa8b09ce5e35f61b230961727eddbb907f69ea3988ea95f73299e4f3cbab4a6506f0b4e083eda4a24a7c309d4785aa1f2b1d63b0b021e3d7227afbb6662c3e61586cbe8aca227c61bd34ef7b7346f1e99da9588deb49bba8a839a7d4cfba9cf43a7b2ee85a97e3754eb9d1c7309ba8fcadd45641aca4ae0612a9ac6872cce7b6ec5493ccb45593017e6f918493b6882ba66fe8f96da93d71ce4c435e405c44f4b67dec0f896da961f7bed7849580f3c88dd156eb8e31e7e325cabc1bc4f58e83bcf8e6204dad75853788f121d9161427a435b06f08507258931fd7c36d39b4868dc575a70062168203d8bdd6067b458eb25aa5ec8aabbf9b1198df0aecfcda206dee2dc4ee5721680ca364464a9e7edd29608a3c6a2677a6492a549446e534e9a66c99b469f5dcc14d9148e280b39fef77afc6d97a48f194544a285dbf1b1d4dabe9e120e77774ad505da0834e55d3e76feb4f61779b86b883d6b3900b9da38f3bc92e03643e7e0c7162b79fc6247c3f887e9c4f763b1f544504fe1c2d4a95be2158a31fbb515431be2b954275c706f3a3ed2b6a6a9705960c868271d0f3fe52d711bf377746f28f7bc1feed6bf7df64abb5c61e3dee6ddab1ffa938ee21ea45ceaec5f064f8436942bb48952e370c35a4dcfa8d73d9c75adf8f3af1d6fb98627258d96a63a398d90fdaa7f5c779dc8fd9733e432ac542bdd3995e5ac335ee30317a40d02fdc34720b84f253471654a709752d9ed2ea3db089a05a579a9299df1f4fe63780cbceece5e21b236368eac7016890b533bf94b0e0eab0d74d9bc9cb209840503b237821d03b71bc922fb4b74441cb8eea845d6703525065395b6a72d47f901d052662465c48c1100c88a0a06799b4076b07704d65675fc3b52f1d0fd99e470b3209c6a90949ade31aeb6272e94fb53398f0f289158fbbdaecb5cfede549d1babd7e34a3ba9939331ff19a5ccdd53bb38d89a5b9255c0051b7392ed07b0d755e04b517934dc3fb6bfb9331198ed285b40a4b4388ba2e7c002c47a37c0b68bfdd72b61770daec39a9a01391d839b84af6b71071cefe6660c585372b3c6f9f735536ed0dfda55f5f352768f716c4f6d412139a47b4304e5fdda714c5e14f16187b9d8ace53f6d3dd222985f8bc14eeabc1ba8ef7e0b68535dc50bc2e0322942fdf1233ef9cf4dbebe205fc5e4604889bca0f95381c11badc6a3ffc71d7b28f0603ec720e7f8fb60ed463e72de159af0d49618e3df16afe272dd628106e3fe4dd198c685b6487060ecbb0e69df995ea97e37a79789f147fc032b399889045623821473fd4d6cdb454c7b468d26efbea0da4bba85d993666560e1faf8c09c2ec33edb379245d8a5d261baa60a6f367a517c2ea1cdf877ae2641ed22bb3c8a05a7fe287632c85230a41c44d9663d3008df8e897e13970e3f013ab2f59a7aae3bc50d28152e84ef607b1f1908812fdd0da3285d982dd7c21c7673f93a3240cd01acc88f6d70b958f89fc468550b6ddebe8f3bc2e5c16fa9e3274668c3c54884412a55dd1eb3e81ab818696a7624705bf515e6b1efb1d5cd504b6a733bc746661c5ab80ca5d9f5694b4e1dd664c41952bc1835e23087a61c050b328de9ccbfc88bff0ca64fbff092ea7e61ef4a61232ad30378053cc59ce77c6bd262ad080342a4578e4ec21d338c717c567106482d1101ebc30669912d6b1fd3d1a2d1302d94bb8f442296c0a6377a90bd6cf627b7923be4916f7442559b2964a70ce3b7f18a032dc0dd8f4c2503d6007ff7fbfb45f2f4cb61efa79c74b301bd7c674d8cd5afa38002744d5669d8f52f67a8783363b015092587db9ec4f25329382d426ab9ca2731c8c2bed242e0d736f18d68ba906d5efcf8845bca9e006212c789355abd40033d32bef40e43123da1b560d19be7b2bedeb3165a27faf5ad3c8f8a6cbf1b2cd385f624e52f7e0635d6f203b04faac9604a44219c963c902b205d3449e9445223e9494fd4f6bc2cb3eadbd769c30fc39f62df0b4541fced20f96dde4420188c808da36d7f252d8d15d2cbfdd9d146a2c3c9ebdf4e7f16a3ce9d7ebbecf6e34f36ad52fbd7a4a90a708380d8c9af17c14202470e61962ae1305789e4dace05218836cce567153c8509796de7f6c59c61843f31adca02b0ac1483224b675dd3123ce720fbfcdf6f10e3c20d5343f63bd2d95c4feae809b2a2d7e30501e8361a1a3bdd4aca01e9d10969c07355133680df2fce122e4395a3692a972fdb4aa1eda5987539b19359c459c27abee0c0bdfaa7a3776b41147c61bc38916b74cbc6dcca587da2cdd6732c7bf1355cf182cd2fa3317f8dc49123442529e9d0042633ae5dd667248efb1d85d6351a625975433b6d51f7b70cadab215bcbeaa4cc5abd3371568109dd1f7a8339eebc1d11e3a77280bc445b3fff984a53aa3dc3c96be75ab26bb4c13da4960f7ceb17ef36443149e00fe7041a83be6dbab71a627e1368febb0f98f710d560986d5a896fb9125bcc19d397661a10d27a65bcaed95cfc3a31f917ca9395cdc25c17aa3e1ac0d7043b41d49957fc93237ef22e0ae7791bdd30369af5caf672c4323eeaf53360341d046b69743bf7d8c34de2791321372590b724b00279575cb44f41a5fd661868a7f7f7a4e035e9c3a2889a58063c65cc41facc1e576d32bc02704610a9c15ed0f45dd4f365fb8157ff08e7cbe7585e314581750b5694cfb382fa16340ee7350f240e277eeb71e4cce7cc9df58dedc7696120aa77fd8e6bfae758cadbfd97ed29f8f0e62f4b142f1cbd43c8f9f4ebab25b0359f9ba02e371a25f9573f1c773f86e53d7548d49f190fb7bf31fc726aa59e89d31039322ee53e203b476f5f32d68c2beb6ec38dd9901434b877408dbbe8b3a88c2ed7f9445cf2ae091c85e4273f0fb7745bc693648fbae3a4c76726e95f20a7fe1edd8e43bf8cbc12379a7cc640d485bdd4747399ba5c206e1d224cedc95617bcc204cbf8c2db0e2c4f269644e3ccfc3d43a896b473a1e1bea155cdb202036e5749c11c981fd80fe00e8ea85ae04a94500891ae660f09655ab9610cb51c7e80f2a7e638b269672a98266f9a358d51f937e5fc7e9bf5d89afe8f5aebc93750f6c1c5f5fab800a17101fe7ad6e85b4bb79e0741aff6418ed4b6c9c2772704faadf6951f17cd76f518bc9e7a77ffeee4748d8043d39699a46358580b639f9362ea1acc6279590f9b691ce17db4ac835d9874710bc3bcf0c18db6b48b62549531c136ab0b8d756b08f9bc36aa13125569370dffcc6c80e6f6388d1f465e0395e144ffd7a7f7ce081ad99c59d89440171a9e8cd606bfcd17567ff3fe63be15794ec3b551e42a2286fffeaf49d9096e63b75b1f75003ffe04475520b640e77e8d8738e155c457ac62decee08abd4af348402395bb6d579d7b840eeb95e186ce206c1a85270a9e541876bb10d560c3ae4a0e5e9babb8b8841ea363ef8ee403b302490e5cf513796ae5092c7c9881a65064fc13b73a4756b886b8f016cf1d997686199ac24a21719ab293ba227e61ee8bd60a37384b5568455402665af11d3d5d3ff941c1fe17cf490691bbdd74a205ef6e4e10afd357f5546deb185422882535cef88a1c8a74bdc7dd722a9bd9db55b80c2f6cf11ac6e381164b6fba41ecbb33af31278a19d6f20104d04011c2a2287d7597dbda5fafdf63584edc3739674f2bdbc9012e0b7741f4c884ca742f7d5cebb594ad7f55826ee85e3c06cc77686cbcda0800fb4b08569d1d8f396bfca7cf61ffbd3911b444e1c603"), + tc("84935526ae4e13643081703b3ad1157eca78465864b7354fd7096cbd21e8518b", "858ef34c28b8939bfb4644d1e458a67a034b9af9f82db786892d81c343d27b96ee49cffbf2c3419eb11efaeb9f8d0410b0eb688156654a8e32ee2a570c5a88007656a18380df33f7756a6004766a6b96bb27a741057a7a0e22d732d797c488eb613f179dc3f1094b2b5c8e5a34bd57f26de85e234fd894a2d276e8f31d0f7f23e570a0a4e13bdb635cc8897282a1ae00cb0c8f04124edc6ede1404272f1bfdf55ab2a0b3d5b3534a4c01cb0e1d5750ee71328fe3dacce5420344108996b6de1a443834b6badfe616534eaaea32681d57a2e41c77173d6dc0751da26a17731b01833ad0b9c4d3b48ba23773aa6d2bb7029377f453484a44e6734a3acb3725ffba0547f1045f18bacb1e9c9c801759b9a16209f0158a4e8f21cca9112fde82388cf1fe58adfa1f684c3158935edf9388b42ac0e5b3b68e6e6a4d4476c4eac78f166ec5fa464b565d481f57a9d263bab7a1fed05d737974c4f7bdaf2ce5ba40b2a993e867e5844c7750014e66bcc5bbf5e500402529ddabf1ea2e395db084c4bb85766fda993b547788fcef35381fb16af0965a08ee5cbb4d68c934785594b37f78e76a6558aee73e570e40ccc889b0d5269756bc40897ec0c2d6c33a33b1897aef297f377cb58a7a1a389ea60f4b5f25d1305d046e48d5bfb1083a82c5aeed09208ff83076367156b9071beab5aa12d9dca9f8aa093872c4c95b84cdbb0b84abbb03f33b63a7c603d723dd9970524548e61f3804b7a914852ae55d377e25a8431b1e7ce78f1294664925d62b7d8ca3c880c4ac80e92f8e86554bcafcd39ca44cac017f4c4550be119ebdec7aeeccd527efb478348425fc3668141607d14dfac90451b2996dd46b23683210916b90e63ea2a4a006bf72c4dbb6abf3d0505379c5b360966a25471e658ee24cabcebb5c6ed5f8dccc84614d64bca54f35189f579c170f2a2b773aadfcf3c1c435885d0ade7121a4d2fcc96afa88df6ea2c519a98ab279e9f75400ab053d457ed3ff957c20f282bd121ef67f53053fb4963d08f70eb287e011dfd015cfe492adcb927bb80093545014736a12e236569ecc3453350a5e57d50b1bbca5c8a01a946248370cde6177d7b8c84f11eabab0c3a5ce654ff85783ee8eefeab58451ce73e164a9df47dcdc9ed24b0f7a87e0ecd1a3dd15e6d656b8945f096492ec72c0b51d13613b3134c3e31cd1a3093b8023649c1b3a3ee8a09e5a8d612a91b4b14646e22d575cc09348a5a58e0f9b641037d7b63d32b40ea5fc16c55a0875f3e64f25ca97f2a70da0f4567c9626030b5b8463d8a55c34665481f2fc308d2b87111d23b1b6ca96013ff033b23216c27c9d2e1222dee25b295e180cb6eb9bbf72f59aec070ac809a79b3306e0bdb701521d4de1d755f75eead5d79a72dca55b6e25fd10c4ec68bc5c585aa1c392da26be5dcb75cf2ff4b31c3c578f9708191f59d04c2e00abeabc6b217d89ba9bcd0cbe76e72e316a4c7f18af642d9faf3e76a6e4f4f5e80e1252e52f996ddea279bae6d88ef866ae6f3d77e0e105111d04d4145f09691925cefc2a8df96f0d340ac86c0b48ef5d18ec1df59748bf5f261580acdc4f58cdd2c2649e4b357872dd982d9fcb59916037a8247c6e374f0cea924a25ed9a0b5dc15da9aeeca8a9a9de9811e1dcc4f05357867871b959ac9e1d3c4aa2633c9445aa2239d02e9e5c7d22feba47e7a0332f025e83da06eaa82f30adbfc8e1167391c6f3fa82b87857deec2f39d08d4aa3ba59e1598b54d665ba68438fc7493b351ab54b907a80945feda0ed5abac57584182e40cd4c8b48f61987a07ae6ff6196e5435324b3546f7712bf453a1f31cac5778f94647b656dcb4fdd8b58ad3645fa1d54fa7d8ea1b627f65d68d40b9256778b46950cc2d1e4c3313d542105a3476a7ae2e51877254bd756e7fb55a55526a32b78c95abd169da1929cb3a1a098d246f029af5dcd0bed4546b2559a6a4739396c2e222c77cc4d3ab47262cbe363465ccb3223452a35b91c15d4f2822fd75a91a191ed0b19ef0d4689a2f793d31607ddfab5fe0292eb3126c6729b37bdf4eef808a1a715982beb2f9d6ad6571d2346ac4cc75374b4641a2be62140dcef6a105eb3aa593207de595c439c8e94e8596f8d995690813b897f150670374dfd9ba0588c7ab7603958565bc31c274ed42da1ba94372ebe0b4f08611dfe26127e7d05aff7b9de63b360b8c3e79b0057f92b40348c4a6cf8c238bdd33c204926c342170d9c15dc3d69c7e61f2a94807a4b84d04df599411c11deebeac243681fdec8be382eeeadedf61f728646fdf9a421ea231fbbde6613de7a4f7bda125af5df1149accaaabe2c2a01841d24e165ae76808ca9af94effb1cf3b1e31c765559aefefdcce122bdfe567e67c970e6b40b468da0ad6b82bacccdcb293722450aeaae801a342adc7f1f443d9935f5d438a16361a087228c2e26c12cb59bc98774cbdd9b49b2a065c8a767334cf16dd2717cf45a0c8ede97b4f4faf99acb013b5e0079f2b3a6a2cb82b611dc7818221a679c007377edbb9fd0a2000b32c6a17e1b0be9b91208edd3e417e631c2e6d23dfa763fd792972f9a9b1c8fa42f0af5297eb49b0776bab810b6856e0175eb1b87ec83cd2cd12e2c5c1610e1eebe9e665a6c3467b870a1574e6f2cfc865687515d8a35f5e90d85138c3d2c9823696e25317b1ba5b0be623fdc6caa1041374d398578167f4352f315ae9db4255a32ee4910f2742633d79d5b5f105adb63b0a3d776350b8574477e572efdca09574445ece8dd6382fc21d7b9d68b3a4bfca158df4a7aa4cc1c0da1016c364c9fdc62abc991a3504eabb590809b2d41e4d51ccbb0e92e8752a4c0f72e74ca7e45eb1968191d564d813131db2b321279fe7a104abc6e33ffacc553d0a62d69ed3cb67798bf8e927d4db02a35334e9466052858e6828e0c28540880742ed54923baab85922e05e0b66eeb401453c82d5b4d7ff25297c1f9f1358bcfeb5d5294773de203d7f2ff9fad19fedce6c12896c1ae44abe0905ea80d24bcaad927aafc6afd48dde526f3ddf4e6cf94eabb9e99ca6852360df3dd2bfc7907e42a35744ba720fb0f366eedaa8e86e44059f64b384e296f00acfb469472ad01232e4a4833d11a2cf097c75492e2aa1fd7e425082195bf43f4cef3b3477d16b606866f1aa62644f1a31119b9bc787ecf70686468a96afcce82625c860cebe802c270605ea003b2b40124e8ca4ebc78a9ce226cbb3ae74a7fc829f53a51d925038ce9a3d3681cd50dc463ac3488c981dbb0ed6e4be127cd3aec473765c2aa762611a641848022e33ae6b5a1bd57d17221d172a1775ac54dae58a1b0e21c54853ba59ba46cce0c62399a547de37557ff4b6694b86fefe062af6efcc103c4a1a2bd06700e8769e4c70641011e5424c2a2f0a6a2c8ef989774fd59c0800eb0cb19edb51b69e88bf4faea43f5c2c2818f9b95e27bc68f60bfbbbcdc778c6f6b116333dbc17dd352e2e97bfb5e33ebe9b3e518f93e86067e75f064a714db634e37ce71d4bad65fe2bbf326ea2793c87cb44572c965ad6598312bd9838dfa8e1b599ccb8701dcb397e8466a8524b568e77be95cb96c79a64dc180a736a6d996eeb896dcd9d9aaa8b6a4a9aeee6f213df1a071f6f309c1abad52fd477f6f707f876f40ebff3530195ec39887e3d764488ec4c7618ffbe79265eee08cb809cdbd0f7d38a8a2d292822661c2273d8270d1e0b2bda7dbf1362349a9206ba25c0dc1c668b39db7724e29050618e43bba05f50f202d9a026894eb20fee7d6fcbb22d7de3eb89eeff428aaa1b3f18a9847fd5fcfe6afb9ead2c864e0db5c0c7533e574cfcbd82f14db20f851979722277aeb0ef579802336c90d3be78b47ded5c829e83956fc9587acd1b0157918c4a1db3676e4001f068128cd4c4511385f4ac39ebc687aeee6ec7d7535391062191f86b4a2f1818e6d1ddbb24e1e7cf743e399106aa1c39e52ad7ec2be7bdfb32b54e923d50ef8ad4ebc6a80c65614eea4aa5d07f48fc597a29ee20026def2f300c7d3ca727e8857a098a0fb22f2c6757268e390f839be20d459830a9e3dac71ef418215d9b89ba559a80341e1590dc964b8a6aba07faae4a9ad8e5969e9471d53e4da76889af5f9923b18bb584d71e30231e0ebfd9fa4891af34007c1235a9cfb2b0db0efc1a5417cbd53f694c6f3882e8553a0e01f7ae34c24439a97deeace8bdd2f59515de0760589ff6230d602be3893d27e34dcecce7b8e310d87790fff49ae5b530fdfdff71e61be2e1b90d4204194efd263d1ccff4584938c06666b0ab675a6cc5ca0c6578851730e281c107b58a27099647541cb2b9c46626662451137be1b34438ed825c45628d7dd49303770bc3330e5e98e9ada7a1ccb694ee5e00b27f7a5aa000d1d9d1f7614532ba4b8ab8a92367b6946247b5f69f870de10775325c65ade56a7d063757371739e053e971179d2bb6517c7d9f8ef37a78dd3b9d15c9d2f021d4b81ffe9437353a965d79b82177b6798a70ef8511d27fff215b741b9"), + tc("c8687a0fc0c6ad3ae4e3af4a00cf9971a4762d032ac1c0fed2472c9d090b8061", "ae82ef09b711951f6082b1bdc5b175c81010e398b8f3b18f5ab99f78cb53a5f953be09c98bd9723740c7a4e6d26a64aa62c6973f60da560e14eed63d001f6e281701773c34a41260a2ae675de543f2bf2feea4b17ebd928aad65a17f7fbc4b7570fefca804956d4d9353937c3faa15516c8fc140ee8f91f2711e3ac0879a1d3cf7af7963cf82e08eee05d3d913d2b7e568cab7655a8bc5436193641d76f7443bef83d5f468d84c80b8d37250f06b37781efc0d885bc7358a2b90fdd5cba3dfdd0c57b8f66750167546494e31bb751a9e08e209952dc1efaa2b1210e69d974454ef55952e35ca7f40cde944c665d9585580c70b8fcff5bbb5635f5d8d549356f48d4b5f173c541cfc00c3f42695541cb778440f64edea6d0ac8dfd38023c7a013fdc28ab3c57fad938937cbcfbcd941d315e751465428b82200b75b3aec1776c18015aa39e25e162adb0ae24c19609f365f45bbb8190fabdebfa042076fcb0f2cba3bf258f6feeebe3823bebd9dba95cde03697d6722a3a64b926311ec03e6b56ea047d2f5c28730ce7b560fde8ebfb1ba74aa1cd2e57a12847bf510c9b16aef0c25ae397dd6022ceb1902c8ed2e24054875b5c0c9e6be87e9f40c99b85f90a4779c109130f0236d1c129dc2962bb57479e65bc99d5dfb6bbc7d96a290adcf3ae9ecca75587dccb2cc318da3a669da92ce2af333897448a669f1a4c128da3f964a01ad64e132cd940165f5708b1583623d20cc5a3ad9fb860f342a4d47e2cbe1c43577c9b98341e15b285a253c32b3b00c39f3f8f9983fbfd2d4f217c857960a7ee6c962c2cde2547249fa56f97b6857348d8a449ce8dc83cc99f0bfc29e214e0a3db6a636bfcdfb15a537979565161ecdfe6a4c29d88f4e465660e4b4a493b3395ac9696e5bf5f4d2fc6587e673f8b65bece1ca4417382a3fe11215c5eac591dda0be4a9f2fe0b1b18325280241bf7dfc621901a0e9f4cd471401064fab73373be0d800fe1ca296b27200de5158c17e64216f01eb16e58b9c9dd51837101447558dcd07c7b782b6016313cbdfa95c07b9c2dd25dfb3645d8f22c3dee68b1a43cba114ed27a6f3e55cddc7fa856fbd07ddb72f540d46f780ef5edfa68f9c28fecfcd3e967a030570aba49c8cb2a178d2557e6b9a3fb387ed2f6edc7856c2b05168180bd6bd7a88a8bcc8a81c5a0331e095363508cd329ec504ea321a6766c58d658e0fab942e1dc2a308ba7e122258719877bb8afe692d1cdf2bcdbb410cba4e5e5e4b8fc8e1449793fda1c14aa337a1f88840c3a067582632b652e2af72ec8874919f4d8aa94de30eb3f1e44aefebc66f58e5ebdc766b7a3a8cd161ceb65b9047e52b931ac59a03b13ea6e3f41e38b40519af385b57c72dec99bb7bf37d71f4a409100a3bfe2045b292f92775aeaf7c2394a15135f971784441637d51467bffe9b4bc853ea4cff5dc8e251263a76062caab0b75824d21c763d33405a95668a53da22951e9626a091a57f8a5a27dbe0b61c0f1c652dd0f04c12a3e3cf014d95bfd35e56447047abe79b8fe04cec137516c3eba6d02e68290f91a47868af424e1dbf5584647eda7fada8f051bebadd654b14912404f5ab54cb7927c413ee02714d74e82b1445f92eaf64c28661478b508996edb82014e014a968242135d40af966f0cf6987e7616eae588c298110ae42333b918b2dccfe4c6a60e97e65b63e419d5f6b640250244268765b5ad9b73a5d016d508331c42f694c970c2bcb745bf037a4d22eb2b4971d96f3689ebf513bcb2a2be78893e9888b623528676d94efbc768174145f212c941938096472551178fccf31250308041fcc72afdc3b845a45fad65487a7404c8970bf8a3ba2c8ab5e768bb0cd85b3a6f0f1c9eb66c103062161d6aedbe76c43f8e39f6bbca59956a5281b5f154ce2e6649701d03acaeec797267aa0623b70c1323d22fe6a01391143901e4f8cb9e8c76f40fbc443427fdf93ee909fbdc26891d445202891389410eb08f52fcc190e04219f2a75fbb50194a5de57cfc0d00f1a7d03a0048b03c5b12f6f5beec8cefcaa7bd41192865810252fad91650cbadc85bc79f33a940d45a39e09a2d5ef506eea567f695560c5189e9a686e3b363b999cbefcc26070158efa1a267a9b532d6c86c07ca47454b346cbaa4b4d78ef9f484cd11b2fbc26e352fc18c69203f8e9bcd8d4f1bcd1c3d4f7b12abdbcc58eb6e61468eb8d0e733d9021b679eb1b5146aa68294dc508ee15bd6715170145a6704d3e3fa306e1f47a6064175dfcfb46856f524ffec08851d558564bf943d9216d6199e75409516b8555a1429254d3677ceb193f163d01b444f418ec633ff9ed427fe28676b6b650c0b467ef2e7930ec43d3af92d6c7b602acd21ce6f22e1e463fcdb22be9f2234a9331e39b3a5505b11531ac729e6f1a8452c9c225fa040132c62165a26c59e4879d93fba9c75d3762fd1bb8d3eff59d628a193c9bec89810507f45edb215602fb65385f99343593881aea6d16b565f41d6cfaa0163f2a2871d21fa3dc94fcfd576f7df9d37089bb3b4b1a0db09ef42d91cef3e35347d7b76f5b057c9cf09c5a418afe3af06d27a4d95bb276238ce06980b1fc9215838fb0730c354c4eeba8491a5b793ad085dc5fc2697a19489132ca6193d96c4e1bb29da6d719f86d3e8d17973e76b57410b298bb72b709addbdebba6618abb26d557c9c883c8f159c91ef620d2fff202a1805de6c3ec478729a761e306a6ecc362c1c1f2ca18c7b5b3f9fec42e41d61ab5963a7a2783aac5e5159cf559f347c805fb7b45eb82986aad4ab6fe71a112ea039cbce663ea37ce6fc8a26277c8908ca6ecd9c40a702214ff22cf2a558e3b9bfda2ddf6bab5c7f182b8393aaf6e0aa1110a567fee52496bd861a92eb175ead0e728d28b2823bae9e40636b6797a6ca4571d76858c44774fe14e2b99a315621a830cafc9b8494d7d973b4fa8cdb8fccb6e5a0a1f47152032747afacda130021b55c6d9161149936f19668d8cf562b8f42acf4165377477e2447565861dcbfbe5301da8663c11d86c9116d38d59c4eac0f63479c90fc8e4f105d718023a1dc064cd1338825ea1241221698150afa47bd6306595ca5dd37b1b2b0819c9e2a2a8802007886de7c407c312f4dbd3e2ec5ec78f623f04ed7e4b27290831ca1cc9cb80aea157eb7362d3079cdfb102342512bfb1ae0b979e4f162410d31e0959e8bec4dde2bd9fc27f0b48787cc541c184c5089d39c9166f83079808a99004049ff1c3cc452b12a88475e900ae0566930151a850bf572a8e6f168126252280853b16e3b815a8fe5c8a1359ff78bb0861b24bf8c5592c49b07c73330e342bd201c03629dde238cc7b85d13cd2131b1e1fcb3957fc51e6ccfc9e4a26af0c2364ce35d7a47375ae86277546caf77635947ef7348759512ba6ded26785b1f905b39c9d1db5bb347f1f9c46c0adf8f02d3c030d6bbc2c89247e8705c7b1a901edb762f1abb4e62e874ad7538a07986803c49171f56dfe055f6323b3ea04166b0961bcb7f47dddfec344628b44b92e14cbf242911ccdd5719bcc8e7909782d890265cbb3745051ba36792da22a556c762adb228809f85fcbd1e521387906d4be384f32379ad5674b523d0657ea7ebe7e1577a554864c8725dd8709c9b4a16dcba3fc7a3b34b2e7c1017e401de15df06c0820233760b1dac69353c25e981d11671b2a2834fa539423c7717173eaa9d83d88885219c7f7f4049b22cb9c3ed5d167cee141a0d870bbc01766278d413027b4b11d016d53936b77700b243d3cf43182bc22ea0d86aa2f0d682193915a933bf693c44da024c656940dddcd469da474940e509c584309d6795c857adf9ae69ffad2fa2a97d367520fb4a62ffa547f4615e343b6626fb263c43a87779ed79fe9c42dfba6f35952fe3945503c7e99f3c4b1d11a8bd92ba34b61738fe2caf33345587f60ec2ed6c6b914253e894ccb7f06260261465f12a66ba93645ff0ea96899ffe0ddfadc9603bc66a89e055fdb75809fc6d0b974b0d7bd7e470090cdadbc91069fe2e17dc7ff9158861b81bfac6ae544a3f0bced711a3b553dfdac7a0fd48934b70c31f8644f8595b63aea8536c346b505583f847796abf0e89371cfce938c4d2148b99dddde677ea04ae4aa33c58d0b2dba36a8b7b6d3362c7b2c7a3be454833e2e19c1c8f828f05d034d7d216641205f916c1a72e512090f924c4574ddb24ba89f760d6f52f40cd511ca2172fc0fec366390b7a26c1f96b526ace6494c9d252d68a9da2a93fc4e4216d70a86e09fa5707562100fc69efc6631823b0576d5ae8c668ea6afbd7843a7a1104355cec3b2032f4693995ae5f018c8a163d593f83c86b3873df812d877ad92970503cdab0f4f77f10b1df1af102f9201f6d92c512eff44d1db32678ff7a583ab6f1acdf6c77d168e88e635f05297bf0e31e4826a27d8ad97702b9a1fef9958d34ba04984de027b22996da1439ded3f859a3914ea27e4ff3ed4549c4aab5d805d593c9acf76648837c4566eede6d307ca8b7aab4ddcc7f93f22715677a23055566a83c12d8330916b49179be2b2979dba70643a3307e4e0c6fc596649300557e3b7b587bfc80dcb36c7e8fba9"), + tc("69b880d35b57919839fa99b09fb048528229e3c943a301b699cdf2de8a811361", "1a3deafce70af6f3f55d66ad9ce78d5f4d5c5f2638a810afcd07d67e9f9a1380d6b34be482ef030c22f1e978f544609cce35a74c5109ee7038495b6210cdbca8dc82c6e9e7b0d593fad9665382b3c401ab8941df71307dd77ebaf140aa66a1f76316478850e58886a9610631e9c722f459fa00c0b53124fb4f12778bbba3760826d3dba67cd030a96b654af93f8e395f5f439549489f8161683f124bc980e6939c83a6085e4b6caaf8bcd89a0e01ed70db487166cc29735d9235a9cdc57b80c9c2e591df6322f5bedd32937073f781a30389552ae83fbe147d1b3d3461a3df96c15cd96900c56718eaae838417057579115936862679f5f2a45dadf65d14108af1641df987b57986384fa1433789f5dfbe87e90bd4e9d8d4d0741fcda7348322b967b566b18612dbb8fe64f151947c3f7e361ee868676bccd0cb3a1afe046be70057a05add3e65af31e3ff414a627c0183e8ff583b41b75b203650420216e6dfcab289665f054cfe3ea0943647528518573bbb1d0f27e1449e98739eaf0d009432df0c1edc1625264b94a71db762659ff5a3a7a867f182d1f1fd34b341a4a181221870dc4a494013091a7e3b2b07e0160c438f1ee1e8a2b989c4ffec36b5083ea427606767c29672f44779a8505b422b25a56907f565b27690d011426a62df0036d57d967cd1d14e915bbc2691e7af818c769d9e1f9edd40894be88fa1d7a5952afd898e837716acd73953bf2d1d448123fd1a0276d2c2ebdc760a4b787416cfae5e963fcbdc8b551cb70f91da0ed4a8090fef178e879f2c34f3a269dffff09772d44a13d7a17468b5834c460957d5243c325f63f05f89d4ed98d361e7f0ab8a83948a64d0cff8514841aa21c7f337920a9983e59be4a0f1339e1e462f92dc1fc070126206012458a499a8111fae078e00b0ca3bc1d6c7087cd318d5603c1c7e0425e6f729ceeca5f35b82f8a42e0e9b950efb0904c5fb5c06d91d239913665ed1f1ee4b82185a01ba86ca2d3ea94e5a8842231a94c05280183b7aca289984103f122203ec2fba4a382e6f5236d6f68da05e3bb0c558421f0efab91dceef6d1ecdc60f9b88f8befe31cdc3c2f024a1af2c7336aa5d151e8cda814a5fe898badeb9dd680e337e682ebc22bfae445417e37d2d89a338659a280ab1206db74dd42c6f25639c1803bfdf2156df613b0f5924d209f7f9003ce8794f989f4f27b82121210f4f65ec5a1f7723305cee438c41f793ee04496bbe337bbd2fd3023830b1c8889c6f4d0c1192e364edbe1cd987ba5d66224ee9c9405e1dfcec0eeffc5c73d3123f6731c6295d1e6b854b884fd22b6a3bbbe5395312585cd138bca67532c6ab71bebc6657c50da87d2ac6068fa3970202c5e15eb7b4b3d2676c0134bcf1eac2b26ba46930b5e660b16060894884c88bfacd6779276b86f685ab6f17c6d53f621275fad66d021d26d1d480afab4b5ec75e0e763ffc45f599ea02504da5d91eb5efc3e4ae196f219e45e7cb05594958c876ff474a020ef73c1f09b1f7f7457e816d3af51d86663d4d461754cd5e907456691e02446d6cacfd33516206a31870543d574592087773653d4086c2bdcbab3c9b65ca11ad0d4e58ddda8b440309989857103929549b7300ced42651d4086661694092c42875cb62858e6d1be5f7274b4bcd83aa4da05caca186a30902830790f9ffa24418e1f9db00fa40477e83b05c2d11ad7d81dddb1e31f94a9dd5e9e13391c22479b570976e3afc1be41086d3be6689d87ca4326a7cde8e5b396a678d3cdb2c80fecfba2bc799ae8b1528e96d880cd098dde910d097eaae660ad4d7ea51c18f18aa1b39614299a172512521dfd231b9840909839eb69c892ee23f1bceec1fadba75786c7ded93bc9983f74ceab397eb8ba84f7e4130b34258d628594a6f9e2348fd91ba2594e07b8057e8a2ae3adfea0ef919555385977041c5b6dc4f3880569171f7217aaa9a85f2f5bbdfe3ffdf79248f2a35fd4dec34980c67290339b1c0a5a6ab8838157ae2f5140b4a24924a6688ae5ce72a48103ee9029ce8a0f15b1fbb19a12faab80a7cd9c0e389fc2775833e3190f1cf735ecdfe7f6b6c326506aa82613cbeda8dd3691b81f4c1e3b0fc32d7e6719cbfc12f4a26e0fc29d6417953abc9568db4ed9a294b9fd5f2a666dda546aba301b1c60985033953efd6f4538333b5c7dd3148814a3fd7927c366f40b3d7abbdeb2332ddb586af80959097663cfab2feecad6d368ae10eff9663d5f8bab95935d25f45776f7f04b46817d05165a9dd4770509abb92f8b9e7373ca780703569981754a51d6d376d65c57f55cd70e2df5fdf5a6b829ae30ce3bf942815c8b4be858db58151d02a68aab9fd373e047efa51bd1a0cd1b61744d9e97ceba3334b3baafea3bc9e43ae097cf2c3d713eecc247ff43ec74d54907d8bf45e45b2e0e11d82b126a8179d3f66c055e11f69ea67aacc5fee8af01faa379e51998f5070f9ee0fd30a2eb22a925586fb1b39024eb5eb1e127c76a149e7f02af1b73c16e9e5a5dbe378e08a9fadf1194c625132ab3fdefe8fe9a89bb8e0035a1a3ac5278f5d3d0ade0e41c81c6853a41c4ac45be3f68180fe23f27f18be2e339de1d559d75de63adf7a32bae42b037aeaa3e123a5314891bcd35ca48d57df4c17540e97202a8ea1328da25b1fd6be2b56aec1e5deb209f3b7a13adb1cbe53eb645956e577a7621d74e42376d70bc5c4aacd239a852fbb7b3f62cf59fe10438c1dc8e1e46566325da0ca43aaa63fb7e0b450a2db3e3a2204704d894db24b72b3078106e096cd543dcf027650cb4965e38ac36a8ad588c5962b4e26548ab88f0bc20e10acc1c3fc00ef415b3c32499264552b14e2c0e789a3b8a8bff9620fd939d0b34e806177ec696a4b3b1ca4b32ba979b2690cfb3a6b17bcee6877ffce757e4116da01099ffe82add5a0c593e73449a96db9cc2b9e846d166b095174f2caf8b35dd878c836d9bb6eeeaf8e1bc5d0e149c739828cc480d731dc16b35b80d4ad82ed7d29bd05018239efecf8deae180c6a459dbcbfe4aab9a5e2c1e1bc31418cf2eeeb31fdf8ba02c9a91525e9163f672bae2edec38c1bdb84ea237b4ef86bf5c0f0ffe178e3761e82d94f66e5ea40ba8170bf768409e1b4177aafd9937bce3fbff590320d7c445372463fbbfb34f57447f42c16e026f179cbf82f617c86d1e8d42f6c908f9c6b77e38d25d51303dbd781ffab569b4cf31fd0b947c45e1768a2e9dfe8369f520dc38d77937b69b821db4ffea8f50ebc404f0587b5598189f54b5a5b98966fd16801c87de2c3c7813dd70dc600824d426d88c55e89d47214d59206a7a65a65da7ca2e42fa62ed17e7aa5b3ed446bcc71f17fec8593be96d2037bd07f9476d4d732b32bc5df8c921316b45699004716fc89f8d45bae402c26dbcdf1a340847b932ff882dbeafbedd252e126c89a1e1fdd8908a1f67d15d8e432dad8e08e950a3bc46b96cb89cc5bdac703b3fa3e986ef1c6e7e6606e6845ba1eb2fbdcfee744b5e45206f4a419e1cb103c8490eb293ee9aec1f0a0d294f9d3847737413d30873f3c94740e8fd072817815ebbce3f09edec9d1211a9e99547d620b2ec56c89e9cb8144ae9e46636324bd13c6cca3ab9cd9fd8f7f937ababc598232384427a2d4ce0cbf9765f7225e208c3ce128602b0ad08a1baab77edb3111f0c6ca7ba0eac9d89d5b4378eb82c17f6ea08308a79a53d150d3f85efab77294f02ee0e2885ee2ab2793392b87db11fa77992f5b4fd75ef2f1a822e87407a4878894215ab89b6cc4a120f5a78b3c31ab80ffcc9acef53fc6f7f85685eb9d56d30d87c21abbf1652eef8f32c7c567bd1f08623b09c29f33561d42727a5649a3850071aa6c11735ae63c4fd31559ce560b27a362786a83353fe460b37074664a9421d3b2f6a864d5aca087187b27e2b82f31cb3df5e985cea271c609b94b4e58356d40c7d5c7ff2e5990fb39588154843ea5fca92f120075d4c4d006661a0fa1b0585454bea725473eef7d58117d5840c8348999003736c5eeb7858ffd273a1c3eb2812f5697c59110275b08f6befbe84c92497d5f73b7b6f794a849713b23ac5f29d5c7112fb2e7a6e89eb54ddfa3122e6c79624c1bf25ebfb9fe5ce6daa779f3ecb2984da42f8c6adc77b21dd291e684fca50e46070962a2d4f00813d8de1b8ed33fed9715180c7ea8e2bb74fa65d9c7f6e142f3c81cdc59172e1020f62f65ca5a12cf2bed9dea04a4d8cabc2948f7be823a3e792625275b3925a6c8d8e2b428c75a5db0f7120278cd7d6cab768755c7fe2fbf89fded1fb38ac7f76a2f8798ca36ed42cb7c07f006271205f546a4812c20077f050d4cdc79459fa686e97f0704b7a9ff7de16318e862c53d361bc635a55a264be15016545dbfce3c6d6849576adefb6884edd768214e0b438b0231b4f2692c2c0b5c177674f8a0de236eacd9e0cec7c8647e4e9a5861b957ec834a2f8572f01304c3fd6a06019e5f1499b62baa8670b652467fa9a4f10f053263bfe9743cc7d933f86136aae3a6fb56754d7d238397a0030cebea87cb255af36138c373dbbac41dd4a697032e4796c552ad9c9b3fa713c3a4e09e0ec5581e94be7f31065157662f9e9c678b1ef1b8b8a847c51789c22b1841bcfc855820af3258af9e08231090b45d10046a00178e89bd515616b8a44e77bf57795dabaf40687b2cda7a5014168f"), + tc("ff22a43b23b579b97a4a255177e998f98568624668c5b2bc42eacef6e3a47161", "93b01f62e3cb9658b4809361da42e9074e311b9d632776d32a2a3e1bf168c7a6173e449f5ee483ff20b6030362a471fb3f02663d9d862a05767cbc32211d47efa54607c052bee3ecbc211c7de60ea83bff1638f80f203ac6675a07756ce149a56903f78db2018dbafef1b93fcadafb639a184155ee7a2aa9e49dc667012298b1d489143ff203f939e8a826290b95cc4c45a6fb25a4687e582328ce2d9690950a32fdc5b5f05f39582c8c689040cd4fedd7919c5bb9ddccefb127750f2776b901edaf7173e90c0354fb375c6249c935890632b86dfb37e475fc480e4aceb702f9353d250ef73ac075b0bb6c6b2f0316842b19accae8e084f0739533e3e3d62c5fc107033937edbf1ec95e92512dbddaeaac8bddc2cb8ec3488eefeb62a1fe844901bc12d9a0abdd8da4a06f7b9a9f095761dd80e891d2245e5d359b39c2e461ba80e33e1bf7c532a8c7cb35002deb5ddd49cb113c4ea214c04b95c4a5a68f68f513d9b470459cd87489e7e103d19bfe1ec3aa8269a611f0a516408232e396463dad842d0c1e92b25e9a3ca1696a5f2a684c23dde0a6b3f2ff2ea1d956d5e6af67f7d72e3d0692930e446aaae18373861a9521d67cf741c61eeab4e8d7a3f496eefac6780bfb826b532bc868b75609b1be86f9ec249732a831a6a8a490e2bb60ba628d144be19502eb99910e3130e5658fb5e3348ce9dd030e68a519407197f563b365f237816d9f683c5b736c40b236fa4ac9cec9a0cf8d5b54d5ab3609131a5a0e6bb4c4caca24aae1989f1859d1cd554cef05fd31220f7f46cc2e2d94c7b94840735a0f3a70a556d7e5f4845123cf16ddfb563346b9035ccb3422197b2f8805ed2d3f6a15bcc02209b40fa7609aa86fb6b91f765aa189e283148517931ede0f19552c945a341b1d693cef0167ebb382933fdca1e9d5b5e2f0d881ae1265e67e39793abf652b2df9813951d4335d988670ffbc2b9c98ebaec716d13d221d8c448474ed813c1dfa3ee8f8bcde51792e87f797a4efd8003ecc9b274d0f8693ae5657629da8e12468524ee8ff1e6790df08e9af05b1c6c061e6a24cf952baf4ce54400f3a1e223c0c181ce974eb413664db47bdcd62a0c368f8d4c2eb457bc5e7554b00e9da621805d97f677202390079bdd8171796dc51b1adc27723ce76f4fc1977b1b0f882599c8c4a1276b990912003b2d2bc9e8ef995bfc3d2993783688e1d7ff53eaa16700a4fd150340f84630d1cf90f9da0c969a4acda92c7c7adc9bb3f0b22b74e956fcff9005b059451add0e9097843f43e980f5cad9942018c6a801f779f04721d6896976bdd2748b3f2b2e2da690214afd6afd2172ae29bfd6b071ce2d693814a79ae083e9b1dad6474eceb547cb18d55d24f98e8d5a343690a0ffb96f7f3213eb1a11a7bdd6c377a2d9e85bfb7d09c53a13640f0417bd0366b31f482c2d8b558ddfb3ab4e6fa99dffa644bd693de9204a1b61e0ad790f83ecba5d445b3ed1c7d4a686d2ab014eba7807a40a84fd39e5a7526a9eb5be1ca254b02b529526ece401aac1899f6fb8c9eb0cdc5092b2208368baf9aae501283f8e7023cba4f17bed1b6bc52c8cde3f84faec6fc1f11fcd1f7dd3c6099486cc8f6cc7589ff18a4fb7ef203f6fccec16160d9197ac419e044d3bbaa2a42e03360585ef383c4b8c8c1025b8ce9cc99fcd3af5f0edc45409d5e3545cd66cdaa1dbcfbf727bde4141d2e659a7888ffaf73aaedff9a79bd5e89a28f4fd692da3ff432d2250774906b737adfdb06fb70bf3f6d90cc3b0b2735f66a005297eab3e3bcef4299bd8127bc8648bf35f4f8a14766f1921985c939799e2dcf04243b142e21516b429111ec9472ebcc9d082ce72ad41dfd2c887bcb86c31435564df1b397897dc7bf5565a1d59a4c7e3a1d5e28844a3c3e98ee11cfac4e91daec761b796617b092ca922725b09492b6bdf7cbb02f327875d2312cae9f312e18472e3efe7497a3ebd4ec105bf6cc0819beaeb6befd4d23cef5e7070d3e6efd79a64b4db4fff8726cca73e180c822497d0a23a1b9802a90447156809efef25cbd7414bb945c8dc78d5e3e531e4aa93cbb12b1be227aa6c9b90538634b01a50fda8e786619a4c4eb1717726f8365ce568f83831fef3d70d1a1bfce193b48593ab270c0ae156b2e4baabaf9f0fae96d0075a59620e9ad574570c1f22d3fac28a6641bd29e51d4d1d6bcfdc684c1470de7332728a82f910d524cd3b89eb568eea65899067d8e2d41a2b6bca64871bf53616ccbb06d50aefcebba942ef0d793c3e5827750d5c18017eba0b8829820beb3e8a938d5b2afedb984469c93dff63db17569bfc9d34bcf1f68d08ee24c4310ce9730bd5c8b0ee84a0fa6bb5e9fcc0a1dbc05de24c19ff31713eed55a672f476605d310190dbfa313c9c7b39c41bfc5727dc5063dc0f86cb5685e721faa85e42bd786206b2f8be20848813ace13afeac10a6f0a5cdb11b67fdc6db81165393bd4efc21a634450bccdaee378b71ae9e3a83a21a6b7c5d8a2b797d9178225c1e3e0a01905105ceb8f8a36faf0f7a7b93b695aac546bb7354ae0596e9bc49c1504cc2167ba959f58b0a2be263072067d5d89311f005fbc146823e9f10017575223c9342757f6eae3c00289fdad155f5d5df87651ed2e1d1ec43ad3abf521e782200e8a2438dc6ad400d549f622ac6dd0f99d16cf5035fb61457be66d1c7977420eac767edb3ee3500d072b04cbb3481be845ec09883886a8a0660dff30f25567abc2de05778d22797b4c1b2588ce383f28ba0c6cee621fb464c5ceecbeddef1d9a26f45046d5332b33deabdab7dc2e07c7732bee25f6b9e72e085d8965ce2a555a8b7dbf728bb85581120e16d4b4b53d5e8bf8a07d6f97010824e11b24a8566ad4d1075988aaa494142d3fbc9e5b29866b6c0090006c5e7a21ad534d5fd4fbc2abf5646b3cd3fd3f3c321d188b608cf6b6657839ae7a7e124984e7aef67a99c939eb3bf3b568583cf9ff13fddefbac6b63d4e1d9968f0fdc223d16046b5adb631c890c89ed795dd351cc51144872e57c750e7c01626a81b36bea868d80d319a4d0eba3de56c41e706ce8d0b7071b130bd946bf5a7fd1f0777b6c7ab9f03daf3eefa62c63f40cd65f4dc4f957acf9c4859819991cd0377370c58a71b2e78ee082e20ec09fda691290241f722c0a54126906b971c18d7669b7956d0b20cfe454f93d8849230340a99ea23b7f07dc7586c293dd0a96f9c2c9731946542c0b39a8bd98f77fa3f3cd1f7dc6af9786297300e7b0fb2c9a46df02732d761443ba36daabb8dff1b8364738b65e4ad6564b9802100c9100cb6b4c3259ae7db00031da4d7b36032344a166dee3867ae1ae499b89ee270dd5e87f421873012e2498b7ae63d40f68a123bdb238192653273eb360d852250024e24717037770454e23046c7dd84f980d76c4a0fe11ef4f02b9435fdf3378e2017f62274933aa23608f9ac426d9aaece609745c71465ec8786d4e7d06a91b24492f9f98ca43703f0c4fa97c076e0f5ff46036506b4eba5ba7a267346dab8e1a9462c956e8f4c3782c1353fd5584bbebe9ef40f92c9e8fbb28601b1138ff8a380213b672f4265018ced7630b5121807ba5a9dd2ff1086f7b70431a9f229f8695d6c66eb8b9fb83720f7c1f6c3ea50aed00ab3247df979288ba6185d424d5548507d6a73fb8169b1e17dce927e0cce71323ce25bc45b2d7ac05e92723ad357f8c90abb6a4b96edff1a76941f2c24bfc577d5b7d15d6250f87dbfe063306f19306d0ed329fdb6e5cdd6d6f6f21e019e58c3ffec8bedc57036ab69d9a0e19da5c1db8d0c6ac801687ed617da94f85161d10868a277d9354ce1e7aefecbffcbbcd441ca9fefd36646b28ac5ce2d9aa0624a5501bd8c5c2b2ed8d1c89ecbe58bd0b373b08e42fb222e7c4ef4bc8061e3555734267c07474b675dccf9156cd94731604d06f7cbf5fa22c316920fd6448c935b5e5663c95550d0634c200656a040ca293fbdac3e03239e0c6ae4b1926a16029d3fb00bd595e8e835b02885275310ec28b52b98c5c079629b8754a14dd4d88b3fea948dfafb235da712a06784391d3bbb9018d0054427c1e92266e980d876b25d8d8d3e163a9e5812e4fab120a2e297d2facc6512542b254682c8dc7be6bd1acaa5d917321718f2a2c424b3bce4e157ee4426eda54e21ca2d35f6b38766a7f684d39f8f3b950c368ffb33a08a7533769cd77199552d773294cec13050f4e7f65b5175a54a8591b079e9bdb496d597c0654700e1fcf3ab5c9942141c7be487b1d57383d334be037f45020885cfe71e4360aab5cf47b124f0d3562770f0106f2a66f0cd7b5cba869ee27ec6af4d613ea8a86fa99ea78bd51f55e8951afdd380d2a3f7ea077e62b215d7871faff18d2f807f6cecb50c1960c2a801d7f0436790877d1f19b708a183b3a949e35034640d38093a89fcb7fd3badb077ec1502187f4a110f26c69a118f4dde2296f20a7c2d444200e3b00b312d8863e478fbff8fc4f9a1f792c2edeb4f73f9a7c7c9e9ab4dcb7d7f5a39403d3b0d1945c2af7c80f54ff3eaab8d7ee5ff2b669fffaae319e6f4cfcc54f6cffa715691959e3504f3349e90d6f04ef7bd0222f57f11c912f44744c2ad3108da304995ca7d9bdb383b32a8110823d59a62bc32d610e083bf49560d76dbaec82292818377ea5ea68ed76daa15b2bfae09d1729b44adeeb45bd4709cc306e31953c74fb6ff05757cda9b0b422746b1cca7fa67c290a8c50a83c242f0bdf8addae3dacc687f4f8a89fd9077590634f984a1871aafbd9f2c35b"), + tc("3558e80203b240a562dde334bf24e27b07be1f69b520f3f3b43d85bdc7bbbd3f", "d80e6fe59b7228d25e78586f94f268ea62ebe04238ab44e97316b22e96c91d186fac862eba9212ce845bedc23cec43fc3da42aa7025637e0ef65823eba6fed261afe9a48074258b235559a916e644bfbc9c3b6f44d760e3047bf7c885f9c70c44246a81d29f9c10594b9359eaa8c8c572c71426a930f02f2692d504e0f19e12f6bd115c085988bbdf2748a2edcdccaa3d29329b4e8a1a17cc20243191d2abc7a4cfe384291fa8d7ec57821e12175a156f893587e2dbd5fd32c2c1a7804756aec8ff12bcad4308dd33eef5cff2983650fefc04ddfc4301107193dfa9eb7e8a6ea2748072879b063c7b0955654ae2a68bf0f00c37f0323c6458310b4707dd2464da5a52b8a8e7ce6589970116ba93eab2024040c45f22e9b478b34edff4bc876896d143152565732fff04efe656e06eac38f19f4eebff224e32159ab375511b42b6f74001ddb15ac0cb4f6baec84901059882f58ebf940cc973422f6f6940a94c5101eb24ed21bc0c417d959b4f08693173e88c6dbe28b487b3d0ca00af26239b5b8992c60ec3bcd3855d2db197ac95c4862b9b90c5b4ae366759a38868a2b838e36d7e42552b3566d7a28fe6e208ae9c6aee50524881c482b8f48e9133ae2a9d9377a6f966f30603e25a0ff06106b2925012313cb3e79fa3f0dada7cc3d015b4c0baea4a79ec8e37d792fa13c19aa173e134774df393e1de9133369e858e47200648b44740f18bc0550266f7ee7805b85d8a19368c68110cd11f14032a099cc797db39390c2930f68059ca31592fbe73619043e1c095777d297158ae6c28c863face2299ea435cc1e7a09fb29071c21780924d2ecc41c9c9749da3ef0819a41ec086cd2b7370870694b0392d18b6b721b81282dadfe3c3c3fe21288e992c7e3a8fa685f32307529e6d50acb4252d3e170334c42c70fab02df832569c7e5e05042f71a4fa939b58572cc8b0cee4331220742d2ea9b1117362a1831c8247e1f6e1f42a88ff5eb9aa665f9a637db1be7e4ff5a75dc21137a50d07087185de1310bc5eb098450914b9c6ad8ddf67ac0dcc99ac61c3cb09b101df6273e0c3abb3768f24fd2c4bd2ef6dbecb1a1ecde1418ae5fca22d41dba0cb6c16b06f2cd26fe1657c1ea7abebc5bbc8dcb58738f855bf38cd96f29bf7732e91b261b70a776eee64145c1421fa300bdf80bdf6457e318f9beba2243ce8cf57dfa8ba0d5f124afc6859ddb0f3ed457fca64623b54a49be589953078dfda65745fa39cf0c4d9866c38f76231fcb44d3131f8df389ab755742f16db218b3978528799746a99dc7e3a958632563748039fdd9ffcbff6f93bd83dacf06ba93e39251621af02f8175cf541f0c800c5d2f5af06894b2fa14b9a6faa3a3c95570423613299f141ce25ee77c0259a40279a2ff60b51acafcf82cae4297e57861645dc7442eb052309001e89ff2ff5ba41f2f7d48fc9a49bd3062b80c75be51fbcc8764ad6d2e647574edc03660c9f4b7ae58888678dea4d6a5c2512f12a6aecd3047ad08cfecb6a4c95993164c583c91fc8558cad146c73c7f4c24704ebbd00d9dc2c72f4af1b24f1dd5c0bf97c283b7f69937a6af0c5ab195ac8d179169bab6b141a5a61f7fabd2b0a23ea29cc295ab04de123290485048e06af0ec211d93cdf8feada722b82b994134911b5ebcbf21d0130f910dde8cf7cd3e46c2be4ae26796f25b653f926569371ea09c7a5e2f38565d08c9d377baf51b6dc854866c50563b9da26fa30e0a87f5b2fc6472291550fcc7a0c423ce82d0aef24f54c05087627fde9c9ee2ebf228d12291b2b25cc8facae9c6209dc43547ba0208d359677c94fd17a76d0d84d9b18084c8afe1548ef4b7fc94d70450087edf0374cc414561e6575ad401c99908ecf28c6aee82f3412379a86f78ea9841cea2d597664340e90714c4f75453cbc4c24c963264dc8f13487a0bfd4fd04aca1315a1b70eb4888265143d82cd63046f0daf27d8f1a6ef988f48d13c56eabbbf27b5785e51d68db08b4ae2b9411596ae87c760d28a4e2c38a76bd6b715293c59131d3d6308c722282495bea69009ca71aa78a28b681cb782fe4274d499a856aab6f19fe9f87931ea93774ab13ca5b3e29c1ec6b300e67e525b9438c2f3c3af3c38276039fe82bb15cac81013b195ea9c430b8381f73b5f95cdfce403c0277be77fb7d8cc38f5c075347cdf1da4689845d803c9360911f0156f6c92b5f775d502674ccd635f642b266a2b03748ed6247250ba23878853ecc69cab7eef2a29a08e29072bdf32db742de368125cddb36953d15712302a9e4ad58c8c63c7721d4647bde4a16e582181144ad9ee4d51133a5534936fa3a7884d0981785f8ec556505738e5293020a2fc31bc3d95080e1f747b2e25281ed256540d279c07fe3218a35d4574d22d4155e3f12f7450f37f8fec6c467566746720538bd1a297dd9b987dbabf2d6110fef51b6991012cf338f2371e394f2d4967a7a462c054e43bca22794a20675854d742d441f0aa289b0dbf55e48f0d495864e3ef5b145351b93a2459b6ca848f40df1d8b588ff679771c0dcb69a7196d19c141ea0846d5d2934aa5c662acf1a7859db2c02c791250bdc1f5acba5638815dcfffc858b0bf1e086ead0be46e9c4018159729e645c486a4973e6e1380f9850128f08f315e9f9d6efe64ea210fb8070a498f98a1ece8ff4279b4028efc7d3634fa3ca8cfc10c58e349b988d3f6a13a18b6487b526ac779c82473d121456eb2c18167cdd74018c0079a5347c1f9873cec00715f0fa530550c814cec5b96121a9914a8291713d6c7b9f65a9e45b85ea43e93a79dcfb9dc6a6bdcef820242dfe7cfa4c2a532e6371681c8f39fd659a1f54536eceb20fb528c8b8bfa96069fafc4a8b23deaf0ab27d8e190fa846669f403bec4f302c7cd5ab9972529827d5819e6d1fdf2b6cf2afa57899f8f9cbe1fccbebe8d738e84779134545614243a1556ceb4601ed1a6da90599bd3b2e2aef9b060e27d879d1e08120651739456062a18cab94aae2807ce12fe0761aa96598ac40d1db86887708041a90d59c66b37ba63ab409c9fb025c5992b985a35c1bc3fea87b7791b6d0832a461a58b221933d2b9ad6ccd80e08cb8f85f52d3b9a0dd6ed57ab7a62f1ac77834bb86fc542131c779dafee3df16e26ceb0953661aff00843def77210011965016942b3ffad479749817ba8a85ef288091f92edba11d7fc01681a80513b3281800632a33b3a73b4ff9187c12a5701520724e77afc84e6a3ffceec439d530b38abd9805ee0a8016831cbfb54b167b65d466f9eec8058e13d25f977bcdccf82c7274ad9a71ddac8a0928e57b4cb732ded8d314e8312f99effaea028cd48be614256c6d39ca0b2f77a13829f172af85fa857bb734383e757a0e526b019d188df17bdd90815cc90d289e6fd1048c0963d65b5bda595d34ed760aa1066f1e3d9e48ae2a2dc492701e9ce465bb5a39ff722cf55e9e2f8611790920a3eeaab9ab0bf86ed270fe658c3a5f633fff633ece639a93332f3bff0746b5c4a5efe9ae4844f996fb842748c2e25ef24b6d0f8d5a476bdeb8328444e46393c0544ec9f1d28cb83a8c965e0d9b066db0366b431306459b6c837dc9e672fdba234a12abec8c8c813d2ff6fc625de96d35461cf72ef1529e5425ea1988ecda7381c0f84d86eba98f4f4d0f45f1c16f5cb3f8456da4bea3030f97b56dd494ee97c630656e58abb7ac6f2d8676c04a34c2ee4ab1bd10d1630dd59b6999acda447e646a84aa66d53c6ac0e3bedd0fc01aea8aa9fc7648d44bf585f6f133a25a590d63b4a83e6519f528b310bc03b9c97fda5fe700978a66f1ef4227afb3cbec112cc4552de5f605d9cd0772122b09ae196e8725827538c4473f82a3aa0f695c5c5d71f91f16cc8674445b799077e5516ab55853d86c1fe9e4bee06cbf8f9601e14684b00579f277964aa162beb92ab476e322e7aba464c0657bcb65059b47668a82190fc093ba433a16f36b22936ab30f6790601907048c6ecf7c4a41a406ecf866d70a3a3aa2a1bc6472ddac9f97d37ac790c117a609dd3741fac7a011960aa6e278100fa04d345796645e5958a0ae0a68b3b6d3f11df7403932e6aabc332a77417f2fed3651667bb0859133c0732c56ec52acdf8614534e4a2e007343833389073db6c212e567b7588b0bc81f0d622e1646acda0f5dfc807073efa779d687357ccd1d102b2b6189680c502435bc6522caf5cc16bec795174503a884fbf209d0a235762c7162ebc66828bb8f23dfdbb6468a5e62294d4597ff4f91d99cb1417b685cf1849d9abb58e63fc77f380ede5ac5e658c103414f68de7eafeb3e69e6641c04b0ca57c7e938d9ebb290d61a2d42e1b5c0f956009a6a80403e0c77773f281ef604c1959edfd1df210df127c302311e4a71a75622def0f91eca4717fae455dd9fea557deba0ebe1aab6186aac0094ef34447c24125706102c0b27e17fdf4f1d22ebc6506f28685e0d1d886dd41729f9bab64486e7e96155a26cfed69df3bdeb62775189e9ede39d7a0d03c511ab4d605343356bad7ae86f43e1e9eae2303028d187bc81fd3365d7b80e094ba629ad56ca352f3de8ed6dfdbbe7d2064c757af36b7db4efc0e9e8f2f25e6282780f7e059ec9aba8c04f5913e9632981241f75514c50d6502d7b01f4008422318c0bace5d135327e09e4f97fdbfd7cecdabbf16ba2887f76d9720e34c26cb8d199fb46b486d47e24ff943c1c23f3e99e472d96fb4b6477eac3f364e02ff192f3fefe13c69560fd4f4a2830a3a74ba8c8eaeb82e84d8eceb983a6f1cb024a9dffce69ae0dc766fe283619c11c5885b2fdf38e230b187d822cc6a54a2b643029a5d9af17c5c705f41403ae62956d977bbbc7ce900ceec2b7"), + tc("85cf752d665b9387099f692f26253a59e57e09ba181d2f2b6728af7177f5dd68", "3581d4aef0860c1729eb3e6b504f00919c656a1c56c10111d21a2f9a77ec0ef7118bff2a881973cba46686d4f104cc94c30b93f62769aa2c0f4c7f3fb931b696ddb632b0fe71d57cadfe27573913cea488a68d0e45b90cceda68d966b725152ccf054757211d4e9eb42a97308c6af1e0b7a0df67c61f9357a1542ceabf000cfdaa1fbe71d305300d43a448a4845ec94df696c7c0129b088aaac29a43bce19726f94dfbade2d0f7cd7c4ef0fce12681fbe496bee9d7725f549281f4ae666110bf40f5235526606127ee041c09874c103caec8ee320d5e9f2d62bdbce321fdf7681fc988f453ef999b9fffd9dc285bedd0ebd36fc22b613765a34bc097dcd1f19b14c60140c8e23d7894132343ffc2b8ed144d9055d2795cbb20b8fee92363f6672bea4d40f9acd55a2be27cf813b0599cab2e08225c4f909e7b647a39f888b9370ec69b2a6e6591604a38a5bc2e0abf8b722e9aa2f05ad9aedef55a37810f935a46eee33389c375613941102d670ca6709506861bedd3dcd0f1467f4a0ab681084bd482730f7c753fa3f5ab47b7845c5cb17d17392581219af440cd67013986cac4ffa4b6fa004e7b5eee95742e7c40044b9c5a4691c2b584dde6a8a45a36481e715d6d9fb2734e5339dd5a1856392e8fecc416778805117989e30d422ab8d358b690787e9fed3767a408dd99234463b25b91c30abb93f3ceec89823c37023e7393f29f5065acd499d22e13b5909857180f7785749734027aeb843245f5569c715a881a1e7554179d5d993fadff2aae71cb0016e1459ae6c2ec7954c0b6cc8bb1116bbb8cbd81cb62dde57442f602147cbc64c2a4c456c1523b8075d0defa053578c31492b3234cc7370aea593aec0359abe3d1c7431cb62973e93b0cf3c37018b468267b664db3f45fcee737cff976bce35e061aef48e8cb20f0461d729fd1691e0d2f8f2e0d44f77b640131b9e62b80c751d0186f183760b63296b6bdce97a9c0ab4cc37a27a85e1897f6de52df9f0b08f37c850f2405831cbe9826f3584e99f7811f08fd05b335f6d07409a80229a567763ad8f20539e1641236e87a7cd3c847c9e8ec063a5a8d21f77f941ca7a56e23f90130b74798b71e7a9cf8416739a4c750c0d5752a781b11f994a0b0e1cb6d8377488ec2de642903f9b44657e85d8a213f1fc80e674dffb747c63aac85e62e295428aa315f60585bb5d5203598db4f6ba78fbf3f053d579534a0c270b542da82848b8189de8a77a339d0052b74b21d806217e902d9deee2fbe97db9e414ca9439ece353fe0ea8a72ed2116f002365c88af05d0e803ab494c3d1720297ace858cad90d30e8185d02de9bb4cc3409772c08734cdbce22b498661d73a18fe2738297f25f5466becf4df3be1f3d9160def5566318620a22c0bcc27e91cbb3f1c586fc8e09c60dcd424e8c68418ecce193fe29e4529e1419b1b0ea3597a98f19e999c780e543068cac84d7ea226ffd535f75de15134556d8d637064cf85f62799cbc15fa46a62c03b0012af4615f7cc4a6fd09396a1a6d13d55d27cc76e9dd09597d6becc0467b098f5dec034ebcae7a660394521cff7d82bd418ff8cef7c1e5c0fbe1c1f262b4dfa186c785b00ce614c4558d93716e17766d4d5eac4b0b81c0b866e41e68ba0be222d32f8d83507925417bafec4b7ee39c374412f71767df78376605a002990845b9e791011724151cd3ef26191e6c2db99ae0eca5579f3a24d021f15a09319235508135b396e97819dcff83617e641d6778829d6e3a26f7186efb14ddeaf2113b9ea538ffb2c2207b737f7b708ef54c16252f2a63297fe571034fb4bcaa1ea6c4b65017fb4ec7050bdfa24bacb1afe2e2d526ee2784e1ac92512e4efddb72e4b12810997cdc39ea1a827fa08dd2d7d88e781862e7c5bd964a143f61223423689b81675231cf97c64a3a71618f7cf6a44ce458037730a2592711582ea9d9eff63400fca70b7b5251da70da3d339b768762111d27b1a2e9ca559f544adf00f8c2372cfbf03c215b75cefbdd6b3634a8fd1054a5c016ff1feaf0526ed8b968cb44d279c04159e77a0289717d3e2515db6a7b5b4860681cf8c7a1589a3d85c5724a28976193312ada24c2397d2c10baf2fa933d71f2bb2794190730242e117c90faa6391fdac1ec61ce780a7864f6f1d6d8cc19c024f4791b4928f1a2b5ec91cdcc1fbc1e89e7cf5feb974a0231dd71d435741d8fdc3730012795fcdb4b15267cd200e576b7d1eb9c5210f24b916a01ddd1bfb1378aa865ed86d0056ca5c92e5f7bd57d447572560ed23107ee6c08f0510e2594f47e19ad6725bb303eed63be2f94c0501c609c565e40ad74211fe97de80cdc82b20977a1d24a7e35c3edcac3a590ebf65ee6f5d044f43eefee6ca71326aa713fdd878ccb6f53d09913420113220af1e37cec5fd5b1f1743ec129267a6d5503943bb5d4d21bdbe149e1ff74626b191bf75b19b5a2e4225fa8ec887914787a75ebd7c0cafbfe0f5caee05f2cf468a1604bbb1c5ffc78944017a814077b07edf48524eb9a9badced4e562674210a5ea26507dfe26211b57ad3f613cf951306ae51d4da53a976e139b3eff7830d661cedb059c92f93bde0209c184bed15e38373a58e8e107b1fb0df85dfffcafe9b005c0ef9c8c7bd3166f777fa5d061ee827cd45bae34bcf7ebca1a47f76f459d6dc5f85efb0d9b5fa5c4bf19494ef7bb5f76fccec4ede71b4bb4a4b37113289873f3d57d06b3d42ce543c113c789cfb88db8207d26007c809be2098c20034eca5539ac8122640a714b0ee35f6b466dd0d0a1d07d6547e6ef4b243ce9835b9bf6ce2d1711744be3a405f6e46e1824a625bebc42ff340220e68b785af5cd5c05c215a6d3e31df1ca97d054298c0399ffb08b4ce8b2d6d08a3cafb33ee536bb01fefa5fcc7aa905c83af895047cc7bb6051482dcd5aada28e906ead9b90cee2b32f9b917123651258d474cc699934fe732a9db3d830ce1c33ff39e946f6bbe363aeb3a3f0895ca09f78ed37b66c919a15d85e9ea68f9aef383a7ab61101bc6d4beb7e36bb9bcc04421a0bf7a263651c30a40b652286a5678ea0da516d4baf89d747d0559b119199a7a615528ccdb2d5b10e99b2bec59b742b40f8a3046481858f70d18b5441974d4a5f4a732041b999773a93c3fb4325b6e5f5eb7849923407e15a55ba46f3804de3e8ca9561bf87622ec8e91698c0fd97b6413d3212f1410d9ad0754b457f041a6805fc30d7a7a98700c5d6adcef4a1f23e51950f2fa40f74fdd6aeed47cbae6cbccb93e8a06950d980ca261705a8efb5b7bc866f5451f709e8f01791600aa7e93ad41088dab6ac9ea25285214d4fa5a1c4c31ed4e0157a3a6bb94f8e0acb66b0e5fb323284c54fa9e07542030679384aaea817576329fad20bf28d80cf824d751206be17a0ce16a9c2ebd0d80c7e643867cc32ff9ca92e9adab3e1096cdde69b6c2990466611489bedaeccb9bc135eb2ef54502acfafe497ed668af4c984959cff284b2ae81638f2e11cdc463318845a363eb08385b4002e3638fffa347039b40ef7315c88efbf01f9eab73a6f0596a1a16943b03139f4e1a3ea5182b401f8dbe08527d81ed45682d6459bcc9362de5d351a5b8437d79d24899bf5316e81a182acf4f740eafffef365ae54832822ab4f58d89596014fedd5b99caabc0e877604bf4da5a4a8f8a93dac8b5ff7f68bf4167560579317a7799d9ed14b7c4719e34aa3312f2b9f07321bd38fe232b66fcebcfa5a15ea6971c92c0eb377c44a8b2c92be3634705f7fd01f8e2b10f61a7adaab93009b0b612cfcac9315ebfaf321c27b9fb2da0120a08f512a9a9149b4797e400da17e749bfd1683df628de0d062a86b931dd19a639aa7ee332c53a8c771e787f7b7c6d817f53b6e57153c32b25f87a1df9407d2390f02cf6da3a508d1721f55e155e6e20bfec0d2550598d815f71e065426e4afd677a1fcf41a24cbd82db6135f45d74d0e8aed94bda26a0f42d19f52028122df55106e45f8b364f65e314ef77ef0b7bcdd82425d8e3212e3c7ddb4ea1d378e2300f10ae707466752b15eb4489cea807e9aa03b886f3d623514a7d16672b6dc025502bbb69a8ac5e374b94a0f153dd86b3b55308ac05ae5869c0ae5e18208bb0aecec3d5e399936d58ff9fb933ba349ad567f9ccf743c264606b41d8aa1a4c164ffdfc9853fbe6acf11f213490a471e80c15def7d985aa9c340c126473d1d14bcc826c12c200d72e1a00b392767bde0884005d270a1c62413aef754804a2f04ba3c9b4a4fc9076951860f9a0dea80bd6cf5fd6e51983af344b887ac3f907a40a694ded8840998c3a273fb1267c4c7cbc2fd0a51315f313e32818b45d6c7fbedb5aa673e7a8069c931ab838de2045e79852fd832c0546228aee6bc77d72d8e984575e27ec610e411de7e02725085aab0ba967a9300583f27a1893c1432f3e651be3e49d60690652d6177b39bcd6b8e0a6a6a812048fb2ccfc6cc636a0fea989ceaca7bfdb5286e9f9b34ee5bbe0ce93d4a4614a354ea91dea8ba0fe6af3a4350288b0caab1fcc135275ae5a659e4c3a08c9e2481febd150596e684009137de6a0f1752e25240283dfa58451c94f9ab96bc4f3df5507230e152bb0c7f0f4c0f00d9480509595eb25bdd3f3b299cd0d165ea4c2f2ba5b022bf57a30e8e39cbbdde047f1c91f87b658215076ef534fdec75580e210a948bb1c3ed8be98181dfb6e15ffde2bb7f3ec5da5907a71fab770d29ada2762d5887b306f7a8586223d8cb89a8e8421609cd80f2175f2daa61ae3bd7bd135be39eddd2b2687381420e2c92a107e28e3e00b507e325f4ace54137b37a0a750e3629d64596e1c1b5091f1761f3eb7c34ab6c54e108a89dd1edb833d21ec688ef3c37ab1f762e5aad7c6b3764b5ec04b3c3f319732c1a3d20582d9ef30c4b3536b7c47197c97c76792145cb847bd2fb84a7cb460842f0956020c320d48"), + tc("c2e3b8554579cc42c4036778527265a9786100116f8dea396f32ce651819727e", "9740cbe9eb65ad1ce913f40be236b1ad7141f8ea72104b122fe9ac99b6552b1b3a34fd6c1b962f1ecadd53b530866bfdad8d81f8405c1d54e20d99f30fb041975f70dd971c17424c601f46516c13534967318d3c5d60ecbec607c42d0825ffb84d7c5c69858512a3c59eaeed3f61124df79f13d63c382cff70ebd250ff94e1684eb5ad806714b4132e855cb20c09951e3d1f185da09b0dfc4c026fef5d3487e5f607578d34c5a4e9c301c07dfbb72233542c04fec18ece500c4ca479a4762e5da2bfe630f6e4558192d6561b69be3695688362dc88defcf859858396f974e59744bd0224f578f3b1c6f46c7eaa523f672b52906dda5ad7a410ac6ed5d3ab823eb7235b1cab4bfac92dd73f4fb796a299c5f39de590cf90307c6bd8a7717c3251cd613917358db837dbaac615acbe2885558fc7a0bea8e15176756cf5c9204c3e3b02764ed608ddfc52a3bccc2cf4aed6f8af14953363b08a0885c131cbb5715eef0a04d18c4e6efd59e999f74ee69c79e96a9ce7a5d9fe27a6a651fef54b09768c1aa49fb03b09b6aeb19b38b00065ec6da01fb471d1792f7e3d5d8c14381cf68c42708a43ef343135929fccff2f6642c13ed2008febfe9972bc164e375383f50798cb84ff34e1fd07940a218dd174dc869025e5fc459bb0bba969a2240ebfe0efc1cd34154ec1aea73ff7858749735f1b2722c6ddd4a6991d2069fc3d61a8b23977faae5c32d95f3f1897b0e86bc8705cfb6984d49c1ee26a90b3c8679effab5adb871a3e219801c7b15d6dfc62e9dccc887cd7f25bb73ea2c3fef764962246390a4ad96610f5e0e7eb59b32e7ec20145308d81f6f638299057b2276ed045aea7103b44bc85d33cdd0fcd738035e670b285762c65c3ef52452ace980c41161d8593f46dd2816d435df7fe90871a9b4ca1c5cbe6362e225e8950889febc47545f607a7ae4ba8b86350b554f61d424fb51c8e9886000e4e07dec324bb5119a5b5099bbb126b0c0ca0cd8fbc304780a9bb44a4abf58d30db4245b65ce20b0975c7869b39b966a59fe69290d5ab3fabb12df5024daa065a48a07a8c084dd61016178e69fbaf1fd87cdf78e1aaba8a3a03b9fb893ab7b674544e6b1e46f943b149cdfb8598f98ac08efd8722836ba320aa13b406890449f985ff8a568c6643b24db35c25df8f0baa2a17ef66c2ee0e6f6e2b42e184475921508fa64632a4629b72f7f39513a08c6b55a8f0483f3fe1ae7528e128fa30745f0583c23c27b9d1ebe56ae86abc1e2448b15c4649e5e26e189090ea986223688f310f707575f7378c7aeb070e0ddfacff069debec773b8b50dc86a012db58612ec90d64aeeea037c3cf40e42f1948ef837937d67c7059732bb32fc618a203ad06d5f8b472d0d692d93707b6b1b85f83161d13bd0930fcdbcff94495277d9e390ab671371d4540e66ac18f03d625c8d38e75f628b06f478e6cc4a498bde811b9dd8b636f7c761277a56122e59d0ad8052ae00c8d08436408ea5433d6027f798f48ddf15ba7600363c7ef67236fa6ef377425cd9f634788e9ce34d08a956e24e3137149b08a0eaa2d831b0f3285bfd230859ad02d50feb10b63eea8044dbe97693c29c105c2164ef436b88d5d870f88ad5919fde8149b39aa65e931bf5d461a109a423e81c376f11495c0671f3cf371f89f81d52235d3413992713141c3f28f14a926407a239d6ad9cfe565cc29f8a836e5643f5ead4cbd6bc936effdc687b758384d070273c8c2d2dbb81123b524147979a8e0c0d99b09812efac739465d1a858ef233cdd13769bb8996856ccf509d50cb33e4480a6ad951e451cc637933bf8dbd5168c06a76dd08d42e6fb0ba1044fc33475a2342a53765bd4099546c9ef96de5b78c59ca6123223e6f72680a0b9dd9a12b24cafee32f0b6e145515222cbf2abbd7330b9a91303b688dcc35db233c71046716821ee409083c790bcaa893d04c09a8f6a9c35c072e5033108406009564b4135c00fe4037d2d51ab1d721ae6b74e867074199b73b93a6d89ddb8ee74681213bb677014d1cd75821eadddd7c1c78b6b75acf574cbb288d2b778d84f8599a421c5b8b9a3b120f8b8f98b5cfb81f4f8bbc416e3e1e9462eea5840b96ea874a6c8267f5d16451b93b72dd5951de2c4d6d3f94ba87a62a19da58e30ccf7fb782745c233cdce1b0ae1b1f1476983d123440a765d5db0c3e38c47bbfec5ce37b94969377394a25a90471aa9ddc98d559dc982a7630b6009a7748e9e5f6fe9397fe4afba1ebe530b8c46f4f18cca271856efbfe5791fb9297114d952f0e37949e3dd5a38fd3805eff33dbec3baeb22a8ee93b42f85c845cf3c2c00c706e11ec7975f6366c6a38c2417edfdefaf03aed01a987e464830d7891b0ab7f67f08096e81f937df9ab605c3abfd1f2673fa302882806718c0b8750c59d0b5f27bd16579e5e5aa1b5a98257627f97ae38bbd79a0c21e48ef7bae4360c003e8f86b89b042b65f436cedf39ca030dfce9ff772cd99274bdd0e6252d8eae877330a21bbf6f266f27d119700ea597a39acc8c212b43da9a775a6e165c60f6efb75701b7aeca101f70ae541a44eba7500df14efc38f2e8e12238cb7479fd4148322f1055680bbca8e535cb5eca77a55612e563f6ce2a57b1fb9b663ad0b55ef27b2ee32e6f779c6f9e74323256d3d0a2e247943ddd75836f4b07b1480a00cf4f62092e5ff60dbe75418b6bdbdba59da397f23b5470bb337487c863ada5cd8483e6c54d201883a3367da2b2f49c66b7fc675ac47ddf7d26cb3b40dd864cea1a178bb1277b28f198937c12bdd8c14573c429fb139bce19ec9b446552808d25f8610dbf949d33a06550383df5f3cbf799b0fa41dc5935dab118a7178381ed3ef9bdbd632472cbc48f21deb7b5b26c6d6a4c6d8bbca609ee39c501787f6ef2a9b45fff5c6c57355ddb17b7a271c7b56f1b7b4ccae8df84c01514cf9f8138150c77ac95ea22b5c7da74df60efb0605f310d42ee071438c514a418b62247dc2c55a23d0e731ef9e514d1dda73f57b2b6fc4b4fce62f1f0d0b545ab8fc08ad4449b966219e1d533f26a4c4f628c2948530c0518b96fa8b97925629dad65fa6551bfe9102cf1b9b7c43c41f8bf4de993fd53c4e582548acc64f67b2e9ffa34b8b9e2f64c87436ca6d1d0f22a2ca236fba10d9192400b11d77307b51487d986ddf418076833cca1639a2c9b80c309a3a4ec9ed9108a69ec136b73982441674239a6a380850a7e18584e9556b7a6295578e4adb010d5ec2a3c5bfdd3062a9bc5a2a8ac6eaa45f63be5002a5b53a037cd2e996d3e0b256aff17783528ab93eb48b2806ed0d4ca4a037022c585ee7789f8f253803d897ea6f0c40f07f04574aabcec469cd98b7cf1b352857ae32dcdb2edf8cc2e3594679b0969095cc4507dc3e9059cde8aa2bf16ff730696fe75cb63555693c9e003b47e2902a05132745d5d48c0a36f101778c66eb9ca3287397e278ba4a3d1e80e5d7e1fa041344e2ff0d4795476b5d2098e39ddc7738f5b9a871e4d963483eb7286681a3dfac38d5866bb95f2d6bc430ff11e395279db711657023e2f4e542d9f39421910ca02ed7bc767b47a33d09696982fc6a0848f12f0a84bfba1a0b42a0b55c3e3d98b05978b53baa813130d18add76db7ee8483a2d6b3410b08c7623c6b463e04d4d53bab51f0a5a3f26b9e0becf98c1067a21677c07822f5f730fcf5239d69d6d34b1d83eae3409c4a9cb26b754f248271dfc775cf455372c61c6099f2d8172274ea4fa40d87759c2ef2f5f4b9086ca4a7dc6a5a2001a3b85076a6436aedc3088e26af74c98f185057e0781bbf8d52c0e4eb3c4a0d3e22cca0a8e2d15fe75d87aaea723a991d813a67d93e26b31289b46e21416a00ee86b785e0f470c2a3d0beb18305915ae21d0e7d3c27c2d37a05c8fb808921d4e759a4d3139624bf2ac8d5ca8aec0ba07d5a03e13ba45ac71afceae9f2b55d0fe70af8334245e716fbd6411800d6043f30d6699a81e1a902f8fb1dd23fc0d9443f05abffa7fd25b6f0d83701578c54e051055a5d2785941bee2ced39e8fcab9923b7c305fd23cdacd5c6f71e645517d0bf34da56f7cc06175ed920cd1a6eb713200a212538690f481cbc4433b37bf7337c61edf6d27ffae8556941aaa5f43a5eb6110ae73e28e190ae0ec6e6a436312fa72d0affdd9ebae433f60507f624bb7a5fc48c3ae853a8c146a332785666367a8b7f9fb5863ae75b09fc3194d890a3f724d6161b0c8d24e0f0f26bdae711ae13ba8bcca55f9ac3bbd9ee43cb3a451a13f9d53fbc4d503f91ec773022158fc66662a7b75a617fd1b9d030727f2d2505231c59d9223464ad713152fdd353ad500d605c38317fb5c6a9e052b95261ba6405389aeea70855c0cb2870e97089d7f6a6231eaadc116aadfe3581d91b30b7ee22e1d8d91d812e641b7786c04c0bb4ab5a58f93dbc48493a51755b5ccddda373cde4d71e2007f5bcc903215983ba847909739c8d80955c350208c61049f40272496d550e01397e8d817b91fec0d15d61a28dba51618ff3e8619ad9de3cc3a9e35fc1777bb6189a1a50ef42cff1eeb716c827f793d80ae55065671a29af4bc1360c36f73d3281c690f7cf2e0f94245f9ce8a96ce53a99cb2302940d09be86cf07cc7a37073382d2f560d733739fa9d99ed4d16fa5a81066f5af01e17e757cffbf9ef6ca9ecacc8128b0361b3abdce840c504c31497ba5e15933f18a1bba1214cf2f019752cb5ae61f9000c85caaba2252754936ba350a60c02b33eb418da3572af143cbb2e99d6bfd8856a88dbfd5329b11813e410a56b0d570ee39e262f902a82f890136e1afeedac591904764cd5ef0676b7ef2a4317a484b994506e9a7ffd93550762975bac0173c90992a9e938e6deac563d7f360ab7da003248a1cbf3fd430672e21f87faf0a1a91b305ba1f6ab46bf37bcbf70210b40aa22beaef8c878315c24e503b5afad8b9d2384f822ae8c3a7941f58f36c484b8298c39c409c32cdf2551cf8a90df08df6c4313799b0b781a4aa05d7631b73969c5404d00cb053b786b5ea67dbfa9c"), + tc("28a84c9d876577bb858158d254f954eb110dbd33f99bc78257c4a90b5aca321b", "e691e8feb744957b275e5fd879a3abe54d6d6c8c7f589f0b1a17c08c299c559e5f07c9eaa12b3dbf0e06342e6344229e9931217e77f34b53014577ab76fbee38ec3f491e7e0a129500beecae77745f98370cf6d4d0e39c4273aef366b9bb28640ccd6b11b4a7ea7f4be9c5c9ee529d0a1bfd1450b5fb9190c1d8bbcf1b80336c212992d428c200b5cd34ca3cdd75e175997ac6181e3d4972886fc9d41038b077cd1e786ab3ac82c3c8ad6dd5d01effa39629540a3d6002c9ab63a183a8a85b3bdfcf4d817f6505c7747abeb763dc78246b6f56a3e12e1001520f5d35c8e3f251702a66de663c9cb9c53e0584207bef86ecd046979044dee55e2639f40a4d0520968be443c46d71f9b6c75a82d8f7cf05a0d4a1a897e6b57c93922a5f82e4e7578612b3c68b7994d8ef2f85b35bd0dddd149ccdd8c9084f6f8ff61900fe8cbeae6525dfb8209026f6380ae677263b1d7ced5f8b2b0b313466966995a7af768a3383215ad8327707c8cb2656dc1e91902806b613bacb93d04b1caaee759b97d7d13cd0770a20a3f229068b28ff36c7048208a4693d4896a9f12162f3dcf18e97ddb84946fabd8fd5039f6ef6dd09540df8d5a6b7926cacba509f985fdf23a1f4eee5145d13d7d0e3e4bfdd1809a3918cac7734609f6c3f38bebeea3ffc3e4b4a8acaa1387440c92d1ced43511654e95b33b56f988dd43ede143afffe3b6e04f4b38b593251e0fddad3d26bfc1b40c5fe0573f010671ac3e3e6210e8799f256b153e1d95093bd452d70968ce651c7717bc935bf2fb77c21029b6550f535c3bdf804e7c2b1a5728ae231c005e31913be6aea15bd90491f70cdd222a548c3f38c7b2cb454b3b550699f6eea351a5bd69b97c2c823acb21f48a52533587b3b2b51eff4f453ea66a1ec559f11c387e7b5a111c7d885bfbb557959959aeafdac8693b22dea0cd30454b2c4b1a07aa64e3df3e4a36fa4666b70e0744f245dc0161ae2cfc5cd0f7a35e67651549e86407cf8d2ef259bf65ee75f2032e3a0d3cc6ea8eaf5fb16563671ae9f1185fda552639083558dde883adf86cb31bbb8764bcc096df60207e37cf5aa87e0d3565d5510ff0ac9359af6540c513bd3ac35f1829c5b342e66ec78be0356735e0ecd97d20f854094d124cd918a3bfd045545b1a7e17fe3e10eed4d11f8aa1b0e33285c7837db7033ac0d712730c0bce1c38514fd02985f99b88a20057c9781ff0444c290a7e0645e17d0beb2cd3a458184f6b4b5e100cb0d69279a9a185736e7eec42d8740e63d5b261cae061b54183e4c21994cfe6cd5c5441d6c3d2300509a40cc2efe67e63d0e1966c781bdf546e9e917fd283cff735b0017550b5cfb8b5ab22adafcee1e3ec19e0616dec2e5e7b45c2e547e48425be27123aaf91b4468b4b1389a095b3bcdc2cfa403b94231411ed835694190e54539f9126688f6232ea13002253e937e508c00165a3ef523621f06fea1440b81e8499c20bd2d817422b48f8e83131e0af707126ccad03a23237a4ad20353ebca6af49f7e8599b32b708f9c3bafea0a7be24c227fb0866703677ed85774f1069665cd8ef88c96cab5ec3f5ceb4cea915361dec906a67539abe4127954fd53b2d734d58f84e4c2e6e90cc1958c20b7080e6e067032186f2b38b80edd45fdf1c7f10e2cdc0f0ccb734e7cb286a97594b6d90228911379ff4c6174b9aa1c8b291ce061a97c82add414f551a1eeb9fc89dbe645cf82dec048d6bde94835d6d476f6e5e08e47616ed5766cb369a94d51f2a5f03e5fee943058c09dad21e08b822d7f51be5296dfc398b141817f5671df7b032b5c4bee779b7b5ecfd228fad0a6f102ed8d7a620fb0c6e8e84a020948eecb10fa27eedf71ff5e11d0e1a2da41be4029472cf6f26dd3f6ed6e4a5a7fd441200dc690307673ff9bbe81d71649680e3f62d62433ce3496269507aa4af5b1e002cb01cad3ae5080b152d5ea0c910376bd06e4c63f72af731721e8ae38e91756d816a46830a11ccf5ade1218a0301711d4889a90cf0527b8c29911cdabc832528d93b9ea9d80ade81b5a1b8b261995ccc4823b09e3b4236ca9bfcf0f430390554e4c3b3a0ef0fbaf130778ee84420cb24490c5863ce5f3d84a46fa4dad06a99ec00a17656b4c8497c5ddd4518ad30f929a1fc4f21df78f87f99fc1f9d7528e63152a1668000776da4658d3181d259903443fb9aa32d5e07d3f2464bbf241cb2fb60cd4c6a93de4a6a93c6cc48df885a8a8175e1a3805adc539c1c98e1091a6b5dfa38eacaac4ebd5fba707da54125a3da9bc1fd1be01e9be53426e7415796ec7c5673bda853b9a9a42fb762a63943d57f6554764a33301220af0ff6648f8709cef5244add70544586b033754f90ce00dd9b3884e27c25fd032261d57320cd8dc3435a22710569b22f95eae3b98ef5ba723c04c49d53a8740603fda4551a52716aa8bcaed8e505df7abf6d85383b9ac93f212b7110cc9aa680221aa148757b30185e2117be1e31d6a91cb0d4cbaa397706bd1a9541a21d38a42b9c86ef146880d00ecfcb293ab9e04d3b4bc12be7d35da30d8ef2baaf8684c07144a3322d355b993f1967bb6bffaae163631c4b7a865ac7c1be3be4b995b6c34f96a5e04bc54bb05b6cadcb6b82849ee9fc0b4a71972bbc36a00832b52b46ecd27117c60abb0075e438bd58611ef61b4e5fb16d58e2c3481ad2cd10002a7929d6764a11699cd1eafda718a1524c6bf18d9fb141fc9cbe224351c6b693316aa81fee436a30798b817c7b01787fb85103e730e62341ac2e803ea54506ee36e13c8de2802a84e9f5562e8b14ef55496811e2c1732caaeb39b030d752176f40b95cd5d5504b055e1a96e8cd190d956814344823f3bd57b5286067cb29a10ed94288ee8bc1658201571c15d79171a3feea48df6d1753f9957df79d7671e1697f17be08cc02133c96f725a2f67d6eae26dc67ff83324c4adb4e11b732513c7c46f8142ece31568c26176326fa4df0376b015620c5adf3c5ead45e547ede93e63f72187de806681d6f69cfe7e03cfa4cc9b39f6f1b6a3a3af90f5050a8a836b597da1a0544c2fe8f1ea7ffb27784d8bb8d8030072947beb283af708febcc00e1b3708b64a2019d01673a2b57d125cb244e0378fa8fd9c36e58e5380bf1b8e2986b203f090780c8d2efabe0c8204a6a86bc228179b165a6b5243c1a024a9a4fc2b60ce150ba1120be333b8c753a6193d3dd24fb4100b29f9f5e0cd41d7a15d5fd4c6fcb11eb903c57a10d264e0fae632526d0cebb43236cfa2605f8028a4d504a0650358f5f5bc09fdd7bd31919c2ad1cade90cdfc5d911691eb16d95c6fa0441f0294592a7565203c651c3057d9857cd1709662081d8c0b9d60625f66d51ccf94603eaa32d1183d1286b2483d251d4b4b16de1647e4bafb560e60f7c7f1b2897b450162d3fd2b491b38e1f42ead2ba92ac5a0bb517cba1080ecfce7261045152570c98f43494960772dd8f2601c19d22ccb586a6c7268af934597a55c79b30fcd44f9859cccf4f04a8d9341a20f9c240bfa3236a115ded021936bfa3ec90194dd617fb2d73ee30cc39e7d8db94edac85dd2479b3b1dfaaad2ac1c77db585586da142d52b0bb48540f4648281ed4c2fdd2d00ddf7a38096bc6c6842038c5a6bfb94edd676a690abe8a82d36c530ade04dd73f91218888d678c80bcbc530c886f09d61add7474be8153bd95a25f18f8b0c7084ddeb3b32a295ddca56068fa5a488c1a6949f2480334a54561d81bad1e63b92485a05786cdaecd5f5731fa72a69b69e4706180d01299ad26a7a07359f9b5addcafcc56c73f0b3e3a53ea8ef35757b39b46aac121372f50aab7ee71da1db329e00d5977c244255d43ba5232701d792534f441e2fc6c522acff45ee18577cf948d780488f5ac4c593d14d9438ab10f04d34b6cd118624c931e85623b4a7f08f844086882edf0ad22fb1594511ab21c79f476e48bdd43212851921e55cf6c83ff3e8fb38d17182eea9203a8d01fdbe8dc76fa8b88f290a25e0c46eac28ecea44cf436b34af668298a4b2911fab6ff585f175e8266f9e8709177305a81101cfc52b3517cbef6ec44b1a27d32361463864e4d15739d85badebaf7c2f58fafa8ea6a6d37379a30ca64e44139c68512d2713aa700c6ff2535b8edb795f6e3c46829edd1cd342fdb0dddf2302a1ca593b4aa3956573d10bf2a920b05777abe2e72a313c26a4ea385237e0f0b3fc6039c4ae5dbb055a4945ce74cc90627a5832c82d7667cbcff0d3cb6425d2e1ba479d928576bd46056e8af0c3b0aeca0a944858c97c0e64822fd3e91c2b763f888ef4a659bbbae149fc4bdac76ab6a507ff2d5c604719b08cff84c871356f344294fdadfbd560bba0d1eefb6442fc88ff1ca10112031d3046d6e83d02cf9e8f31c5918ed87987dd1ba2ab8731126ee506b03f85d6f9ffae34479c6a7d2d0facab8dcdbafa9b2f02e21c666d78fa034893f2d1026608984d68f9333395e0bf195bcaf7f15cb122419e1f2bf3b3e1e831a61e27336e25eb6480403c90980bc597e87b52cf5d0ff7770355ed502810d265f3265241b75fe2cf37fe0d5acc2fd3758a0414c4798bba778e2452dbe86bf9e2c7a3a5563019f60ddd5344f29ccebdb9defbb60c6c0f503432d76eeb47fe36d637a673596dbca4e55a880fb28f4cb8193c7a40844309b55e4c010faed3fae05d85648a339b47d5ae3cd142759f6d009029719f9af5f81c88eca88edd4372c7a827ece87060f2576ea82389a5f3f3ee51f503709b46a6d3b7f2a97fedc9bae4f156a0abdd77444dd5981a9882e5534ca71e315feaeca7d5d06a9f74b6f089c3607a17eac3a200557666817db7efd524bf37c58ffbfb0feae9d00f78d6eb7240c87df54d1774f87be2dcb26c9f0ac70b85c482c672051ea0020c24265cd97f265ea3e2db3c93fe14db832d20f5bca7f0e57a3b7e438afbdf349215a8d83b6dd80bf2997403de350bcfa832e2f393b434ee515b46e4125039e0ab05b7914ddaedb87c2c0a5bc32b2de4568fdff238c9c39408c604ead6f4c5a24a7c36d96cbd897cc38bf600d51ce093b71c63a1c8c36f45730d18ebd951a6ab74b014d915eb2a951458cbeecb4512240342a83c58379b5c98664202f783453b8c8cab89302883195"), + tc("21437fa9755b88921e1d0cba74ca3116e4357aabc01ca3bb287ecd81a1e7f51b", "63d016d64a04183ea6abfd3d353790e22b04ca89d7cde3f2c60e8d36c7143afae96de671c2cd915a7d8f41c9f80bf3c47aa37487cb9c938cd5af8aeeb4a3c6294d2e12caf1f2c7ab70dd144074fe083cf82d37cb38042862da9fde75c0121fb7c04f7a9f7114f89a7ee34869c1bb3f78bb58fc646c8b16ca2288a25a050e34b8f6315af684acf3629ab24ee6ded73571c1837fe909efd3de34985158579f049250b1874021354fb1715047d9158a31e906738fb32d3ba01f559b19eef248781e23dc47fe2a3b12f1fc70d0ae922f6078f8bd1a9ce13e3c18df4c5121c132f0ddedc5961429adf7b32d62058b4e7b7aabe5c5d050a8ea33d3b5b6e90d22044e58baabceb223539a339de9917d7f5b3d72b910c1c8036c240cb1a58b7bca0d77ad820ed1fb2f0ecf85a5d0c0f07976ea27871ec7d1fe9ea51ab1f20052307b3f09950c6d1e3822440e7abcc34dba278379115ca0690112efe4b95699e6e135ca8debb173cf7a3ad551dc41e52e237c084c318de290be7aefebf12adeb0243ae576392c767f087180a125bb30d8d56bfaf703a85c9359182894f50daa694255d01acb1d436b270a7b0d88772659e74910277c2e2e9ab0e9d3c72203e59770904789c12a38d0465090ac6285ca8b577505a25cc87caba99b43166b521bc2bb6857b0fe26d99fbdbaa34849b2075b672d1fb6229254bd7108b199971e8593e1c8e83c2a631798db5e84098a67f2164f16780481110ce6a9eb4c1bbc12e681e5d248b1e8e2faaa081b3762f64dad22c9e1c2ab45ea3abaf547ce405ad56794f89e2086807279828782d4ba5e820d5d77d020b06536a2584d78d392b4ece456606a6b7aa9c80b8d5ab0758a36e77f97daa24c080fa4249f127b16cdf5bbf0f69b4dd425d2c422c0117e9085356235f49d7bd2773fed4e17712935bc4468a9adaa30e9ccb9b160223064a2ee4e17e885eb3eab5b72cf7b3715e518aa1bcfdf6cc3eb734fc75e6ad793a439b1221f895f67e6a924dc5863973b5304a07ea1118e7c72d46029048aa73a8baea1d8c9073f2ed3c0867f5aff73aeb4bf477e58a8b6a5a57beeb382117d3d406f3f1cf99c3c4d28d69a0bc05fc37735fc26ab83f11b6b31966831072eb960096e2c8b790679e816a5868828110047c474ba31f095b3eda571b6cd8cc9fd3b6b96b8312482a69e3caee423aeb6027645af2aaef27414327e45afb1c926e3094b36a5b3ebba3acf29e3687b6688321e4e244e50ea725aa0303acd522505866ad9a6e4e7d5cdbddaf2f7e327436ac51fc243bdedbbfb7c27adab5dfb41446ac204833270acb716929ab07ca60da847a14a2b248b56ac082b3bd8c643878de662f6bf764e45dc25fded62495029211a5788d0495f86c4cd468110323334ccea98558d5f9e2370d46d36286b5494042f978bcda3bc8b89bc7bb7cc2e5c9050bd1315c74ce3e53a4c5adc25121d15c7e55d4e290018209b3a077c73b1908dadbf21a42d2541fce96792eebef091f6067294bc541671918374e5345017bc39fd64a4cc56303389cd5bca803e897414f783252d937a61ae422956f8e6b0ef733de26ce29d7d54a86cf51aa009e4ed1c5c859a3e23c7cd9f0dbbf0409d465548aa5316da3fa5c161f4fa13020f92736d9337d2c172f19ea9c4532ba6cce8e00f133b213b0816d85d83e3a390d0fd0f7cdb993c355614540b27039f45e5184dce4c01f1ce6adffcfd35f3cff682b5148bfdd3902624046218e5e84399e1f9f8f3db5acbb2e5f61ce51223ffaa867d80bf09a7fab7e221a5b690e74d01c468269ee71df69fa2f05625129c9ceba4125208a9dd08fd68d618c687a43ea5ac1e6fb20507dce29699511c2fd7b565307edc53d77cd6edea24a01420659ca9712d8a76a8b10a0cd74cd144b063b1c314a0e965839e290c7f177e51b41cb92bfbcabfc6265901cd422ba182a218ae329d3a50efa15626d66df2f05ad2a9c0c6b9f1e445e9b0b52d37d6f3f518650e1792071b6e7fd9e1ec4b9049332b6253603683b4326120d9a978e8eba2ac3b84c56c2b322d61bc693e3e1d151ed48b9bd2b255c3c9c600bbddaf728e7e740b40bd564798f7be87d74ec0647ba3fa094b6255db4496c0c6ff7bc080f469e6b4884d998a4000090b93aa18b87222e41ae8e1b96349b2ef2a4ca1d34c5973b3830c894e0da076819ece774ea24635c2812736148d69420bd3112cd38f45100302ad4ce41e932c447ab25b79a1f5bdf86b11bb52098102359798e891d3939630223f973b904d7402c61e341f642f9c8123e472b7143875564f87209e94af8200810092be1fe1680b49889995c7e53c60af9d4aae258cad2aaaad1b2c0885d5bdc6a05c23aec33a58c0855428c3f72cf19d53df5bcab8702b1169d0d3f920f11a1d5284cf6f9b215668d8aad8bbeaca19cd28a2eed4788d753405eaac40ac97790ed76f24651d0a30ce100b83943ad138d88c9e96648c3061384cb0ff87a331c326dc9027e13ecb70319947b345979c001cc44823a5927198265dd2018c18bd8d164b936229d69dec5dfd656c74648532e505ca8cebf8780d8c4e107945403da3d3f2722e1bec6a6c4732717486999b57169bfd627c255cbb1d67ac5b5cf0316edce1c1318ea35a53b28c6fb61728f0c47e06793694572a0aa1b5b1561810bc99e9654e2fe1725ef20f8d1f9c0cb74b025ec7627a18fcb2c4241793727f122183e5613f20eb3c7ae3710ff7b8da1bfd52720cc204a1d796bd40996a74e77bb234d3ecc3d6627c6981a45eb18f03ab5b70e9e33059faaa61841e8a3b2494328c965bc46d5b3aff06c269f0c7e066e3e70a5d4279216511a78f2f68fe7b7d95c8b8188a4ca1dd0eb1642ff4dc25a5ca0b407d4e87284925473153b4e4ab1e97bd14c74bb52d07db409cc11e1fc07dc7972126cbd819adc8360222b4893e2dd6dc6305d4cb0002928f45c7f776631675c6747672ffb35a8616c24cbb0c079c1d201fbd02f934138e1adfa82d506c883060b4630ab838ae8bb1360811ae8ca3af047e247c026300025be214a392eb05382cd5ee866553b7b3a819d9da3b03b41258af3008cb7df06a325546677b954dd8031f2b2391f7b8e9b0da937a7a47e43af34bda0be6d4528bae57bff89b1b743912aafd7a169a1629c285bb00633101efb6e2c717305f57fa9967d174275cc603eb122941e0047b75e7a92c381d18549e222c4e3087de670898d789215abef33ce70b2c2eb72ed51654d1fe3fff93f813ddd86682a64ab795d19459aed7e0baf9b9c01ce522e718755a23264d6748b5e7475e1d0fde8d9828acd57476fae119195faba768a1ac66bd3b0af3af80e14ba86be075c7850324553114cea9890089dc9703210c824abfe3f656595ecdf0a9e9e59c2ef58be826a5f135f3942c63a6d3eb40b39c8e234e8e2371ae94bf17e378d4e9de4fbea579052d8e0dc1bdb0dce319d4dab6c7f53a1fb75903fd2c281f033d985806e476794f40db738bf774454df114466e47bf57d93432dcfc3bfc09a59ceee9877117287bb48bbcd5069ee3a2153a9c8ccef2d2783884c399e59071f698e1e85dbeaac203271852a056d287ef3084368ae32733d7adc8ca4c1e732fe4a0ee41b53d2ed50ccc0d79e127a5ce58bc480e432d8c855648bd44eb87ff98d58e3efc5eaad36bf84572350672f7fcf07c47c23ac9465931baa94431de7ef44aa957ebca2690063f3ec9a966b04c69b341c90524826d27869cc230327b883fe529eea24bc11334f08cf836705c3cc22a460d142ff21a292b97634805b873a90d32bdc354d574a0afdbb6b0bc472d27fd9116d6d911746bb104dd9c65a6a277e4e60a6a5d77b191c904a4babced9cd77f7a7b5c4b7b8c473a051b5eff46d71c4a0ce81b87d449f583e6bbfeae999d6efbdfd6b4a7ff709de3ece939cb5b127dcb4dec94ecb1cebffdd3d22237be5800c371afb56456882ab3dde814eafcd13a398c1739177312f8f7ddef2386e08426355c0ad2ac2111eafc57a16ea2385d38ddb85e32827bef105366617bf8585a25eddd66037b8f1b67c3911ce1052b2255b44f157e3e73f845b3f179ef540cecc30e41731b02a9bb3ce6f390d273ef3011c9227d274337670d2ce90629639e62a5171cf26d30daed75ef9a02663627c142f9b84c9fb5cecbe070b4854e6e406bfc937c8a6b44e7ef5f56cbbe6d57fd5d5c1504d77a2a14d844aa14ea52ad888600fd7dda4f819716963369e839b847e95ca7d89fbfbf11778903a884ede8fcd1e00d80f9bb241e8e6cdddf07a2dfcbb5ca7028a339999704d12903518fd33898ad0384d67d22cc63522ae64d57fe19cd0b778b56fb531c00b9f915c586a0dca070eaac22e84ff4dc718915aeb37a305be02d0af2f4108413ffd5df74ea1bb50a6f7b172eae0531384f6b11693d9ad6588c4ffa1c131af8bcc39fb49d03c850eb45c360f9941d27c4ceebf4d54dc75f40e3920613c372978db8f9f5005a634e22ce15cd95f3eb73cb35854fb5fa9eaf0d8674934d2e44ace74f473b1bf1daea7cbb91ff0b7524b51fcb51d76a21382d1e68dc6ccaa32432266a60ec5a15f154f390f17f2a32f22ec37f9546b066e5314428f1065ee24fa74e7c104b880991124d5f9c2c431c20497cb404f04662f583d9e545c08e10717d4a0331edfa018933c72200667beef4a3042e34cb81777c20171f1334ec1863f5a77ebc55aa06e4edd837c2fe057f8da87dffbf6107386573346a17bdc3fbd873ae7cbd3e78579ce308bb6cbe6deec7a24c5c7de64c2c857787a22bb7a9e698e9a6bce854d3be63eacbe4e10f1b1705cbdadba7da8c2037a0438a6fa54e032547474ee8a28386b4f0f638396ef7125e77cdb760465a0e196f1e44cf776ecfffff94da9d45a6c2a7747034ff6110f8c818a0b4dd7468d5237b6dc5c59b2403fb0bf2cde553e0e7203ad2e08ac447bd36cc4259d404058d499f7d51bbf7d8a18fc598a9f322154db7a406fc67c65b60ad2f9e4aef5a20ca1a988292235d9e37f2cc6caa46063124e4edef6f6db0294637e0f5c24443fd9e8f14d04d44e6a0f89d390334102990991dade07607f40feccf010afbd732da1d70305e61a5c36ab8613bb27524cb64b8766f47e1c5d42f1778fa02ed13c275b12906fa1d6ffcc114310cde819d3b428481af4739e962e3a2d90d864d4319dc67f4bddac76448b67fb439793b68b79c1deaa6def04a44ad66f2596cbb3a9b5460ddf05235d5"), + tc("b46f75068cfe2285a8a912df3e895b7c2af63993d63a5522b4a428b7422b52d1", "86a9e1ce8c1f20458683cad72cba1a63789e61f8cbd6a54343064d72495a8c686a073a819dab8582a29ed75b327f02e001f64bf54d4c7a42df210b5fd2e225607289ddb67d002492ec0b2773e86097e631709ad777e9a12b14e6fb0b27d7a6b072a0cf1071365d04e7648470b6be00d588d9378c8e1af2f31768c56daac4282bb3911969b381371ca46b62f3eb1f50df99535c00ff523d781b2519e1bcfe4309ae568b1ab92293fbcedcae52b1bd6c605bd35ef6330a61adfd5cebe62b78df1c95168e41260b520b76c40cb767ce844837cc486e66a6d6892632b87afd270a965352eee8a8d234c566bf78b5390d940948f3dce2e973d68656977939a787916922ccd949bd41a0640bd44a33cd772040407baa11d95a6f2ef18e9f2974f1039c63c14fff476d976ce57cae6f2aca0218aed385b6d91262977d23e5a0ac9fffbda87c54c895902465bac9c5c6bb3e10a276c3aa7ef902b2ffebffb014a77f65383a277b10d1ea736b6d8b845408251046a1c5548f718e948e290bc94c86a05e030d7c0183cd2bd3f3166c8df648fcc3764ab9c27e77f06ba72de87c79cf7f8d6e83a8d5ce6287d2b17b9e7d90b25b9e757beca72368f9909d8935179524c136b0ea3240d4108265558944d8e77701ca4f187bcb48708f4c015cbdb81ddb09cc6afa46f4734f7853b1409413ada5d5584494034f5b4517fa7de3dfc07531cb6fd213a6874c212805df0c8dae74969e54cd9ac4ae778aba8ca52619e379ea17fd109572f2dd38c031cdf58de8f00e1e5fa2f1cf2ea39442116fd90dff3dbc92cb52afae95eb17535d48415998f1235fd966499faddc4135b269f6db55e392c0d0b50e2a94d53b13f220567729698f4a6465efce138a5db3aeca6ea2da5a4311706bb8e3ac2fef19cee75877151898beccf75b0555347e52c571b8c0001acc08c3bfe64fd455628dc7b8bfe30873760306c548acea0b7e8c6124065a16b04ac50998bfa57fa1f595df8272a0cf7d4cb401977f048d9530e5a1e5cadbcdb0a3d524b6ceb7d16b5d62725aa083b55ba595b46d68310edf2da3d6e0d50d87414e6421c6c31f51fa56bf9b46ed5216549e2a2e0900eb2194fd413a40e5bde52a85063aa11e51739f7325e109e16882b9b8e0e2df04727fb3ea6c66e415f2faeb65cb02db101d97b47932fcde8f121051363ffc17debaaf557de6314e8baeeda9d79cf7cb612ba87c7a2910a72ba20edf2f6927cdd6d6792b1c7973a604b7ee9673eacfa9c014fbd088f3706cd762727169bbaed092d80c75b47cfff0c6affd8b7883d8a8e1fe97ff1ff6e6f39cb47d876bbace345e205900eca0f6a193c1150748c2c5ee97ea8820b846ac7af90634aa62cb6e9ba39c5d94434e9da3a566ca2a089927aa9ac9a5e13f2cab624f2a7277ca415fb3cf19cadcafe6fc586e0439bfb120589c29012c873d5ed751e37727f867d0fd4e2dfe3b3cd53995a0f4269a3db407f5ace9fce499983135fe2ebc48cd7e708680444afbbe55838c92d23bef0713a9fa381789e760fc64aba84548461f62dcf93300bf8e1f0de9b1e2fb54f3722e0748802704532a156367984604b64eb400559e907c1a2ff27d3d91c523afb0abccc7575de30c1aabcc4d0ae4259f0acac4b2eda798970e4ee99c39c09e85ac281efe136b835eca8fd709d93c138e2c32e3d74444af0cf0146fa8e90c25869edb12ed7fb87cf170b65f203431f6c74c982fcfdd6a079e1487f1a8f2f1b2902564031a1ecefa435b6e6a6f61bb1a8b6ac55d6e7c8709291f1dbf627ea1565f1b834c3765ce94a3a0e012dbe7d70146ada1c74b08f21c38f05c05149eb0e7a6cfe7c16a1909cc3b5796768036813142cea62cc49634e07ca2fb27476019d3f5d32670ad88919579b91f79126415896bfe30e16b82289d8bc775b5d1cabf9ec28c4cec19e6ebc91e40976ed3c79b02dbb257458b84cd256cfc559e5be5b9ee9ae24270e10448d497923a71fe428f13fd1d9fbb49f58ec62c5bddfbd34276872bb3e7d55fdd1668f8856cd04b4f616b7b21e2b4eca9f727903d935a8075ffa7f388df6a89f5eeda1de46e26275ef5e5cbb60a0a745a38586fcb5bbd48312e6a665558cdf315969c2778f270ca2a2320ab4b99080a008c56e05d7baa9e82fa53b0463d74ce409becaadfbe45a3665945bac2188d9607def5546bb895e5ea89ea1e0f77ec533929a7db48410eabbdb346a309be64dfd207dd20702341f1819faaf938923e44b041f1127679549e3bbccc5f8f6ec7131952522166b5352420cb210c09c20357ccaab709628a92f57c92a21c1c9a7a7f4f83ce87c6033305814acc8101013760ac25dd61148ad63f8b96f1d629ee5f42a09ec413593fa03b60e3ba22423413320c674a8ecb1afee2d6a7b0355eb8915709c1d4a0b42741e2958988ecdc18abe89ac555543bb9aebc35243397f9a3f7a53baffee6badc82d8a5d58f20856219f28d5695ee59bcf14ddfd5b703d93f371cb608fe010dc96688060b59c185b043dd8440558bf6975fa7b7933350fd69d70f9c9c1b89675f4b08ccb7ccf67ca457ab338f744ad9d5958e8ef51f3203f8d70e05c208a310ad2ed9898b1601ec3bf5693b020abd7fe2b5019901b11d7407f13182311cb4e7e2368542106e03b5b3b864a42649485915b2584c62feae05c047b1ead2303245c3450d7e872bf3d3a65d9e6ac6dd623d53f879000a3556d46760d0548a552b83adc917ec8617e953f4d84064e3a48a795291240ece56dbedad22d05c78752acdd3b5854fe59c217efa6f538d15ee1a78b5c4791b06057e593b3bb754aaadc08d71f7913a2202d53c8bd1d83b2142de9b70abfc0436312c1c59193f63e2d209f1d5f1f81fb4e8fb134f656887a71f68c07323fc534cf6acce144c4f7c7c41818b18e76e15c1c0258e5bc63887dcea5f99738be85cb1f4c9fdf1faebc534ae8a285c80f13b2e208c5fe4afbaf5f6c38ecccbc8b1e9afdada591247ba07f16ba9e4e36f940e569834f1f80cbca9ce8e3edd4caf4d32d37ef45264864b36a29e1729b2ab9aed0c450b45f9d600e773da9d6c5f780055306eaa6d19e7aaafa8f41428247360fe13bdd873bbc749dcb509b02a76e79e2f2e528e4f381b7bb3ae24b26fa32a21d7723ecb9853c1b606a54f14b881bab00c2231c770448656151f7ed9e4795600122c2fc459c872cf53e80e4241220882054708ca33b2f3b4f21fa384884c810512ab5eea4b6612d3e4def6040d9e5f3603e6eac73c73cb79cd540435ada4a5d9b82df4545b69ee0f4840d2954e023b48da0444564fa3ff4259b7d8a20fcb66241c6f06412669cefb3c8b9e9e0a83e9ca0ac744ec7a26d896cd43e994588278a56dd5827e2539b55f8dd9f121725527b07623d4876adedecbfe0b4bd41510bd30e3aab253098844c60a0b598e69db2e1cddd77a964db8433756b5f954453a2ae8620fabe6737b615dc4006d03327df04e65c8e80946d9fe5ae5ff38a58232eeebe85797a0d332b55b21770b10015379a9a5294404f4d356eeeea47c129e40b6014c19f84cc1b96e3445935aed67ab3a1aa53dba570aa0f29ba19b2ae1a6c48e345f39a1e1a28b2bf637723eb708d989eb25decdea9ecae0b2b2d93fddcc68377ea3fcd2a9386b0cec6893bc3a57867b2e48201a6c019adefd0f05553b0554b938d744872d061fac649a68d834ca52a4752ab4b36471c5d4c65754868c031fc35d8050e50e803960f8175ff0bacc08644b51d316dcfd9982d076a6a2cbadf7006167fabc7753084b7f75c138b059f145a668c59957637bb8f7abefb8922d509fefbcdd357f15edf1f62bb16b94b6a1e7b4e353256dbe17b2a466e6c4ad72d25a177ac333be505db8bafd9244b5068ba7d4dd283ee08459bd47b2276f3c25dac5d7001ebd0e8892bef4f0f17272dbc70d15b626e3643e1664cb7bcb5cb6521c0cc8e2e08357ffc3a329c72dff9b4f0009a3011931a1630020ad83df6a803dc11deda448260206475fd7b2d0c4ae8057ee9358ac8efefeadd2835de0ee6d8f5a2593b7ea0bd5f8a7fb0d003e5c175e4080ef77fcefe8c34fd09ac0c5621e034ba475de9dde49d2d80dde836e6a75e9b257e46373ac2cd1c81f8898cfe8b2dab05d35c404477c13303438eadcd2502ecab5689c3b0b01cc6128d27784bb283ed8888b204160361dae14aec97cbcd81a9c1cdfb372a543fbe7c0314613fa7a1149221a169f58f26c0f0086141292b10b9124d91123d95074e53ea029801e6b187e5d28d83ec90ad51c80d9b350813cc019c7e5a2be39bfcea02bf08b865e6b31c302f4090166ae3a624f19a219dd7e6ecbb29578bc96a919e04b650d794fd17c81ee27ae97455939727ffecf7bf36789ba34b04e8892fd2236d6402c80884eefc9388de1621c746d663582305b6a7b3e1d8854afd6339785769d4abe411b9f990a42a5ef9b43c8f5c459d50559f61690d7816d43ca9b15bfc4c5dd5a2f520527729bb187c14cb494885142bcce3d9d1737ee57518a1cbb8e9194dd9d27276e875edeaec931b1227bda3bec2f7c3f0f5690c5de5e9db8aced68b0963d35535e6f8962ff45264b0112b88c9bfc9212027de6468e86f1ac632d4c8b264ad1eefcf2f5971acfeae6b8925617034814cb3c9610e3b34284ad38fadd07def491ddfd660f4a54880c6b71d9daff18b5ae53a7274aa6f62c9abce4538e5cc76e9fd8b271778fb8587f154bfb0ed98e20a4a5e58d21c7201e70d268a744fb9daa1a3dbee501c6552ee5eb7e98fb426decb5db8771f0d1e8692ab8fca7b62a5b14093db30ac68518cf728fa8757677b67b95a732d8317d47a615eb6772817e89aa523940b9b08aa503e74a5d755e8c267480b57f398bac10fc8717688d23a737822f7a031a3e02993896c57324b0c049e0d0a3b6ec32bf78dc16d5436b10a3988112ac87aea164c0612dacd8fac6ade09eaa14b443524dadc696570f8bb9219fda4af04ddabcc83bec86a4e30788242337c17d721db5fa208883d2f00af1d1599cd58d80dfeace67f180ea8953f92c16bda7e620312cf4599b4807a705c50354d8dcdc2d4b518cd66225bc9c90bd3383163b24eff6de768eabd77e6ebfa2c2edaebce46e58ed8b858b55ed87fb14e3489ba11861ed0434d1c3c4d2090d28bf72ecd252df9dc34bdc41dd409049811fab93254b2395271244772dbdb594e2da076906d0e0f85a025b2ff3c6ec1088c5c37f85b92f6ee7db41f7c867e35b1aae99a4e6b60f734e01e5980b608022ef2439d582685e5e1232b25c71bced4a91e3aceed3e6a1488d8b582d10b7971e9871e5ac2e6936ef2d55353d"), + tc("9c624190ef1c90ee3aa0f69a3f679b9b08a93de39e3b3329f4defbb93c7e0d35", "1633256ab03b20ce079196b708a1c02d1b6072219070712c8589ee21341d50752acb6cfda17e982d828bbd6cdf54bc7232fd418a323d64939928597b9b52f07cf488250c5e42bfd3ab48012d709f8d747225839296386fce5fc5aecc4ba7a1076d089dea8ecefaa0cf66fca8602395719c12a04f929321784d7ab8239fce2ff3bdae046a266132b5c2ad9f7261f3014e87b389a6695978693d9371d0b1ff9c405f338c2fde4687359603950a54cf4b9cdd9b24480b239acc5405c14c886bbb0378391cef0662a38882bdd09e3866ab9a66cfbd28eb5ee4f8009bdefc4aeb16700eba7dc557b489190a71fda75e85f7ef841697f70ffd4fea185e7a67c81c5b8f273bfb97b2cef695c1c74446c4b425be6b2e66dc0aaacb247e4467b7c7d84ec33b6b5ab8fa1979f503008bdcff948cdbf1226b1b066cbcf34797298f3ba8c60fa01e0ac8b803223c656112fb91435d75453bae4707b63330467dd13e0a4b992e6f7e46995899a2d95d23f4ac3d0802b2a6e7d024dea19ca408c4bbe053f14c9ce264f129724a18bcb18f385b1ca091a11434ea96d98c8d0602e98edc8dfa14141af93ed0ba66e885e9fa108591ae59e109ae34d6b9f5586e4b4d75e7df7c32958a65e88a9baf41082a0a3f11539dc4ea2cbd9e1c6c3c439b622f1de574fa75470c8c939b51d2d1c2a7204b859881d43086bfd8fb90346218d099c5ab36846f3b98a7c847318bdfa01e09717943fcd864c5a8a17b6ceb89d98e872d388f20adc2be5e2006846904f41682fb1283214f3d20dbc9fc9e0ff571844a1282e88590d7c085b2c568ec5acc4462b389feaa5757f7033187e2de31955fce55fedc909255048b327ccab2e582bbc9d8054bf5cb45145c7d3a3af9cd5cf6ecba490c634ecf00e646bf95e8642c43a4978ef08a574ef1f78f6ce57c3b34b5a123d123617fc8ec9b2ac0f9b70a7f6062d38dd7b8e9fb4eccef13ded5c0477483addae4f1cc0cfca274b1307ed0de72fbcb819154cda897d7575213042615f1741a8cb646a39f8d134fdf9e60e000eb8220f65cc30f5fa52c431b9e3b6101b96e25b8d0440b96e572a18a01747c02afcd7513542f7aace194632099d16274f31ebabb60ddd94fe43dacce900ec0902eb5e686d48ed8d09ae63da0e15c736809903a0297a92de84e0260f11f446e1fc448e0ebf59faea3c726f97925c57cbdf85b1f77078d36257c85d56cbbedce180fe12b687ada2dc9912fac60334166bd2cef06b089ed5c9563844d71d8fead2f3a93f3c07c52537336a8a70bf5b596b9007b9fdf2d082000f20e6b70d2a7e6c7ed27c4146895a6d85a246f623c1b9258a2f891f823ade4ceffd59d4ffad077351e2f506e9a5bdd3900f0204b9e8969afe72f5dccb9cdf986d197ae4c4db53014041ae6221b750e5290e307ad292c8de6b899235212ef8ce954785537dc9435af11e0f3427a9c7b22efa752ea0b7eade5f6eb4093bacb78676e506698139e4f774423b8942166f9a7d22480d814fc0ae19cf4960fbf6e01ffa65c8da5bed4f1ae2b9ecec5be7b3c38dd4045b0c93ee6cc77a7e61e85d331b23c0d164b104518b3405497054445a353e9b48f2ac5e8e96298d6655614336cffe6d8c9c915e387391519ad2632366aa3bc935030fd12927efca17505ed74c94650c778539004854df6c24269aab9c273a493d3e5b0b1d687c33c2face46b4bb3742d6df743d09164d2e0ee7f6ba128bd5fba2e3b33c199ae80fa9dee3ad811d02baa3d42a6362b2ad47bba8a2c5cd00b46cf22cfe367281488a4852eb8b7face79f0ca6f8e78d32578dfee01711c4dcf3c26d0ba13f3075478e708c5c5315afdc2e4c0062d16458213bec506a9e991a61825ff78da9ba1baabbefa56b4a8c9e2e7b60ec4b7b541c8e0f79c86bb5f03f736761a37169b2aab8884ec6ea217b02c59035f5bb327243d126b78d4aab430212439b5a75b80618dafeb66aa3aff866c4daee47d374b512e74ada933ef24a841ba271c6f02c870e8ab950fe06e93c91df0e99165dc01bcb190e411eccd85358fd4a88127a22e4cf4266a90845124bf97b25d7b1c46d3a0d68a684f84e2a638c692a52cb6e8c651a3ac492b0460004073d5349e35552359ca37660f77b2770d6b2b3f7b1922424ac4a8598b4c61a6db507608a72a6a7d573cc055206276e14005a28a0ec41f28d7e260611d40f089ffe5e529375691412f4e9e12e62c3be2c563c26d2444ea9c69e6c935feb4dc4e802e5fe3906f8acef4798d940c3cd574bb5e74506c3e0b70cb62454a25f589eadb6b0709fe3b50417cd1d98f08e08b7cf68a04cccf8d6588f9fc2f31e533cda6159baa4297fa446450d71c16ea2324ec09773e7c8817ecf680ed12f64a04863efe3d9d8760f34de5b0860b3991ff0ee5edba22c4d69120de19d5429e4aae91c9e7cf05cc807159a58f13b480872ac1609d87e7009dedb71c09ceaab640a2b6135855ceae4ac2954933a0255b425d9fdcd9c246f82aeb7c3bb78c6e73e03db7aec4245a28693fbd36ef4938d59cce19eafc00671a0851612406a075713c5d1154d8e13b59b7c5b0902239d4bacfa386ac817ac5ee02a181a9a47c622b3ecf287e14843d452af347110498a620b34ab4e116308d976062c9ee9cd35db6cb79805b93ac9a15afbcb52f1ed4309879d1924a4ba190b0b86e60a516e77d34b4e0a49d4ef2cef3cc2f410fd8ec901363fc9ebd75eb460d4d8910bdf27ce26a8b4aeb94f9f76242401dc35d0644842b99fb6c439b82d82ecfe1af0d01f9becb15bec83f13b260f7f714aa381032923fde8f8018f3518547451435c9a5207294d08a907c73696f6cb000745e072e25b73b3ee11595433d27a1f11468686f08094f1d31f5ada81f11f0677a29d72ebb2e1c4792ccc607cb938647e1f153f9eef03d982595c631e49b6b7c1fa003a6eb8d59cb8892cd0888b05240f12701753f89007c859515a2fef944bc60b36003a26702ac6fe04d2e942978fc31a97eb29871d6752399d3521720729007b6a7215a4282b2a4efc2c56bd129e74c9b00847692b96fcc71cf7a7f19f3fd6b45c519fd73b4860880a2dd74e5727b31a93f0a87f0078155344ae9f7bdbf00d83393b634b5dca88a398e42c320eb95c4a826acea90b65e4767b2eba748f97c247568393e2fd3a66075cc12935b6d7eb5c2ff5282185cb62c73972a37b3ca508004b4f796bdf82b83b5bdf90d6bfd32b5089b0ca2683dc7fb2337de42e650ed911dbee1ef98257f9ba5af54b1a54b04c0087a5a64ba779d86461ba15337c2e7d4955fdd777a025de226306a17c384f1c52cdb5946fb0b46dd5c13bd7a55fe2e27e4c6d40d61d6ffc024468f8edfc7c7992df5dc5d05063fe723199224f53678e48f25250ea28bdf1089718eb8b730d1c06735c2f871164e2eb5e885a8dfd2a083be97edc94159ce9bf75d2433f1d782762f771903cbf9a1c9d13f710ba0e151b079dc0a8262bceb1dbcbbc0f35df6eecf7baa7105b9808745853c96b4372e95e482035916b726dac7be95a72b19dad48db1b19e6eb2edab5ac1b3013839e7806625abc129f41813e6d71ee4ab2040d81e42e6ed73abba64ff2eb433b910ea7d4f5ed3d8d27d39bb454ec019df6114f544d7b155549d0c56d14551faf353994a80f30f3c97e863a4f2af316468a568038eb4d799350a6facaff90ecd44e0f44efb6dc42ee4b0dc2c59ea9c1827326df08c0a6e55cf4f9c3ea0e78cff3635f5d08e44f1400d20f638d56ba84b4832090454de57ef04b6c8805a36f63e5ccc6e830c87ffc164647ced20e4c486d09de7a5f9e4b68d5456cdb22b0dded2b95b3bcae529215c2d25d6823c7d66a4fae0a1e9f022ba5663204f2314dfa51a1f10e11d6d62a8ba6c28b6ae7da1deb5b57f2b65d7456059ad9f03dc5a524054da39dd100d74eb657de219795e3c45a0e4c762ba22f9da9d8159e425a1ee783b4b22c250d8894cbec706ce16d5ca393404ff478f141be7cc69e45b077ba1955f1f49efbe4847c795347f703300f672334f490abf8b644a34b56da00ec45a350314b9adf27caf7c51cb7dba0c5477e7d37662f4f23247bcb8f7dd5f3e9cb8bda40fa97568832af0adc68f71422e412254a6bfc8943bb465b01fcc8de0b957677c78bc1f7566953e9d2446239f602c682a521c14f741fea98c7e27aabec339b6f5b94c78287a894afdae971f8da7c7e4a4c92c8da47be82dc2532ec2da9bacedd2be6db2b2fb34dccdcb34116507376578cbca105e5e443bec0f2ef23be34cdf862edab34f0ff21335e3acd92f59688b419f824ea61eea82bc80e3463452192377131ba51fb0795e089fc077d0eca8012e58b0637ad7022206887fe9ec00ee5df7ad2e26fe819ee35c7a179c579098aa3df645d9064cd557da90bdd21f871ceb048ca56df9653a10ed60f5e9f0ed7f8d89bcf5c22d1143cf44718ff2dfd8e10cef8aabb67d2305f18177c1426bd4cd03f2625e459ce905067826a214e08e56d8f9455593e6b324e72dedcc429d3befe2ae0599e360df95e80d453a3a849e48389fa745635bede30e7932de6a3816e31a2217f98d5e40238963d0a36c159fd4ec32d8a5cf59d433def3378634af6887fdb3f3edb96fc8840fe1b538c329674ae810e8c8b2b46db208716d38e9d1aeab097068ad83add7dd2647839b3a7388b0615bde26f8692e9c07d8adecc2a875203c3d3a9c6cb1d7d06307e9e1d9c3bc536dd8eb271e9a2159c904e61e8c9357fe759f36366aef5a3d14cee82913cd2708aa6069369ced763c8e830d70924e82e9015c2998e86efc1dce6ac2ebcb49455542a6d7dab265ad6d7381ffeee1aa40f8fac0659b6fb56bb03cd8cafaacd48d13672f7d524eb9684cfed4dbb7476e99149c28ec08f33ba6aff839aa178f86b8eeaf1739c829177ba78547ad394136aa3fad451a11e9642506568b39668b2436610e06ea45fa11d04d3759b033b5382645f15b3c39270b81b80487643913a24f2f1c1a1ed57c85ccddc8cd6d59b62fa67cc80572968c8fd01894f0153634c88792a7c4a407a4a4ce46cec5fe5d2569f95a27de242444ea0c715b357518caea23e767e8545983f0d3a4df66111b4aa1d399ccafd796d7a80e592d5a51d2b3f60b5b04f8d9c009ca56cbd4dd84127a29b72adb7645fb7279c9818b2b43963bd605f45b6575a5e2e369e0b401f5ec10ec703f1179b0ab9d4a89d6f096573952e513827364a84d38922734137e969d8167d6959b70f42f2bda37e4c989abaa8024c1a84ed6beb74780927f78b32ea736b9b2b4a795c355c0319811729d9cc399d23519730338d62e16e5035fc52a817090703fe776d65ef9fef5ba5f4ffec3cc8e9eb2e312c50a479bdd4e6ab0a56c18c2df69ed408417bee28bb41dd13f8366ff6eda4b34090fc9bc045271"), + tc("80129f0baaa2f46e07885236c997f27fe59a8593947f697e8bd305f92bdbb475", "d1fa123c468d0052c58c93bf306c7c1dc90968eccac6b0c2f4e3ecc3f908f2be6a54ab69aec5c496d291d0263243bf1827773dbffc02a9d5fbf7aff63da5355d5869f492b0cae82b229a36331dfa642557c6f6627ffb0995e593df8e0fbd3abe66126b7e5da1f1891bd28873fd9c96c0076215b60548075d16cda92a2b99eae5776f6d1e7ae2d8ca30546bd33271d50a6fd34b23809534ae6e4a875d6981492f23752a68d1e870d174a021ff8c550e01993e3dc3358274e4da2b45c000c43fac307d82ee2b5d42a6d9849c11a8a5676605b7b3c45220bfa5bfc7118e2487da143df0797870303c185c9233af63f447ce53d8199b986aba367735fd745f84f8d94ba0c1a08db280d860bf656ee871ad1094fcb41c7bfbf0fbe0959a31b3ed573498ad35026694abfb90684b35be284b18d4b663cb2ea5de1a88e2947be8012858c57ad8601cfd1744573f79906849b4a5552aa87d5e64c21a054a7fd31b7efe2a5e401553463fddb881355fd94c9ef7f6f9e3da4cd845ee16db2d1a7d4e31c9e8b42c0ee5d30296a606b00aad9ea09509208188d4a15b328ef7564c2e36fe79c06e466af598eb64280f0019a3ac81b18fa254b0b79d7b80ecdc9ada3dd3d13f1899cd3eeb6cebdacc7f69e6ed02b2550eb3c76e1774e189661bf791ab8842a92d86af9489771d1d6fe8302be395d6750ebbfb1ed7237c2c97ab655c7a6c7157a5ea5c51036acd9f1c031277a90450bdbab5d4a9b36da52a6ef7865bd0b5571f228d5d61d15a01c36c3a7d2f133f04d0a17255271a1fa0bc7cd60a85bff17efe1d49a24d7e87807f67a01358b7ced01a112d8b71a6dd70518bf6aa8b65a5d510e6f5084f5ecaef108f6e6564032ad9259e8406076b9348ce337005fa4b205f47736a79693c7e963635ea634ca67b9749a098b299879973ca9cb355b1080de5a9054c7f39c158aae4e25bf72c0547fda9f66a234ac3e2df8dd22752ae66d26fdb32515b9e9239d1b583a314ae231dbdce9013da4f98213cd6ab9f42582f62d639e20cd183560f341007c600b2e01233098fc61a50c27d949822c6f99fda51ad788bae101cc379b48ac8cfac3b5df81c9f7b4e1e0353f7f10ab340157fc7a8f92ffc43d31f24b498b381c7b20bf0a31b74c45627b9e9858d8479af5977782a5fa88d258ebce97e6d0fa0971dbfcab6afbb3011f9cc24d8c4e3db57698e299f6793741810e2a94295d0d91f3f769f184da2fffca771a148728a2f95237b77864dea20d80e09e7a0a36637062800065acf15572355bea8dfe27509f4a757079bea6d6df3429353aaeb7777a143e3ed1a20de487162f6666ba5d27eb9d350d6a5c724bf19a1d058acdef126f6008ebcd192516fb0d4797dd5fd3216a9cdcd2ee24a2c40bb2cbff75cc59db2e3cdd8df4e0b36a670952b07871a0299eeda9377e8296ad3788c78728de71d31241f20c2b2046928ca2751bebd10bc0c455f49f30d7ad979b0d86b66a55429853aab62575f9219483603924b4d39935489e8b2ad6e105b8d11e74785fea9d1a9377d189ba290cd414f57ed67137523a355f90c9ff5ff2cb760d4a31faa2f1c478f9547fe4c4b0b1cbbd71b690fb3eeaef87a53eaa1eb6526b7488963fb64d17b37bb387105e39969dbabcd79ec28dffb5a61a1101b34c14202bd0ec1cf800e21d695ababa494431ddaa20d88ed7501ad360f9b6ed41b4ccdd0fb5778cf53d7c18c36c4ba783e68b9b0d26c7bbcb3b2a015bb1e2fb51fa9c323c08a7251e48f140a4e519b1c389e0fe4a87b9d65b8d91a2081405f05d01f71024a56ba89550a418bfac2863928bc870413cede1411f0d7c3e77c760b76e0d94f7d9adedabe9e4a632b6a1fb5ccbf8eb2cfe772fc31dc0d1ba63adef21caebf846418c6adf2e474edb4a0207271c326f14d20c0b95e1af7d86331f0d4f792d7545874f550892b3e081fcfac6074e172f8826e04f289201420f064ff3c53ac7b9a15a291e878b590b3f47f53e6ccbba897653d6ff4c6cbfac90cb8666c86dfca4d554d8b87ad1ed5bd39411ea35a417a51f9119b630904be6575aa257be54716a788922972cfdc5a5bcb4b6054532aab913e7d8b0f5a450180a3e7deef61bb0815120d7869086291c3f3dfb71014bc66e3d5c8854b52e800c8f64765c4089e482012550e2377104f93ae57f047a35c0db243bdc7e1846ba0d0eb42aaf97742984d2637c2f2befa40213900c3af5a758f85fd4342f9fc0b3821ff97b5302332db2ddeb688c8572691aac5afd777c5ac88200993d3ef100a5f95b51aa92881acaa5faf68e6134221391c04a88c7c07cb39d07d5a245aee0d63395018c0c8e1d45bb640220c2f993831dce2597133c87014d12f5ced521aa45273f7434da8acca2fe35a26feedcf701af1328e4c2358bf74513cdfbb6a0864ea9670c281c5e7c51e26f3de15649985de90abbcfc2d85c26886f0a0c33c8fd74e33122c42012f1c5ecdd01ff96fd75c6b067c60f2501088e2c8c2530a0191c17a113b38240345414569b3b80f8db755dc3362c52dd1d36c06d02442c3afd24058343f9cfc6931cca0442d3ca2ccc84c6267c42cc85f4d6bb78f34df54d4e20205d7c379d924c649ac7ea8fc127b7ee288064b2b69fa95c93f0e0df1e863c590e2069a0998a3c2b99c95b690b5f7014f401befaa835cfb414cca8a5b7cd027e52ae3acd21b618b6c9d1e240e5da3a5f9d2ef4f9aae6b3224070fc41c130251d93e8e4a9452a3a2ace2e919b6cbe248a3eab6479ffe4acc55eaf54dada9a84130cd6bbb0be41637558a170982eae9a88853fcbfa6cb076841a8e36e8824d21ad2e3ad1d7f829a5b13296b15d0053bdd521d7d15e76b356d62f02ee20eb78d04838af7e4b084b0d2575ede7de2130c5472d8eb1839c19fde06d7e77bacfae1058a0a19eec6485485d401a70a8920290aee1d0f36c67324a6d87d3b9628ff69479aea4740ab9f2563895d418f9a5f04c67cb4529115146629cadf3c1949b3aff50e8976003688a376c5e889aa0b764027335fd8174f648c3e072cf4618ff2c2189521056c03bc2e530fb89b391cc5627964a6555e8ccf102d628cf1b3ef1c32499013cc37d81a0e8e3e033facf41c1295f3f8abe317d1cf1d2b1c5fb891e197cca3422522a4b68c6481d4c74556e05e06d0e45be294e910632b747f3fb34fe5d3ac012690d9e3b7a4338fd58818e622ae7f71dd90a34081496215d8f224252c50cb1bbfaa08810fb6b4d0da50d007ad6d5309e5c0e27fd91b8b343746b6cbed1083f91523a7b51b9046737f7114f507bd7668835ce2eae7398617e3f298f5b8b38966a9bdb3beb342eefbe61368dd8302747c682b83c3ad4e184a5f8da2ccc4f7ee99a33156274d97134126b0b89627d811bf07f7683e840c51f15b711d1db0c3c41be989e0f156a5b948dbffc19b4047a7d93c9d9a22dcdf0c14897b5cf29d03cd426c62e6014ee396c11483eee85a2d3212387b5c48df8252b21db40aec9f8a2a1ad8a55baf263812094e9c81d5c18fe1fb56593e51888c8a0272ea2f8189782f462783bc996541b86eee4f7be53d535b4e85dd786bd982c5d494ea017cbf354617d219a495e6448b00992df83c94fabb3567e75220705bc449c2367c118c76ef7482c9ab77a1e0afcd3dae4dbf0b211ad79ef141217db599f4f74557b9333629a11ab482ec3d7050d95ae052f3e1f814f630e9809b87046d1cc408076b5cdddb90a389f8eb3fdf64fc7dd71e6c0d5d6c2c2c507622a387c5a3ae60a6d723dfcccd1b6a08b40cae54d6fa519b5897497e0b972bfa1966ce2a3db3fefc41ab3393171ba46bac2fa93ae3613ce88273ab92921f4cf8a5840cce3f3b3c41622eda57e33b436ff8108268e358cfe0aa72fd0d2ad5576712195f6336293bb3067ed389d69b8618dfeaa8cb1d12b43b370b17d29ec2035ee9348d69f0e64dffd8096168f6cf020661e72bf64fa0d03e0602750a7da0daf4a4ed909bcf170e76eab0889ff15d84e9f8233b3c57b34084bfbe7732500a4034bff241ae4b89f9bcb2be60715c14d3b2f5f8e3c9b9c7f7eb11e6b0ebe70c9b63571abdd5a10a86c8f9c2eb64f7075ea1f0bc02573bf83c3af68ca831e16f5f40e558ae0557de3b046d5418a10b9e230ccd22d4cadd50cc912df9a42af82ec97c91aacf027240f95b19fae3ddd36eaeb11fa79c6c17bc6a43796151700f587b64cf3a2ed521b22e36c8259dad86de55f46eb665ac0ce9c59d91d5310369f88754ccb1484f4999b9dbe31a9085c5d8ac64b624904a63df3d1dab994e0a845365cc6daaf47633f32a929ac74b9e8a76f4091da80e49f3aaebba70544d1705b60e408055f07e3cc71f0d2cea3b4b16d5995a6a9f476466765bedb2bd44f97d45858d1403c6348884ac7163aeb21364534d8c19b0796ccb507d9441f6266725fdb340ac1159d7c63ed0c5ab42daa9918c0a3dab2fa70f84d499acdbac62f74201302d6e2b854e37b1c790196e878adc03c001f006d2196c285fe5001fe395795d843b2aee6f7d6c22acc3a42307f5f3ffa861e996f169318248c3c41bf9d1a51bdbcf5a33a79815063e07cdf5a0c4676ec96ab576d27e117b3dff7a2f1dd219a1f1b1637a64f38b122d60b9501bbe3639cb76d8e746165ebbebb90692461b61086eae6f6f5aa5e0053d84b6fca8147d976de801b11b306c1a506110c6a8b0ff62236889718b18b5507cc2ed073f1ac48ad1db1aa771295634272f338323ae5dc1fb19b937d891efe2e960b17d343e38f2f208d4b8ce21f25fe87b5f12038ec57e29c7407914473e96c3654162e5dfab9939a011baacce48010898f1157740780946b67e5a44d49240b1a694f679cd689e720b0381adc691c8b24de8ea99a4c739fd5a82c06ce12c9df334e91c63b48cb833cd0e57d2435fbe1a6083278aaf574e2c0b706a27625fb4934e139163006ff91319773e996e08e91e0bbf55779971d458241cc27b4adb43f3c52533f4a61365956f496332af91a4b50c21365c5da82feafc6ed819545c23e3153020b86471563af40143a9213658eab2321f5667a85de88d6ad77d3acc205d130db4d88901e92f6cc8386a60cdbe8c604d2c577939b44f6b6e8db738033084f84990932a21f8ff91a09a28ba1173b2ddb32422c999eb42afea02172a1451bd9070e281885024bbbe7008e340881bb2cd61eb36b6dd4aa3883a79b8399acb8656d0dfd7520063ff1ff95a62376c1c99adf2dcdf0e62e06fdd1c23e8b4b25daac31791ec772dc96ec1e8ccc4fb5455357dd6c32682b27fc929e5f28caf9fd9ebfed73a13b0d74d9d47480dbc7d9550078b25492c9e2d3e3f5a86f58f0ad35744edf214e3f810075e77be797de0862d4b3e771406f6007f7c29426c16fb3cc5483498646fdde644c5542b28371019efefe50f29b2479cc68e7c40d681d8e496356d71be30798897ae496416d7a3ab9751cfebb143e00c036c542f79f69f051887e42984954ea5844dadf56b9bbd19e8d20cb9a4ea"), + tc("104ac12a602945e1c1a4f6611ed275d87dced67c4047883438ff52a17f63bbbb", "03978fa9dbf1ef4beacb8f58d4c82e29177093520cdd8f3af019fd34ee23b58966bd793d8783ae53b4d00238b833eb41d72a616903a1c4d9720b066745ce4cb320967f9beed566903e1db3c3124b64072ce048d2fc4c1238f909e152f46178ad0d597299323f58436ba3e15627bdf962343f862c766379260994b71b4a5a8f54b90b0c5a6b6b6b5c3c54bb7e4112eb7c86c4a66b73c3a010a57232fdc79af02beafdcb9799efd434a9883cdb7342bc29bfb176d2c667f6556ab415f54821cac1d756a1c3300d8e663b2418a6443ed47965b05e2f2ddf339daef0378315aa0c55afd9c416badfe090e516a1326119a97bb8421a67aad2051caace47122a86dc092383eb35bae836676a238df813e6013af9420371e7ca95c9da9e77202a74b0240bd97412b7b7bd1314f2eae0f1be897d7ddae8cb96cd77887589f66a9068734f9c0ff7778ab45c1a59ec7de35e6ab5fd6fa0fe055474cdf0cd1f27cca435a66291df8c1db231e1ad3b38ecd9271259c770c69ed3622a869089cb917692f5fe05beda10d6765b15fdf9ab0a1f5d209cb5766842a06f6d8a298ad01b735fa4fd6300e46b0f54c9bf47089274dcb5dcd0d14cad04ece24a97196ae51176d4e1024487c075c8853c9faf36291b9ab7b8853e366c9079544c528e103ad509c2a486050153f606273b7252691e83be3ef689d37e7048396c175c7e83a5a2d45bb48fd867b01695636e3813914a7715ada04cad838dd98d9448935c004c8a905cab06b4d6b3162354531e8aa74bc58f657070ed5f10e8559a5b52c3ee34441a0003d849afe0e6d6399d79a6b6b06358119bb8f658685eeae9a392a6e8d558f342eaf86b1c833218e0de3d4e69ad825c1dbb966e9e13812b6416d9ccf03650f1fa85982eb37cb926e472634234f94be6b227e03ab27a60b89c760f8a9b8c9ae8dab4317627644aa2b1ca9966c446dd597da367a63a30b1e3d44d1079598f235574202ca6f64ead0db6e0d1402097866b147430beb0c6ec3ebdd02b42610410cecc3ceda09c9cccfad4ba95c32f266b8486843ff7c854bb8d0e44e177ca294dea15f6a865266343ca1144a7d30bbf48b0b4b4fb48ee2bfa531e2379f97e5d785f7fdc24270e981f8de2dd045b36d6b2ce12ab7909c6cf2856550d600ca99422905c36179c61876e5c763324522527a2bae8b6acf35422583c881be74d943dfacc1c25df0b16de5ef8fd434fc3396fdf0dcf272aba5516a8221c29f01d7dc0ce3858f1c556dbaa8e5016fcf8a4e2be7b7deb5d388d617397d67bb798c75d7fd699a7ccf1e40192d636c1ab19cfd6297466b7067d2c434dc1fca9215113a2c8caacef0e294282347c650e41e376ccfc3d883ebe1a6096fcd274fe72d408aaef73cddfe8573586a676a942a80ddfd2abd860b1d6e6e77e65646676940750c091c0aa3710e3e3ec5104cce4bf7c65cb32cb168e0af7f63852bf6e40d67619c0a5769acc963beda482a060b9d6e1f51b7f244b4e0032e1a6093a89b4ba1ab9fc076f25d1e55f6493f3dacb5b9a6e61bfd62967ed304518393d3d3dc5075469952368257851fc78c85a2039ab204ffcfec5f140b4ac999021e53c49c0b5b7b9f95907cb6bbeef40088ac80f4a2caaab8105ca5a04123ab4d83a1fa1e7738ad2590e0838556322020bfd5d603529435fbdbb951b76aae8a9d63e68ba0779f197d9e50fa31d112a5ce4afde8f1fec94412df44d78c9885ff678140fd636ad28d9028a6e193b5db85669d85d13fa7ff5b9294a0423b3dbce88454ba6c6386149b471e5e20e5c39f5fe85869d0b23521dff667e5744c2e0c273a9db06405e95346a94c3a94059c9c7a23bea476fc1afbf91c00718b844ba4f3af815896ecdcb58124d1d0218f292b65d152407975423332a8fa7676add341d4bb8f81086c417aa489c4125727f42cf52df3164ea72886cb8cabb7872e3898cc557efa6be5f7431e99ccdcc2e087494da483751f961ed7b60f5e7f93e10287d686ddec675071f5dc0badd6f4fff07cb27d251fa41582a4f708ed9bd77662096e0f2be33c4375bb51a051709c4f41026ec22d9759a829d654f166da6bc158d076e753f6c004f3f2ea5562cb65abc5d532bd3d782f9ce9c6006a0ebb8f09eeaf4fae40a777081bca82110ca6d3fe25b56589bbecb52da668b22f5fa38e1684f1868caf018e49dba28c45db86fd70921aafa4e56b589a01afebddab78436c050a4dbf26aa82b7618ae1b2eac7fd10d2d3c974531760ac0ed2ab4f1d42d92b0e445f32a6fa697ecba299fd061a29a848ec636e0b7f00e6310208f9d21d229cca2505528ad91dd22e4eef4e723314cac648c26cc85a9f68008763838025be8aa598f1e43331c3496095bc0c5e0399ee1cc56aaf88aef50716b37eb156fcbbe1b10c21ba301158e422d63116d7db8c6935701fa7b3c2ace9968a81f9063eacce5794a255dd50a412d68a4d1dd12189da612c293aa9a4756adb25b585452e6f38479d63bffabe4e87ec43956684693025ff59087423c09e8df4bc204f89aa9c0769b32a7004dbe9f4521b993ca92945731cac800eae738fb17432bee22a931952d7a9342e049e5c42207eba15927bf1630e03a02aef6dfe2f40d5a45f771000213f64cc07dca61c5b2a68dfb38e42bc08bf3b64c146ed3c291e5b94f09ede90d6c9c6ffe752aad17f9777aa86bf1a6b5c4876cea631e13443c00700f93b4f7972aa7fa21173e7170de5f3e3b7f6f755baeeec9d880155a640be61267ceb05cea102c944aa45c6d4023ea258737dd752a44b27e2b5c16906156a774dd40d47d13e221c27effb4cce100b3eb5429ff115b27006e338f5fdf663bdc90694d3253c11a24bce40503bb04917ea4fe22ba5e8d87837c344c2fb17eac5487aa7372ec202a6bc81d80dec352cd090c040f1ad33859c02b7e21381bea28ff0e971d52d9006424eedd72a92951da1fe3c48b45d51be8430a99c9ca159093dda75ce2fa8acd7e06a31a1cd3a60fe33a241b9a649ff553333b9c9cd70380b494a731478698d833b89deb3ad79c5fa80ed8bb03ad1d42f2c8d2fe4c457a019be95f267080b32038e55a57e48d58044acc691dab6d7e17bc629e78ef7879be8d398c77c115f4547f32b1cfa181aff8f3dfc66c6e9a754b64ba932e700127476c590401454496f62742b85c17687140e13222512c369fa0825c07d26e5f01a774ff7a755d4cf17fd7f21ded83a9fee43ecb42d4498ab1c3a937aae69219fcc7c20780cc5ae86f969faa067bca9c6c3c7c866da50cd1ceb255d104a314d9506957782fcdf1dbe03945c36d38a43202d7b34441b82e61e72ad401355fae7557efee0bb6a90760ee54cc50e39a844e8f037fc9cac0b055f71dcea1aafba7f0e770e61ec8370159f8340c6fcb1f37270e4a71cf1110b7da399fef7e4604fe7bc41c71f366a451b4cb53cf4569c8c9a8a19897a40be96a92ee6bc888e8585832dc4feab31a40743237bfb0a346ea90acd848cc648b9f844c606c018816ca0dcc189036a2cfff0c41891bd4c32606d87d67a6ea61754a150bba6fff9a0e57414a0b91cfd087955aae7bc4fda95a4499fede1b4d844ae057045adb7c86b72de4b25fac0995effb5cfab372ad895796f9873f5b0b954583ed815b4523b3b5a140a2ddb7a5aa2b61ad8d06bb70efad3ea237f6606a5dbcb88f46dd75ac8432039090ae2458672db7800a7514a226516f41ccfd08a1f0720a77e5fc016b1b927b828784e421c0c9e04c45a79c2e8db16e7a4e092d73c5124d2bd1463813c79581ab468e2d37df3925189833f8b372838a9f89aec3587e2b19044c095107acd435b9340fa49adf11f105d2c87b9f5ab62e382eb569d79cd8678a5b1a1f7d00f38031694ee6aaed223ba123e8822be1181a7bbce5dc90f8df9e3fbaf96e4d82b388889f2bcacdd81abd5b777d78cab2eaca14ca97281603e5fd0c6708cc9e223d045ee2d1994b5dee248390fa01cb4047473312f14293bd9f574535015a5fd0d16face6b0f88ffc949ab458a2c40fc9806401bc5e917eafc56a8db323c1fbe6a57ceef5e638288c0917f0be1b8d718bc0e7b2b96e461edec2d0c53b481845bd906a972336db93db6499d27b9b2a5cadcbc94dc093b8b11c9d8bf06330d15a223236d6e167cea457c46b02bef94a936e0bb98e14c8944c053dfcd7a243e748709a1c625a38eb156d943f12917aa077dbfa57b24e26c284509d534c1451f425d5d25456787f97401213374ef77d6c86be8e0d311543b15ce7e42a152ffd10303b50046be07464c9b99b51018a42062054dd7654193e81a05909ca7bb28c617f064f9936c0d3590882f0c26ba89fe6d2b2f50b3c67da8cfcdcdea6e600cdb6b09fb3c170979bc9b1abff44eb22e66e74fce4ffb6e5b373f2f10b0ca5ee4c621f21f7a6f863fa739172c135566b55916f0bcc6ee4cf0d35b160ac8a1556e4327199dbf53682471baef09fab7abedf518427baff19f29429c2dfd9587843d5ced12272a80b17a5fbdb1ef77a510cffe70b7ab6f592763418af4cb555c080d9196bd8951a2047046d8e1ed60a67dc63e01294e734cd77f430ae771e72a29a9a15f0dc10a40b18729173a54b62ccf0cbb5b68604431661b613deebc200d0f19f142b8550ecf256dcb2233069bca839365eb8ba87204f83a8fca237b11f1ada6c44eaf2aaebb1b8b5faeebd2efafc0764175aaf9a9a9620f533c26cc237b208ad6789ca6f60b39fd5a0e07ad582f70111c25616d0a3466769b3e945fa23dcfc7255c03aad09977ebcb046707b6aae59ea803cb56d223816f006b3ef06fb67787bb09f41a6b41d6fad04ade014434e3cde87f5cfe8f14d9822d990745b26a6c0bd49a70d340c6288c133ee43a1b22bb1ffc3948f1fb03047cf6ea604067d33d00f3a1c88ca529b7b515f3fecaa3bf1cf5686e50e55a2c3e4176e7b0280132a8f21698137745a3cf2b5d6d32ec91e340cdb6328abecc4364f3285d1fb9dd502c1d3a84f47ee09440899094ae539710cef433f066d3055a466fc9bf5803bf92bc65cb5e0b1abb4985ef85b00a3afab4f8f59352ecd7e8550bf46b4d9aaabe858766751ef9a99889083a2ad6fc178d6b142e1d9e002e07430f27cadf6481683264b1f6629f9e186a16c48286257d3b35427d765a12e918ced157cb1e5745713b0eccbe529c7b304ce7e67ceb5431b832019995f89dffdc96cf6550fe056aad875c8d37099edf82d1df1026d605cd939985c372f4e95b6aee386779527f79afc24e18750a4ef0a93e1e315c09f785c3100fb43e44d6248bb7bcbcd29a7b46ba9beca5947eca2f5d7fd12f588aa30e2424fdf0f202000c678c9ae598345d6aace4e29f85731b775cb49e7517e38104b7b287dc3a490c2e30d3e35aeb1e6d35d2abc8d450d07463e3e46b908126dc831f624ea010e0e2ff87d7f4793c8f075215c29d45396f4a65d490dc1c2902babef3ab1d2ff8c9a24d9c5956c9a925c7b88703c7bb8e2c6f3a1bd6fc5ea0acb4ec669c0fc03e974389187d97f49d2a6643ddbe1b1aa9cd2335a90741ea0bd044da6fd28e78d35992b5b1a851f2cf5d9353a052cbf1e57b9782ab3620053c89d"), + tc("28390dd2f3c4e9409e5d96e0d96622b0034b094a40cf537b0a9d5cbb8afc226a", "09a96a0112b823c2212aba52eea13c48cae3f5e08883453e2085a803ea852a9d6ae075392bbd8fcec567a1524aa65c70c3ee1722aa079afd41696cc5391f529299c71e99b0e08e411d3e1e05c43e979ce10ac9c2ea80cbf4b58371273c3d3a8df48e2de7f50560ffed25f6e65e9d96cbb379573bdbdd2859d13f866ebf3af488c6d2e36505a70d81afd07e321157795cdf40c1885dbaebf3d4e425eb4ce0cfd6ca5019c8dfd54bc2a30095b312af4548b9acebb6a207ad3bca29f4da443afc7fa789aadbd65c9d45af71594b790fdf84d9235933ee96ffd83d1a1dac8c1f3c2bafe668b8ea5c9649d1d8daf2806f0519448a97af1104dba3fb05cc46d33fa0112dd0922cf1ac84d4a343f1ae597930042aff6daa75011be4b18acb257d889a80f0c3d4094ef7295ed4e8c2f1e2b8f56edbc9b831333677a4e6cf6110218b23523eeee1e024b6398d1cc572df7aa103cf7f50fa488e19936d20393a55843c042b275b78caa9eefc77c44437c375d4d1100bc486fcb0d250526086ebd4d2d38fbc8e3e55f9262b4be5e6ce6aa3164dc04d7fb12be70af6861ae0c21a04468339d335be15f6a77a8bb84ca4bbe94b0782241500b595fd671b30dc6c26317db3af8db1214b144fc42fe936771469d3838e41c16eaca8b44a5915d1cb4550a451be5f0673ffe846f6a22bbd194c8870193e1b68459b0593930e178b765d5c19b0f9d65b1e25d60b727e50ee4c39d1b898cea9c8e95e40110e40ddf882a7c89303bf1b6af90506e4709a7916f207beecb850c9e227c9557536138594513043febe778c471dff08ed2ed7f8a54952f6f52422aee3240eef81d34cbbe47c0cc59a239d7ad75852cf62473fdfa9c723fec82c384d06f2320c8c3e5a1483d9b573aee03fe0f85087087b896a569d9a07b02a387ee09ed6b7c8d6c0eca76ea61301791c9f835e5cadd14862393a698db23577a68effdb68076fd8a0e7bbe9d93098e73f5a174ce406441df48b969b4d2502ebbbd9937856777d6f13852d9ad3a39702bd556009082dc207c442d327eb33a24f18dc56428b181c7b4405d0b2720b8c4c62417ab9f9da5566bd85fb7fc567f542d05111842de09114d27a382f009bf737ea2d8d8e4f0900a0deba080ce6b0fd7f0ecb3f81791717eb121176412e7ca618735bb065a659b689a18069b2c43b00ebde9de272106e180eea446242f7889e50920ee5da2b7026965b6dfcf4239ed8ec7f7363903f10ef6cd80d76cb42298e913c829b66ce1a5b18047ef06fad79de360908065e3a0f61aae3347a1d7601188dcfd62c56d67573ee4550d6269ff7181cadcf84059749c8a4ac1a46746ffcab7cbfa1840e5990483795452ef93075c92a4d115eca32ebe5ef0211764ac8f6d61ea5a75530508a8724cd118462ca6ce37e85e337e312c60e43342497a9c50c05b38a6d5c253e1c001538b43d1312a369d8301544cc24b3bf3089c5d119afe9979430223191adde6b68135b5d8ad5ce4a808580bb1c34f650eb63eef3d592bbc45bb7f2de9acf2d4bc2820ec1aa765f35fcb3f71d862aaa1156d0838ef06241914aa4b8632733b823aca5801bd281d127238676380dd88e9be0d6ddd0ac7e87cdc175a24343b752a46afeecac7561766bc33de4363ddf07f43e9bc4a1ec5f0fe36f40c3001f1647058131629e2ccaf056b381fc3b576d8dd072b8b2ab0d251b9060a9f76b3d99f2d0ebf65781b9511de31027d8286299de9fbc9c1c28be2e4df9ce8240852fe042d572236ac54b42b690d25adf27c26a96d0fd048f1d11299636b55631d378826370af97d76e402cf49ca5687c17e2f8bbea0ed83535535566d65f0c6f7b2d887dcd391df0e9ceb27bc0a8a33039cdca515c886d890d69a389af7bcb9c9e58de0636441397a53af09a447bffd49bef7287aad01a1827fcb8641f9bcb7642952d7e19448c4e88dc5abb28e24bfeac16bbcb9f8e9962f532b428ccf86ef635697e179ea3283879e9375ffdd4dd9986cf862170d3d8574340420b43512d5a368cf338b732ef447a669c511466df0ddd9e5e26a19a2adfb17cfc593570f6c6775d70319e4dcdc4beaf70ac768e7837fef94c785eb54b1a6c164e09fe88791b3db6d1bcfa4ec170a3695fbc21c9ad1942d3a483cbd433d3fcc899f12bb9cf788666c0658cfdea7f29d5cafbf3b16b3ed95e52ae21e599e6354d65305d073de34b677cfd9f0ff0ccef956bf561043e0db96762c3b9365247c151cd0607551bf8d781e0ce84d23ef7b6a3b67b698cf3edc2db9d968174c0a26010fa7fccc5f87e8b25d12463392bbc404b22e046b53d183a6861f6677108484fe59e41077bcf6c3759663112ba951014f40952d29a159ba3f838a8ced3707e45e0837dc62d0576a6dd12f3c75cf902636df5fbbdcd9b19037e69091b26082b7de2edc1ec5bc218ea760c4b534aaf352252043dc5b4ada60a9dee3218f18de60bc654a90d82430567f0c591c7219042180fc8440b5e422a4bcaaf92553ba86b149c7d5e761390c52ee0139000595e1712938030f387bc886c7e66bb11f74a6ccb5ca93099de06a0665a1a99e146e480fd80d7ed360e95fb3b2b08bc2354dfd56bfd4031538f438ac102b73472dff3ddc93bbec4f9987dca8df79115892a004e86f4fe8e76bec145a19c013630de218b3ae3c325dd622dbf17897d68833b43e8708349680ad1836044f09d0a36a00abbfb9eb9b9fbc39eb71defed6b2b86f4b85091a2a1c6141a2d74a8b2f11e7cf4b5bd0a7605f43b752f1fe2b5529bc0b913b5981149f8e3e23808193741f152d77a1018498b0459820a8e6eb661b63325d41ea2e80d071417816551272edb519df379e5cb0730ed933597e0d62858275390331bb048d21f753fa50262fe9ed6ff00f54fc0752db016a3f5f8dc4114dfb760e8e9e17a3b876cb7fbc3f443843a9df4626740bd0c511bfffe4f7849c77ea64857c489c204da3fbf6c08acdd705d7524245bd23e3767c6587b0a70743aabda3434f0969d007bbfdd27968c4e7fbbab2621ec26fc28fa984a0e7513c548f5be67bae139098873932bd651fa85f8978f8a52ca1111c354aaf07728a1a4d8e8bc45cdfb174fd8cffdb8b23cee7cb7c7cf2ca590b93c4eeb403123b2e10293021a737b2146b21327c0f2ffb59176d1b26dd3a1b7dfe3467613207426b3253fb61b0e42969e5f6f01a102699083010d5f53f10c36f0c69ef73b350b144fd295063b19770552cb2b06f215cd5f308658dd34c12e66ebf3a48df9c81ea40a728a035e4d20ae4172a6556d3cd5da109e0d2c5c40f58f104be11509282b67826831a9560584466a93860731c3f88a293685f658c15d4e3a12b38ef63a4a175f74541f0aac499e3916a5c7fa3a2b3085cfeda45939c319b39f887705b9f089c90fa38d6269f04756152d6b58fc22ae8a16d1f093548019e9eafd24323aedc65fb80fb9a9950f96b9aa56886a9695bbafa19a9df70a97eb73729915af4433d38a2ecea40ece0da71c0eaebb61d5c906815135882932b2442901dd719bfb3386dcb45746fc7603e1b57c3032d54af98a528f3d2f8d801026caf94ffc0a95258f28af7bfa05a6cdc6dbc84dad9f7fa520a708ee18e6acb316235479c49ce5b8f0d896eee70293f822fe85e9d15b5f4511cb2e5c4926d9efc11ff931b465609532d3f78accb08b3c2597aad082852238f24de905d433484d041c9c90e1f9bdd14524ef593adac25b0eaa59f5d0223de157dd2aef4364227fcefc25dc1e98785c40b8f491dbe834713c9c9e8ccb72133351c8aac3625a3337d637c264d9ab02de30d78a7aa54891ea52c96e7f061afa558f5f661d8752f54dbee025a68b72e205ecb2d14ee84c7dffcaad8f2829f21b778d85ba3c590394ad4de39587d40eab0915a3c7e7bc89bfcc7566d1f44d250fc3dca327e7dfd2dc7e04c01c5773b7deee558106a3a4d7bda8347c8f5c11500551345e963a34fade1aada6f88b7f73c3206173c5b5a831cb308aa338dec57f0bb4d0d43b09880e2e150d438dba83ab9f49b4c3b812386dd2e11fe992b32ddeb5f0b3f2f100d989a6482bf705e190e8201b614afb5ba6736d0ec81dbb35778e65653f38f49694b85b0e78202c3f805fedbf6ec5795f93966db9656066944bc2e52c10a5dcff5ba68f008c40e6c801d16edd033512b2f2f5e51f3b49c415eeadae4f878fa44b3aa6965ba6b7a118297fe5993c9a3efc22dd57369ce8ff709d835c977874adfef7d8d252d1e8f6f5ad548d8d14d89c72f2294f954c6ce353d0c3f43a76bc5e3ba7c0707bf21e4a4d6ebc4b99d34506b4ab78e622bc32aaa7307ded2f55e575eaa6a601e627f09dfe02e1cf3d59d027e7ae1dbf4c1365231b724b03451e79a3512393c843ebe474d29e3ee1828b1010a746fba2e6483a5f29336a6cf0c95e9d3b929d1fac334d2f391beede2f0dadf399805b4c039e66fc2076734df2062a09aadc69df10afdef8ed1a15b5d81041c3b0a04e1a12958627b58056fe33397bcf5ebfff54d34fcbad32906a5685ee8a4dd34dbedf4dba832f24640f090ccc21701a4b35ed37dac3e6961ec89ea0815751d55871a13125d734ef178124ae1f593d89609d121878b3151ba96c8347b166558fbd27d4d201d76cdf4774244cea527896874ebdaf9af7293afb14a4e3719d50d9c9902b92741bcfe0572d37ea11da57298dc9f710f3d2294e48f68c47d972cd0261508482eff8b290f52737e641b61e9926eec020fdd74a1a0722ba534954d4cedb3ed44eb255069708bce9a22564f5b4990562278e2e89245c54b8278ec17c8b564157e87dd52cdc1a67d60dfdb6cc9ace96a5bcc77cd33a10c506660548a167193a6a13fa6fbf310d803bd732a51bf85c72aaf8688979591c5d829ef4a7eeec82017cad0880209c4a64a0ea4ba22f40d26cd559eceb9c879ae413d60240e26f88a7df0557db1e0bf938327dc4cc0664e0b40e9d0c67a431c1580635bde040ff2079fbd5240d2de2db112ae4f3411d0f5908dbdaed1515163b621a66cf0f3d80afa0e0b563f3ab81fd06117a7f21e7ae2faa461efce961fefdabcdcae27c20f0114308baae0228d8490a0a840e39bc9a501c0c591ca28fba025e145e4fff4b432a161bd821fd862855a955035c1adeade98cdedd8ec0bf0881ce6f3fb3b69501b164fae089ff007a923f42546019844c06387b847071716b30491a50a836df913dc2ba22d0db14d18f848126ac124e5e9a004b5473c606ae5402357a93fbc7146324e269b6248b43cfc2291031f7202153cbc4563fbb0e6d008d6b0dad9a02d03f33fbf8bc9207199bbc19eca800efaebb5b7e1f8929846aa2cfca4b13ee3652477fe3eeed93b63cf2508a1e540bd35250fa40f93a41ad70a4e5b547006968106531419e8dc60e423129fd627bcdf14bf0d294984be3037d4f5ce6eb8aac28744aa31325bb32cf85d95f04573040faf554d87ba7e1bfd9cb37d43e2df6a8ee09b8423f8faa8f0744b0f383fe9ab403d5b77f8b2da1e26e3bf20e5c195ef3415ff2da74003b9f82d74f5a4af4ec1f9e87e2ecf50b362990fff432fc7a986578b0d0166175f087dc8d724fe43ea6d7a541aa7e5445b0273b41ba7818a96c28265db7fc729ff72c8dd635269f0c35d43d8cd2a0d4acedf13c75130f17d65c57a9ed2d26d5a6abe06e40cc7b78d2a554c0a2"), + tc("be9759ac7cf852f887ed38c1c855de0bed1b468ed99320e0ae0e971caabe595f", "0e31c80a1c41df5f706db8da40dcc06e61b4d6969715ea02113e2a63663da6aa3df8ba755109a580446e7e94e9e33dc98ee6b119d3abe78b3b9f990349a63dd609008ff72e2cb6710b99522021aed51b7485b7b3b89c4c5f81e0ce61b5310789556d93646d531744e6b393aa38e1aca0d6565728560403423c5c5bf3b80b4e99bba5fa53765a8f0b67e99bbd0f1e31929731175b49e237eec448d08d282ca634d720ce74c55925b68f0bcf08b6493b180465ce03ab4db3c74bea45d245d21698e9f1cc8ed46ec4bf4ad4ffdaaab9ca2bd75af81780ae59610b652715a30f45812ad1075fb9051340167f69ccd01b05fdf1f4cfa36c053269b06e40d99c5cbc685dc18e266f60b23c19565776a5f76c5ea917aab08db863dc88c2342c7f7f0e00246b66eafe2d3de1384a108df5dd5aac8fce8bedbf495f065dbf4307c98b879a6b386f8a4261daeaa00a331fd2d827e895d2fdeb840126564ed561624abbef25b0427fafefd705f56e2f362ab7410cb777591bb0126cf70e390efbb4e7a13b0745d7edd864e6f90e8e38b61c706e144bd50fbc6b5a5abb810b5b82e2c0d13bc40efc83e9e790f990d34a07a5f312e253900b57b04eb634ddfeaa537aaf8d118f1f0db57d6e6206c0eaf94ce2d540f6ee2e2e204941848a537d868f9d95d681f0c5935881288e0d91c12de10c3629e43e9dc43620189f44985b59427ac131b35ab4574a3ce288806b9f4cd23ee2463f3ae947fe6eee2fd3ca9b7780e0939b615e8dcf94ff684fb41b76d25c6143c282ced5d9799ee19fce194b047e8072b8154e9c3cef1a34e7dfca73d4ea1636cd8120a8483285b21affd02ee06ec882f74bb8ed08d011a9a17043d12c4bfe83efc8208549e53874497741aff8e5b8fc594191e1b92e581b1b7361ee57bd39c5b2dfd49f15c758b57de8fd5f2d65f725804148a641a2e30a732b5932fe8ce43a212c6be5563fee8510e3b8a9fe21a0ffb64979180ec1d0842f612f1d58ea555f5b5c7dc82ebf2e4ae783f608a37dea30186f558ee743c3c2d2c90d5f51269200a28455d15fd4065166ae51d5a8268793011b7777bb8dfb523c278c9a7d1f690aaf05bdf90e076b3db87606e6575a8ec0a5c5bda9635fde179d40deb4466dcd2cf2f0ce93554ca6c453b302498fdc5dfc4fe1cc717529c332c7771540b29cb3a3babb522dcc7c0c108296893b4d90629492decd4af23ee8c84830fea1482c9b9cbf0af143af68354ec57cdd8b3e1a503968a9691233d76b3630b18e1c79263eb05f8105d1e1070701d35cc032dbfd5b2e877212e7a11940df6cdca84d5cf7f7436e655716a0090d2124cb0dc6f07bd051ee9356287a9196f43b17f73d3f056dcd38cf0983f5dae8db939803d6866ad4f2800cf1b316ca31041eabbd59d0df8380fce7f3b62461a4dd6e14f9e48dc130791c04ff7c6e3e8462f38594b62b11d6b865622170b954ff9909af29990ab4134d48d494bed3bf8f939ffba76e3a3a8a263f75f8a1f3b219422525d8ac91948872f221f8c07987a4ee8529f71a9f2b8ae7c7a054c73f130f97a2ea8ebeaeeac308ae9e984447573bcb295654261e09804c65d3accc932c32d265e8261250bc045fdb5896ab266cf3ba5b9254dd8e54112f2bfcc9f119ef4e2059a9ec0ff62b42c3dffba2868e4aa1c7021ac35c9aec2cb5d133e2db8e11ce6b2cce87754f4da3a40789f95ef560bc90578f50f8087f4e53444af8c453774d998e13bcbf0ce54cbcf56958254959685979e56f0cfbee657fae0bdb0480907627ac2231eedc0f78bac28b5abe1d20a0e428973d15e6f1296e3b0ab8761d77dc928a43aece9ca5c502ef7466874b681085ddb5e7377e16d11ea20fd931f9bc213ae0f544aaedea9e4fb7f5465b94c14979f3bb231676831ee73fb7a379b789365701d6e2e2a247c1c21ae5ebf6f3edd9c46898974b734306e70c9b5573b4d052e63f9c44396bc89c9566800a7e2a9a4c32902f799b4ff22cc9bc21f39b8a8af4e34007cae37ade40cb859decea4d274bd11dbb2f5ba304313c66a77f7a43b5aa7dfe18be40a9f499b95e4b825ea00bb83a0d4f11f2f62f5ea70fb817dc6c666b09bb6c919972b2a0c7bfb10c5b467492b8291a2b9820e2d8bc6e7250355e23f50662dafeece00cc8c0b74fe20278dddf870dddaf083480b1504e11b518d3f9e9933c3ff2f0ca3c423b8691683e3ea1902004aa51cd823709bb05141a8b8cfbcb4ef9f73aca2f09716558d879418dc7b0882faa381aacefaab611ecc336f36d6fd789eb734652a08bbfc61d19925067c6e5dcac4f05bfdab6fcf083599f2845d0429c05a616d1977004803bfff90874514a79c0f45e4cc5b3182be7c45a9e2f9a0dd976fe3c220542cf11cb527861cd7461e03f5335290e24c483b15dd2617b31371a6ff98cd62fd8e713f166c3954f0867dc8c3641c9cdb1a24f3a9ef270d56337537c98d3fe904cfc320d8d19a241e1540cf5cbfc5a5579c76026cdf961ae88d3224818e8eacd70ae4307b335bb759f8eb71d4a32d12f61184d91cb081737eb9ef5c36b8bd5a762b3781253403a9d621b9070eb22b77d41db6a8b23b6b6dffba92b4d54d4c625ff55ba5bb0d1bf499fee64813e762cbe9be8a66566a5f733b0eb407cb025a0d90e6e2e8a415d9ced3217a541c28deaff17ab8655db001f523ff303921337ed2ee0aeac6070f8adf9ffd61332ecbfd2df68ab366751c83885367eff7f589ecc4b7da65103f814ebc2b399930316241323a1f5a2bfc71fca019bf888f6d5632ac916d73a3e915d26dd11a2816a27e238568ed0bb4d2dbb13fe6b6db57b0ed9ed500559791b01755179ea68f57fc40bc357925d114401f328bf6afb7a3548f957ea12f76976671a8b5cc2a5b0d085eca87cdb911d1344aa7fe02c92f10e5c86b6bd4c1fc2c6f02cccec98aaeb752057cb4e64434d1e0986b93e3f981d1b5835d75691cf580654b5ab3468859b214b0f3bf6a75605901a6a1ec1ef0ee0a6ce0132d28cd79e6365ec6a992f8ad730a4040c8bc2a0feb8315a90639516b0d30db4a66f7e7248b04ffec3985b2a1995df3ed58c3531675c4ba885d5a488a773ddb3e94d2982fa22a7d424414305a336171992de01e31e7245cbd3b54d46209014921d5fbdd3077f3bfd86a3a99cf998d141c3a433e7d68aad547c0ec99ca30f2d15ff5f26cd00f0d45f2f0461ef700a30038877004a22d54ce3f198b44ad470f20adead37bd7f7482e727c1202145335488a739e688f7823254dd669df59bfd94ec7b08c526286aabded0ec28714e7c5c56379f5f0297e53512dd1e71de493e463d3f4d180097b8771b5e055fc7e436c57b53365423d359eea41496effc10f90b46a2204ef4cf3daebb60a3047866d736da1a3553bd32a78a370ad149319ea4cdfeb9921ff920b4d6a2752e9f6396da5f32ab8d79e717f04ff90d198d249976c081cd298159bace7d7200e34c82d0c76a61dc0898287ca0a77e9f4d649557e0cd4c5e138da53d30d47c5bb4cad52677321ca0dec309cb658f73d93d6c05726bba1204d78cbe97bb3fb76c7b9f640d6f96170a8739f77430111711e13d9e4e734863cb39a3e366c406de7bd0a716384e5a0eefee581cd16705d1745f13d56bb5053dd1866bf9cde1ad9c462a76d882422f5c9a6025131f8b5878adf97d32a0fd89321d62ea5f5b8ba3c3809057c4d553a391dee75b93c8529ac498a49b52f2e3ab6bf659cde1b210aebc3a85ec8709e9ab6c6134081ea0b020e0b0867c72a654583b954cbcf8ef2a73ce983c2f8f0b5db5776c16f56079239a6d7464696a182ac51855246f8ddf087fb403840fbfaf215b1c49d2e7b8cdc567431f1d00c37954dc7b40bee9a868f2eada29e6f0a73186c119278197af054c7e3f88e5f696b0d4c94c1196f058ffd72722f6495f16dc41e675833df9e4394afbcc7a32b11cc2388e4929786865be4178f540ca525d067f0a786be596f10670a25fc6f2f525cac40e7e341addcfcb91f069a5c2aecd7b86ed1fbebe8e3bdbc3fe27f9042cc53bec16198ad42230256c8386b46c4ad987eafcb6f07e347cc7c4ab4b04ef9d20cb581245c9c102ccba044a1884140666f38be3e64fac46350c4b3e983d9678845e9cfcc655a24bf89a8fd4600536d605082668612038b6255285be6e6266c90756a3cb116931f7d7412d1725143760feb86f4f8c4cdf1f4b06beed0f69e3f64c5dc7975face5f209b4b67cedb734c23073f9ae1d824ef849be836f02d48aa40348221fd09e111c076e617374cc430f3120383fceb38c42dd6f4b53e66ef6c80368e0fb96512fcb25ecbc4c2c3b99a07789e6920311daca599dd8c9f0ca413a4bf9b9b986d9202137c9dddd36f9b16b048268f8334af0cfb13dcc2589007e4ddc4b34a216f7358f316effff9dc33a8d5fd91a95bd433bad8bb31d4a14d6d45f6cb5e8a8ec31180a76aa03e83ab3b2cb9a7de0740ec7bbb55ddb5632d4be1182e2abe3f625923bf5b94488a00072414deb2fbfc79444465ef10aa7eaabcc52e13f68601250f313b40d3dde694fd935e92d04a940a8bb7d5670ed454b47725d933e64ea6485e4589f1d9e53de5eb1c9450049f35d14bcf8c38d20be815d5a61703f3636f6e293ea384b742a9088ae006d3acb0a296ead4680f3152deb56801530d6044aa040505ea4fb656ecae9e389a3e5040fb66490c79f26e980af8856bcd381a53e0736a9a4bc004cea03f127c42d3aa75a0b9d9084326f57caf346229f818a94fdf92cf32fc3df08080c57e2bd9342fa0e60004e9091d514c500d4c3100c73610ed989ccf5c2d40996f52e4fa469bbcef2ce1a7b2efb9d70ddb30b28728f7b599f503d921c59370fcae3e07bab4885e197261f9d0959ad49b418b2f445c02728db039042824245ccc77542e0c0b15c73c8e01767be6a2b0ab3709f35630225d9749e23110bc96f591b8d822491b90d7f893dc1127812181565954ee6b8122865bdb496ad533eb7481806d4bb061e18170a1da37cb435cf356dbfa570ad4eeb26342612bc73f6e0d39ca45e0eb44ceb07e49d29e1b726712f510b68ef2975cfcfeb6561640b0f0c2ef923fc6810fa5cc5e5f7e5c2992fc7e023ccabc78b8d9f3eeabffff3a3c3c38d321657e595bb4e6cf104f8b59dd4491a88c96b11aa5179c3e2306305f49f813a0598037cdfa95a2449290659c8c1f1671ca688a19ca498eda4f5771a972b234f02fa70cf198ea4222701d1f30131310f1ba4401eabc06190ac9dbc8eb1c7526d045fee0a9cbdcb24e04464e0033123d6f8f2f24f123c5e6dfd75d943639c47a6114f8ddb7af965b8dda8460fdbc4b258489490e66326697404592a4ad4124e3d879db864186279d307015d2c3adf96e2f57160f0e4a2037d6c760c5c4c852d4000ade51ebc6613a7b48c0aa400d0fced3c5e47074c3b917703d2fb1b04b058ecf33ff54efea37495e45c9bba1c1b26f160de0beef21d26f19131041163babfe16e4d38d83519bbc16622ef690e3fd0a61a968f06320a4454aa43883da580a396bc65e8af8727495cde042a0587085399fdbdf68f77ae2bec132d667904d1ffbff7a2b361e67dba5018d0d75c025d2fd77e4827448beba2e91c3c4f456565aabf8f9e1caab020a797d6b8c2c9a730fdd80f5d227ef9daf88bb0026a9e93c3d80a1636830ce832b09b942cc6d6278cec5e29429aa6d672f2ba0712d5a84580c154c0df08b6032e5902353ceb6793ad9c8ec953531b4f0602edbf3d069a070f4166f12a3ae3d3"), + tc("6f5732af54272a0ede6d7cd7f309968f402867008fbe2b1300ea2863aa850ff8", "61cc4d97f411d959562a0fc4109b442afff59bdb158415e8658f9545a4aa2ba8b64a65b0c71fdf338d2f48ee031d8b6fc2b049c2be4f25cfb5d959c77099689b4c607a2f81c5fafecb3290633c5e7766c2e766b4cbb1f97f8a4d61c6c55ca996c8ffef51ef28a90c06dc75e20aff339a28a44fc057fe51fede7260938e89ad74acfe13767bcdb8d25bb890c1401c5986c6a1ddd366eb5edca55ec9b65c1014dea47597dab811446ed3c7a3571bc77d1e3e97cac5608c43cd032c63455406ea98b311688f11a0792a0b8e65ff804526638a730ef25a6f4a7b1892de226b6fa40b65e4fef5387ac4eeda6eeccf155712e7536c1a4aaad5f78f8c8668420da7a48d486f4c4f006329e7bd8750165512005d88061ef2354a84749ef29fc0d5d4753006878686d09825825d25589aa3f386abd079396312e8cb26cc92420ccac17492895b2abd20170a1df011181f4051aeff2de784913cc40fa85e5f26c3fb36bc396f6b1ee7117e77d29a253e37f02769f7574d6ca23a56f92514aa615a424b484f90f382e1ad0d3ac3915e048d99d74779be1403e49c61f6760949e8945593f7a0de5d884bdc2e94f4bbefd51d83e331fdb7fe8e4c4b0429a24ec02ed9f3df54404f6d7c8ff52895e5853442c03dcdb9a980377e09c21ab8819b29ec96ad0d0fabdb77e42bccc7fc7a70b83ee053f556b905c63f8114c17a45ae8d19ceca6e5218336ec11613dc11532d91652e78f86726a80749d70cc54ca41c33d643f280f109f6269c3da377bfe87bfa60a1d167881af220fc8d2a6e290226fc2d0f66d2a1a7fabe47aa8007eec301e82ff50bf7148463645dd2471927f9546da78e15d102e701bf06c972eed2b489de28bc2148577fb489141de8cb51d24282e621267a4139d1f0bc35dc9d9c42bc33a04f81095ab3f176c4212e50b77ea2d132c9a5b90b1e32e631cf1e10c5583514325978581c1e55f752d76dbffc8576f56422c5da1ce20e77387f5173894467ab0588653bbb44bcbebe4853fdb9cd39157068c907b32cfc4aa5daf2d024e0c3b2a804fe5f2cde75d299b37c5fb4422d7e61e959abf90c9a3d8df629e76f4a44b76263e9eb7ad1649ea89f8838705e1a632fa0b91482e907f077b992c96b3064d6d7604d09e682a5886a2d9334138161df5141c3f06251bd470163875387afb38163394b335b79210bad92aca568bd1ae1e511c22803bba36079fef028353aa0f5734a1c7c6c4e6ed684742babad0e93edbcd0de50473a5f46cbd9c592fa92d42ebe010acea97bb36fbabe2f14bd02b1a07b35701d131623bdd526beddad0b4fc6fbbfd4cfca8e513767ed4d10ec64f920e9e6cd528676875a070ca6d7aac6807b53b7b13749cbe350d5fcfe1b34e4df2e89507019bd6fcb24afc3a1c9bd64ca4c7572fac9d87649510d5f91b373efa0b97ede7b8768d0186b968e5a7f6d181cb5f45be2ebb942e23d821bd7a8a1d7b0e2a735dee8944c378ff9a98e13ad7344198b576c6cc770c082d640936d3f07021293764662f77195cf956e91aa7d14ebdd06c41b57c9e881f2de467aef05fe9ea1809c450a45f3f0d67d7447f8af8f87dd52b30362f44afaff54a2a0331ad8c18c2f3594a1e187a1d94a4c1829d87ea67ece045772bbc82c231feaafa81e2f6e18d7f45020aed2b22fee1df13fc31847ebc9f8ca140e4944f8d111a4deb56c0888880eb95601fbace79918a6be2f0bd370215689988725e9afa988553867f898c939a8dc4a58faab9b105185904cd543382f7dde811ebd114ae109b97efb4a7bd4e582c3f60423b48c1e35b8c0f291048c29375f9cc2d4c01079a207bb7bcf875fc7535bf47db251ed8a60f03d84e49e32ec5e72f6df26f18ba040f508a02ece21597bd710c36052ebf7a0cfa8c1fda132ee3f9dff8814cfdfe0a514c91fc79546b82447926d422b8aab87b94cc29f165e1f970ff274fe2a6621eac5f12816129d27b8df06e32a41752a65de0b47283ef049422a9af58a9bfcb042ace1ce470124369a915e2bddcc292431e88982bc6f9c51ac4c8b3e2aa96bfd6b574ae9d702a2673081ebf54fb332f552ee17ba638112816286e94fe43c6695752e67f69ce6dc4893e1dbe8c5df8f292271f4f5c140f324afb49a1565717ff0e0681f01cb7cf96d2f8c50a2d76fa6b4509701211e07c7142d22a2fc40c2cb47e91a0dde3cd86fbd02ee53f8367d1f25e6bc0c04f19080ac19689e721327634289261827333594cf23a7cc0b0c95261257f2f2a4d9eaf72704890f29a5c6f12394c6a57688bbf61e187012cb7c92d720585976aee344bccba607e74f5032c14592c317d211c3848fd76cbfd84d7a621e3d89feca1ac8d1c0090855d530df05978ea50c36db86d311fde0f8c6598dffa94f094e4c034fcb13986eccf09dcca03f2ba06f86d2e49aa9115bd80cf49549e6fb23a81419826f7f3704a1feacaa456da4c107614c992443d5dbc8940f66c2f396786ce6520e08b715d1dc3ac0e695b7be348972522dc4c831f02b4253dc76181f979c6f0450a7303288abe8b9fd6f8a9f46aa93c4e6724e29c9891ba4eb8403013def6068413bcb4d413eabd69b812f479a2b63437abe8c0ce4aab34386de580bd704aa364f17c031195f0def63cfd88f1246a5895d3d06c2bfc919939280ecb8d1177d799062b4f4f0b3c7d855aad76a525301eb6706695639d03a4c0cfab11a1ea74aad65d68baeff5b0355aec1bcdff9fca7f3ba5dfa9f7e1d073305bd96f9abf5ec76a14be21ee536e09866c1e0c4b9ab8e70430bc708dde617f6686c2b6165981bf5c70c747ab222482b076f5032656ea780e0798c5f18223590d3872804c379e5114c6a49c8dbfc14bee6804612eb21b2055afcc7c1b2a8e8be67fad7fd8b5e2641fe8cf37792fe47bfc35c76949610f6718db9403b29f536de09ce4a254f9d612dcb93175f5592466cf9b7ed0585f83d1eb7ae64cd83c117a7f7a02a859a73377baf4624bcb5b7c313f6c2613138245fa0a40b844cd8670111aa27bbb01a7368aa56bf24dc7d8e21fca56e84a32776571ff60b3ae38b3ad1ed3ed3bdd19ede8c24c2aa71ecc2b28124dd0afc382b784e273e838afb698d8aa336016c4988a58ece0d8a2a2d94362d738346c950bf05008a10e29bf27f15b802f213b8e69473b023332952912f20472fdcf193077a0f460e684f9fdecd2d7793ddb33d5457ba30b4d91ec272d1e6e5d2795698a9af406a8c05d1ba2a64f883d37ea18505a0f5745a2dda217eab1774a9d813b8387ea16c48ec8e5600c16d98fd4ba0a1ef2888fdfcef5365401004b02cf704623277ce502a54e4e03d91914ba829c56740f777a5c28fbd45eac571dda7b89ec7fc982f65e5cb382415440162091c2f041fc0f97f20693305c25de7837a2f650577ac6cbffc09f1f695417cd5ecc555697693b12166cda47b8a745f489406942c599af5f085306efaa508b5bf0a406269f600b68b58ce3e204c3bea59e4f3c19157c60246edf18141597bc0cfff8308878e4f36e4c443ba874be58278fd5b332ca73cbaba2a302579e5df863db92251c597cd45f836d0b9d3add59ef92468542b377e3250fbade0833914066a39566009507d4c3d575851224e860429f617ac7ebebdf534dea8f3ac3f7a14f815751c8e72e907f9077b54aac645b4bdadbfd969ab38312cba888f0568170c02f36a4c5f0055e6702f39d62b2383835a719a60e3a6055df550b3590341b66770897bcc66f8a52016f1491973d565eed5d1c718ca8997c8a64d467170608991d14f1e50a8fc63c4a3b464d08d05f72e9ceb8ac69b5f889ee16f99454bb5f822f153c9d9dcf4f0bbbeb510d706f2c7ce5e516e1af5cbb838c95d973b34230379cc30299da585bdd8ad8bdd43ea6ad8aa8f4447ed4f411e4a3ce25c4b338882c6ddda40a8c2228491cb6cb33a5ae703d29865a151bebbfc5910b6441b10cf64c189a71911677c10f11decba96a420de0ef9c124f36fc5d27fdd75c388533c88667347b5277c2e1fc0026b75e38e6e46acecb2ba8efadbaf1489cf45f4a9225691492bc63785966c018a8fbefe58c5533e2bf2c601a0a5ef08cbf1b115bf3452c94953acb7294ff09c7fce12f8cb86333ad7eea514ae12333558f255f5d7039af77d724f66c962192509c5b5e07ca8161d6002417b793f25b92532c61d67ca6ce4e1718b414a86b237fcf0199d7e560c2136afb290711b3e2b8d403ee0537c351972e86551e5eb9a3535d7d602e5c444219b3b3d63412592399a7ee91bf3bfc26f5a3a91bfe9ef9a875cca8c6cde2fcc7cddc971897e559ba56761599c5f883a7c2aa2e4ccb753c659678d16d158ef12af987260fe98087c23773ae20f81fd590e1cf738d3c04b0955f2b20b30f54381c106115d4ed83fc7f6be6d143b61c2d589a317e98e18336150b3350ce0e16e8214da55476887da6fc64d61ba49425da1c156db5eca01411fa062183dd29fa1ced491e5e1e2e3ce160a8e9e4ff1000baf632a92d8e972077c23c666810339e9d9b5f62fda5bcdc45d315bf8db41b81aa165763780becb544f277bf10491cd085a8f5ad91eb6dce793f480a27c448ac8db95738e36ad0f15fae23a98f73e1bd96a6dfa6b1a4df4d34441d973af7e1994c06763bbab81ac19b0be9ec856365de6721840b2a4800f27e32d9046fc67b7f443a88c3bae70138305e9f162dba6219e6f8fd68494fe644bf6f198d1b3a934416e96ac219c72ea95648b1d9677c7dba1e3cc6e6b01198ea85a6fa3dbe68ffd5b936747bf86253622bf0656c4178e9b259f01fbfbef7dbb6f45ea2d01cf4ad075718440c00dc66321b311f399436064e7f56d254baae85a3199c31357b70e81180d1bede7fce128d2aa7fe5c5095a05314b056205e719f13c738fc443f94dd3dd8bf28a8024491d3625794cef85b2c2c810208a8557b04f517c42efd9502a1b4de412b47c530ee6f0056b5eac5be87ae7442aa84f30ad9b6c50d8518632bd4512783bd3fb358b9cbee1ecd5d634d617a57ba8ac2d8f12c99c1a146d6fcb9901f46c05fdb5bc3ddb7d8c0d694b5e0432e29a2bb3bda70d3977d7967082069aa36f425c60d3eda2922fd41ddb7d1fa2ce76256e3ec53a5575de0b0919bb719cd73884f052a35fda47861c13bb523509bf91233acfb82c674bc46fb5aaba970e29ea48af84feaee9aac0cde8ad0ff2b38aa378963d62217b8c079bee27985b725ec8327f8a496cbca3ebc763793cdda9c5c84b42678a0aa6b9baa03a5cbd7e2f733e956dd9b5712317068ea369d7821e3c288c019110c0eb792ef4fe7bde199ef10c973138af40fa52fb35dffa939e3d843ada7750896917772101e49076812e623f8fe435452cc39cfc3c6bd3665bb7e20fd09e28c08d9d29cdb7a4c5f7302acfb63a56bd481f535a136182577e0af1436c050ea376b335da9cf960042a1158fa842a85dcc923bb7ee96016860c2231a1f0d859275801958cebdb374cc1bcee2069ae9a2e970391172ab7bb9d39d991f68c2e087a0773387b62a608641ec54a7e8976b7a9ad1fa9904bb3fa98fbb332d14b6b23a82f4ffa69a8dbeeb4733b7e69430bf6b1ec5c8e5b0d7801b4c745d92dfe071224c7da687a7e1991b03867e82e7f3d3dcc2c63ad5ee9cfc20451bec862d421e03b11195bcaf01ec4428691cde6618db63ae73c08181304d4b06561383176664d20052bd5b7373a7ec53d1a4fcd63d7390d00d2217cfde499036e6ea389dbac12d08f40d6322a26c5681ff0c1f9b638cc08ced61524ec48a8e8bf93d2028dc10ffe063cb807c94cad3c2b694ed9747385ef5934f4c84f907af7999e476c2735e1318aad6ebb7947d9a64b2f625e2dab7e77b6fdbf"), + tc("466f4aeaa29c0b39feec5749855085429e90db0119ea16078c1a29e8112e4c02", "6964e2d650b19aa6d31b839c604a90aa3d105834f3854df4a41d4787a387042cc413de27503a1efc366b87ed677ea63dccd40d9c7f699fff9b8a42b596173014b9e811d42d2de574ed5c80fc6a5a86b772a3eebe5640102527401c0e9bef57e05bbf06986535d3163788c60c92ec7460b7fc685fb45fb8a18fa84ef5a37c94a2ec3a948dcafc69b83e2434abcfdafce948d8c76ed9ba780d39ac3dd098ebb3208538a666ced8acf1b3031c5ef23592fc432ac25be0552c1a6463e3cc778d456a520432a8c55436573219c49e6bc27474aa4bd9b1b3bc64917d016b6580aef2a0ba41acfb98ea869d3995012715d06d64e0aaefb2627125f225e2efea21af7ec49304b8e6399623fac15d6cdf39c4177a12254e5d807b4aaeced6d591f4d0bf50c07f5b69e7d60055062654e7b1c8b96af726bcb34b10c60b7fc3b10dbc69107c7d81043bcaa681eb354cf19af26ae3c7cb321b892c33cd044a24a5249f2f2ea3f804944b5049ca887eaa6d4cd5e52d17cf01cb0fcecda47425ec85f8362ae045ed0d109758c90134ea27c4cbf6e3abe7f283f4c3ffc4455eae4790329ae47658db2850b09a79a89bdbf72a20dc887b6ad7adecbd5a86c08479d446ef817e13a37819c227792e3e0ff80a2831b20760b879a489a4ae50aff9fb1dff39132dd84300ef7abe05e98c5929683a2ddfea99468996d652dccc698183f9a3103f7c049a7227733d0c14ed1bb809b4ef750df19094885961d327394d6cfa0a21b341681738b7e6feefcbf4dd474b7b45ea64a3a60b97bff297e8a52cd4aa6e92d6dbb7fef7f736d29ce3b041488a6db68d28f3c0204cfff82ce6ead11e11d7b060a0ff550711be33288acc63b6dcf2062251771544ad41b35b1ea5f11c4b196c48f5fbfcb4b39c60044a70e1dbb2cfbf9d8fcc855dec9a0b33f2021cc76b236adeb5a1356cfb8590fc1ddf8a721d9331861db604ad0c8c89871e960019a0daba43202addd526c1706e782fac5f98295f4c6036b8e2df6d225b6335d1314143573ab8e66a7750bdb6420452fa060b24fbbc3618d951490534718d97e3ae994d5e770d103e1f405a5357a2521e9393ed2ed197e7aac87e142205c69dbee846034fe6f2168dc207d13022e11427e8f02087d2f4e850e7fd21237d56be970781cd779f7b0d8e9f689e6cada3386b5e779e382ae3deb6cb20dba16a94d147101fe594790a5b331d40551c8c1d81052ce00642f56e4048f6831d713e74e3f4c6ed2352854854e35c06313f0104fa47eeb282aff9a3ed3d683fc88e9684aa0c460c3ec333080d5f557520a359803563a1d8ba7c716082cbea808dff6296947cef7ea30cbad458c0b90a07847e3e557cfc33b0e3893d32924934da966f6d451ff8c6b28ecad7480cc137327e5db10d180d41e8088660297dfa99126f7e2ab290f206c02644b4f28a603e959077e13d65c650cc0513259c36324cb5223e829350eb0a567317fa176213ebefd40b98c38055b24ad85171ff0724410926b38d8a06de500775d39e472b0b3212122204136d0793ed28f6ea070ed50745745df57079f7d4c63c6b5a862a031da93f4e576873e6ed03eaabc2a59d4fde5a670850a52b17dfb059ad5a0822ff68b9a3a11a26bde519e86a2c7f743bc932bb29208383621d509da438a56b56e1aaadc330a0ceade51ea65eca256e3dee4495c3fb94f41c121b03b6996981033d5d9e5540719e29eb1ad9a10c2182811629bb5a7d2296842fca6018e44b1b2b0ee56db63edacffa837a0147d1fbea737fd66b614fa097819422daeb9e1f04b81d4b1e569aa84708afac7a9dd92f95d9cac1137404f950c70d0e337c956bfd2c7b64a3b99f1499f9b297391b0996b42289e205b47900168f8bc3a30c84cdf00af52ef14d8b4a87675f8859e9c3980524c2081d3a08de576c1fd5b40459e299ba89fbd05c8ed01e3bedd893f7ec17dd1be1eee0b19186e739dde650566c395eb143451c09eb51c6e14eef3fc3e20210ce62c49d0bb862cec28872c0cd989050b56eeeb062ae2645c1510635e4085f5940eec6274ca982e312ab21ba0930be4d6b57ad8d36bbf58dc05d353e32a5cfcc4eb008610e24a8f2a1fcb57bf6e4adcf80d3408997d6872a2ea5e94e74cc184eccbba15882745e03146045dbf6e94127581cee20f2f408841d1982132e5b7f582b91784740761efdc9346bd9ebb3c1fd341c3056f4dbb4b36ea9e4bf05c965b367346472d0eb55b840ed49ef245c6f86ec5394063305ba0df45db30f7658d28aa988cc9ad97f0a9c0b3e59fb623d55c61f2e202521714970c2285bdbf7c6119fa3097d82f6a1ebc33d42c85a93630ae18521a2f8aa785a17c2d9354033fe297b172ea1f06cca686de4e1a28836da559e4405856411d58533ce33c6b573ec51f0f565499f6fca8837fc80bc4925867eeb6bdcab15ef1c70b17e02a535e2f423d36591a22cf527d118d5c540a6749e1c206058c0452bd9c8f5885eb5823ba8515c105f8d96fd5f4af9337e66d35069d19ec041a7d979452e5743a6a22da0df662fd9d1a2ac3f8d431a61f4fb944089c52233167e0d53899b6d45c51393f3ef6894921486d2f9f4d5e6e95107fb975c8e856afc76b06bf3648668eaff6aa59fc9a3aa536e548880d4d79c9eb5cfeba4d6d5d402197c0e1608500d69833c069bd1c45d766a7bc783c0194a2c813b223f141d8ae2868fd65d68dd67ef463e34e4fc20c7777be17a3fcfa51a9d98fb23b341b12cdd2b430b0602cab03a226c7cee4f9488c8319ddbf4eff1b614ba9c1fe0098e9ce1b57ea4d8d777ee89f4c2e3c729216b6b2ee2685db9c1945b27986c748dca51bb361824824fb2a31ef9d7244b734779b86354bc0dba438d378193a84785b6b864306eb6e6a0a80fa2af04985fc540477b911d7ea859d8f956dd1df3e464d884a6355e8b24ca3f05f25ee53b23ebe43ce6c32774fb74bace33a12e7c1798b7116bee1245cbe323882464c49b647d61350adeb84cae9c61b02bd702e6d8de849fad57a57ad297150312198d89ef1366e45286f4bcc8217db2edd39b144d6ba1d6cb0d1c74be396cb535e6ff9900ac371a6f9e8daa45dd1b1842fc685d1fdee0fc127a165b03b561002ca56f19143024c41520489befa82af92fce5bb220dcec1aa047b299f7fac2bcb489c99044aefaf2920c324597daacab92f6347bc0ef95ca619dc19a7f28a1cbbacefc85ea2bd7a769e6be424f4af08af7f24f5475ee2434aceecec18bfbcfd3479226363b2bfa915da76b839857a33641b4b9f64f15702adb3964e5db19faf48489a7f6f9f56a2a258c54e0e5f97f382fa99274b2607d3d6a6b94ca075eb454080b01435c5aba8b45ad6edfd00ddc0270c7e54919a43dc715cb7b37e8423c7d2285d222aec66e4dc4b55bc6eed3ced1b8d5b364f5fed35ba0312e46418192937792e865ebf5d5315f1e395194004b77f1d15e48203e3e5bd2c508e3cd8d1e2075b3d6db0a5b28c10f155b13954297db518a3e47db5da1c0ed8536fa7b5945e952bb5c4e24c40fca0c344302a5202b92a351738225505209d810789f7a590672ecaad866ace771a3f9091a1695da17b3576213a0db5c4641e78045822afd939b6272d12e05cdd1040681aaee55523be6371c53fced13638a9448c0e9af8aef55acaf723d513dab5138f62e28feb489e75be072418051cdfdabc7ceb0c99f181c6ad95e7eff5226be5c4f4edee7a8c4a0500241edbc363cb4c3ac12dd046ef0c840933740f48c63de39c082ff4374c2d8453f2b813f397b822e90fd59b448262c00fdcae73bcf90cae164bae33041cb809e7cafcfdd658065b84b7c370ce3e08d505f7a2ba783a666ef74d8743336fa4df4e20a1f8d58cd0b0eff66fa77803f42169f51109a4ccfd008f36e23edbaaa6abf53b822c9ef356b79027cf28df9e5d5d7ca360aeb407cf02ca2689f8131c78ce6de7c5d8a2dc65a91df7aca7787ecd5145c5c791b7428f3f9cbb22f584eb564af89263f5750cb37c8151f205b2fca7df746226d5afe10177c8bc456452ea65d449e19b4253d6625d5c3349f3ec3be554d4158df219de226af379a422a8dac91b97c64e19c120141d806767f7ee931d88e293ee8ae1efd2766ee83174de19bb645ee3455133b81e2270ac1c7cceb037a50a5ccd540f00abe27f05bbc29a2f10542ca0ae130d2b2a1464db03c0b2510a9e7fb98ab24fd391cbae0a112a66eac05304127d1545be6638b6d35606877719c3cd782fae3faad1b89eb603750c7b5c3ce84f93fac147346350c750cf2ca7ef9099d6725bd31172c37fae60e07f33572cb843e67e8662d0dd55e424afa9b1d57cbf9ddcde447a3b8b604efa253ebb2cebe01521f0353c521f457f451cd93346d815d6bcba87b3d2f96a22ed17c17b90be3ddf7e04838c4f780be2c96b781558b4a4999b3adbaaca1229f22ff637cfc068ce8802f8684aac32ae32bd09cd5c1d770900cc7c5fe8df41e8110faad782c15c3cd637ba619a0037d7378c72cb0c95a35a9109be11f8c4bf0d3bb80949a15c64a0d6f52a9b52a521d06dc749cd0e553418e127fb519bda401f59e5ae517cbc2f538b6c32f9ce5c34088cfffe03fdc7d36a7a912ede8254b7ea46f1262973be564089c44825437d8dba98be04cf7f6ab5d03c5c10ab51557f331d0b1b919b9643efad709080d2ff46be83e081551f64ecd826db09469179ff4b068cca172d1e16750bd6d4c805146dc0221a555c81931a60206af7cb485d2fb7fa9d7aacbc3e3729eb09979d2e683ea3ed8ad3b4baaf4fe25f8b227568795563d3f2902a97375c521ba20f9c5e860dfac83a0f0ed347588f250a44f01c93611405aeb04c6e75153f0a6e064e19d895df7596c6a48a0bb3804a78937a45bcd05486314baf72661349e8eb516fcfbaffb31639716c5b88caf4255e20ee90a6ef402e7a39c5ccdc5b196a99c5455639e9ecb788f94abd7a711833b92db9fd56b6412bf84c6bee9505682ea300ceeda496ef06cc477c272318f9883212794e9ad88177b65cecc1ba8474d3de7656b61a3b832240578a4ff704caa65e01a29f365c1abb1df23d61ad41c08b092ce870ba1103857da37d8f5647a5d5ada7bb1d9804c547c187bd1767258292514491cec97ba41f48c6b581fe8e74520d0f0f6b0e44c9920290f68684d169253087540ebe733ae2465254ba04beb703f3a309be14b4f22ea63aa2de8e674d2ad446f356d1cfe1207a5ed7c2d565ccd5cc1ef0eb1e7919c26e92b851f7478c83de6cd21a3547cd4464a6b6b4da6a8d8f736d16d818063d1fb95a8d6a46911fc564aa5cfb1cf76ef09d0c104648f92db9b28816f560a24935923a945a1fb52ad9fac6ea2e640a64f79f25693398b0a0e567518d950714a8aaf343a68a4eb1ee1f518acbe99e05041ca505ad509f6ebcc68c84d85dc17e60f0240ec848a842f4a1abf11371f46ce69d5b535fab5634523e359ae14dd07749fbc6390eeb9e5330fc07926c971c719cd02aa565048cda7e534365a3f39555f25bec847c0fe5a76ef68f038ff552da2722581cc14eceda1297942a1a67d0501815747c61517a9ac876db662641f750a917edfd8ea199d27e5d56855e5200791cd404d15b6ef6f5771234b0902c9db5b033f260359f1a52e6646dbde9e4f9876a7bf2c5fd7ba028baa306ff9d4287df88aa055c833ed8ac6a56d2c83ad69ad3fd6ffa96ab717444035d3b8dae9d26d475767cad452b607b3eca6c573c38530db071968b7e1dc26058827c57ad84a14b495fe082419255c495c5e13f5bc34d96cb498378cc16dbf6683d54836e12d44d76956a6b0ce788af5282b9f06b5bad30347a1d537ef864bb0c1496176b1b10118d02a56407609c0208e860b86a47c82b6fde76cec5aa70cf58b4b110d67e70e7b37dfe3a1d530183111e2ac6b30736be4ffd6dbaec5dcbb7194b9f0cc733f0a3a"), + tc("122ed6f2bbf68f5b80bd8a32d6716966ed6544c0788641de0b362d1ac09f3571", "0ebf64ac017febdca40ff85fd4aeb8f1a827561c150f74cd5e864857fbba9c08a46efb9ff7a16919618c9fb06bf8fe3f0859774da6c38c5a0c54d44075d1baf6482b7705c8e1a86e79b0fbf0328246b5e6be013f934d4ecc34808a3639c49464309df5ad250bf4521e41b4cde563566b8625076ad7e260018eee2f3252d15f36bdbbce3c74758c68a0e72d83a37db4d2022a80a4f6b16f4515053e1fe398cc6a74d343d4cbb403597ae68533e18ef893f756f6f554f98bccbc84702d19f875d347c345b09edfcb1c71bd6955c5178dfcb07376728cfc3ab9565c0a1a8dca78221028b00b51b175a2da2cc0a90c33c169ea8a1a2e375c087ac3657d28ac481d5b5c225aefa85919fb8628f32f42f1fc0806a250143c084322fe9e30bcc8b89f3da73469ceb935ee25887d843321cc8abb3c75e15f4894cbb5731782372a5631979bbe6aeacdfb711a84f83bb89e0f92b88c5ee83e4f9a3c4f80624a17ed5549b77ed7d939c368cd9a92eaae595105de7e2889cad71d01201580038fcf4ab4d285a13befaaa14facfe5099a83f9e71d512519d055f44f757757bcebbd2734cb91c5c4ce7b411d1e83b42689fdf8a69e63ddfd255b977d7435beeb5debbba7a47b19cdab3f1c40e79b926a481a1629a818525c2a198983f23f0da5da8b99633689292332bcc2a4ad49769b3e448f8cedb87c1de2ef5d04e1c3a8116c3328f6b19b59c318e18f8db29875c22a0a0c8ea615a593439f7b90aa2ad9c16ca573040a8190a834370378554bda9da9486aa3ce3f1b01dba14b032b3c334f7a1e961426302ed2d38c34f61d7b4d182c0ca303c2f379e74eaf65a48d3d8a95616b2aba5c88dbf6284c5cd68d902e3958a7b9529f49d38091bac288734247fce886d49bce00ab98b1b962a8da8b4710644e9da418529e79a27408b52eb655fde025a129a38eeeba936657f725f0ec668380b9dd50c0759ac150f81b696b886e86a5447ccdcb3fbec08c79471dc3111042fcfffadc1a973340dd68610631fdda6ad25148c2d0b48ada24e6b2d42ea7510991c0455ba1b7f393ca1fa5801f182f89117885455dbf88f63ce0b77669ba965b73a344d3c4c787e3b4aaf58c56fb2589967c71969b1fe92c5e7d91094dba84f470437339c1dc6918dd5fb835cbe69750363691b0cee7b80f0d0aeb31ddbdac52f904a3a91051b54adf75b0d195763d29fd1b88244f61d6a304807b646326e76fb880d1df37281a11ced52d5b5873668642c72cd7c70e36825cc17da6e03c577d12f79ccefd6b7b76c88b19f7606536793d8d33f7e0a598769883b417c3662b6267001be7d0914563b2402149357d1c64680da80b32207a67611edf0e8d9a767e2be2abb21956fc764200945e4bc5830acccdc80de485086514c63da7f785cba0c1e9ce5b249b52e7ee570d8657c63c4fc90856dfbbb24c8d2a711cb3a0960685ec55540f6ec2641a429d3dc99d82f26d2c7eacc614ffe988117d8f13b7442a9ae2929d3e1f67b974bb4a4aa960015c9216afa844a243095f167b114700fb11215eb54357bcb1d4792a0b676c708e710211c3af73d61b82f4d04bc40c898ce00a2d34c77f5e22db71f46c939a0ec9c37d1647e23a0f44cc23d710a1db20e38d009c6580192295c40b478a85a0930181d0962c3b18b6aac7f5be99607add25bbc23a7530e7e58c93f9fd4afe5133fd228c6b4d9e43538cd9dcbe76c4d7dedf22741839558a6b561847327df7cff1c17e03eb0ed9dd921974954638cadbf645a4baa4a62774ba66e2e458a1f7ac67ec394429339c2f620d457f5dd06ae551b69baddee99a6c93edc7bf7cd56898cec4ab4e633489dd1934b3eab68601e3560092cb9b1c64b90c9fbb6f62be60b0e221f1f6e1d58444b731b30ab04c40988093fbd396e65ba703f52ace0695035ee78e7a4969d3eb5834c58ef60fee1de0f0e03e4203ff50f957ed68513f9134a4332b56044ee14ce80ead7ce532aca58b1f98423c3cabd9921f83a81c7467b3c6f6de4b12ad741c7345bd645854bbf859d110c8d60f53afe65f6a8a0856bbf78954f8ecc831fbb43f030449a9fe72a3c86ab7962acd28975530cb567cea713be76b2262a70896b8a3cac66772c56a9ed139f3f4349898aeccdebd5622f32a75ebbc9fe202fe56c957356d40afee4718c52d30fbf683ce17056e672f909110c087d361c196fe33c6d80dc6925769f6a93dd41b5bb5fea806f3bf56c50d69b45a371ac5dcf5cf356bc552e0cf87e22af0f121b21278b067138e2afa098e7ede1c0a8ab29b6ccf6b85d39adb9cedd013886d2c23c273a21267b2f2c22b5bf25d5a5cbd083684b454ab5fb854f90cb49b2f53795bbfd46348b30958398344f9c362040e1412f331e2dd1679f31adbcf8d40bd3bf707a4f3558239a0b9488b3d6a264e6da3911b0be47dff3257b193129dda9f3c0d9adeb161f8426ccd01ad2d0854f7319e12df89da88c98fbfea96d1f40bd098aa46beb471834b039d6f001f804ed3dc47cde01d5b594d565d94f70ba7387822dbe8d0e44645a914f25d1d15f38eb4e5c9b4f48c6146dbeff8d36c17e5e57a3f07e561af390edfa4e2cfeca19722a3beeea23782cd7af8a61767b010f89a7082e04e807f5cec49bf5ac71db3dfdc5665cd1fa5b9eb0c3c7343c674730ec650a13bae412126c68daf917862adb4e220461a6cd0c7511f4f31c47f8a41ac54fc0b34f07dc15060e9a43855da0a162436b1d3a4b6d8ea87229faecbbd9a2f0792266dd160e0f8671690411f8a9bc7429f8e0f4eb0098e33f0532057d1e05e6a78ff48a6bdf78176fb462e56b305476a78f099786a6793d2fc3f4f74dabd3a34bc2e50267f06157494b9966e1bf1f8708cbb770580cbb2467fc1b3595345a49a5d8ec528f3e2b3f91b06e0693f87fda3b68cf99df5c47bb18b767fbe74ec0b664ac0f1cc6592f01a5659a7f3b4293bfb8541f0a9e3f923a547dd1c784ed624cd3a2c9d1b8c308b3236e4146c93cdc36e17dfeda5c123e735ad4feaadcaabf9581bc1060726f545c308e56c7310093f7938a2083dc468bf5ff3c84c2271ec7533838ff15e6f7ee8c71115ea8e342bc33ad64467640b11efe5f33cecdee3058e8c17f8ca0488918506fe2fa10b92b9fa87dd7305362d24d4ce454a779612b112c7c7e6d4b632a8475edadbea13486cfaf5647e4df1fb135ae793f8e6d23216b0adf664a14397dae07e133a1a58e15b25e9092b61fad3619bd551858144b80d9075d34128c351643f101baddfa990ce910f2a8d721b64c495a12f07af3d32cedac92e20dd9638c0db36eb7b1286138fe056eae9d91c4a0ab7dc5267fb16a41b771eb01d54701fb43570482157be10c6fa9e4d866b8b5d650ee6f3fe117b1fa79cbe4f8a9b97928eba2fca6d7669b384184895bfe76abf484b03b9f7ca10308c6ce31197ca15b1a36cedc3774b6b9ddcc2431e732db36537f1f4f4883e81e7b3c6d368b4ebdce34c657711d2cbb4158d519e027c5b4b64575afcd87ce736c45028d378d70c0fde73b5499ffb307b9dd82073833c0a84769964fbd7d01c4ece805e122364b2b485b6dac7793f1ee7f1cb2a50ae6565cd210308260ef64d9b2fe8378516ad093960d5ca8cfa9fcf28762998020743086b93eedec324784428eba23141466185c740055b1e87bf7f6bef5ce8e28dd270f9da64d6fff2cb0d73a734d45dc56cd03be9414db568ee2366f2166fd6d43af9eeecb13c4a5d3a8e775bee5942194033ae3ee1754fc046be91130979ab88e47a4cea379ed9e77bc4310ddae2bdb7a5b941e3fb6081cc3ed710e0c603d47efa82fccc0d556d4ab58825ebe6ec7700f02fb7a0eed44cd2f8772ccdee4ba4b883daab46264a569aaae97a0e4369ec00f59d841a1a7cf2d39bc58725b248c7b159d6d68b3d9721231cacaad38f26f357bfd31ffcec18cf34ee42c3b375c827c4284b31fcd4374cfe8c2c7a6b952581c9837587cb161eeca237290afe8095e069a99514dfafc2fd8cfc734b4ae64807d3aca2681ade0fa018b17d2a61f30f0307ae86d67d4a14d9ce214cf41edb702735f99d58bdace7f28c7d95a9cae8b079708c6c7e78accab67f67a598f7c86fe8b8ab6539b2ebb376b6a17144a3adad6be5d617d30607ab4d94c11a7c1858e19b5c7b12f9be4ac245a9e91a22ee5471aa58538afb35319f53d6dd69bc2ae48d2b649dff6e71dea3b797c071a0c345f66df053475a40031f323908c71a198c7fb2d542aa8dd97b2f1ce01d11e7fc458518ce1cb451bb9b1d57530d15ec95ba73bd5a386947ff8d0f565549a11b88759d6aba3a83967bf9543e640a33f6f9d9655a547785b42b11216ec1cce067630e89708ca896733129db5312169003b8facb8de3f99e65427aef51cc1c91005bb7f5b65218b1b492fdd67761e0f7c2fc60f7eca13b09df6b2a53b5b9ceab43d5a1ec3260a89b54bc257850f0659e3240daf073cc5236f65b159cf8fdec771911c054bb417f6799330d443a735704e66ea9f1d3351837197c19e8226d27a1eb5b664781cf0651231ab25678a4c9f9973da830ed3871a6ee638dda3c789b0ac27df49717ca2a1df46968d56035ef02b712d12067e72b1e661120fc1fde4fe6b11bb4d189f29112dbbd6dd0d9e4e78501c8e5eeecc333b208d7b86522c61de4ed0d4454d19ca62027627344f455c05f429fa17e4a6f0bafcd4b575cba3755b042382942190b6eaf762df7714797e916d58e7dd0de2cfc41d8e6e410de623ea5c547334d3a21488cd2d65e6f9e0f123166fd8304309d0160b5a605317e0122308f9ff065ae6ed422437c962a1362ec1f2804b274e3bae118292ff930bd519efd3a93e40f949abc60e8e9190daaf9fc8099a44311861b02ffb4f9a0aea81999170ed5bd14b727b42eb44b0b55983f1ac2f2ba80734630d6b7500d65eab42f6771eea6872677e252da4c717baa07760bfc5fe09d4835a65200a80c2759a476a930b7fb4a738241c3e73a9c75d5a7f0a0be52138dda2812aefba8fd78d5840e6ea1d15c57ea66a59b3a882a8fd04093a5815a5324be75c4f83cf16b785d2d3bd36602a0026a9895cb343688eea40cdb483edcd87788b668a69d0bc75b07d8c8247a9780e4c1ec342c1198295d699f0829e41a78f0f9978dd52d6491898f1f05b979e587f711df66f38c23c2bff9c69086ab970c468315b3b6c36d58a7ae9c749fa06429e6781ae7d49b3b368048641a63db95ee293a1954201cad72e92a85e34c7a74b2fd1bca6aa61435af2dc32c1a2f559f63e716a6c96f076097e6c45f4372ae6828e9eaae3eb82361710ec14f67f7d0ed9859249c18d14985302847fc8f3301bd31c7e1e09b2057e9ee46fcc7c9bd8db59eb0deb0b1d8d291508cb3837c9b2f191a49595d6aaddcefef0dd59ed3a05fa8f6ef03d38f534139d56bcbd4bc3256e1a120d49a5db1badb528b0256c61a2f179a23c49928738f9c0fa81fc196d1a74507998415f070ef9c38baa53951fee7b68008bc675e0e15bc32a61c30bc132e79c58a3a970ffddb8b806782dd31242b3c3cf9810beea5fb5a1b250ea62336456e7694b4c829508c7dbfe090af6a850f79d04d2c698637816017f8a920e1b1ade236e227b3480899bcbb991f6c6c240bbd4114aaf9875935558394a486652b0942f3409b66faf8b8bf711cc8c34cca41b8e16c2cdf0160b92a332c1f04bc64582446b98aff34189675b7a10ffc6f13b3f74654ed7c0590d4af7f4d747bf89bb2a8f5c8ce610cf4fa4ab714a845e15649b53e54a95213d5a73905941d9467b0bedda2becc1c219e1cab699652d85b8cd7e0cd11ce5b0cac76f9ef3d74bd829877898e7350cca72101076a970bec6756c3fd1aaf3396f72833f8d4e716aec6f93718b262710b0da2f3fd6cbdb204ed0e91d65cab39ed35f22a01e5d509282752837ebee968b140989ef5f4d513452784bdb892cafd8387e05b3012c0458a369e62191f5bdc57dd63ce42e945f493c2b42306b8084f3b25e94abacf08ee155f3621acc9626ee487c7a7e4667f0377ae4b2"), + ]; + + #[test] + fn test_digest() { + for t in &TESTS { + let input = hex::decode(t.input).unwrap(); + let digest = Blake256::digest(&input); + let digest_hex = hex::encode(digest); + assert_eq!(digest_hex, t.expected_digest); + } + } +} diff --git a/cryptonight/src/cnaes.rs b/cryptonight/src/cnaes.rs new file mode 100644 index 0000000..b474003 --- /dev/null +++ b/cryptonight/src/cnaes.rs @@ -0,0 +1,451 @@ +use crate::util::subarray_copy; + +pub(crate) const AES_BLOCK_SIZE: usize = 16; + +/// 16 bytes, the same as AES 128 and 256 +const ROUND_KEY_SIZE: usize = 16; + +/// AES-128 uses 11 round keys and AES-256 uses 15 round keys. Cryptonight's +/// version of AES uses one less round key than AES-128 (even though they both +/// perform 10 rounds), because Cryptonight uses the first (0th) round in the +/// first round, while AES mixes the first round key into the state. +const NUM_AES_ROUND_KEYS: usize = 10; + +/// Cryptonight's hash uses the key size of AES256, but it only does 10 AES +/// rounds like AES128. +pub(crate) const CN_AES_KEY_SIZE: usize = 32; + +#[rustfmt::skip] +const AES_SBOX: [[u8; 16]; 16] = [ + [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76], + [0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0], + [0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15], + [0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75], + [0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84], + [0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf], + [0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8], + [0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2], + [0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73], + [0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb], + [0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79], + [0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08], + [0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a], + [0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e], + [0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf], + [0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16] +]; + +/// Cryptonight extends the AES S-Box to 4096 bytes (1024 32-bit words) using a complicated +/// system of nested macros: +/// +/// The table below is the fully processed result. +#[rustfmt::skip] +const CRYPTONIGHT_SBOX: [u32; 1024] = [ + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, + 0x50303060, 0x03010102, 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, 0x9a7676ec, + 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, + 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b, + 0xc2b7b775, 0x1cfdfde1, 0xae93933d, 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a, + 0x0c040408, 0x52c7c795, 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, 0xb59a9a2f, + 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, + 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b, + 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, + 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85, + 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, + 0xcf45458a, 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b, + 0xf35151a2, 0xfea3a35d, 0xc0404080, 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, + 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e, + 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, + 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, 0xab90903b, 0x8388880b, + 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, 0x0a06060c, 0x6c242448, 0xe45c5cb8, + 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2, + 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, + 0xb46c6cd8, 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810, + 0xd5baba6f, 0x887878f0, 0x6f25254a, 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, + 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, 0x120e0e1c, + 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, + 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, 0xa7949433, + 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0, + 0xc3414182, 0xb0999929, 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c, + 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, + 0x30306050, 0x01010203, 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, 0x7676ec9a, + 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, + 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b, + 0xb7b775c2, 0xfdfde11c, 0x93933dae, 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, + 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f, + 0x0404080c, 0xc7c79552, 0x23234665, 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, 0x9a9a2fb5, + 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, + 0x0909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb, + 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397, + 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, + 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a, + 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, + 0x45458acf, 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3, + 0x5151a2f3, 0xa3a35dfe, 0x404080c0, 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, + 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, + 0xcdcd814c, 0x0c0c1814, 0x13132635, 0xececc32f, 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39, + 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, + 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, 0x90903bab, 0x88880b83, + 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, 0xdbdbad76, + 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, 0x06060c0a, 0x2424486c, 0x5c5cb8e4, + 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b, + 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7, 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, + 0x6c6cd8b4, 0x5656acfa, 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x08081018, + 0xbaba6fd5, 0x7878f088, 0x25254a6f, 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, + 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, + 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa, 0x484890d8, 0x03030605, 0xf6f6f701, 0x0e0e1c12, + 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, + 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, 0x949433a7, + 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8, + 0x414182c3, 0x999929b0, 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a, + 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, + 0x30605030, 0x01020301, 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, 0x76ec9a76, + 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, + 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0, + 0xb775c2b7, 0xfde11cfd, 0x933dae93, 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, + 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15, + 0x04080c04, 0xc79552c7, 0x23466523, 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, 0x9a2fb59a, + 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, + 0x09121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0, + 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784, + 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, + 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf, + 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, + 0x458acf45, 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8, + 0x51a2f351, 0xa35dfea3, 0x4080c040, 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, + 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, + 0xcd814ccd, 0x0c18140c, 0x13263513, 0xecc32fec, 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917, + 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, + 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, 0x903bab90, 0x880b8388, + 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, 0xdbad76db, + 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, 0x060c0a06, 0x24486c24, 0x5cb8e45c, + 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79, + 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d, 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, + 0x6cd8b46c, 0x56acfa56, 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x08101808, + 0xba6fd5ba, 0x78f08878, 0x254a6f25, 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, + 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, + 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66, 0x4890d848, 0x03060503, 0xf6f701f6, 0x0e1c120e, + 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, + 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, 0x8e07898e, 0x9433a794, + 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868, + 0x4182c341, 0x9929b099, 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16, + 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, + 0x60503030, 0x02030101, 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, 0xec9a7676, + 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, + 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0, + 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, + 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515, + 0x080c0404, 0x9552c7c7, 0x46652323, 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, 0x2fb59a9a, + 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, + 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0, + 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484, + 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, + 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf, + 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, + 0x8acf4545, 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8, + 0xa2f35151, 0x5dfea3a3, 0x80c04040, 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, + 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, + 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec, 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717, + 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, + 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, 0x3bab9090, 0x0b838888, + 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb, + 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, 0x0c0a0606, 0x486c2424, 0xb8e45c5c, + 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979, + 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d, 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, + 0xd8b46c6c, 0xacfa5656, 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808, + 0x6fd5baba, 0xf0887878, 0x4a6f2525, 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, + 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, 0x96dd4b4b, 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, + 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666, 0x90d84848, 0x06050303, 0xf701f6f6, 0x1c120e0e, + 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, + 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, 0x07898e8e, 0x33a79494, + 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868, + 0x82c34141, 0x29b09999, 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616, +]; + +const fn substitute_word(word: u32) -> u32 { + let wb: [u8; 4] = word.to_le_bytes(); + u32::from_le_bytes([ + AES_SBOX[(wb[0] >> 4) as usize][(wb[0] & 0x0F) as usize], + AES_SBOX[(wb[1] >> 4) as usize][(wb[1] & 0x0F) as usize], + AES_SBOX[(wb[2] >> 4) as usize][(wb[2] & 0x0F) as usize], + AES_SBOX[(wb[3] >> 4) as usize][(wb[3] & 0x0F) as usize], + ]) +} + +/// Extends the key in the same way as it is extended for AES256, but for +/// Cryptonight's hash we only need to extend to 10 round keys instead of 15 +/// like AES256. +#[expect(clippy::cast_possible_truncation)] +pub(crate) fn key_extend(key_bytes: &[u8; CN_AES_KEY_SIZE]) -> [u128; NUM_AES_ROUND_KEYS] { + // NK comes from the AES specification, it is the number of 32-bit words in + // the non-expanded key (For AES-256: 32/4 = 8) + const NK: usize = 8; + let mut expanded_key = [0_u128; NUM_AES_ROUND_KEYS]; + + // The next 2 lines, which set the first 2 round keys without using + // the expansion algorithm, are specific to Cryptonight. + expanded_key[0] = u128::from_le_bytes(subarray_copy(key_bytes, 0)); + expanded_key[1] = u128::from_le_bytes(subarray_copy(key_bytes, ROUND_KEY_SIZE)); + + /// See FIPS-197, especially figure 11 to better understand how the + /// expansion happens: + const ROUND_CONSTS: [u8; 11] = [ + 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, + ]; + + // the word before w0 in the loop below + let mut w0_prev = (expanded_key[1] >> 96) as u32; + + // expand to 10, 16-byte round keys (160 total bytes, or 40 4-byte words) + for i in 2..NUM_AES_ROUND_KEYS { + let word_num = i * 4; + + // if `i` is even, `word_num` for `w0` is divisible by 8 (`NK`), otherwise it + // is divisible by 4 + let mut w0 = if i & 1 == 0 { + substitute_word(w0_prev.rotate_right(8)) ^ u32::from(ROUND_CONSTS[word_num / NK]) + } else { + substitute_word(w0_prev) + }; + + let pprev_key = expanded_key[i - 2]; + + w0 ^= pprev_key as u32; + let w1 = w0 ^ ((pprev_key >> 32) as u32); + let w2 = w1 ^ ((pprev_key >> 64) as u32); + let w3 = w2 ^ ((pprev_key >> 96) as u32); + + expanded_key[i] = + u128::from(w0) | u128::from(w1) << 32 | u128::from(w2) << 64 | u128::from(w3) << 96; + + w0_prev = w3; + } + + expanded_key +} + +#[expect(clippy::cast_possible_truncation)] +pub(crate) fn round_fwd(state: u128, key: u128) -> u128 { + let mut r1 = CRYPTONIGHT_SBOX[usize::from(state as u8)]; + r1 ^= CRYPTONIGHT_SBOX[256 + usize::from((state >> 40) as u8)]; + r1 ^= CRYPTONIGHT_SBOX[512 + usize::from((state >> 80) as u8)]; + r1 ^= CRYPTONIGHT_SBOX[768 + usize::from((state >> 120) as u8)]; + + let mut r2 = CRYPTONIGHT_SBOX[usize::from((state >> 32) as u8)]; + r2 ^= CRYPTONIGHT_SBOX[256 + usize::from((state >> 72) as u8)]; + r2 ^= CRYPTONIGHT_SBOX[512 + usize::from((state >> 112) as u8)]; + r2 ^= CRYPTONIGHT_SBOX[768 + usize::from((state >> 24) as u8)]; + + let mut r3 = CRYPTONIGHT_SBOX[usize::from((state >> 64) as u8)]; + r3 ^= CRYPTONIGHT_SBOX[256 + usize::from((state >> 104) as u8)]; + r3 ^= CRYPTONIGHT_SBOX[512 + usize::from((state >> 16) as u8)]; + r3 ^= CRYPTONIGHT_SBOX[768 + usize::from((state >> 56) as u8)]; + + let mut r4 = CRYPTONIGHT_SBOX[usize::from((state >> 96) as u8)]; + r4 ^= CRYPTONIGHT_SBOX[256 + usize::from((state >> 8) as u8)]; + r4 ^= CRYPTONIGHT_SBOX[512 + usize::from((state >> 48) as u8)]; + r4 ^= CRYPTONIGHT_SBOX[768 + usize::from((state >> 88) as u8)]; + + let mut new_state = + u128::from(r4) << 96 | u128::from(r3) << 64 | u128::from(r2) << 32 | u128::from(r1); + new_state ^= key; + new_state +} + +pub(crate) fn aesb_pseudo_round(block: u128, expanded_key: &[u128; NUM_AES_ROUND_KEYS]) -> u128 { + let mut block = block; + for round_key in expanded_key { + block = round_fwd(block, *round_key); + } + + block +} + +pub(crate) fn aesb_single_round(block: &mut u128, round_key: u128) { + *block = round_fwd(*block, round_key); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::hex_to_array; + + #[test] + fn test_substitute_word() { + assert_eq!(substitute_word(0x12345678), 3373838780); + assert_eq!(substitute_word(0x00000000), 1667457891); + assert_eq!(substitute_word(0xFFFFFFFF), 370546198); + assert_eq!(substitute_word(0xAAAAAAAA), 2896997548); + assert_eq!(substitute_word(0x55555555), 4244438268); + } + + #[test] + fn test_key_schedule() { + fn test(key_hex: &str, expected_out: &str) { + let key = hex_to_array(key_hex); + let expanded_key = key_extend(&key); + let expanded_key_hex = expanded_key + .iter() + .map(|value| hex::encode(value.to_le_bytes())) + .collect::(); + assert_eq!(expected_out[..64], expanded_key_hex[..64]); + assert_eq!(expected_out[64..], expanded_key_hex[64..]); + } + + test( + "ac156e17cdabc0b92e3e724a06ef21e5317eb71fbc7f1587403b30ae6962a21a", + "ac156e17cdabc0b92e3e724a06ef21e5317eb71fbc7f1587403b30ae6962a21a072fcceeca840c57e4ba7e1de2555ff8a982785e15fd6dd955c65d773ca4ff6d4c39f00586bdfc526207824f8052ddb76482b9f7717fd42e24b98959181d7634ec01e8a86abc14fa08bb96b588e94b02a09c0a80d1e3deaef55a57f7ed4721c344fcc6fd2e40d20726fb44b2ae120fb044557c6795b6a2c960ecf53e8dabd4fd", + ); + test( + "688dcc56a1c9b8c9cd9e378a98a1388f17a2c05a698a37232ecd4a567dccdf79", + "688dcc56a1c9b8c9cd9e378a98a1388f17a2c05a698a37232ecd4a567dccdf7922137aa983dac2604e44f5ead6e5cd65e17b7d1788f14a34a63c0062dbf0df1bac8dd5102f5717706113e29ab7f62fff48396801c0c8223566f42257bd04fd4c5ad9fc6a758eeb1a149d0980a36b267f42469fd3828ebde6e47a9fb1597e62fda173a8a1d4fd43bbc0604a3b630b6c44b96dcfc83be3722edf99ed9f86e78f62", + ); + test( + "a6116fc295f15ff03d538581a560a9c1fdaa1e7f5745d8e6125d6eb092c71b15", + "a6116fc295f15ff03d538581a560a9c1fdaa1e7f5745d8e6125d6eb092c71b1561be368df44f697dc91cecfc6c7c453dadba7058faffa8bee8a2c60e7a65dd1b2e7f9957da30f02a132c1cd67f5059eb7fe9bbb18516130f6db4d50117d1081a144f3ba7ce7fcb8ddd53d75ba2038eb04592a256c084b159ad306458bae16c42e41f17532a60dcdef7330b8555308535b99635c079128499d422e0c16ec38c83", + ); + test( + "80784e1c1d3730e6f422aae6b10596ab16b190e41eea452af9aeedc97aee4b74", + "80784e1c1d3730e6f422aae6b10596ab16b190e41eea452af9aeedc97aee4b74a9cbdcc6b4fcec2040de46c6f1dbd06db708e0d8a9e2a5f2504c483b2aa2034f91b05823254cb4036592f2c5944922a89533731a3cd1d6e86c9d9ed3463f9d9ce0ee8679c5a2327aa030c0bf3479e2178d85ebeab1543d02ddc9a3d19bf63e4daa5c656d6ffe5717cfce97a8fbb775bf822c76e233784be0eeb1e8317547d67c", + ); + test( + "cc08712809fd4c0f0b63dc21657f22b3752fba8f2ed5882e7d75e65906bb3399", + "cc08712809fd4c0f0b63dc21657f22b3752fba8f2ed5882e7d75e65906bb339927cb9f472e36d34825550f69402a2dda7cca62d8521feaf62f6a0caf29d13f361bbe9ae2358849aa10dd46c350f76b192fa21d0c7dbdf7fa52d7fb557b06c46370a261c3452a286955f76eaa050005b344c17661397c819b6bab7ace10adbeaded0cf409a826dc60fdd1b2caf8d1b77905ffdfd73c835e4c5728248247859a2f", + ); + } + + #[test] + fn test_aesb_pseudo_round() { + fn test(key_hex: &str, input_hex: &str, expected_out: &str) { + let key: [u8; 32] = hex_to_array(key_hex); + let extended_key = key_extend(&key); + let mut block = u128::from_le_bytes(hex_to_array(input_hex)); + + block = aesb_pseudo_round(block, &extended_key); + assert_eq!(expected_out, hex::encode(block.to_le_bytes())); + } + + test( + "1d0b47a047340e32cbe890ca0d61720a09bcfb39e01b7541d1100d1ef91f955f", + "274fe9eeb2d1e4c71f0f0244a80e93a1", + "be98612d6b05a6cd72df39326180066a", + ); + test( + "0093d86fe74698f7b02774e6d4f67e9e29eb71d1754804a19b77d986b8141434", + "110f2e5d81f73a512ec95aa5b8e0d7be", + "1f1750d997704943b828df66661f7cbf", + ); + test( + "d5044939d15af565447ef76445405cd899f81c6f41f4493a5a1323712f815e53", + "6022f491b67e27909f74d0e71becebaa", + "9f75d250681954d60e418b4333d247a5", + ); + test( + "256670ed9eba1db67e6ddec5dfb78f6bfbf55d0f74e2a46d06f2e3592a208014", + "4de6ecad6a885ac88f09f9b2be4145fb", + "cb286e70825609cb97b7c7ae72548fa9", + ); + test( + "e1077c3566d1e8bfeb2e8e48540ed76fb61e973f4951a821c3e8bb918facc03d", + "2a2ff0dd38c79ab13fb6b06751824e93", + "82f65ba66f8fc6d8e1f4e1f41976eed8", + ); + test( + "dee818b6a894121e5e967e2218bb8772b9486bec2241377fdcfed7db75f3b724", + "eebc705f33d00fdf7c8add2481c62767", + "bee070b25e969ea87578daa1c7831651", + ); + test( + "c9b653644f3d3adc3498c029a1373b63f548e853deadc48e559b1a0a05e5c543", + "bef0968fc6adb8ce96bfa99642481624", + "859fc5f637ee1ee835b6f9a3f16a41f8", + ); + test( + "8e65798ebbae347c969ef9778e04e06649e3765aa58f5cd776b6ee58afde98ff", + "629f87e95b67e7bd5a3af528379cbef7", + "04a697b4fb82466950e9c0668e8c3eb9", + ); + test( + "4c0f6a402316b3a73e2a778f20ca3f8335e7a7bb5aecdaf9db91664604b74d62", + "3c9ab665451100d8d21029f96edf85f3", + "de5e23b1ba21a16ac01098937b26f3a9", + ); + test( + "a0b2cb30088b6145d9651ed019b0d051e4e6bf6cc0c8165dc76e3aa9fa9849f0", + "6a007f218c3f8b97c8489fe56433c99a", + "1885d448a81b0a048cc241275b9d7dce", + ); + } + + #[test] + fn test_aesb_single_round() { + let test = |key_hex: &str, input_hex: &str, expected_out: &str| { + // TODO: Show that both big and little endian work + let round_key = u128::from_ne_bytes(hex_to_array(key_hex)); + let mut block = u128::from_ne_bytes(hex_to_array(input_hex)); + + aesb_single_round(&mut block, round_key); + assert_eq!(expected_out, hex::encode(block.to_ne_bytes())); + }; + + test( + "9af7bd044f96bba5251ebd8065f4c757", + "8844d7f6f6aa2df5706ef0e7b26a3410", + "a03593fc2b9b906069bfc3a86a12e7fe", + ); + test( + "9749a59d1ee692c3b70b9c38a0c88369", + "f96b5a1984f7c57b92d7b2e82dd0ce46", + "6b03d4c6edf7a914265ca765b784c5ee", + ); + test( + "20959275e137a08267e35afe66f9adeb", + "3fce6f546d8fbc15bd9c6ac7d533eae4", + "34692b49471e37df3cbe43a9459ebe97", + ); + test( + "5c429524d022dc5dd48f7cd529fdf4f2", + "3edae93308c9aab4dfb6bfcd8e4012af", + "2e3061ce680d75177bac5b7af3182543", + ); + test( + "e76c56ca69a7309866a730e8976da086", + "39d8ee732a115b6b21f89ca181bd9ddc", + "069ef0b7aaada2b65ea9665827dae9ae", + ); + test( + "afd540af324c4fcda6246c657424a3ce", + "a5a01d75141522ff1ea717083abc5b5e", + "e0320cbc9dd8279c5f7d121ef7e1ae46", + ); + test( + "dfe9ba9468ccf1ac20a305730d1bdcb7", + "7be56ce9d924bf2fc4b574e225676f3c", + "bee2b49ed0b578b2c94b03a8930d990c", + ); + test( + "381e788a8d3389f27fe9aff054a0b407", + "b8d2600f71b0e9535d17c00ba90246f6", + "2a305ae7f7f3f44a43cd0342180b9394", + ); + test( + "16a94460158a5512052626f6cb080d6d", + "5ea0c238c05b8f3a913c1b36102eabeb", + "ab8040d7395cc940ea2a47610989ceb1", + ); + test( + "7e584682efb38bf2adfc6f1958fe08ff", + "80c78bb6ca2f114cbcb49fbaadaee9d1", + "25f7717cdbaa9c614424ef3d4e9543ec", + ); + } +} diff --git a/cryptonight/src/hash_v2.rs b/cryptonight/src/hash_v2.rs new file mode 100644 index 0000000..28e944d --- /dev/null +++ b/cryptonight/src/hash_v2.rs @@ -0,0 +1,470 @@ +use crate::slow_hash::{Variant, MEMORY_BLOCKS}; + +const U64_MASK: u128 = u64::MAX as u128; + +/// Original C code: +/// +/// If we kept the C code organization, this function would be in `slow_hash.rs`, but it's +/// here in the rust code to keep the `slow_hash.rs` file size manageable. +pub(crate) fn variant2_shuffle_add( + c1: &mut u128, + a: u128, + b: &[u128; 2], + long_state: &mut [u128; MEMORY_BLOCKS], + offset: usize, + variant: Variant, +) { + if !matches!(variant, Variant::V2 | Variant::R) { + return; + } + + let chunk1_start = offset ^ 0x1; + let chunk2_start = offset ^ 0x2; + let chunk3_start = offset ^ 0x3; + + let chunk1 = long_state[chunk1_start]; + let chunk2 = long_state[chunk2_start]; + let chunk3 = long_state[chunk3_start]; + + let chunk1_old = chunk1; + let chunk2_old = chunk2; + let chunk3_old = chunk3; + + let b1 = b[1]; + + let chunk1 = &mut long_state[chunk1_start]; + let sum1 = chunk3_old.wrapping_add(b1) & U64_MASK; + let sum2 = (chunk3_old >> 64).wrapping_add(b1 >> 64) & U64_MASK; + *chunk1 = sum2 << 64 | sum1; // TODO remove some shifting above + + let chunk3 = &mut long_state[chunk3_start]; + let sum1 = chunk2_old.wrapping_add(a) & U64_MASK; + let sum2 = (chunk2_old >> 64).wrapping_add(a >> 64) & U64_MASK; + *chunk3 = sum2 << 64 | sum1; + + let b0 = b[0]; + let chunk2 = &mut long_state[chunk2_start]; + let sum1 = chunk1_old.wrapping_add(b0) & U64_MASK; + let sum2 = (chunk1_old >> 64).wrapping_add(b0 >> 64) & U64_MASK; + *chunk2 = sum2 << 64 | sum1; + + if variant == Variant::R { + *c1 ^= chunk1_old ^ chunk2_old ^ chunk3_old; + } +} + +#[expect( + clippy::cast_sign_loss, + clippy::cast_precision_loss, + clippy::cast_possible_truncation +)] +pub(crate) fn variant2_integer_math_sqrt(sqrt_input: u64) -> u64 { + // Get an approximation using floating point math + let mut sqrt_result = + ((sqrt_input as f64 + 18_446_744_073_709_552_000.0).sqrt() * 2.0 - 8589934592.0) as u64; + + // Fixup the edge cases to get the exact integer result. For more information, + // see: https://github.com/monero-project/monero/blob/v0.18.3.3/src/crypto/variant2_int_sqrt.h#L65-L152 + let sqrt_div2 = sqrt_result >> 1; + let lsb = sqrt_result & 1; + let r2 = sqrt_div2 + .wrapping_mul(sqrt_div2 + lsb) + .wrapping_add(sqrt_result << 32); + + if r2.wrapping_add(lsb) > sqrt_input { + sqrt_result = sqrt_result.wrapping_sub(1); + } + if r2.wrapping_add(1 << 32) < sqrt_input.wrapping_sub(sqrt_div2) { + // Not sure that this is possible. I tried writing a test program + // to search subsets of u64 for a value that can trigger this + // branch, but couldn't find anything. The Go implementation came + // to the same conclusion: + // https://github.com/Equim-chan/cryptonight/blob/v0.3.0/arith_ref.go#L39-L45 + sqrt_result = sqrt_result.wrapping_add(1); + } + + sqrt_result +} + +/// Original C code: +/// +#[expect(clippy::cast_possible_truncation)] +pub(crate) fn variant2_integer_math( + c2: &mut u128, + c1: u128, + division_result: &mut u64, + sqrt_result: &mut u64, + variant: Variant, +) { + const U32_MASK: u64 = u32::MAX as u64; + + if variant != Variant::V2 { + return; + } + + let tmpx = *division_result ^ (*sqrt_result << 32); + *c2 ^= u128::from(tmpx); + + let c1_low = c1 as u64; + let dividend = (c1 >> 64) as u64; + + let divisor = ((c1_low.wrapping_add((*sqrt_result << 1) & U32_MASK)) | 0x80000001) & U32_MASK; + *division_result = ((dividend / divisor) & U32_MASK).wrapping_add((dividend % divisor) << 32); + + let sqrt_input = c1_low.wrapping_add(*division_result); + *sqrt_result = variant2_integer_math_sqrt(sqrt_input); +} + +#[cfg(test)] +mod tests { + use digest::Digest; + use groestl::Groestl256; + + use super::*; + use crate::{ + cnaes::AES_BLOCK_SIZE, + slow_hash::MEMORY_BLOCKS, + util::{hex_to_array, subarray_mut}, + }; + + #[test] + fn test_variant2_integer_math() { + fn test( + c2_hex: &str, + c1_hex: &str, + division_result: u64, + sqrt_result: u64, + c2_hex_end: &str, + division_result_end: u64, + sqrt_result_end: u64, + ) { + let mut c2 = u128::from_le_bytes(hex_to_array(c2_hex)); + let c1 = u128::from_le_bytes(hex_to_array(c1_hex)); + let mut division_result = division_result; + let mut sqrt_result = sqrt_result; + + variant2_integer_math( + &mut c2, + c1, + &mut division_result, + &mut sqrt_result, + Variant::V2, + ); + + assert_eq!(hex::encode(c2.to_le_bytes()), c2_hex_end); + assert_eq!(division_result, division_result_end); + assert_eq!(sqrt_result, sqrt_result_end); + } + + test( + "00000000000000000000000000000000", + "0100000000000000ffffffffffffffff", + u64::MAX, + u64::MAX, + "ffffffff000000000000000000000000", + 1, + 0, + ); + test( + "8b4d610801fe2049741c4cf1a11912d5", + "ef9d5925ad73f044f6310bce80f333a4", + 1992885167645223034, + 15156498822412360757, + "f125c247b4040b0e741c4cf1a11912d5", + 11701596267494179432, + 3261805857, + ); + test( + "540ac7dbbddf5b93fdc90f999408b7ad", + "10d2c1fdcbf7246e8623a3d946bdf422", + 6226440187041759132, + 1708636566, + "c83510b077a4e4a0fdc90f999408b7ad", + 6478148604080708997, + 2875078897, + ); + test( + "0df28c3c3570ae3b68dc9d6c5a486ed7", + "a5fba99aa63fa032acf1bd65ff4df3f2", + 11107069037757228366, + 2924318811, + "4397ce171fdcc70f68dc9d6c5a486ed7", + 7549089838000449301, + 2299293038, + ); + test( + "bfe14f97a968a35d0dcd6890a03c2913", + "d4a80e16ad64e3a0624a795c7b349c8a", + 15584044376391133794, + 276486141, + "dd4bf8759e1a9c950dcd6890a03c2913", + 4771913259875991617, + 3210383690, + ); + + test( + "820692e47779a9aabf0621e52a142468", + "df61b75f65251ee61828166e565336a9", + 3269677112081011360, + 1493829760, + "2254426ff54bc3debf0621e52a142468", + 2626216843989114230, + 175440206, + ); + test( + "0b364e61de218e00e83c4073b39daa2e", + "cc463d4543eb430d08efedf2be86e322", + 7096668609104405526, + 713261042, + "1d521b6fac307148e83c4073b39daa2e", + 8234613052379859783, + 1924288792, + ); + test( + "bd8fff861f6315c2be812b64cbdcf646", + "38d1e323d9dc282fa5e68f2ecbdcb950", + 9545374795048279136, + 271106137, + "dd532ef48b584a56be812b64cbdcf646", + 2790373411402251888, + 1336862722, + ); + test( + "ed57e73448f357bf04dc831d5e8fd848", + "a5dcd0971e6ded60d4d98c03cd8ba205", + 5991074580974163125, + 2246952057, + "580331e9a7a59e6904dc831d5e8fd848", + 7395390641079862703, + 2868947253, + ); + test( + "07ea0ffc6e182a7e97853f82e459d625", + "7e403d950f4adc97b90140875c33d65f", + 8836830558353968711, + 1962375668, + "40a40e3f08db7f7097853f82e459d625", + 5478469695216926448, + 3219877666, + ); + test( + "b77688d600a356077021e2333ee3def4", + "7a9f061760287a69b57f365163fb9dac", + 3127636279441542418, + 1585025819, + "a5bb34d8bba848727021e2333ee3def4", + 3683326568856788118, + 2315202244, + ); + test( + "a246a7f62b7e3d9a0b5ac66166bfcba3", + "23329476afdbd46d3be9d3ccc9011c11", + 12123559059253265496, + 819016365, + "fac2e5d23dc4d3020b5ac66166bfcba3", + 4214751652441358299, + 2469122821, + ); + test( + "3e1abb8109c688405cd6c866cbdb3e13", + "b4c10bf5e06c069928afa173f62d5017", + 7368515032603121941, + 2312559799, + "2b43d451df231caf5cd6c866cbdb3e13", + 1324536149240623108, + 2509236669, + ); + test( + "a31260db7c73f249b5fbc182ae7fcc8e", + "b4214755b0003e4c82d03f80d8a06bed", + 1904095218141907119, + 92928147, + "0c5abeec6c3f1756b5fbc182ae7fcc8e", + 9883090335304272258, + 3041688469, + ); + test( + "e3d0bc3e619f577a1eea5adba205e494", + "cd8040848aae39104c310c1fa0eed9b8", + 4873400164336079541, + 2436984787, + "56c22935133bb7a81eea5adba205e494", + 8226478499779865232, + 1963241245, + ); + test( + "f22ac244fd17cf5e3ec21bece2581a2d", + "785152f272ffa9514ef2ae0bed5cbaa7", + 6386228481616770937, + 1413583152, + "8bddfda13af62e523ec21bece2581a2d", + 9654977853452823978, + 3069608655, + ); + test( + "37b3921988d9df1b38b04dc1db01a41b", + "054b87f38d203eddb16d458048f3b97b", + 5592059432235016971, + 2670380708, + "3c10afec40e36fc938b04dc1db01a41b", + 2475375116655310772, + 3553266751, + ); + test( + "cfd4afb021e526d9cbd4720cc47c4ce2", + "a2e3e7fe936c2b38e3708965f2dfc586", + 11958325643570725319, + 825185219, + "0895d52d3237fd4dcbd4720cc47c4ce2", + 2253955666499039951, + 1359567468, + ); + test( + "55d2ea9570994bc0aeaf6a3189bf0b4a", + "9d102c34665382dfd36e39a67e07b8aa", + 10171590341391886242, + 541577843, + "f7f59fbe85f4246daeaf6a3189bf0b4a", + 6907584596503955220, + 1004462004, + ); + test( + "bf32b60d6bbaa87cececd577f2ad15d8", + "9a8471b2b72e9d39cd2d2cb124aa270a", + 9778648685358392468, + 469385479, + "2b9696774746e6e0ececd577f2ad15d8", + 4910280747850874346, + 1899784302, + ); + test( + "d70ac5de7a390e2a735726324d0b52b5", + "6cf5b75b005599047972995ffbe34101", + 2318211298357120319, + 1093372020, + "e8871a66ea410e4b735726324d0b52b5", + 14587709575956469579, + 2962700286, + ); + test( + "412f463e5143eace451dcb2a2efd8022", + "38ed251c7915236b2aca4ea995b861c9", + 10458537212399393571, + 621387691, + "623403e9d4ecc77a451dcb2a2efd8022", + 12914179687381327414, + 495045866, + ); + } + + #[test] + fn test_variant2_integer_math_sqrt() { + // Edge case values taken from here: + // https://github.com/monero-project/monero/blob/v0.18.3.3/src/crypto/variant2_int_sqrt.h#L33-L43 + let test_cases = [ + (0, 0), + (1 << 32, 0), + ((1 << 32) + 1, 1), + (1 << 50, 262140), + ((1 << 55) + 20963331, 8384515), + ((1 << 55) + 20963332, 8384516), + ((1 << 62) + 26599786, 1013904242), + ((1 << 62) + 26599787, 1013904243), + (u64::MAX, 3558067407), + ]; + + for &(input, expected) in &test_cases { + assert_eq!( + variant2_integer_math_sqrt(input), + expected, + "input = {input}" + ); + } + } + + #[test] + fn test_variant2_shuffle_add() { + #[expect(clippy::cast_possible_truncation)] + fn test( + c1_hex: &str, + a_hex: &str, + b_hex: &str, + offset: usize, + variant: Variant, + c1_hex_end: &str, + long_state_end_hash: &str, + ) { + let mut c1 = u128::from_le_bytes(hex_to_array(c1_hex)); + let a = u128::from_le_bytes(hex_to_array(a_hex)); + let b: [u128; 2] = [ + u128::from_le_bytes(hex_to_array(&b_hex[0..AES_BLOCK_SIZE * 2])), + u128::from_le_bytes(hex_to_array(&b_hex[AES_BLOCK_SIZE * 2..])), + ]; + + // Every byte of long_state memory is initialized with it's offset index mod 256 + // when the u128 blocks are converted to bytes in native endian format. + let mut long_state: Vec = Vec::with_capacity(MEMORY_BLOCKS); + for i in 0..long_state.capacity() { + let mut block = [0_u8; AES_BLOCK_SIZE]; + for (j, byte) in block.iter_mut().enumerate() { + *byte = (i * AES_BLOCK_SIZE + j) as u8; + } + long_state.push(u128::from_le_bytes(block)); + } + + variant2_shuffle_add( + &mut c1, + a, + &b, + subarray_mut(&mut long_state, 0), + offset, + variant, + ); + assert_eq!(hex::encode(c1.to_le_bytes()), c1_hex_end); + let mut hash = Groestl256::new(); + for block in long_state { + hash.update(block.to_le_bytes()); + } + let hash = hex::encode(hash.finalize().as_slice()); + + assert_eq!(hash, long_state_end_hash); + } + + test( + "d7143e3b6ffdeae4b2ceea30e9889c8a", + "875fa34de3af48f15638bad52581ef4c", + "b07d6f24f19434289b305525f094d8d7bd9d3c9bc956ac081d6186432a282a36", + 221056 / AES_BLOCK_SIZE, + Variant::R, + "5795bcb8eb786c633a4760bb65051205", + "26c32c4c2eeec340d62b88f5261d1a264c74240c2f8424c6e7101cf490e5772e", + ); + test( + "c7d6fe95ffd8d902d2cfc1883f7a2bc3", + "bceb9d8cb71c2ac85c24129c94708e17", + "4b3a589c187e26bea487b19ea36eb19e8369f4825642eb467c75bf07466b87ba", + 1960880 / AES_BLOCK_SIZE, + Variant::V2, + "c7d6fe95ffd8d902d2cfc1883f7a2bc3", + "2d4ddadd0e53a02797c62bf37d11bb2de73e6769abd834a81c1262752176a024", + ); + test( + "92ad41fc1596244e2e0f0bfed6555cef", + "d1f0337e48c4f53742cedd78b6b33b67", + "b17bce6c44e0f680aa0f0a28a4e3865b43cdd18644a383e7a9d2f17310e5b6aa", + 1306832 / AES_BLOCK_SIZE, + Variant::R, + "427c932fc143f299f6d6d1250a888230", + "984440e0b9f77f1159f09b13d2d455292d5a9b4095037f4e8ca2a0ed982bee8f", + ); + test( + "7e2c813d10f06d4b8af85389bc82eb18", + "74fc41829b88f55e62aec4749685b323", + "7a00c480b31d851359d78fad279dcd343bcd6a5f902ac0b55da656d735dbf329", + 130160 / AES_BLOCK_SIZE, + Variant::V2, + "7e2c813d10f06d4b8af85389bc82eb18", + "6ccb68ee6fc38a6e91f546f62b8e1a64b5223a4a0ef916e6062188c4ee15a879", + ); + } +} diff --git a/cryptonight/src/hash_v4.rs b/cryptonight/src/hash_v4.rs new file mode 100644 index 0000000..818cf1b --- /dev/null +++ b/cryptonight/src/hash_v4.rs @@ -0,0 +1,548 @@ +use std::cmp::max; + +use seq_macro::seq; +use InstructionList::{Add, Mul, Ret, Rol, Ror, Sub, Xor}; + +use crate::{ + blake256::{Blake256, Digest}, + util::subarray_copy, +}; + +const TOTAL_LATENCY: usize = 15 * 3; +const NUM_INSTRUCTIONS_MIN: usize = 60; +pub(crate) const NUM_INSTRUCTIONS_MAX: usize = 70; +const ALU_COUNT_MUL: usize = 1; +const ALU_COUNT: usize = 3; + +#[repr(u8)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum InstructionList { + Mul, // a*b + Add, // a+b + C, C is an unsigned 32-bit constant + Sub, // a-b + Ror, // rotate right "a" by "b & 31" bits + Rol, // rotate left "a" by "b & 31" bits + Xor, // a^b + #[default] + Ret, // finish execution +} + +const INSTRUCTION_COUNT: usize = Ret as usize; + +/// INSTRUCTION_* constants are used to generate code from random data. +/// Every random sequence of bytes is a valid code. +/// +/// There are 9 registers in total: +/// - 4 variable registers +/// - 5 constant registers initialized from loop variables +const INSTRUCTION_OPCODE_BITS: usize = 3; +const INSTRUCTION_DST_INDEX_BITS: usize = 2; +const INSTRUCTION_SRC_INDEX_BITS: usize = 3; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) struct Instruction { + pub(crate) opcode: InstructionList, + pub(crate) dst_index: u8, + pub(crate) src_index: u8, + pub(crate) c: u32, +} + +/// If we don't have enough data available, generate more. +/// Original C code: +/// +fn check_data(data_index: &mut usize, bytes_needed: usize, data: &mut [u8]) { + if *data_index + bytes_needed > data.len() { + let output = Blake256::digest(&data); + data.copy_from_slice(&output); + *data_index = 0; + } +} + +/// Generates as many random math operations as possible with given latency and +/// ALU restrictions. +/// +/// Original C code: +/// +/// +#[expect(clippy::cast_sign_loss)] +#[expect(clippy::cast_possible_wrap)] +#[expect(clippy::cast_possible_truncation)] +pub(crate) fn random_math_init( + code: &mut [Instruction; NUM_INSTRUCTIONS_MAX + 1], + height: u64, +) -> usize { + // MUL is 3 cycles, 3-way addition and rotations are 2 cycles, SUB/XOR are 1 + // cycle These latencies match real-life instruction latencies for Intel + // CPUs starting from Sandy Bridge and up to Skylake/Coffee lake + // + // AMD Ryzen has the same latencies except 1-cycle ROR/ROL, so it'll be a bit + // faster than Intel Sandy Bridge and newer processors Surprisingly, Intel + // Nehalem also has 1-cycle ROR/ROL, so it'll also be faster than Intel Sandy + // Bridge and newer processors AMD Bulldozer has 4 cycles latency for MUL + // (slower than Intel) and 1 cycle for ROR/ROL (faster than Intel), so average + // performance will be the same Source: https://www.agner.org/optimize/instruction_tables.pdf + const OP_LATENCY: [usize; INSTRUCTION_COUNT] = [3, 2, 1, 2, 2, 1]; + + // Instruction latencies for theoretical ASIC implementation + const ASIC_OP_LATENCY: [usize; INSTRUCTION_COUNT] = [3, 1, 1, 1, 1, 1]; + + // Available ALUs for each instruction + const OP_ALUS: [usize; INSTRUCTION_COUNT] = [ + ALU_COUNT_MUL, + ALU_COUNT, + ALU_COUNT, + ALU_COUNT, + ALU_COUNT, + ALU_COUNT, + ]; + + let mut data = [0_u8; 32]; + data[0..8].copy_from_slice(&height.to_le_bytes()); + + data[20] = -38_i8 as u8; // change seed + + // Set data_index past the last byte in data + // to trigger full data update with blake hash + // before we start using it + let mut data_index: usize = data.len(); + + let mut code_size: usize; + + // There is a small chance (1.8%) that register R8 won't be used in the + // generated program, so we keep track of it and try again if it's not used + loop { + let mut latency = [0_usize; 9]; + let mut asic_latency = [0_usize; 9]; + + // Tracks previous instruction and value of the source operand for + // registers R0-R3 throughout code execution: + // byte 0: current value of the destination register + // byte 1: instruction opcode + // byte 2: current value of the source register + // + // Registers R4-R8 are constant and are treated as having the same + // value, because when we do the same operation twice with two constant + // source registers, it can be optimized into a single operation. + let mut inst_data: [usize; 9] = + [0, 1, 2, 3, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF]; + + let mut alu_busy = [[false; ALU_COUNT]; TOTAL_LATENCY + 1]; + let mut is_rotation = [false; INSTRUCTION_COUNT]; + is_rotation[Ror as usize] = true; + is_rotation[Rol as usize] = true; + let mut rotated = [false; 4]; + let mut rotate_count = 0_usize; + + let mut num_retries = 0_usize; + code_size = 0; + + let mut total_iterations = 0_usize; + let mut r8_used = false; + + // Generate random code to achieve minimal required latency for our abstract CPU + // Try to get this latency for all 4 registers + while (latency[0] < TOTAL_LATENCY + || latency[1] < TOTAL_LATENCY + || latency[2] < TOTAL_LATENCY + || latency[3] < TOTAL_LATENCY) + && num_retries < 64 + { + // Fail-safe to guarantee loop termination + total_iterations += 1; + if total_iterations > 256 { + break; + } + + check_data(&mut data_index, 1, &mut data); + + let c = data[data_index]; + data_index += 1; + + // MUL = opcodes 0-2 + // ADD = opcode 3 + // SUB = opcode 4 + // ROR/ROL = opcode 5, shift direction is selected randomly + // XOR = opcodes 6-7 + let opcode_bits = c & ((1 << INSTRUCTION_OPCODE_BITS) - 1); + let opcode: InstructionList; + if opcode_bits == 5 { + check_data(&mut data_index, 1, &mut data); + opcode = if data[data_index] as i8 >= 0 { + Ror + } else { + Rol + }; + data_index += 1; + } else if opcode_bits >= 6 { + opcode = Xor; + } else if opcode_bits <= 2 { + opcode = Mul; + } else { + // remaining values are 3-4 + opcode = match opcode_bits { + 3 => Add, + 4 => Sub, + _ => unreachable!(), + }; + } + + let dst_index = + (c >> INSTRUCTION_OPCODE_BITS) & ((1 << INSTRUCTION_DST_INDEX_BITS) - 1); + let mut src_index = (c >> (INSTRUCTION_OPCODE_BITS + INSTRUCTION_DST_INDEX_BITS)) + & ((1 << INSTRUCTION_SRC_INDEX_BITS) - 1); + + let a = dst_index as usize; + let mut b = src_index as usize; + + // Don't do ADD/SUB/XOR with the same register + if matches!(opcode, Add | Sub | Xor) && a == b { + b = 8; + src_index = 8; + } + + // Don't do rotation with the same destination twice because it's equal to a + // single rotation + if is_rotation[opcode as usize] && rotated[a] { + continue; + } + + // Don't do the same instruction (except MUL) with the same source value twice, + // because all other cases can be optimized: + // 2xADD(a, b, C) = ADD(a,b*2, C1+C2), + // Same for SUB and rotations: + // 2xXOR(a, b) = NOP + if opcode != Mul + && inst_data[a] & 0xFFFF00 + == ((opcode as usize) << 8) + ((inst_data[b] & 255) << 16) + { + continue; + } + + // Find which ALU is available (and when) for this instruction + let mut next_latency = if latency[a] > latency[b] { + latency[a] + } else { + latency[b] + }; + let mut alu_index = -1; + while next_latency < TOTAL_LATENCY { + for i in (0..OP_ALUS[opcode as usize]).rev() { + if alu_busy[next_latency][i] { + continue; + } + + if opcode == Add && alu_busy[next_latency + 1][i] { + continue; + } + + if is_rotation[opcode as usize] + && next_latency < (rotate_count * OP_LATENCY[opcode as usize]) + { + continue; + } + + alu_index = i as isize; + break; + } + if alu_index >= 0 { + break; + } + next_latency += 1; + } + + // Don't generate instructions that leave some register unchanged for more than + // 7 cycles + if next_latency > latency[a] + 7 { + continue; + } + + next_latency += OP_LATENCY[opcode as usize]; + + if next_latency <= TOTAL_LATENCY { + if is_rotation[opcode as usize] { + rotate_count += 1; + } + + // Mark ALU as busy only for the first cycle when it starts executing the + // instruction because ALUs are fully pipelined. + alu_busy[next_latency - OP_LATENCY[opcode as usize]][alu_index as usize] = true; + latency[a] = next_latency; + + // ASIC is supposed to have enough ALUs to run as many independent instructions + // per cycle as possible, so latency calculation for ASIC is straightforward. + asic_latency[a] = + max(asic_latency[a], asic_latency[b]) + ASIC_OP_LATENCY[opcode as usize]; + + rotated[a] = is_rotation[opcode as usize]; + + inst_data[a] = code_size + ((opcode as usize) << 8) + ((inst_data[b] & 255) << 16); + + code[code_size].opcode = opcode; + code[code_size].dst_index = dst_index; + code[code_size].src_index = src_index; + code[code_size].c = 0; + + if src_index == 8 { + r8_used = true; + } + + if opcode == Add { + alu_busy[next_latency - OP_LATENCY[opcode as usize] + 1][alu_index as usize] = + true; + + check_data(&mut data_index, size_of::(), &mut data); + code[code_size].c = u32::from_le_bytes(subarray_copy(&data, data_index)); + data_index += 4; + } + code_size += 1; + if code_size >= NUM_INSTRUCTIONS_MIN { + break; + } + } else { + num_retries += 1; + } + } + + // ASIC has more execution resources and can extract as much parallelism + // from the code as possible. We need to add a few more MUL and ROR + // instructions to achieve minimal required latency for ASIC. Get this + // latency for at least 1 of the 4 registers. + let prev_code_size = code_size; + while code_size < NUM_INSTRUCTIONS_MAX + && asic_latency.iter().take(4).all(|&lat| lat < TOTAL_LATENCY) + { + let mut min_idx: usize = 0; + let mut max_idx: usize = 0; + for i in 1..4 { + if asic_latency[i] < asic_latency[min_idx] { + min_idx = i; + } + if asic_latency[i] > asic_latency[max_idx] { + max_idx = i; + } + } + + let pattern = [Ror, Mul, Mul]; + let opcode = pattern[(code_size - prev_code_size) % 3]; + latency[min_idx] = latency[max_idx] + OP_LATENCY[opcode as usize]; + asic_latency[min_idx] = asic_latency[max_idx] + ASIC_OP_LATENCY[opcode as usize]; + + code[code_size] = Instruction { + opcode, + dst_index: min_idx as u8, + src_index: max_idx as u8, + c: 0, + }; + code_size += 1; + } + + // There is ~98.15% chance that loop condition is false, so this loop will + // execute only 1 iteration most of the time. It never does more than 4 + // iterations for all block heights < 10,000,000. + + if r8_used && (NUM_INSTRUCTIONS_MIN..=NUM_INSTRUCTIONS_MAX).contains(&code_size) { + break; + } + } + + // It's guaranteed that NUM_INSTRUCTIONS_MIN <= code_size <= + // NUM_INSTRUCTIONS_MAX here. Add final instruction to stop the interpreter. + code[code_size].opcode = Ret; + code[code_size].dst_index = 0; + code[code_size].src_index = 0; + code[code_size].c = 0; + + code_size +} + +/// Original C code: +/// +#[expect(clippy::needless_return)] // last iteration of unrolled loop +pub(crate) fn v4_random_math(code: &[Instruction; NUM_INSTRUCTIONS_MAX + 1], r: &mut [u32; 9]) { + const REG_BITS: u32 = 32; + + debug_assert_eq!(NUM_INSTRUCTIONS_MAX, 70); + seq!(i in 0..70 { + let op = &code[i]; + let src = r[op.src_index as usize]; + let dst = &mut r[op.dst_index as usize]; + match op.opcode { + Mul => *dst = dst.wrapping_mul(src), + Add => *dst = dst.wrapping_add(src).wrapping_add(op.c), + Sub => *dst = dst.wrapping_sub(src), + Ror => *dst = dst.rotate_right(src % REG_BITS), + Rol => *dst = dst.rotate_left(src % REG_BITS), + Xor => *dst ^= src, + Ret => return, + } + }); +} + +/// Original C code: +/// +/// To match the C code organization, this function would be in `slow_hash.rs`, but +/// the test code for it is so large, that it was moved here. +#[expect(clippy::cast_possible_truncation)] +pub(crate) fn variant4_random_math( + a1: &mut u128, + c2: &mut u128, + r: &mut [u32; 9], + b: &[u128; 2], + code: &[Instruction; 71], +) { + let t64 = u64::from(r[0].wrapping_add(r[1])) | (u64::from(r[2].wrapping_add(r[3])) << 32); + *c2 ^= u128::from(t64); + + r[4] = *a1 as u32; + r[5] = (*a1 >> 64) as u32; + r[6] = b[0] as u32; + r[7] = b[1] as u32; + r[8] = (b[1] >> 64) as u32; + + v4_random_math(code, r); + + *a1 ^= + u128::from(r[2]) | u128::from(r[3]) << 32 | u128::from(r[0]) << 64 | u128::from(r[1]) << 96; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::hex_to_array; + + #[rustfmt::skip] + const CODE: [Instruction; 71] = [ + Instruction { opcode: Rol, dst_index: 0, src_index: 7, c: 0 }, + Instruction { opcode: Mul, dst_index: 3, src_index: 1, c: 0 }, + Instruction { opcode: Add, dst_index: 2, src_index: 7, c: 3553557725 }, + Instruction { opcode: Sub, dst_index: 0, src_index: 8, c: 0 }, + Instruction { opcode: Add, dst_index: 3, src_index: 4, c: 3590470404 }, + Instruction { opcode: Xor, dst_index: 1, src_index: 0, c: 0 }, + Instruction { opcode: Xor, dst_index: 1, src_index: 5, c: 0 }, + Instruction { opcode: Xor, dst_index: 1, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 7, c: 0 }, + Instruction { opcode: Mul, dst_index: 2, src_index: 1, c: 0 }, + Instruction { opcode: Mul, dst_index: 2, src_index: 4, c: 0 }, + Instruction { opcode: Mul, dst_index: 2, src_index: 7, c: 0 }, + Instruction { opcode: Sub, dst_index: 1, src_index: 8, c: 0 }, + Instruction { opcode: Add, dst_index: 0, src_index: 6, c: 1516169632 }, + Instruction { opcode: Add, dst_index: 2, src_index: 0, c: 1587456779 }, + Instruction { opcode: Mul, dst_index: 3, src_index: 5, c: 0 }, + Instruction { opcode: Mul, dst_index: 1, src_index: 0, c: 0 }, + Instruction { opcode: Xor, dst_index: 2, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Sub, dst_index: 3, src_index: 6, c: 0 }, + Instruction { opcode: Rol, dst_index: 3, src_index: 0, c: 0 }, + Instruction { opcode: Xor, dst_index: 2, src_index: 4, c: 0 }, + Instruction { opcode: Mul, dst_index: 3, src_index: 5, c: 0 }, + Instruction { opcode: Xor, dst_index: 2, src_index: 0, c: 0 }, + Instruction { opcode: Rol, dst_index: 2, src_index: 4, c: 0 }, + Instruction { opcode: Xor, dst_index: 3, src_index: 8, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 4, c: 0 }, + Instruction { opcode: Add, dst_index: 2, src_index: 3, c: 2235486112 }, + Instruction { opcode: Xor, dst_index: 0, src_index: 3, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 2, c: 0 }, + Instruction { opcode: Xor, dst_index: 2, src_index: 7, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 7, c: 0 }, + Instruction { opcode: Ror, dst_index: 0, src_index: 4, c: 0 }, + Instruction { opcode: Mul, dst_index: 3, src_index: 2, c: 0 }, + Instruction { opcode: Add, dst_index: 2, src_index: 3, c: 382729823 }, + Instruction { opcode: Mul, dst_index: 1, src_index: 4, c: 0 }, + Instruction { opcode: Sub, dst_index: 3, src_index: 5, c: 0 }, + Instruction { opcode: Add, dst_index: 3, src_index: 7, c: 446636115 }, + Instruction { opcode: Sub, dst_index: 0, src_index: 5, c: 0 }, + Instruction { opcode: Add, dst_index: 1, src_index: 8, c: 1136500848 }, + Instruction { opcode: Xor, dst_index: 3, src_index: 8, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 4, c: 0 }, + Instruction { opcode: Ror, dst_index: 3, src_index: 5, c: 0 }, + Instruction { opcode: Mul, dst_index: 2, src_index: 0, c: 0 }, + Instruction { opcode: Ror, dst_index: 0, src_index: 1, c: 0 }, + Instruction { opcode: Add, dst_index: 0, src_index: 7, c: 4221005163 }, + Instruction { opcode: Rol, dst_index: 0, src_index: 2, c: 0 }, + Instruction { opcode: Add, dst_index: 0, src_index: 7, c: 1789679560 }, + Instruction { opcode: Xor, dst_index: 0, src_index: 3, c: 0 }, + Instruction { opcode: Add, dst_index: 2, src_index: 8, c: 2725270475 }, + Instruction { opcode: Xor, dst_index: 1, src_index: 4, c: 0 }, + Instruction { opcode: Sub, dst_index: 3, src_index: 8, c: 0 }, + Instruction { opcode: Xor, dst_index: 3, src_index: 5, c: 0 }, + Instruction { opcode: Sub, dst_index: 3, src_index: 2, c: 0 }, + Instruction { opcode: Rol, dst_index: 2, src_index: 2, c: 0 }, + Instruction { opcode: Add, dst_index: 3, src_index: 6, c: 4110965463 }, + Instruction { opcode: Xor, dst_index: 2, src_index: 6, c: 0 }, + Instruction { opcode: Sub, dst_index: 2, src_index: 7, c: 0 }, + Instruction { opcode: Sub, dst_index: 3, src_index: 1, c: 0 }, + Instruction { opcode: Sub, dst_index: 1, src_index: 8, c: 0 }, + Instruction { opcode: Ror, dst_index: 1, src_index: 2, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 1, c: 0 }, + Instruction { opcode: Mul, dst_index: 2, src_index: 0, c: 0 }, + Instruction { opcode: Ret, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 }, + ]; + + #[test] + fn test1_variant4_random_math() { + let mut a1 = u128::from_le_bytes(hex_to_array("969ecd223474a6bb3be76c637db7457b")); + let mut c2 = u128::from_le_bytes(hex_to_array("dbd1f6404d4826c52f209951334e6ea7")); + let mut r: [u32; 9] = [1336109178, 464004736, 1552145461, 3528897376, 0, 0, 0, 0, 0]; + let b_bytes: [u8; 32] = + hex_to_array("8dfa6d2c82e1806367b844c15f0439ced99c9a4bae0badfb8a8cf8504b813b7d"); + let b: [u128; 2] = [ + u128::from_le_bytes(subarray_copy(&b_bytes, 0)), + u128::from_le_bytes(subarray_copy(&b_bytes, 16)), + ]; + + variant4_random_math(&mut a1, &mut c2, &mut r, &b, &CODE); + + assert_eq!( + hex::encode(a1.to_le_bytes()), + "1cb6fe7738de9e764dd73ea37c438056" + ); + assert_eq!( + hex::encode(c2.to_le_bytes()), + "215fbd2bd8c7fceb2f209951334e6ea7" + ); + #[rustfmt::skip] + assert_eq!(r, [ + 3226611830, 767947777, 1429416074, 3443042828, 583900822, 1668081467, 745405069, + 1268423897, 1358466186 + ]); + } + + #[test] + fn test2_variant4_random_math() { + let mut a1 = u128::from_le_bytes(hex_to_array("643955bde578c845e4898703c3ce5eaa")); + let mut c2 = u128::from_le_bytes(hex_to_array("787e2613b8fd0a2dadad16d4ec189035")); + let mut r: [u32; 9] = [ + 3226611830, 767947777, 1429416074, 3443042828, 583900822, 1668081467, 745405069, + 1268423897, 1358466186, + ]; + let b_bytes: [u8; 32] = + hex_to_array("d4d1e70f7da4089ae53b2e7545e4242a8dfa6d2c82e1806367b844c15f0439ce"); + let b: [u128; 2] = [ + u128::from_le_bytes(subarray_copy(&b_bytes, 0)), + u128::from_le_bytes(subarray_copy(&b_bytes, 16)), + ]; + + variant4_random_math(&mut a1, &mut c2, &mut r, &b, &CODE); + + assert_eq!( + hex::encode(a1.to_le_bytes()), + "c40cb4b3a3640a958cc919ccb4ff29e6" + ); + assert_eq!( + hex::encode(c2.to_le_bytes()), + "0f5a3efd2e2f610fadad16d4ec189035" + ); + #[rustfmt::skip] + assert_eq!(r, [ + 3483254888_u32, 1282879863, 249640352, 3502382150, 3176479076, 59214308, 266850772, + 745405069, 3242506343 + ]); + } +} diff --git a/cryptonight/src/lib.rs b/cryptonight/src/lib.rs index de8047f..a52c8d5 100644 --- a/cryptonight/src/lib.rs +++ b/cryptonight/src/lib.rs @@ -1,63 +1,40 @@ -#[link(name = "cryptonight")] -extern "C" { - fn cn_slow_hash( - data: *const u8, - length: usize, - hash: *mut u8, - variant: i32, - pre_hashed: i32, - height: u64, - ); -} +mod blake256; +mod cnaes; +mod hash_v2; +mod hash_v4; +mod slow_hash; +mod util; -/// Calculates the CryptoNight v0 hash of buf. -/// +use slow_hash::cn_slow_hash; + +/// Calculates the `CryptoNight` v0 hash of buf. pub fn cryptonight_hash_v0(buf: &[u8]) -> [u8; 32] { - let mut hash = [0; 32]; - unsafe { - cn_slow_hash(buf.as_ptr(), buf.len(), hash.as_mut_ptr(), 0, 0, 0); - } - hash + cn_slow_hash(buf, slow_hash::Variant::V0, 0) } #[derive(thiserror::Error, Debug, Copy, Clone, Eq, PartialEq)] #[error("Data can't be hashed")] pub struct DataCanNotBeHashed; -/// Calculates the CryptoNight v1 hash of buf. -/// -/// This will return an error if buf is less than43 bytes. +/// Calculates the `CryptoNight` v1 hash of buf. /// +/// This will return an error if buf is less than 43 bytes. pub fn cryptonight_hash_v1(buf: &[u8]) -> Result<[u8; 32], DataCanNotBeHashed> { if buf.len() < 43 { return Err(DataCanNotBeHashed); } - let mut hash = [0; 32]; - unsafe { - cn_slow_hash(buf.as_ptr(), buf.len(), hash.as_mut_ptr(), 1, 0, 0); - } - Ok(hash) + Ok(cn_slow_hash(buf, slow_hash::Variant::V1, 0)) } -/// Calculates the CryptoNight v2 hash of buf. -/// +/// Calculates the `CryptoNight` v2 hash of buf. pub fn cryptonight_hash_v2(buf: &[u8]) -> [u8; 32] { - let mut hash = [0; 32]; - unsafe { - cn_slow_hash(buf.as_ptr(), buf.len(), hash.as_mut_ptr(), 2, 0, 0); - } - hash + cn_slow_hash(buf, slow_hash::Variant::V2, 0) } -/// Calculates the CryptoNight R hash of buf. -/// +/// Calculates the `CryptoNight` R hash of buf. pub fn cryptonight_hash_r(buf: &[u8], height: u64) -> [u8; 32] { - let mut hash = [0; 32]; - unsafe { - cn_slow_hash(buf.as_ptr(), buf.len(), hash.as_mut_ptr(), 4, 0, height); - } - hash + cn_slow_hash(buf, slow_hash::Variant::R, height) } #[cfg(test)] @@ -66,10 +43,11 @@ mod tests { #[test] fn slow_hash_0() { - let test = |inp: &str, exp: &str| { + fn test(inp: &str, exp: &str) { let res = hex::encode(cryptonight_hash_v0(&hex::decode(inp).unwrap())); assert_eq!(&res, exp); - }; + } + // https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/tests/hash/tests-slow.txt test( "6465206f6d6e69627573206475626974616e64756d", @@ -91,10 +69,11 @@ mod tests { #[test] fn slow_hash_1() { - let test = |inp: &str, exp: &str| { + fn test(inp: &str, exp: &str) { let res = hex::encode(cryptonight_hash_v1(&hex::decode(inp).unwrap()).unwrap()); assert_eq!(&res, exp); - }; + } + // https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/tests/hash/tests-slow-1.txt test( "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -120,10 +99,11 @@ mod tests { #[test] fn slow_hash_2() { - let test = |inp: &str, exp: &str| { + fn test(inp: &str, exp: &str) { let res = hex::encode(cryptonight_hash_v2(&hex::decode(inp).unwrap())); assert_eq!(&res, exp); - }; + } + // https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/tests/hash/tests-slow-2.txt test( "5468697320697320612074657374205468697320697320612074657374205468697320697320612074657374", @@ -169,66 +149,66 @@ mod tests { #[test] fn slow_hash_r() { - let test = |inp: &str, exp: &str, height: u64| { + fn test(inp: &str, exp: &str, height: u64) { let res = hex::encode(cryptonight_hash_r(&hex::decode(inp).unwrap(), height)); assert_eq!(&res, exp); - }; + } // https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/tests/hash/tests-slow-4.txt test( "5468697320697320612074657374205468697320697320612074657374205468697320697320612074657374", "f759588ad57e758467295443a9bd71490abff8e9dad1b95b6bf2f5d0d78387bc", - 1806260 + 1806260, ); test( "4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e67", "5bb833deca2bdd7252a9ccd7b4ce0b6a4854515794b56c207262f7a5b9bdb566", - 1806261 + 1806261, ); test( "656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f7265", "1ee6728da60fbd8d7d55b2b1ade487a3cf52a2c3ac6f520db12c27d8921f6cab", - 1806262 + 1806262, ); test( "657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c", "6969fe2ddfb758438d48049f302fc2108a4fcc93e37669170e6db4b0b9b4c4cb", - 1806263 + 1806263, ); test( "71756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e697369", "7f3048b4e90d0cbe7a57c0394f37338a01fae3adfdc0e5126d863a895eb04e02", - 1806264 + 1806264, ); test( "757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e20447569732061757465", "1d290443a4b542af04a82f6b2494a6ee7f20f2754c58e0849032483a56e8e2ef", - 1806265 + 1806265, ); test( "757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e20447569732061757465", "1d290443a4b542af04a82f6b2494a6ee7f20f2754c58e0849032483a56e8e2ef", - 1806265 + 1806265, ); test( "697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c6974", "c43cc6567436a86afbd6aa9eaa7c276e9806830334b614b2bee23cc76634f6fd", - 1806266 + 1806266, ); test( "657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e", "87be2479c0c4e8edfdfaa5603e93f4265b3f8224c1c5946feb424819d18990a4", - 1806267 + 1806267, ); test( "4578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c", "dd9d6a6d8e47465cceac0877ef889b93e7eba979557e3935d7f86dce11b070f3", - 1806268 + 1806268, ); test( "73756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e", "75c6f2ae49a20521de97285b431e717125847fb8935ed84a61e7f8d36a2c3d8e", - 1806269 + 1806269, ); } } diff --git a/cryptonight/src/slow_hash.rs b/cryptonight/src/slow_hash.rs new file mode 100644 index 0000000..8f92fce --- /dev/null +++ b/cryptonight/src/slow_hash.rs @@ -0,0 +1,626 @@ +use std::{cmp::PartialEq, io::Write, mem::swap}; + +use cnaes::{AES_BLOCK_SIZE, CN_AES_KEY_SIZE}; +use digest::Digest as _; +use groestl::Groestl256; +use jh::Jh256; +use skein::{consts::U32, Skein512}; + +use crate::{ + blake256::{Blake256, Digest as _}, + cnaes, hash_v2 as v2, hash_v4 as v4, + util::{subarray, subarray_copy, subarray_mut}, +}; + +pub(crate) const MEMORY: usize = 1 << 21; // 2MB scratchpad +pub(crate) const MEMORY_BLOCKS: usize = MEMORY / AES_BLOCK_SIZE; + +const ITER: usize = 1 << 20; +const AES_KEY_SIZE: usize = CN_AES_KEY_SIZE; +const INIT_BLOCKS: usize = 8; +const INIT_SIZE_BYTE: usize = INIT_BLOCKS * AES_BLOCK_SIZE; + +const KECCAK1600_BYTE_SIZE: usize = 200; + +#[derive(PartialEq, Eq, Clone, Copy)] +pub(crate) enum Variant { + V0, + V1, + V2, + R, +} + +/// Equivalent struct in the C code: +/// +struct CnSlowHashState { + b: [u8; KECCAK1600_BYTE_SIZE], +} + +impl Default for CnSlowHashState { + fn default() -> Self { + Self { + b: [0; KECCAK1600_BYTE_SIZE], + } + } +} + +impl CnSlowHashState { + const fn get_keccak_bytes(&self) -> &[u8; KECCAK1600_BYTE_SIZE] { + &self.b + } + + fn get_keccak_bytes_mut(&mut self) -> &mut [u8; KECCAK1600_BYTE_SIZE] { + &mut self.b + } + + fn get_keccak_word(&self, index: usize) -> u64 { + u64::from_le_bytes(subarray_copy(&self.b, index * 8)) + } + + fn get_k(&self) -> [u128; 4] { + [ + u128::from_le_bytes(subarray_copy(&self.b, 0)), + u128::from_le_bytes(subarray_copy(&self.b, 16)), + u128::from_le_bytes(subarray_copy(&self.b, 32)), + u128::from_le_bytes(subarray_copy(&self.b, 48)), + ] + } + + fn get_aes_key0(&self) -> &[u8; AES_KEY_SIZE] { + subarray(&self.b, 0) + } + + fn get_aes_key1(&self) -> &[u8; AES_KEY_SIZE] { + subarray(&self.b, AES_KEY_SIZE) + } + + #[inline] + fn get_init(&self) -> [u128; INIT_BLOCKS] { + let mut init = [0_u128; INIT_BLOCKS]; + for (i, block) in init.iter_mut().enumerate() { + *block = u128::from_le_bytes(subarray_copy(&self.b, 64 + i * AES_BLOCK_SIZE)); + } + init + } + + fn set_init(&mut self, init: &[u128; INIT_BLOCKS]) { + for (i, block) in init.iter().enumerate() { + self.b[64 + i * AES_BLOCK_SIZE..64 + (i + 1) * AES_BLOCK_SIZE] + .copy_from_slice(&block.to_le_bytes()); + } + } +} + +/// Original C code: +/// +fn hash_permutation(b: &mut [u8; KECCAK1600_BYTE_SIZE]) { + let mut state = [0_u64; 25]; + + for (i, state_i) in state.iter_mut().enumerate() { + *state_i = u64::from_le_bytes(subarray_copy(b, i * 8)); + } + + // Same as keccakf in the C code + keccak::keccak_p(&mut state, 24); + + for (i, chunk) in state.iter().enumerate() { + b[i * 8..i * 8 + 8].copy_from_slice(&chunk.to_le_bytes()); + } +} + +fn keccak1600(input: &[u8], out: &mut [u8; KECCAK1600_BYTE_SIZE]) { + let mut hasher = sha3::Keccak256Full::new(); + _ = hasher.write(input).unwrap(); + let result = hasher.finalize(); + out.copy_from_slice(result.as_slice()); +} + +/// Original C code: +/// +#[inline] +#[expect(clippy::cast_possible_truncation)] +const fn e2i(a: u128) -> usize { + const MASK: u64 = ((MEMORY_BLOCKS) - 1) as u64; + + // truncates upper 64 bits before dividing + let value = (a as u64) / (AES_BLOCK_SIZE as u64); + + // mask is 0x1ffff, so no data is truncated if usize is 32 bits + (value & MASK) as usize +} + +/// Original C code: +/// +#[expect(clippy::cast_possible_truncation)] +fn mul(a: u64, b: u64) -> u128 { + let product = u128::from(a).wrapping_mul(u128::from(b)); + let hi = (product >> 64) as u64; + let lo = product as u64; + + // swap hi and low, so this isn't just a multiply + u128::from(lo) << 64 | u128::from(hi) +} + +/// Original C code: +/// +#[expect(clippy::cast_possible_truncation)] +fn sum_half_blocks(a: u128, b: u128) -> u128 { + let a_low = a as u64; + let b_low = b as u64; + let sum_low = a_low.wrapping_add(b_low); + + let a_high = (a >> 64) as u64; + let b_high = (b >> 64) as u64; + let sum_high = a_high.wrapping_add(b_high); + + u128::from(sum_high) << 64 | u128::from(sum_low) +} + +/// Original C code: +/// +fn variant1_init(state: &CnSlowHashState, data: &[u8], variant: Variant) -> u64 { + const NONCE_PTR_INDEX: usize = 35; + + if variant != Variant::V1 { + return 0; + } + + assert!( + data.len() >= 43, + "Cryptonight variant 1 needs at least 43 bytes of data" + ); + + let mut tweak1_2 = u64::from_le_bytes(subarray_copy(&state.get_keccak_bytes(), 192)); + tweak1_2 ^= u64::from_le_bytes(subarray_copy(data, NONCE_PTR_INDEX)); + + tweak1_2 +} + +/// Original C code: +/// +fn variant1_1(p: &mut u128, variant: Variant) { + const MASK_BYTE11: u128 = !(0xFF << (11 * 8)); // all bits except the 11th byte are ones + const TABLE: u32 = 0x75310_u32; + + #[expect(clippy::cast_possible_truncation)] + if variant == Variant::V1 { + let old_byte11 = (*p >> (11 * 8)) as u8; + let index = (((old_byte11 >> 3) & 6) | (old_byte11 & 1)) << 1; + let new_byte11 = old_byte11 ^ ((TABLE >> index) & 0x30) as u8; + *p = (*p & MASK_BYTE11) | (u128::from(new_byte11) << (11 * 8)); + } +} + +/// Original C code: +/// +fn variant1_2(c2: &mut u128, tweak1_2: u64, variant: Variant) { + if variant == Variant::V1 { + *c2 ^= u128::from(tweak1_2) << 64; + } +} + +/// Original C code: +/// +fn variant_2_init(b: &mut [u128; 2], state: &CnSlowHashState, variant: Variant) -> (u64, u64) { + if variant != Variant::V2 && variant != Variant::R { + return (0, 0); + } + + let keccak_state_bytes = state.get_keccak_bytes(); + b[1] = u128::from_le_bytes(subarray_copy(keccak_state_bytes, 64)) + ^ u128::from_le_bytes(subarray_copy(keccak_state_bytes, 80)); + let division_result = state.get_keccak_word(12); + let sqrt_result = state.get_keccak_word(13); + + (division_result, sqrt_result) +} + +/// Original C code: +/// +fn variant_2_2(long_state: &mut [u128; MEMORY_BLOCKS], j: usize, d: &mut u128, variant: Variant) { + if variant == Variant::V2 { + let chunk1_start = j ^ 0x1; + let chunk2_start = j ^ 0x2; + long_state[chunk1_start] ^= *d; + *d ^= long_state[chunk2_start]; + } +} + +/// Original C code: +/// +/// The compiler would inline this code even without the `#[inline]` attribute, but we'd like +/// to avoid coping `r` and `code` between stack addresses. +#[inline] +fn variant4_math_init( + height: u64, + state: &CnSlowHashState, + variant: Variant, +) -> ( + [u32; v4::NUM_INSTRUCTIONS_MAX + 1], + [v4::Instruction; v4::NUM_INSTRUCTIONS_MAX + 1], +) { + let mut r = [0_u32; v4::NUM_INSTRUCTIONS_MAX + 1]; + let mut code = [v4::Instruction::default(); v4::NUM_INSTRUCTIONS_MAX + 1]; + let keccak_state_bytes = state.get_keccak_bytes(); + if variant == Variant::R { + for (i, r_i) in r.iter_mut().enumerate().take(4) { + *r_i = u32::from_le_bytes(subarray_copy(keccak_state_bytes, (24 + i) * 4)); + } + v4::random_math_init(&mut code, height); + } + (r, code) +} + +fn extra_hashes(input: &[u8; KECCAK1600_BYTE_SIZE]) -> [u8; 32] { + match input[0] & 0x3 { + 0 => Blake256::digest(input), + 1 => Groestl256::digest(input).into(), + 2 => Jh256::digest(input).into(), + 3 => Skein512::::digest(input).into(), + _ => unreachable!(), + } +} + +/// Original C code: +/// +#[expect(clippy::cast_possible_truncation)] +pub(crate) fn cn_slow_hash(data: &[u8], variant: Variant, height: u64) -> [u8; 32] { + let mut state = CnSlowHashState::default(); + keccak1600(data, state.get_keccak_bytes_mut()); + let aes_expanded_key = cnaes::key_extend(state.get_aes_key0()); + let mut text = state.get_init(); + + let tweak1_2 = variant1_init(&state, data, variant); + let mut b = [0_u128; 2]; + let (mut division_result, mut sqrt_result) = variant_2_init(&mut b, &state, variant); + let (mut r, code) = variant4_math_init(height, &state, variant); + + // Use a vector so the memory is allocated on the heap. We might have 2MB + // available on the stack, but that optimization would only be meaningful if + // this code was still used for mining. + let mut long_state: Vec = Vec::with_capacity(MEMORY_BLOCKS); + + for i in 0..MEMORY_BLOCKS { + let block = &mut text[i % INIT_BLOCKS]; + *block = cnaes::aesb_pseudo_round(*block, &aes_expanded_key); + long_state.push(*block); + } + + // Treat long_state as an array now that it's initialized on the heap + let long_state: &mut [u128; MEMORY_BLOCKS] = subarray_mut(&mut long_state, 0); + + let k = state.get_k(); + let mut a = k[0] ^ k[2]; + b[0] = k[1] ^ k[3]; + + let mut c1; + let mut c2; + let mut a1; + + for _ in 0..ITER / 2 { + /* Dependency chain: address -> read value ------+ + * written value <-+ hard function (AES or MUL) <+ + * next address <-+ + */ + // Iteration + let mut j = e2i(a); + c1 = long_state[j]; + cnaes::aesb_single_round(&mut c1, a); + v2::variant2_shuffle_add(&mut c1, a, &b, long_state, j, variant); + + long_state[j] = c1 ^ b[0]; + variant1_1(&mut long_state[j], variant); + + /* Iteration 2 */ + j = e2i(c1); + c2 = long_state[j]; + + a1 = a; + v2::variant2_integer_math(&mut c2, c1, &mut division_result, &mut sqrt_result, variant); + v4::variant4_random_math(&mut a1, &mut c2, subarray_mut(&mut r, 0), &b, &code); + let mut d = mul(c1 as u64, c2 as u64); + variant_2_2(long_state, j, &mut d, variant); + v2::variant2_shuffle_add(&mut c1, a, &b, long_state, j, variant); + a1 = sum_half_blocks(a1, d); + swap(&mut a1, &mut c2); + a1 ^= c2; + variant1_2(&mut c2, tweak1_2, variant); + long_state[j] = c2; + + if variant == Variant::V2 || variant == Variant::R { + b[1] = b[0]; + } + b[0] = c1; + a = a1; + } + + let mut text = state.get_init(); + let aes_expanded_key = cnaes::key_extend(state.get_aes_key1()); + for i in 0..MEMORY / INIT_SIZE_BYTE { + for (j, block) in text.iter_mut().enumerate() { + let ls_index = i * INIT_BLOCKS + j; + *block ^= long_state[ls_index]; + *block = cnaes::aesb_pseudo_round(*block, &aes_expanded_key); + } + } + state.set_init(&text); + + hash_permutation(state.get_keccak_bytes_mut()); + + extra_hashes(state.get_keccak_bytes()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::hex_to_array; + + #[test] + fn test_keccak1600() { + let input: [u8; 44] = hex_to_array( + "5468697320697320612074657374205468697320697320612074657374205468697320697320612074657374" + ); + let mut output = [0_u8; KECCAK1600_BYTE_SIZE]; + keccak1600(&input, &mut output); + let output_hex = "af6fe96f8cb409bdd2a61fb837e346f1a28007b0f078a8d68bc1224b6fcfcc3c39f1244db8c0af06e94173db4a54038a2f7a6a9c729928b5ec79668a30cbf5f266110665e23e891ea4ee2337fb304b35bf8d9c2e4c3524e52e62db67b0b170487a68a34f8026a81b35dc835c60b356d2c411ad227b6c67e30e9b57ba34b3cf27fccecae972850cf3889bb3ff8347b55a5710d58086973d12d75a3340a39430b65ee2f4be27c21e7b39f47341dd036fe13bf43bb2c55bce498a3adcbf07397ea66062b66d56cd8136"; + assert_eq!(hex::encode(output), output_hex); + } + + #[test] + fn test_mul() { + let test = |a_hex: &str, b_hex: &str, expected_hex: &str| { + let a = u64::from_le_bytes(hex_to_array(a_hex)); + let b = u64::from_le_bytes(hex_to_array(b_hex)); + let res = mul(a, b); + assert_eq!(hex::encode(res.to_le_bytes()), expected_hex); + }; + test( + "0100000000000000", + "0100000000000000", + "00000000000000000100000000000000", + ); + test( + "ffffffffffffffff", + "0200000000000000", + "0100000000000000feffffffffffffff", + ); + test( + "34504affdab54e6d", + "b352de34917bcc4f", + "2d82d3509a9912225cbcbe6b16321e17", + ); + test( + "26ce23ce804055ed", + "d8e42f12da72202a", + "1f531a54b7110e2710c8c956b3f98f90", + ); + } + + #[test] + fn test_hash_permutations() { + let mut state_bytes: [u8; KECCAK1600_BYTE_SIZE] = hex_to_array( + "af6fe96f8cb409bdd2a61fb837e346f1a28007b0f078a8d68bc1224b6fcfcc3c39f1244db8c0af06e94173db4a54038a2f7a6a9c729928b5ec79668a30cbf5f2622fea9d7982e587e6612c4e6a1d28fdbaba4af1aea99e63322a632d514f35b4fc5cf231e9a6328efb5eb22ad2cfabe571ee8b6ef7dbc64f63185d54a771bdccd207b75e10547b4928f5dcb309192d88bf313d8bc53c8fe71da7ea93355d266c5cc8d39a1273e44b074d143849a3b302edad73c2e61f936c502f6bbabb972b616062b66d56cd8136" + ); + const EXPECTED: &str = "31e2fb6eb8e2e376d42a53bc88166378f2a23cf9be54645ff69e8ade3aa4b7ad35040d0e3ad0ee0d8562d53a51acdf14f44de5c097c48a29f63676346194b3af13c3c45af214335a14329491081068a32ea29b3a6856e0efa737dff49d3b5dbf3f7847f058bb41d36347c19d5cd5bdb354ac64a86156c8194e19b0f62d109a8112024a7734730a2bb221c137d3034204e1e57d9cec9689bc199de684f38aeed4624b84c39675a4755ce9b69fde9d36cabd12f1aef4a5b2bb6c6126900799f2109e9b6b55d7bb3ff5"; + hash_permutation(&mut state_bytes); + assert_eq!(hex::encode(state_bytes), EXPECTED); + } + + #[test] + fn test_extra_hashes() { + let mut input = [0_u8; KECCAK1600_BYTE_SIZE]; + for (i, val) in input.iter_mut().enumerate() { + *val = u8::try_from(i & 0xFF).unwrap(); + } + + const EXPECTED_BLAKE: &str = + "c4d944c2b1c00a8ee627726b35d4cd7fe018de090bc637553cc782e25f974cba"; + const EXPECTED_GROESTL: &str = + "73905cfed57520c60eb468defc58a925170cecc6b4a9f2f6e56d34d674d64111"; + const EXPECTED_JH: &str = + "71a4f8ae96c48df7ace370854824a60a2f247fbf903c7b936f6f99d164c2f6b1"; + const EXPECTED_SKEIN: &str = + "040e79b9daa0fc6219234a06b3889f86f8b02b78dcc25a9874ca95630cf6b5e6"; + + const EXPECTED: [&str; 4] = [ + EXPECTED_BLAKE, + EXPECTED_GROESTL, + EXPECTED_JH, + EXPECTED_SKEIN, + ]; + + for (i, expected) in EXPECTED.iter().enumerate() { + input[0] = u8::try_from(i).unwrap(); + let output = extra_hashes(&input); + assert_eq!(hex::encode(output), *expected, "hash {i}"); + } + } + + #[test] + fn test_cn_slow_hash() { + let test = |input_hex: &str, + expected_v0_hex: &str, + expected_v1_hex: &str, + expected_v2_hex: &str, + expected_vr_hex: &str, + vr_height: u64| { + let input = hex::decode(input_hex).unwrap(); + assert_eq!( + hex::encode(cn_slow_hash(&input, Variant::V0, 0)), + expected_v0_hex + ); + assert_eq!( + hex::encode(cn_slow_hash(&input, Variant::V1, 0)), + expected_v1_hex + ); + assert_eq!( + hex::encode(cn_slow_hash(&input, Variant::V2, 0)), + expected_v2_hex + ); + assert_eq!( + hex::encode(cn_slow_hash(&input, Variant::R, vr_height)), + expected_vr_hex + ); + }; + test( + "a83cd815319596c6e4fbf2ff9399ce99eb092f58b75c351a7be64a65a118cee031c06a8542b758a15b8a7e", + "19dff098bf4f330c466480c6bf59cf89c5b4a9692a8941e7625435275e7b12e5", + "89def5411f4c877e75a0ae9a49c73e63071acedf69e730d9d56425c01ecf0c9a", + "4c058a38985b6b064d85562f0bd2ebfbaa2915de6ec90307b660b5525ef7d7c3", + "4729aa8a82fcf9b014baf5c2d2fdeadc3ea58bcca7934bb98359caea8f0227e8", + 1901601, + ); + test( + "0020a84a1806a9cfd957bfd243de587891d6e732322289a9de3db5b15aceacbf1d5b94d04f143bd652bc2d5c6980c2db58007d03b4e5cf94829b06b13ccd61e9edb2ea910876e2eaff62304658a07144236ac3492edfbdee9077f62622d6b9b56173b0eb822391857a33c17e4fed4a54f42f2ea7d85e9246e6879a98c9576240477060ef4f4c88056d08acbd36825b314cfba95082aa66ab6bc89b9831ece3abcc8e7764eb", + "24b03eefa6fa730cfceb1fe3043dacdff9242f69a3aff86b9d75e08d5ce6903a", + "b624fcd7345589b2531f29a9d47ab53f4f90caaf3e76f20aa3573d73421e55a2", + "6426f2a9e3274926b41965f389ec40625f32d1ae6decfff8966d5af208789bf1", + "efb1c48e81b58e0f1b0d574ab142dedbf3b089a8fc6c9b50f2bef507147d8e02", + 2066323, + ); + test( + "93935e3d10b73a45f77dfbbbc08c819ca65a2d279354f945d5c2b244cea6af82d9b39fd293b7ec136e2a0e584ec6faf584d669226a976f90dc61acc62f13de42a04d68dc2e80310a692fe2e1a88f05fe0e370117dd9b8edaa9bacc827571c8ba8b55dece38bd67fc22080d10b94dae5fb7caf68297b240fe026e19140c88873ee764a8a8ffcb3a1d76fcbd530dfd1b7e6cccda31362ec9b2b6eb420aba7c6088685ae5d75cdd1334dbd979b916a0f1426563e8e6ad778f1110d49ef34e446346dbc9c358b29e6f923351a8368daacf6661c19c0c1658c8f7d0386ee9f61e729e5f6fdcb4d740655afb6d2191801c0126fd1f18b82604dc753dce507313c4ade7e6c01d470f18b701e21f", + "e6bb2a8e6331b816749c0dde5036a417c0f33e373177e4e93b7bc2452d6c3fe4", + "e162d6dc79a6f2cc4497dcc65b6f7395fb9a8df4eec53e87f49c0d14bd36318a", + "8ecf8c005bd5539c0c16cac9f2e5744f641de5b886f069f31e2d90ac8287479b", + "6a26f39a459325b43aaaa38d2f746678907cb4210c1335096f8300291ab22797", + 2045824, + ); + test( + "4c1818705e30a631e152fd7f21f3fb836a0e441d7450a7fd38f119f95c650a66ecb79fb6d60db04dce28e0798c8e5cdb9f13738da06484b2ab302b548ca0736d0a58ace27be67b07219e9db7e50c4243e9e2a6b60eccac1b9b4b266f85d3814a846239bccfead53b752ead9ddacc9abf100dd9889c3fd7b064f4102f864df79138305b61937f5bb458cfdda2e3e1dc553e06f90efde088fb995314c539295899e31509c4198d1ad056b5fe9ac7d7e3959a3d73c7f8b5203f9f21c688b196841abf8511630864f304ba44d6fbf627164596c6cca56abbb8a3a005abe6a493ac08a3847f65cb3f90ef531d233b9953b86d7a659ec82cfb129f98ac5852fa73a2b84920669ec8d14fe1ba20f9ad488b86d7d344c143474198cdfbd6cf67df3e3dfb293c62361c3e3a4246fcf1558cdf69ec10f63770f9a8a23ff80f3ca46ee5ca35ba384bc3f8eb0d159e58b649045e87b0095e800673e0f33d4ce2cfdde727009fa0c74d9866bcb43477a3075149068868f58a33f7880d609ad2", + "3f3e491848ba252d497231c696e1538a6e1a7f6c79a8a1b2764c5810f9c4d38c", + "9e272f82a466c86a1d093a07dcf7c471d8e31a056dfa45f902bdc4ebeae9bf3f", + "8a5260d0b5a4a0e82827daff7072f95822e316f2bdbb5f021cb84a9d44f76127", + "94d96aeca25bb37bde8497e9e45f5a695ee6e7a6c5ba489efe0bb0a10494eba4", + 2099507, + ); + test( + "b27c89ca34c63efe96732f8cda3eceeeb12304e0e6262e9463d516994adafb11ba2db10f9de8e5fb3b271f2a10065a77a7da998c07f679d7b36bb6ed0051e6f5f6871247fa167dc2d0b19f55f9ca283e6c4486967ac7783ed4f6c292e536aaf8425e436bcc38e55f942f797b2fdc9d4d73cbe4737d2ca849b783c4560ea97cfbb448d444f46bd360aad1c3077e032fe0c43f95e4d5e263b8fb8c2eb55aa6d6403c1075883b4fe657e7b79341b3f88ad48817caaa6cadaf0483c7d643a9b2f0eae8c3470f1df4ffae2453f179c90c0efd51fe8fbfe85d9e4d306266eab9fc5739e3869767aac8ef91e4df881e2595dc235e1ea01579b21fd964f6e66269907bb19f2133534bb34be607dd582775475f81263ed5cf09a9c11a0c290de2dc9fa1a99247fc3c6031cbe46dd833dbaa7722f7dcbb5fa037c7bdc6f4b58e54e3b8157aa893eb8038eb6a0d4364c5fc66352065883e8da1b06840c8de1990c00fb88373b9971351cae3762bef7bcce71e269f76b832321e4b83528e8379132f43fd562553d3745e44dee5a0ffa8a6245c8c228831f22b865feeb7499c54b841c69279a822491eb76600afb25b22885e51822e4ef48da78756dfea36297f2ba999fe99bb80f1432c5e858d52a6ed6c", + "83698a19ea0c50b8b411e3f7206522ef96fd0419cc9fed72de6ecb43b4e8b909", + "80b760cf63d938d73dd2d13670f9425dc3a50dcb66e237facdf75ea3e6e18103", + "6a5a069cdbb29914b1e189c5665e7a7ce3bb647bc7239dc855950e5c0906fe14", + "e05b7377f8099a0c0b1dc53121fc4070d65cf3b896a06bea6a6702a37201e79a", + 1930040, + ); + test( + "5641ce08706166c97f2b13052aed63ed360565f0c74db968cd2cac2ed415cb173ab3583ab771ef3d126648afad09da2b35038f124238c8ca916c3e3fa4c57626965397d1bd865b904dcc3a6398eb2b501d6fd82bb28d80b38da6d4f333b260a60375faea13e3fd9ac2d2745e660965ee2624d68114fce6520f62796dcc4160d1f3cb55e48bea53911b91165782b52e05a02f71368379c428b196ef921f832383043421447784f75cc3bc5dc33ef00acdee9a77cfda31d6bf5e4bc322d09c97977aa826cf02f87a9b2d261689f25605b12997bc51cde56f6b95eb643e7ad10ce6f6b9cc1c9513bcc69fdff3ece5c7e9fe989c1f0797b8a6c0eaf17aa63c173ee7ce4a7439a93dccca735cdbaf2ac52c2626ca6dd18d190c28c7bcb74dcd5e831cfa691a9e02f9e1fc4d991d19f6c771d6a317d2335a5faef74b9485f3b708a06f9a3779d4ce1e224c1fd4c80ed682374988e9a80c16c489864cbfb9619eac25e7a529f75963dcfabcaae75921ff4f37e90771683c365dbc6e49394ed42a4930308f61dcc5493f6fbc61f089d31e38304f88b03090db752cea1ed149b01347277563c372fb9aabfa770c5324267b3a1a0c7d95ec5d787dcc6955a6075e7acf51b6f9498bbe31ca3a134fa4870e3f3d85aa8a06fdc260f67843b030b55eb57e386769481fbdc27d3a42cb7c67b626067be82008bc922d0a13cf2819426a434f867688d4c866a1323a3afd5f0d27e5f706aff5d3ae8e808dcbbbb527eba451a3fa92012f5dd450c8727d9812f4de6b35c9864b94112ea0e1a0236e905f16188be5bd909318945dbaa48c7b48537cab", + "5e63777a37718d23d4d27df6457c3287e5df76d57e8b1576dedeaa0b48f686ba", + "34b967683a272498d46eea6f0075ce8799e8b3871fcbb8e8af2016f841b0a563", + "17173452105a1d8a797d46881af00f131276c2c46aedecebee357d3400948690", + "23fb53795f04ff342508044091e9d5bc5704e637979de4491d420620f91515ed", + 2013003, + ); + test( + "381172bcf453dbfa7f0015823367545d83483a6c805355f26b1cb4019391c62b227530102bc19272560d2160642251ee92d009c478210e77d5fc0edd20c332648058d7a5f052423023d651eaf415bfdd6e03588afc2ccadc0b63dd187a3e585802fb6e14ee753dc778688856f9619a883de3f68300088477f26aee159ed8f67296e034e020f1577eccd56c9e5ffbf4695e756635aefa4140e346fd3977b159a950f71b506a3ffb3cb1802914ce81df8ce8ca062953eeafa7dd8fa6cdcdd85dcebdc43d411da2befff42f250c7de4a29793f1105bed7f1d7e0adcdabde04060f0370c6f66075441e06447709519cf8fa5cf89e7965399323254f0faab58c095107a61b8f07bc9a368a538c2fc2939daaaf8562c70c3d9c878821df45547caf4794e0a3e62f7f14ef4004d31e9a8feee7c9e97721d82001646e3c32551a7ac89b7cc17be74beaade5f613aa30a5b3077859c579dbdef27e27fd219947c5e5e8456c37da5ebd7fe3f1cf369ded0451098843e197db2d82788fe0ec39cb0a855cbd6a2d54aeba7f57fcea8fdfb3f153075d2b9c28dfeb3582c4f19037c45e39bfa9500fb7144f836d82c65c166f901605fb0e298396dac47d8ea44ac00ae61b16c5c53e93390698b7d6704c6a413f8244a96bcb37909404a0e1ed8219a388936e2c3a3038598aff37949b2e1f3e4b64945087914c1a24f957767e583fd875fed316aa02adce9ac1325cd88e5287c3fd7e39e8618104864297d979d27ccdb66b28005b3ef8ef2e8ebd17a1ffb5f466241a521854ef5ffc41af918bd3a4bfcef4d56fb506ece4f4e043b02815d98c64f8d3a39fd2ba5cb788cddf83e7c970113414c0b745b260336c4c376d78ccdd87c1bb1e409145c912584194cd95848039d265674b00d24a7f304d2526317bd3a60858eb05eaf9b2047905f6104371661bbeebc0e06bb6f20", + "54ff2f094bb647ff95d3fac1411021beb2519ed4921654a464ca1d91086cf566", + "26128757abddf96cf31e24b752c4f49b9f7e33b17719923412ef681a8b0b80d7", + "a2553f122fb1cd293c3d3c442705cc9875705fcc374fa0b8944bae52ea818037", + "64e11fbf279c8ef768450c5516106270f960b9c514bf945cca82a08e2db553a8", + 2098488, + ); + test( + "45d3cd4ada6cd0689c08ba902effa67f424b4a57da5512a9fbddf343b4872f48953485c0dd0f38a96ee61e655195354a8c7c9d73ede4581eb943fcb4bc376bae314d7853350ae805587d00958a63dc547ae4b7b217f39fff12ae684c18e1533a6acb1aba6551fc6ee78aa103543295bb81a720e3dc538d1ebf156df8510bec0960e55d853dbcdc360e3c7a39a5f4393c97b1317ea57b48d8413b67295b89860c5fe602b4a0b05724e5e1563f229645569703c3acea69d75a8ff72efb0539340747333735c74d0ad6126ee22f2b5b47b601af4f6c36f9a7227c457770dcbd31e85b63b57da98f4c6dc926afdba99851b44365f99103841c2887f4490daa63cf10954b4a8c354346307dfb3d5e2762625bdde96b6bbcf3e7af7d7bdc7a2d58ce607e97bc2e8cd39f9871debbe7b36e3652f54f54d02d5470c7662ced04435b3771ba9264c107cb0b513844ce7538d7529db455e0960dce9e309d1978a7d0d5debe55dd37cc8b1cbf1b41bdda5413ed68f51db2ce70d31232be4095dc4c4b82f9a46d8fa278970bdc72632e74eaf6ca756ca72e6f5be75823b0502ef8deefc249f8a9d37a8778ef1429cd4ad1b8c9a53e6e179d6f62f5e966a762da0db3b6974ca79c5a8be615bf57bd11a31a1b365b7f47f71a041a0c46ca18c7af20a09e22085e767948860c58e84ce56c6646d1737d4df9714362deff97ccf56021ad85ba01da06bdc46bd377cca8c731ecad9fa18013ae0a567220c4b99796d3cde96eef16aca8bf10d4e1914faeef35e505e44153eb3c2e1b0cb366751b993e662a828eb5f824ab138aa7504c8f2bf92f36a9c4c978ad064dc4e877fa37f0cbb1fd2f02eefd8b54714a7952b2efe522ae00315fd147174bb650314e20395bc13537b65aef11ebb1e54a34f169b11b895731c6eaa95ca6ea813c7aae22d19720c30265e0d7242afc4ef7fe1a685eb2d48d18b135ec6656bcb1538a6ff0086e6949be38b4d88a95279fac4475bea3c91f3cfbbff56f0896502417a8e2678e98ce4e0e184d7ba62add18082edfdecc36b79734e1d11bb62593de91d9138945caeb0d0e8e070a397388c5b010720fc9829f22fc80ae1f39b469aab343", + "26465b05a2b7d822a180e5da853c3bb31cfb541ac57bb4082ed6baad2b7a275d", + "3914d155ab3b103a29ca1733f165739dbba3fd70847680982c72378097644f5b", + "47f117624851b9f56ecd40dce1a345348c7abea4223e22c4bec1b992001c05b0", + "83be592ccfd6cbf00f2e299828836ab6a92afea6b7b32e5c4ca4ffc6abf095f9", + 2051767, + ); + test( + "f7ffd535353b3296adf5b9264b26462fb8944ae3aa917ac2454bd3eb967308c8665896082f1b8daf1c352dab97c0158cb88ba10eaf323e5ef7cdd3c5ab1188c23da90a1aaa46c18dcdc5f32c5160788cb6331a5fefac666210fad0365bab019d1e62f3389d23ea6de4bcee0ccbac2ee2c86dcfd446c90d3c8290526b1d818e2c28d12e8f2a09f2672578aadbd2b1b612acaf7e35727543cb14272bc807cbb24bda7cfe6ea7eaa3a0533b0b64976c24ceb855b16929c43238121c99811fdcfb80e7217825397655ada6fcfa4b5a7527dcb154ea905d8a7d9b1f7cdc5ba475aac290d8fd0ffea6c9fc08484205335b78f07985df2537301bfa1bcd96f10cad3053aca2900dce33082b01bd66c0b2d801e17f97ec693b2faffce2660b7b0ef7564d8df418ea2c150187c8dc717a737843bc1bdaab00d3d2820f592d912559da59056d659da775d62d30588a778f454b99cbc50578867c8e6274d23683886869d0655c68e65cd79292893d58a55c6ac1010856d94a7d298c1c2f9f503b1ce62bae9afe5057186a10bd7464eb03c47b0e847e6ef690c248f385a121eb7a6a3fd4e9caab6c0c6ac5d4817b01dc72ea12a73ebd47a3d32f740c12cb076a43f35ffb6afa460642f75d05f0b3ffc8999d4030a80e61ce3a54a5c5067968a113cadbcf1aba7d92bbf0aa672a5aa98dfe2f2638d5fa7b624d91f4fd66b02f7a11e798bcbede2de8cd6332fd4fb4fe6dea91cf1000ef58a9dc79b280bdb9aa178f19f39766c965370879229f1b2151a9029b601060cf4a7c28e506b14a58d0624d7838338738b7f54fa865a4ad3a8ad730bf12c2b48c57b7edced9026780d51b1cf3eb91d36e0e76c746db891e510035089225e75db6abffab62f6072d97f5e0aabf9437171026c7aaec2fd28fee570dcf6b9bec649cb6559f761a1c9b634e926e226c5b5c581dbbb1ef3f5c6c489c6d2e59679b6b8d6d9a2200bd59fe453ef70b84dcd27dc062999bafe6bc856d4bbd5276c683fbeba3e57bbc247018b22711b603d2f9ac18ee93338b4d561eef7031f7b6a76388e40d88dfaa5e7276b25911878467e7d887bef4f83417873a496474021e44330068e8081fe84a1eefa3bdd05adf962fa5ecc907d3d6e1c1cbab1079b5c65a560a337c8e305e6d6693bbf539d18d6efe8777146925741957067a0fff681ea2306d5657af9a9313eca5e879276a92e1dbc3d29dd65874accf34c399ddd2a39c765f335d49f3a3862a", + "19f361976db5833325604c31f6a3362096606d953f34e78caec6348c179112ca", + "20731740a7ac3501898a5d309ecb095abf07e4db57ae8ee25083596d2f41f747", + "170910bf127750aeee7c26b0136f2ce3c5847d0ddc91e5b7c3d1a561ddbee6ec", + "157becb8920260a38fda21c79285e9c3438e22fb1322f52cbca78aa07a90dc26", + 2014127, + ); + test( + "042907fec45e611bae8a453343ea30f01758a717fedf53d522e45ade68ceab671e3918875a01204b40d579672d6f1f2780f3d05a50e260af372c43900e5e314da8e15cf5a6d5c2247ab28a10cf13994a7e1bd125b157c98365930fa76c871bdfc1a2380cd15b7bf7dddadb4fe36bc5ae45f13a3ee128a4f74b9b77501d265763966d9e240093539294f9d7aeb66a87c6021ca2d9a6e0a4dc22f2845e295aceb99cca53ab3e09f72476d988f4824034a334d6f9314836fd60f35be29ee4511113070099c7bf8390af96eeb9a1a820f005181a7ebc33a68724eebfd9d841f9f574d40ec8ad93e3c65fd73f9bc042dccdc6b5f0ec0aa2fc12676baed24f1be7111cd3d221015ea9919434dbd25f987c985a0d4478d45a43627f13419e4d07d2f5c64038c9d5b04e8aabd950d98e2563a68df1ecdf22b3179969022a82acdc16e90ff9d3804bba8e3ea7f8d06a390cc97a823bfbee8096bb93c399ea2d04d592f4c1c5ddad50d977974c14711cc7e0ea339f0469b9b688688080aaca49ea33c545d3941cafc7732e1058616b22df13c9bce1923f13ebce4485c2e8591881f2f8ecd4a3553731b3a800bdf6125c7298cbcf2e060bafc7df05b4c239e553b4c491397d8033d6d3525f5da13c6397e1019cb3799c4521985cb910f186aff6998f556c3c0a1e8d9a47348c35bbec752bf5c0a689a529a50980f27220305da3ef7c550d34be357978c7774077ca623cad9dc04d1eea0a4a6db6db071edc8c6c86d77f8796d887d773190c5f351146c092030978b9ff3eaee5cda8d0715e05a2ecf08e31bc2761f95be070053f999ddc9d6936af7ad1b8177a180132a90e738e8cdc45ed0c2c7e1a4dca126610fdc286e62bda134aa75dd7ef95e5247e6a6999ef7488000692884ad450df70582003d39e1c4d1cc808ab880d02b93a3ec1f784a4ccfc60e584385a0c0e7f8dec04af24990495e7af4d43699f1d8801c4c86abaa2e6f42f3f64f634065eaf5ccee63ed884da857778cd627c4f4cc1c0ac31740731284c851258cda05ad9d9eb55760c81036c74876038f58fbc5033d944e877a92083d95eb35cfd3b480e90ac6da13ef422324d00d7fbfba7c6ebb6c726c1bb9f8be01ed3d340858769c25c6643fe2f046f2ba5761750e5a312ae2742873f845ea59940e0b6c1c0c02bc88a9047a09e63aaaaffb4c26ff416ed086d7ebfea314baec4539eba00a6b11cf7e07a0426590d07e11508116db2250cac16f225368964c632ba9d4db50d11e7e0002d8f9b2785b38fdf1eb10f6e87e6a2ba4d6e0c47c0a3fd651e9a10c4db3abd3737b53ee645d3c16696af0573f9eee37c3b66fc", + "87f56803c3079e4afc89df7285078b8f3483dc8503f3309470cf09d68dd1987f", + "88a19559dc7c0f46314cb9fbe23c7ca2946db2081eb2b19a4856f4c18e8ced24", + "f3651d3fe2e74ee010284c6a46b9eb0b888dd8271d053cef7cd3a68cbb4e67b4", + "410f675f6904e58ea5c03cb10455018032c27623cf9000a8b8efbb2847c0abb8", + 1981542, + ); + test( + "f7704c6b62bff847096e20a6ef8f8bb97e6887168321a1345312c547e2ab146f35e6f95b76f2e8ab26ca3d9061e0da59088ca8710731b458711c3b8f119808959987645ee92db847a4c4c5240e483b1ecc7b59d2967dca862a86c2db086e58a78367acd4579ff1353a9d695f822e7dc3751319504636182974987e0f5d5dc8783705c252678c90679243634d62841e062bee4ec7e7a4966d3f270fe6c9463acf0bcafdbe83e2d70a3c55a83ee6854f4a80a163cba43e479d3f32781570acc5150255489e159dc6a5da38f3e9d8af45bff5022262216533954e2702ead4c12b6d4b4f7bc475bc61bcbab54ab99a880218d2285a8a2db11159a2f8e8234e171ffaba33f4c2d8019c0000c50fd213c5ab40160bc89042708500c52c456d859516b5d31118c570a088db80fd42490703bd752e0ca770f4c471e1ad15855b91c7785534627bfca514fa978bc8e8ee5fe6a5941b950e8e7769e8dc25387f7844f939cfe554ee41b345dbd8dd8a13449e3e4b51f900e53c8ca9dd49d9faa87ba52c60517b8d6a866a8da41c5593dd7dfe9f5e90a726ed07977f5efa12079c374109f698e6f85321a1be2105cdceed960bb52a23f7da5dfeae630145ee61f2b33e3935ae63380f4ece5f6ede54048dcdc706d993dabcf27cc4eccb45ac28cd55ab2bb775daad1f3ef57eee8fa23fedf25e4455c51ebf9eb5610c2f17b8a95daf9c87ea1963ee95b3bbd8d5117b133f3e2b68fac2c96f480b49567e0eaa92f234a2d75917f31de96aa41ee6e591771dbef286db639ba4b8fff112b0d3a1c6d6b3e8aebf2108aa8f97c95e41c3e059dbdff6d33ff321f857fb845eeb32ad6c09cba8179ff8dccc77924605d57eea98afa8c033343379abba0ca666a77e2dafa24f22ba7e352b668d56b723b7b00d0e2a0ace7bc17ec596fab3e64c06fa5bf6ef6644bc2f2b7b007b113f241cedee319009f689fa68496ef6f186c8ed7fdc82a2373d00d326f57b743bbfd608eeca2233b694e39a3fae56a4e2a2ed638e8f11bdb633f6e7ea253f2d24cbb7270202da2b9b65ee97b362701c5f404849a1badc7f289bccc65dfd903253a71c8f0ab1fa6004420e1ca05972d3f40258762722bd6abf58617b9e4722f24eb3d2ac21304efb7fe5861051a46b409d28de0e457d66552baf80eccc72af6ec1018a7e118644ef255aaa8c0b51ed8bb78ab5da839de50c345b8275871a15a342e05fdacf0babb8fd22b502155a4e86146b554146666e623baed63fe99af9999bcf75eb3c9c4d7697693aae8e728b1c05a4b209c025e715f55aaff0cb959a477e16ceed7097968996c02f2c19bd702b32c4a063b95f855ab426b0b643727091eebb1b5162d93f9e770922e4314b1ff18fb6a3771a4fe7ce98df012e0e276bc939e608b71f05a5bf84b2b678d98bf095e99c857becdc4213e3c8837bea456ef29c585a20b3f0753057fb449a8a85c5f3c77316accb0977764348895b5c25ca4a7fa09ff201c5f78987f0323fb73ea5ed79e02f742d277d69d5840152b0", + "d42c537985350ba4d9c416a411046383d6f57654b6fea6a7d5967296ee710658", + "d66ed3af79389cf644d688fd978b84a8212fe34c5f5e57a8d993366c7e0b788f", + "46cbe4a65dbe8713727c1412d01b0b35412d8339b4f18cd594b4ad5ee616f79b", + "953d2476c2be1329cd7f2fb60a6cefaea6e4a9192491da9420669e60eb49d7a5", + 2056079, + ); + test( + "5f63e95421ce62ac207e1239e9e2b763ead4ecd17580d333005e5c724646f2633101f0ba797ae905ce68167412e069763838fa4c5c0d155efafcf021320cbce66df4f4288eec02f5734c866743177eaa18e32a19afa82133921108ca54311aa79cd5dd6c7bce26732412e614ce44d8f5e2867ceab20f61f44ec2a8ffffcce141aea7840924135aa68a14b9024b7726b9b7002baab89763f9997ecf5d6fc2c84e048df8f46c8dfe3bb12b5675a4f68840603860cdf58d1fe56e7cbae7b180e717891974f8a4aa032d89f2771da9b9a63578ea76b7153a4211388a0d6aa27de720100325cfd8a88c480e7e2b67685c0afdda678391e1ccb81936f96bef461131b3f4289f902d7cec94f3cd651b5c3a36134bda12297ccb7913f8e76f0972306a5bf119d6777c7caa01aa8fe8f0173134571952540d0168a895b6671bcd19532fdbe3d6138ac1ea9b7bb64ff951dd9824e037d34cf98bd0e4bc705b6dad47633017afa698dbcc20090d5741c1e02e918dd96979948ddf6c9ab8f889749af54c4f6916c20a74349ebc36f46f6b6d47bf7cdbb1a5bed4fc5b9a3766924483e2e5d837976d12122ad6e75300c30d5a94f0322dcb3825c835167699c2b1be79ad28e1ca37ab87afe90790611f3a6d07faa365dd1e754d50f8c462b8d6004ac40397e6571b5ec6dc86c164cec07f41ff82e6f4e29219eab65b0ccbae12c9fe482f18b8e51564013b1ceb3c2f3570f86330c667e21aaaea2a643500f9d758d533f6582239a1bbb0bbda04e22f749ff436ead6df6efce849879e0bd3918cd6d55e875cde0716e96da93ad752ecaf481ccc5d77a9d5207dc912f5972719e105023cd2ab8ccc3e7c5f38d04c6c37667bc8e2036bbdaa969735e07491ee9ce8191ba9a5f0d084bdf616674a98754b6b78510670c2b74ddcc744071d25aed8a6efcccb3d03be80515344915674ded598c855738b86eb68359b801d61daa1b80708dc615bbbd1051c76ad05023ec48f458403adaa0d7becd3a6799cfaf08abf42331e639012669b0398dbe44e0251bfff268dd1a38c63a9fbb22233fd22eec520976e8a9c4c4362fe555c1fc575d731a1e910b433a3d64cbc5b231b623f0c49a41355ee86028013295b8f1f59f0c3f6abea0887c74e7de2676ba4a411546fdb5f7ddb3d3c5e19e1b881fd7b623b79f9e4bbf43f0bb42d2b195130b4630ede7851cc6fba851b5e7bf145039081e95bc8b2e6e0c73e7126fe734669ce53c1ca58ebc72cc787361993d585680fda60c4158155eed6471c8997fee5259fa3405b10e1d5f955d28a12a01c8e9d04f53ba9130daf49618cb553e0d95296adcd0d4b72b73f0c6cc531bbd1d6f466d1e0f60559bdfc7b3385d671bfa3f092346ca16392871c2b8dd091f6de8ca4188b40dfb9c7ec7fdb8fd1ec83b354a599d917accf32875125f20407bc20e3f08cb959e9f84cdb95e5508b0be175156c22d4ba37f6fcb51d1ec109934ace91564ee52240ac49ad6ecd85fcef7db006fc840fd32f387800446b8b4509e917b5001297ae6515db88d28d5f19025c8370323581c47c8402cb93cc2918f8d93f109e46d92d43d95db7ce206fee34d1d4243ed7e6b49722edc4772f71", + "2c2447a2cdd8ad2cc9b02a499b2cf42c96f8439b49a52590b9ce44443f37dfd8", + "d689c14e5b89bb4e4cf64a7d60b7e242747c34b270c292e255d97c7211a44aeb", + "1f3cd81486cc6e71f6e7151fcedc271b3195c4407895eb09ae3337006aa9af4d", + "99b2220c7464edcf63925a8ed69ad4fc3d1554b9ed9b6762823881763e79aec6", + 1952566, + ); + test( + "fccc58bd0e81bd73e483f2d8b57b750d29ce3e6c9f18ba24317153fe06d2f5a0081bba69920baa2ad4e0e34bc4613644c67aa841e6210ffa7818de3f703cd12711a54a60377c0f24285851aeafedc5b405026957782754f3deec5c81d9958f07e377340b08c7447e1ada8201d01c8fbeb454d131e4c8bb284a69149f2caf4d0a535845ea63f5a67100bc138c3b578cc615105f76946c9eddbb2ea23a4e71c2cfb3e45d0a1eab2460b39815f652d10c7251979abf7eca0519013983266dba8c6b08858a48f1993eb5028b0491d66a32427b9554ae31251476055e912c87fe9e088db85d5610e25a728b313b58fdbb066b14746c6f35ffb35f2da95aef4c498661a95fa27f102d58e07e445415cb54ddbd74a593c02898eb4626524430bc57173864da6c84d4708be3a5006ae06a0cab5692d456a721db83c066cb871c9ea2452da767cc0520fcfb5abc45b365ed06c699178646c58fc976eacee3f2eac2c73769e3e14a989e99e67485b29f13deb17a4b28d77cd033f1f2848bcc6422023f3fce73e6fc41a7e7c52ea4e92691b6eb5d8fe9c14ee0cabe631674fd6e3eaab1f273ac22682c7884aac1026fbc4194a8a9b7134f823d42ac1db8639066bf400880413968f83b124ddfa27e91ec771dfdb31a76a27d21be47e8de0f22fad496cb6cb3b1570d410c461c360f8ffb3df08fa879e1e48627dac97fed6433d493e2c6ad4c00dea2c1f2e35ec29ac40eb7d166e13f2f08dc86000edc1875ef8be03a3c93c3dcefc0f810ba851fe788332ec10a243580ad7442e21a7d7c0463baf803c6897886071aeea69cbdfd5eed0bc1940da9cea4017f7fcf17b2c25177ebd6a8e9c91ef68ce5f356386d2d66c4d118708c097a5cde3c59e939920f8b9e9b950a905ff4099c1319cd07b0bce12d5171a9b0958315489fd200e498a3f4888899c6d5edc7bef03a8572708ff1a25f011eb86a5cf18322e009b0206dccaed059a89ca9b69fbf7c3e30929a9d87943a4ba5e4c81f4d4c9a1489c94eba20e57d5d2f0266185edc7ff292b99cadc43c43af7ae364b3a42178fd1176cb7259a1ee485871bab42dfe255a4b127211831ae24c1f2976b0872459813f5ca04750c384eefbce0da48ae1a4ab6deb87085fab8e87684b76a51a158855dd5912f7a88e806e7fffc26ba0f80bbe65086bb15e72226eb3cb535a8cc3043f720b7f9a76fe7923fe2810b55524026799bac74d7b98c0904ef6a50bac30b9cc8f89955f8d136c0ca4995f26c6436b025c04a8fb12e77fe742b962bdc447f0959233a2629799c441cc993f7790c208232c226e3d4e475edecb0c8cf7056eb40b35b2efab1492cd8c115c171d20671e7f94cbe2987efe03e9f402c08dfb1436e38ab73e4545ccb507585b351c77059f3c68ee0a5f4cf390e1655defb4c610da4c3bf5d311267f3da3f650912d50aafc4d4921e0814b8d5a6f243c99cad3145e326589d990223693c4e246709f720da817c22dda18f89261145c19a873fb51cf82486fe3cb8b6109182acae07d33d51932bb68a3befa2ef826cf4e9462d69943dc01ec88ee323f33b92062b0af4ff7c2b890ac62cd7159c153abc330716c26d7bf86d1d37a174cd6730b6c72db7f2df5d2303bb6aedd4ba45f5952ffa7992eedfe0451059ac079b4882cb0a0dbc374ca5b5aefcc435e46bf2de0dd1666142e19cbd4b261f424c533a75df07a90f3cf5dd9ed9bceb90db2cc731d34128e34b4940b7b2d067376e6a25797faf43c708937985de1abb3baba99d566daa75670b6d3a50254b0abbeb58b5ba29453ae33d1fa08d989d6c3e60037601f", + "6f8a76d9587fa93b25e384cde8311a84e5ae6fca8bd46354949353fc07c779c5", + "6dd4fdf7fe8582635559d1602e89239df518f4495306548964bd0b5eccaec1c4", + "505a269e57824be55f6936360d5652a112c06c38de6635fa4c6a954f028b19ab", + "dc96de47b8876924bdcce08f6a8105e36b90bf2a7ec21ec581dee9f3062bb43b", + 1984075, + ); + test( + "53b6c05215001d094d1e4269e05d98bab5b102c557df1e06997aa9e6b45228dccef00346525046bd2fee620d327d755dbf64d7f6631ebcdf6c8f6b5e148791981d51b630bee56b9ab69a18bda51419696433d83ca7921f844884d7d8b8d3e945ef2d59d833aeec84d405b27e6a857794711a5a177ed0d36ac7dfaad9af9d66c3ccfb861fb4ec2e70f2bf2fd9e3022874706c17e21698a9c3f8a3714062b7168c536cf01217704061a1eea3444db53625c8544bd8efc3bf981c3cb76e9e7a4c3784c889ac94b7ed1c6c5f72b847863f5a07b1afafbbb34099cc74d649c3fbe9c7f7032da8a144be54eb10bcd9f323a796d217b9bb38722ca1c967da61597e28122b976eca99e84b2481c73dd71d19b49dda5a510c70fd881eb25fca8df829d523b384cf780c8be5675e2ba804e197180b1d26f44f0ab5c6945593d6f00ab17427df6f15eb61cee22a4c467baccd8c5ad984d7bddb4f8f61e342af9d80674d722ae648279c2f8be70af375f15d704281d3f11ec20b8eb494479bd7de2358237db6bd17e65535bcc4b0b2f869c328c180460c279d5e76b4994789d17164e4c7ddff22eca959a939398524a927adf71704cfa973e68da45757b6bfd7762cdb7dd37aa51467d74973d81063a4e3338858c62bfd84d71493ddeb00e93e6707ace0641c695a20b6785f537cf4a38ce1a92b8a453a53ca9c691c7c26182833c9336005254d789798509ef57ad379206c70e8c3d2dec0c63644b6a994aae7bd9b37736be8384dacc75cd538fc508ad277178e59c194589c52556d69173812af631736572278a2bbc234d56c6260b45c111d94c4d8599169dea6ca7dc42942fa1cbc0787ec7e99156a28b7e76f7be17e6a9906fef8996e550b1ecb5cdd79f6e35c955cd23e225bac424a3e2949222d6cfdd26a18f623ae6f12e80c93c082f5af8049067906e06bd6c4c8d314b9e86f6242a80d7fc04960501e71215d06ead949530a7dfeef07a3ec7d5e60ad838fac4653b063cb94db33accc5e617d0c4583ebe13c5bad45f3f5ebf89e8aa5eb4ca37a030593be406c9ecf323ccbac9fb4601df6059cf2b68312c8d6188d9b68bb80391caf4519f3c87e30be936a8d60b839ef72e9c0d8ef70236b0247aa2111775d0472c09e2a0a0f66321cf8b8ceaf4ff2459eda9ad086710c7d251f513006e226d6d2e596077a0c20c5f265db081cbbe1e684cffdc101c64122a1a3570c6700f39a5bd84814e057c7f583d3d26749ead3605be0efd082bcfbb942b6594d960ced7094fd846dab043e7c2843dd4c1d2897c693a320ef333a648875c2b7cefce9157154725fafb615574b4f9d26d1b6d0b26c06d8db338fb5a625e8e54410e4123b57b829b58c22f51a8c13d3130449f17a419b34ebc3e7126b25a06074ecf37621e6091c0bcab3e6977d968081186ad8b0f810e3727b8c1b300d38c597613feb7da938cec8db8fcc3d65465f4a21a00e76ec82e52b487537b77f1f29857f087b44065d7a7fce12d1eec4bc9e3be27c939e952011aaa3c2c5776662cbc109eb3acff0f011ddf7f5daf9eaaa145def628cde1a43728296ff5de99584ec3ee11a9caac352daca0a48538d14203cb5c94d4fa204372257021ffb88f3ab20c649187588033b3af6ae22219863b77989cf3abd15e83c0a959c1fc9117c98784d89dbbf2b3b1cf7c0473d66c156ff7d050c1856b3cde628d8ad6a155ecce4eba502657519f208f84353063dd90774b8ce763372d833cd04cb4524df0e43c62d235e254a20f2cd30b2faca5dc26d67014604d79f80f4b7c56322c5719feda03725a10efb88b398437445e55d7b29dc9d9f23fa0df236d0184085954c6e088ca23af2f86368c32fb7cd332b4db5586ea17f554d9f544f5609e8cef0649b5feb48e3bfb16b9b584db98441135bc8781d17a22b3c87181c1e88", + "b6c81e5676e663f6099af4bccc6581346bcef3e5101c2be5b7f88c2138126b6c", + "50920f3d5301612bdf629fce8dc1102b7d063da89a5524441f732702dea35842", + "89eccb638ba9d300fdd97a5688654f63d2aeb75abd697af9d1d1c6ae5d7d26ac", + "1e7ced5b56c2ff576b7c8d1785b425e4485ced977bc5c4e538e7c5c0584ed288", + 1926467, + ); + test( + "fea0597851550364fa6f1f84e1f3200b947e560d8813d99c241b156b0671ba7d4282bb0d3120679108af6d35ff9508bd073b915e7a1d340c34866821862386d00b6cfc49e0cd3dfd20d57b21290b480ffbb16dae66b5342faa85b81d6732fd8a39449cbcb5383d38117741e07dce5b8ffc7e3c23900dac47a70256a26e064b409d441bfa31623141a516839c3d5844db9095358df83b548a3c99d7a554a05f343675fc4988c952c369873de30979d23b2635fb33cd2149e4399d8e63d97cf30ebb7d51d95414939f7cb59398fb5bad7f9167f75f2b3a58d37d67d02f3199fe9812b15085411c1cba3dbbef8e35298e2a4f47821c576d6bfac422c1d2e0531c4062cf47b3e8c7ce65f4ea164fe64c60925f05c7ac817696a199e548aedb83bda51c2378137e8d50e45f44a6dec5f72e57073c2f7c4baff0e29e126ac1fe46083869ebf11cc8882a0026029fbe0132cf467368a9f146fca65c2b2fc3a207c0fb0a3248a5336193e9beaf9f295ec054e306e7a2578da66f1304a408724c63b1d99a2bbc6f02f70d56f321e1cc0e9e6b03dc153d9e92dee74b076b8a601fd10565fda9808f657b7ee99332dec0fc99a3b305cfdd6b75b3c8233a60c9fb5e9cd65fa63d10092fa719a9aac13df4dbd1e6fd2df972ce390fa5121097400bc78bfe0585de549602efb66fcdd51605d22613f4e330649eb21c71aa8b11733be221efb6d48a0a7c2cc5e334eede7f23bea7baa2fb2d7b230bc8971174a9adb7e3eb4faea466c78005f654e24782466af155a137d637ba33448eeef4fb297a861c3b2bb42cd9e28cc35c0cd3cb57b570c99db65c9b6c7dc321991b29d9362eda56e1a347f13027367d9f29511bab14a3d38a4cb2bc13b16e4cf0d8f9cdd9271573f9f95b60d429e72547740061de74b4956efcc9fe0f3492b1c59c4535e6d6d63334a2929a12e80c555d3280aa190edcd83be4da8930c21c302e33348aa3e0072a551b80b5842b06c3a08c4a14ae9554f8a61497f40288634d969830b2f3824425b11228adc9faa055d5425371e21fd7dac9611acf9e881ebbc7e8dec3c3a4b4c89de888e58df5c5bded2808c91b2f8a2307e335a05465aecce38f00261d2f262942cea01e903240b1f15cca9af24bd115da73d71acce5aa9bb87179ae4f1f1167afeb103740a4a2512d4ad4e9698ce31d0a1d4cc6045e884408f564a01d9616db0452a7df3145aee000ce2c8b472bcbac63b142beb264c868a859effa454b83933ee81dfd7c4c841a1c5c00a4f811011ece782e5efb00407c7f55b4d1f01a8b9776e3c72396f1745b3b8b8c85bf281d6d43d16b631b8f780e041d504f8461711db8418eff66f34f19e4f65dd75a91752dbc21fcc62cf82fd15333dcf22adf55657999b07e0fa31372db80c6adc5fe5d3185ec2c6ebf17ed78fc0eeeaa3c24ffad20bba6a187726b322b1c4ef3c9d816c985c68cce3ed4559d5feeec12b856abb660cf78ddd2669dfb57f5a56f2bf5ab7f735fe55527f60685285318ba71caf56386218b97b978234d00bf7c2269a8c37cd1547601e1299a59d094a9ff748b57547a7a801452a1014f113ed91696a2659302db83813643cacf47bf1db5eb4fc90695310ef068a997a5157101859afb04317c9a2a6cc6de0285e22b5931a4028e7e03238d2622ab30de3d0124e94c59b72247a2830e93cc408ca98c75091839aa2d717f6d5ddca9282360cc7652261937a2cf47357a1ffdd0a814699c449bcfddf0162c2cad115ecac70bb2b7dcf484da9034c454d1267c5f06634de66705a8180c36ef553b8d3a405898052368203a08b6e23e2aa1aee7d2d30bc3802e48923ee2d29f77f1533c4ac2a61b14d5c9564582d2aa3dfe7f8d7af42344270c960da377f4db95def703bff359b74789dfb8624115cb41e896f9184327c7e0753e24b77f4d1d326c7b13c4d122abf430098ec1e5c9ad35b86b3688a32c5fbf203cefeb4cf3f929d689e2e193f570f219126f01165ba1c44689fd236662803020c7237c9da1d031d71786a59124f48eb6b601a5593885f0d8a32dcb9616e336dab7fc4db2d42958670604ffb76e27f5b8adf66c6f66", + "82d2dadf5e7b00fe9332c9c8260dbcc290cbfcb05045acfe020074f6f9c86344", + "cd473d1f6ed1a6b8966e94782dab630b796d429db046762848c673a799afc830", + "b5276f1c56f17be7f49d6cd76decab4fb42f8909d02c4a7a5bc271f7563dbf68", + "dd8046e7347648d685913f0e160714e9fb79d5caf7a535f6ef28a07b92ac963b", + 2017232, + ); + test( + "ef5ba0f749eac3dba0bdeb602279382971e858521b2fccfaef96bc67531061ae411ed6f0e50212925697dfddf5a0eccd3bd53a1082d3590cd91404650f520f26d1243c9a3bd79b4f3d2c8d2db2c4e4c9a441063adde121cbfd2be8ec08de550149f594cc1f3a17dc9d96ea0b919aafce9495caaac0c67434f924d9b94c0e2a5a5de8d687fd6d5576befdfc18c7c73dfa82f5417353b8933bbd1604c96b88947461fa3fdf46a251c1de3d4e7dbcb64748559775eb49f7cc17d76346cdb3f03fb798bc7569f405ec0a38755376afc891fa343ee4562095410c3abc2d4b3c07775dc0f5c0cfcdfa7feb3d9d7ae0662b6251a48cf54e3b80fcafbca3d0546057563cce4959be9094081776d535f10136143304d121cb3f383c006b1e969e6ae06ddef20065dcc8d1164a3f2bc965ddcb859774a0a8756c5efab7b8c5621b7a2683961dd0e2d66ebaf4087037a98f9c25ce3af778b79f78bd750609a7306fc7cd978169752918d32a48195eab0001f97d69f3134d82147fdf593c1305b895b830d19df18ddf057ec7ae5316616aa8fa722026b554b56277ad9a39ea7461d4aa2fa470a1d10410938d258925bda689e3d91455d082c6cc116d068fbb041d2fa12d9f98ee298ff7c7126ce31840e3c6a812738e13a4e67fe868ffadf29bc3de93d1815cc58e2837432a56c76680919caf86335d1779fe6233730ebe2062ff97a0d7a2361d7ad223a079b46af26d9a27a04b543ceaeca532cbcf41959e052c0227751e6cc359c95375c0a8285d5f49da8a6f8ee30621a53ac50925a5adf9f4c7c4661ca91f796ddddcbc35112ea764f0e1f30ac09aa07575611b4693769cf3f6bb7d720cece80b367e7626a51cec28baf74408cb895b4b8dd3377fa66d851123466d4feba84dbb3d0352f74fb42e0cc3527d542b227cbe322fa9aaa9acac233c852cd9a0cf5e8b2de0201aa0cd5e1331dfcda37f49edfde0d0d160c105f9a9a103a83c8d834bb30e64e938e0888de74dcf3708912d264f0ee879ec5ecef7d519139c692a4eef4511680958364dfde76530a3b025dc8b0b38f108baa90ef38e6c865ff220ff8b758bc3703f42f02f024df055e123b7031b9eac43d5586e4e56855c3f6608c07def381fd6e362f55dc9aac415ffd3b7cc76e09b674d79a1d6083ff14bad2b3486c767c327cfa2fdbe82c20ecef086a0f8283e0e097ee8daac5d3e26ed1e20a1ee323de8a9c3c3b721d7f8519d37ddd1d652a5a2f1b8dd2df7567ce818d86854c046637385eb7cb109edf2d657f2e53ff7c262556938a67d81e856859cf9c03f19ffad40c786abd410ac8017baaa4aff0b0ec9d58141c792dac57fe001b2c8567d2add35eb6c296de559ea8a1c60bd0be8e1c84bb7729d5d220d563c7f50d05832ad7c7d372d4b379e691f76d1abaeca24e2051a2caba32d8637f99260fd35d26a916ee51c23a9d7c6763e9b20c29eb96325e2492710ad980c298405c65b985fcd3b5c39faff903c828ab6d503518ac7a007c218965178aee7a4e498d18386de315d29cc47d5ac55516bebb695883482032331abbb5d34bb50ba6fad5a5d04f052e175a2a15e2f97b3708fbb74f98a6ef890a3c5d378083c979511ac19746711e1ec21699b8f35b11f9331be5e05aa810a81a58e739aacdddc7af9bc166576fe190b6ce6068876654783cc7c61ab7219a8ffb41870802397b88ade1be45bec664166ad84bc6f220a8757e0429317a38f6f12e445a3c42153b6daab1bc634d2f37ffb79584f679acc236ca57f6eed67d52daa829b03d5b736588a1b8ad39a701ff1010a35f51ebf9df71bbd1319c8b4a787e932da697a4d768ca2eb5a302881e054edf3f939f35c7e49fbba7c7c4d0086352c113db982fb03920ba47aec5841d719f844905a8f854ef7e8d56db90c53fc27a4b67e738a845f5776936370ea9297abcf4f0910e58d5cb575c8858affd438b9424a0a6711178dcee3e95b09bf1614201547f0c3f8c31691df62506ee310e72a4e6c509eed692643eb720ce99378242634f0f0eba3551f226c07ad85771ce766ab44a5b7ad833f32223ba47d4a0ebcaddb9c424cf322a8ce18cfa6e0499c9bdf27099fa11d2f826f3e1d767d28a53b1593712ce571c3daddd4b2ac116767fd4c5f6a448d927364e7af07ef01c8e901542e1d71c45128810e44d569fde355e7", + "b917d264bcef07402a7d292a4239dd9f655c4c8542e8836f6b2906b6ab47cc22", + "018559faf93f9ea06fe27dd435a5f24c7377380b0726fd2131e49f8a7381e94c", + "056b45c6a93570ebb345d2ab89a85d0191787a8ee6a91b52b1e04a1558c029f1", + "8d23a8b51eb7c738b4e6028ef43cbec3287ac96a1297bcff8f3b88b7c2b059d5", + 1966223, + ); + test( + "8485ba6b12bbabbcff7a44bff9f1e308a1776e256b4f15fb0f30f596a10db307f9ffb2007006430ee984050525b2cb43c0993c88f2834f462c793dc64aade4a452342c898320e237805b63caa35b4810b75f81052aebd31a08842b86aa36d2283d2032e334dbaf76dbcc67624b507b3fbf466c2b97ff73f96f3db6da969d042a79da16208f8238d02f7aeb40fe3244b30d8fc0182a32b5970838c2ddc80978e142af2a7f2cc6c6a8dabe0b30067485b9970414831475962261ae53559a5fc54e72817a77141689c56062df0658781ae3da7ffdea443468167a07a7e93e18c6f53fcef37c318e1a02210bb018edf30ac7515fa57b838943c0f2282f48a2561468ff952ee778920f98a9b19351f9fb519ca7e2d24addf4378adffc52ca71722f99dc3097fbbaf138707c50861ca01a70f9f1ed4c6559ab16006b77a84cf4eafaa2d734f5127ff73df36b2d9c665909f60fc89cae8008ce93b3bfa13427572cbf95405596b88c7d9c05478a501e6a71bb646c25155fdae5dc959e51461d4de448659eb43a03e72f378f22ecca834e410acb4293fa453cf213396ebe203ad718efc0f25c3cd6afb844300cbb61fbd64b4dd28eb6cc299a494c28416a0d3d1b8a493a55285b8c37b789d515ef80696148c1935aa46f0b6f80b3bd41decb6d82db43f58a6dac1870ac8895c2c281958e465dd8bf02c031f0e48e91fd1c7eb6e85453292c48771b51ed857e33eb8fa4b3f49df6704d8eba1c319bc675f3d9a64d192aadfad6ec755af690bc595bb4f994bd64bb5c224c24819a24a0818747d019fb45234d950c5f1d2514525a06d528b9c09b9584a2a9413b1f3bad0cbd42ee9a8a3361153323fc5d7571197a9ef5c68d485a9d9f24a97ea79f5fb44d4b231325651414b12b12bdf4b82ed7f350109130c9de47ff541fd0aafca8370a78b844d7d9e1776f5731efcde4e7dfebc4513b59e00c6f2ea02ec05cb6abecfe104d2b0c5a8e57f8698590c5857acde90b60b631eddd1185824535e7aebc9ebf1038e6645a2ef0b50e46caee5cff23d0f264660610bb5f2174e3f34e274d8af1d97b019962e6e22cfb2c3c317982cedfaf491d9936f32bb16d5fc9b99412ae1172e43f7fbc46cbf0495572a631fc9cb91b3560ec40642b8912d6a110a960943476fdf04a3d24a9a5e75feea56d638e5e06ae7fcd204d165b3fe713769e3db9be850228c36571d5e8b4e2c77802cfbc78d206d106bcde289923fc9cc8032a585de8b35a20527eb8c51e5d18aa52b7c8f54da6803ca8918a18f2371c5a4f8d64e1181a27dc6cc3968d1ec4ec5a2b1d785f37b70b0285884a69cccf743c55c8828965f73885e1e242103eaaf921d9d13513641b7098accc325639a56e09d0a218223284239d4395b78f66067931d527665ec88505ebcb2adee9adc2000a487d4054a73b1fa49d4afc8d54763f7f5eb6e037d776640b69b92cc85571788f9cd190b2116106acd6bee16d1b9c348c6852ecfe778255a92957ee8b777777716a2b6ec5ea745595bfd6a2f9086ff1c76e5f20a45bc035bae8e28ad035a6c896864f2f9308b1a97ee0d55e20950c3154dd71170f93cbc57a3055553cd48fadb555f1898955ad6ee2b64ed5a97fd392da46995cd96c22e35fb454c9422d8edd35f7413f4fd4a908fcedad250b7d99fa099eca10a5a45b5c20c10be18137dc4ad06f6dacbef7716e44b0c67611d6d31499dd3e4ce3f5f7cc14ac02f144aa9505e5a3ea68baa211ed85ce2f5d64776a660b37111b3954a35184586f54dd8cd41a323d7254f28fb915be987577dabd326f367ce41a86ec425fcc3ed727e5133f22f6166da945c6efd1e22b882b31639b2533090fc88952ed60e6566118ad7a23ec3de3b2faf7b74fb59a99bb9b804114f3302be893b91a51c449e2d2a0a583c710de85e95fccf8fcd9b41150727d412a0bda7d646b28e1b32cb359502cf7c7343b4dd5d8cba9632fc482e38b9340efcdfc232088d48587043d9b345e9a3fb7281f00a89555b664f07f47bb7930d50d3189ee25d611a1d4bee675c9ad9e388be51f67d84365639af11594a1f1737197150568daf0ae6038cb6efaeceadad067a7efb1e0663119051ba92f76dd5089927492a694dfb5b951b6d7afe51d32ef6c7c956cd3bf19d3a53033800fb62c60f5892f0b628930780c7332c1d8f1726348122f3a1966555c3732397b1d6994e1c9fb9013d6eb4f8c5ec4eb8afecc6b17aef75fb28631a2aca4f87912f51db46382614fe73da18a6e624d7206444e8e03d28bf289c9c5b6c163ddbfb9310481d86d692271081e5202323ee6553b8c9be94e140563c2900198d8a23751b76a1e1c7006e8c7c41e5b147d", + "037b5bda8b6d9deccf0ae09c3095598bb91dbcf87ff61a447799161444f690c8", + "c4e1c78612dfc921bbae49555983c7864e14a8b26267ce7564ab5efa59cb2878", + "c9e58907967f4f9c897adcfb0f8178891821f8b60e0e446c66a3a46f7e780285", + "048ff4fc551455e6f3dc792041f466fb2c58593b54119e80486418c281ea2fa1", + 1909909, + ); + test( + "3401683eb417146f38963c6dfd678d93d5fb4f3e1fa11526fcbb484c3cf2b79dbd1a220da3393a80a45debda44a839efcf5c66e94875c2dd3b987b5b6028bddb6ff3fa0b57b5ed9231452ba54be8a0dbf2b93bd973b5883c4c4d0a7f2a42b14f25e988515e6f1f306b9eb1d457e6162ce02ec46edf04a334cd4b75dc2210fdafd316dbe39a6f45a4e38330ec43ab366baf3b2558062bf7f1a9c7f145ebae00bab4346d2e685228c5865f1cd33060d90ad6721f37982df35f064bc9814f614e548f62d784d0c025d4ee37efb59bebca5ad6741770373c06ad176179f48a46e279e56cd5e801ce29aa2b78e040dc53d9be391abc4cc2fbee567088de723d6288d7737ad3b82fd9489fdf151fca8135af88b05a4ed431408361e0ed7c52ba6b0680ada3189a470e23b71993e059592e9efc32817158f7a94b62652d4cfba4f700436e01e83e5a99a1a43af351324cd3fff7f90eabd805feb9f41124e533761a4f49c6792c50c2611d2134aa249bb75094d9b0e95b4eefd31771025981000a3df1f875b1b04a4ae161c6a2540c5a21b5e5d134192f4d99acbaf58fc1d2c7ea440266db66b41efa328565205b65a2653cdb4a316a133b0dcb6cedf79f2fde9c0fd8bbfb1946f60cd426c677a2cdc4a79b267d70144d9c5a98652803edc3bde7a17c97e0e9ab5ef96e283045b6a9db1ad3ada916e34244bf87c4a8d468120df738fdd2a827530484b3e64f4b0baf1efe2e3d55b69418d55188e57521481bc0cdc763e4ab4a2bc3b5de426f6dd2a2977d08ff2929a8919c3345c2684e18b25fee6eadd5ee0444c2316955cbc7ec5844767ac1ea8ba347ba22d1ff4ba6b40ec36c3dfa6b488b6a3372ddeff0917d1ef166ed94dad6fe46ef3947ec1185e2a0701d537be274ff948534a8072f8e7215ecef72a41eb17b043422330b9555254d797b264baebb805f26d7a4e121f7bfd37517026761642ec12120db610a112ef527d68865ad89d8ac5ef053c99fd224cc3c2f9ee2edf23e342ab3891d5d4aca849530cf3514139dec39527dba24ca3db8ce158512a2b514eaf2bd052a5c9b3fc7217db1d6da409c172bfc60591401b32f1f51962b21227ea259dacc18be6fbb4f2f65d845c8d6445c63d9789714bf6cb2ef8ad6c5ac26556b3d23566e9e9c37a490290b8a452c25bdd7e62b0ffdfc80e3786a728d7ea5f44aae7ecd7a696a6ede1928a0e46f29b4e5c85ce1e9e26b40f267dc9706550883e3a331ccc9cd704b26341419e605365069e39ee732a047fc40406aa69d306439e8d8095e12f371f61f161b46e1b4ca3ae969daef4ba39a0c938de34b1056fea27711602bf17696bf7b3763f9e70f35a8530e004b398bd950cb1ca423abb48c8f91d469cc776cf2a5feb9c888f6aa76615402c1f401acbca4192b5f6973ab5b324cda5a8c52e1e0edb4e1b94fbc5a6782db4604ae2b01f6417e9d0d9225c0f49d4a3a4f59ff87cba68a95941ef6c4c55643bd45ff7eecb75c12b23d223dafe1fa595be4b7c1d439f126dea09f9937feff9ac6decc90f1171fd47a657a8b6ef3032f486b32173cdd8ba7f902d8c80e60f9b4da5c01b613cc6ab4205854a95b1e9f36dd66a76dbbe0fe8bc97028aff8da8e8e2846867037e35c18ce7cf9046294940b89ddebb4c8963b17f6e43552db85ce0ecf2890dc17f49c858670606a3eba4fe06f90f342794f50e52018594ec19e6a47583510fa7c59ff6a498d41335c60006c1f5f59fe84d73559bc6985f6869a6c0e6929c3901f72314b66df22b3c540a8e7b3a2db9d61060e042cf0cd1277297d7b549bd9aadcc2cccd3bde7abc546a8b47d3ee427aae9be849d4e7b0ea5fdb23eef1bec230a6d4961d229b4d4ab8d92f75df87b7672e272edae1709db31424f0d2afb809c5f696898a944694f6e5e42c497fd67581d9f768ddeee4d5ad2560662da603f7e3ae8f6133ed58d2724eaf8f729248b11f42e99cac82625970af71524545f07b13a10cc7dea21899fea71e45853d828fd351834ad3e12fcc33f6bb3fca11e73f012a3631e4aad93b8a091cfa0bfa3f0178228de9ef9e5cf84d1e0cfce439e0cb86b4b19b7580a83ed0eb07271407f6c8c336f461989d901ecda9d2dbf26e4f724c6bbfbfcda49048f7bdef5e062cc3dc600b94a833408c9bc98ea06d68435e719e2c5b13fd6708ad69a92049159bece630b8da3cad69c9017fd0e7b31b676362194f0d2f0a0c48c3a3a74325a9381d811a2380aaedee67226ac2f2839b5b93a77615ca9c1645a66c9cd44a5813610587eb93a5c0d15653d76b2bb7db5207f5a2c528667a024d2ece1c1fa337a61398a783f2785f8709c307fab83a51d246a3da9de4084262027045f3498ca561c41e373e0987ec2d69cad17f11677a663e14b600a4b5fc22ba967eac838b0ab741b75482503f81a4fd8e5d22a358f6268f976bf32dc87ffe5cf53e04cac02999ac1b01831cc4501d695d8c1bb71245933e994682d13be2e8fc1ab3cc7a", + "ee7fea6125131c1ef85ae2e02099d97a312e0274fc2e8daaad90468a12893ee0", + "67141c9a4eb517085e6f69510c2af038b5fd1e8a9839bb38a64ea16fd80cf6c6", + "2e41c9d38220c5472351c4457771aec372d6c9b175a339af8b0de5e483b9f4bd", + "b27a43a7a854e5b5cb82dd113e8212ab2d1d20e447ab2ac668948bff99d3d134", + 1897015, + ); + test( + "61d91c2cb6c51f6943e05c180f07fd83f1659d9d6dba2eedb34bb9cea72d865f16d1041ff034a307d33525f2c9cbf2f705d848596aee587680650c8b7ee47ecaf206da52940895df702397c7d62fdf504c67fead6c7d1e8d4b3efd66a1521b6f098d1ef3d736a4c4086e3d7390ff0d9db22e72226ebf88d66482ce056462a3d6f04e8cc05efdbb7808c1dcd8fdf998e897bf62af1f8588aab703bb83a22d08e82b35e32b08b0eeb82480c395689da89603649eb29d727cb82392b32edaf622c88c03ba841cc7070ce70c4cee6bf23f91825aa0895889523f907acede44b6f8e7d52c51ee88108e85442d0373a8afee9ca2802f27fe0dcbfb9f312afe9aacc01d12d4e3ceb91a08680bddcbe95f3871fada15deab96fe0ae5978d64c455e47e9b75a5b01be9b1b45bd062bd055eea5c865750374a3cdb77a6c880fb62dfda093283f5586c0ed63d1e056991748fe143466f6d2fb442e2ad390797f90bf46c36e35ca87ec2a6230cf0080caa335485eaa64766677d6b6677bf8d50540bf942ede603093fea16c14ecbd02da626afb0fc6667d7c0290c63f0ac34fecef58d9a0280f5a3346a3319862e871355d7047c7f5ed68a6bb6270e4972e14eb4dc03309510f022d7f0cd11bb471ed5aa217018e21a883630fed648eef25f5299c37d17c76479e715b065c2fba2b975321278eb2444497a217c49a139de95c1d1f5854d798b2da2ef55617de9e80b01fa8cbbccaeb472c6c763645e74a73a0c79b6e022d1003b7d7934ec2767b99e036d8464ba3c7a32b9657281850999535f0179d7211bacb9a9512d39fb05e91c74d0d71cc1b7d3b0d8936499be5a4e8f0798539c7344073bad3575a5602df7dc7bbb8a11b1bfe0a63c2894d409c74e0ebd1a98e77defd3112608d0a39aaebbbaf33a95ee638af8746ef9e75ebbc1132f9a98c0c660653c34b6aec8362c490acb3beb26f74d54a28adddd4c499b74dc59ba1cd553e2757b9a74c701d1a83ce0894d034526ff696810bb25da75d4397048dab03f976a11ac81716c1e4155003f0a91a58238a8642ad9927ce53a2d8cbb8ff143e57792e833e4e70f322cfe5e8756be7631df5c5a6c9754734912f3830b38c5a751b7a2cc42b803a37c171753d9183bc9048eb4bfdce4102e5c24e5b2645796aed7e23d998e2f81ba6d2783820d46c31e7385bc44bef605042cad643321b664878b2d87fff42bff80e231eacb2349ebdda5a2355366c9e08949b0bef928fcf7d593889c289529fc58f6c7f8c3fbd9e95d348dcd7ed386da420519ac62785dbfee9909760070163d0b84c8f785ea1f402d9e3243b33b8f5666797cdb6a7638b3ecea02c5b686826dff584257e4e182a3181a5b89495690270b163e0faaec5298df565e3cd06493b2def18fe59a509ea850826d931a13ae1b54fe0612a10ab79c6a1b9457c5dd8975b57d2abc72d979336531950b3d511fef096590d408cd63198be9dd7b52ecbfcfc1867f8f473c51a861b6bcb1307989f115880e4f760b5c10b1401b06421f94ec5540683bcc5eec53c2a1644ef5f4d82409674e6e0736189d135b65cd4824d2f81cec11a28f39c89bcb70caa2b206e4867172db67445df3df49edad0832759b81ee1f53945b7e68c41a783b0414c23fa61114a597fd6d4f4d0582aa1d104f0c84424938fd4985ff763ac7cbc3cf0ecb86a567b4eef0b2066e92ac1bf09ac4d009865d612a11f9169c2d43ba355a19bce0174dcb0ea6cf9551e51088b816be38c0f85defd088c76583b107b00561e82d7674fc544592b6fe72fb51cce354b69623a7c4d9938ab5184c87d740a241d667eb3ff2b331ad4f4ff8ce369e2bfeb183b7e619161b8a76011980d852494053f199b7da8ceffb7da6a068816a15f8903a672161e6276d73cd672cd20931bf68da165033dd975a879a28e4e90bb5f2087aecc93174ea2aad990e9463190c051523c73027325a6c667e0e8ad32f37504053fb0efe9c43f4e55253124fd79d2b5b452895fef0bb8059e973dd9942eaf3275dc3140d41ee82a4262436f2ebe10f3e73cfc75193c82bd9f24f0c16f04930e651fb0c2fae20721bdafb724d54c3f624145c513b5dd050f105b8160511ec79955075b777d5be23a86bb9278fab56ed564fa85d7c23a995becb589761b2f5d7f773580797e93924d6f0899e47f57dc35ae07a3d9423cdd31555518c9be510b2bc36db35e9c25f900f75c53f313ccd6b84baa932999c27ab6e347ac18706ffcc91ca823fddc48af7e49a61140e032e00347d51d0807470574953dabccbbd589a0f1dab7bce23dfaad7d894d897cb4e83ce508959691e995e082e4d44d7102495429df5bcba0e201d4263c5175d83ed1531ac206ce66969f5c6043cd7b638e8c67a9c98a6242d47e0f941d1956991a96e008c3fa9526b941a9796078cee88834e857bdeaaab3a8d4a717629450e0474486aed6f2b62650c3030f492df1bd91760860ed19074e8b9c7e253b63dd05b8399de2b4a0fcfbcf1499969e777c5f6d3f337047fa1802d4873c1ba17dcf93d0400515ceee00587b87b387a4960698f39ece4604126996e9dad7a20e25cbd9477cac28ffdacf061f1fdc19e3ffa9db8", + "6b536f96219488c8d63934b43c35dddb87f916356a07327387e8290915e39a52", + "c70653b3912e28b147f6c726e563798c90eb02eee034f3b8689f7f416b60cae3", + "441ebafdd34046af65c823abed216fa0272e1cb439f87ef1ac677565f552d75a", + "1c43ff5c2cc02cb596dec0296fbbb8e687ab63ba540054424e5e6b6c5c7e219e", + 1934947, + ); + test( + "3cf47bfba91d45c921bcc188c5f8895d051b65e81cb3b8eefeb9713702e815ea8791ab76be7e90a6d59bdc99d6e653e3ada83fbd209375e0924c619dca0f7596d6d18b5a881f9f02f3f310b704f62d6d84baf79c42612fd0644917c628dac313fc95b6f3da602d8d527186e2dff109a944b939aa69bba10abd9d7398fe0146631e96b38807f26dbc51c904806eaff11fcf007b48d9fc08de733bb6fd7d46781751096206979aea67c2dfa27f3850c05b75e4f41a7304a71847cff1eb6173f555a7dcdae194f8a5038bfae48fadcde0cdd3379adb5307d18e9212d8ad62674e21ab61e794b514ec7766e091a625acdad84f981dab19f8b540568e254fee6d3124628fa952c2abc5397259b048a4392ae30a529de89dd7309ce11f8c1ee3df7a1255bb60d1be45452cb6a6c35497b3127084d3820968a884cbe6c875044d17e8ab173fcb447cfbcca4bd9fa6e9af175f0c8632fcd4488cbf035aa4d218d8b6fa7431f786969404b915522174be3463f7de6ad107edf60ecfc9f7cb857bde12cb37e0a14e9af6f86428f5531c323c3911b08e98077f7292a114287ae741b6342b814fd800f321f437f59d9f2f4d21b32a0507b626d2f436726e10aa9b4554ab426863264ad2d9009f2bbdfe5fe04108e645c06a550e3061d1b84178c6da1522e2f6dc5cf308ce0829600bf03a6d404dcd983fc05077d10b0fb4f0ddd3db7332f4fe7ce0bd9c6af6c9eb5bf399ac55ab897a418370685472831a60b2f5ff1845a1ffe868ce5eaa88ed123c030f0a24c0194765dc113180d2eb2b3267b0336ec1baa94e0133089730c2580b0a97b08302303daaeb878f9f9a9d178dc4892a125eedb5f384e351fce61a7cbc1134d36b254a4a3d9a40327d0e0a67ab1d28256f0340199da6e99ec8ea6ff2f645c771c76e56f35e1ec2521d9b7ab544365c705225ca8bbbafd3fc6af43481dd3d61ce6148d88f43b23b62fbcc90b18ae186c8c9f2b5bc4644d5cc0e2e04c3eb0fea6df38cf542cc14abeaee123c1718e3afc02d78edd04d1c022360891b2a8cb6b8f17f3f862a33236d172aa9cf3940c9ec4d0735d4da4400f500d650e93f293f38558d26f5cfc6a182748dd34bd4c0b5beffc26a7faae08f9776222eca8905e6db3f03bb3e299e20c205989e33c6fcad00eda3e65d0e2d37d92aec69a84731d2b5d1a0a2965dc627b09c529ba389bd38649555844105cf5b063c05ea709adddeba6c3df08c2521109e5d83363c4bd4a4af720c5a61bda81009f01a4d5211225c518f449d40a81da0a689eef64f602baf83b90c1b9d357f1379b69c41c936320ff15ef377242f46c71836156f7f77501f61818d7faa548c14b94a118dcac7f1c0e2188c15b47ca7b48c092c8629f6fc0c22c56ea38c5e54ba4d3241ddbec37b65bd38aef833c9f102fe513c1ff4a59c26ae4e7f434330e200925ce75f475d3eb1884a752852079acc67fa5cd5f855c2b8dd335efe8f98b6bb3b0c2fc529ad150763e562a24308895adc427de32a143b0602bd9fe44d290f2d65d70dcb5c1003f4779d6569e1816942ac1d0680bdf6e791056c8c4023a9f0deaa9b7b4e8a44398dd88c06e3f8aaf591a07a1fa5ec8d5853adb85632457e18aac93caea4cc86d8d4c3e21522b0c6922e9ec7dfdef65c0a8fe70ee221a60532686dc269c203c843b201b57a66627870f6ceb69d09450e178031bbd03f8c0b1b3ada1b146a9bd307c6329a97f9e184a4ee7beffeca483b80185b9263358fd6377502699f2e3dda7a83fe38e4e9a3f7d6907cd03e384977eda1c92bbd9d93c144daf3a1b69633ae4fbf153e1b60e169cd9a371d3223e57b561446265c2b9eb9c30c8bdcd78d213bdebc71d6ad4a6265f10b798bf29d09ec42007258b3e5064e0ee817901673fd17f75332824656db8dbb47a898ad5c7c36cb8e69f9588db9123a45473a92e4ace33d9aadc52f9fe316685b7e9d44f6ce0d098be2bc91ead3b34a291d690a8df94f06344b0e925a049f49d65d6eb668191601e150f05b787c0698fa505e0fa1e4b675456bb71add9460c549aea8691ef9533325fe96094df3892debd064f99293ba5e300589b436373c93f1e104d3aa904001d5bcbffbae627db493eae5d6edf5e6c752a0517a982437c18e3e96a317d3cf4ff75629590c2e29dc5d9d33424eb4ffb5ff8490f0c02b4bdea7367962db72ba148f91f8d49f72100ad9495f11361cd52f29693a0316e8797bbd5e9affca0f7a78b267f3145f4f37de2eaaf189ea7c484323a39aa29dc409a139b7542659732b20562ffa48bbd37a99f7de44fb12204de65984d3a648b19ef971983dfd805f04006961d6a99e7c525a27dac684de4f5da77980f5d91ed7da34b9a2fb5b543185782f2960c58a849255152ba6120799f6a1b90b7d8e0ce1d9162f8a6bb68f9a60f624d551353da2f475f9f6df759f85f68e042662a553fafbf07cc105822dccf8b800919018e7b317ecf465066016eec912f25b10858da0688331c9e31f55aa76b6e338d270d45aaab0be303eda173f0208126525637e1aa7096a330f0d8d5ea159fcb84a1c3517e4796c590bb335910266edf7beaa9eef08647bb35ad56d2b0c7ea064a94b2abea71187bd9ff587a92c0cdd78aa397e4f60bc3245c6f23320858daeb89006545114a52f6f530a51f111814e232f919d071ba3eda1cf826d3e60df4e402399ff91d19de9bec47284c37f9e0886afd303cf763067681aa845c105ea59f968f2b0e5491d18a8bfaff28b09c3e2d77038e2b4a5", + "fb00c9cdc9f928e39bd5c924b50e75e3810f2337c72380e7a170eabbfcc23bb7", + "764d574f338fb5d766924371e64c2bd5fddf224d4e74788c0175e720e0d354a6", + "38be38c47eb6419c28cac8f7b09871db03f2e6cfe7962a63a4e668b5d40b0349", + "329528545b00249b54ad38569122ce9db9fb5efe2ecbe43b915a4e28ce5cd431", + 1944301, + ); + } +} diff --git a/cryptonight/src/util.rs b/cryptonight/src/util.rs new file mode 100644 index 0000000..de8b70b --- /dev/null +++ b/cryptonight/src/util.rs @@ -0,0 +1,122 @@ +/// Extracts a fixed-size subarray from an array, slice, or vector of any type. +/// No copy is made. +/// +/// # Parameters +/// - `array`: Input array, slice, or vector of values. +/// - `start`: Starting index of the subarray. +/// +/// # Returns +/// A reference to a fixed-size subarray of type `[U; LEN]`. +/// +/// # Panics +/// Panics if `start + LEN > array.as_ref().len()`. +#[inline] +pub(crate) fn subarray + ?Sized, U, const LEN: usize>( + array: &T, + start: usize, +) -> &[U; LEN] { + array.as_ref()[start..start + LEN].try_into().unwrap() +} + +/// Creates a new fixed-size array copying the elements from the specified subarray +/// of a parent array, slice, or vector. +/// +/// # Parameters +/// - `array`: Input array, slice, or vector of copyable values. +/// - `start`: Starting index of the subarray. +/// +/// # Returns +/// A new fixed-size array of type `[u8; LEN]`. +/// +/// # Panics +/// Panics if `start + LEN > array.as_ref().len()`. +#[inline] +pub(crate) fn subarray_copy + ?Sized, U: Copy, const LEN: usize>( + array: &T, + start: usize, +) -> [U; LEN] { + array.as_ref()[start..start + LEN].try_into().unwrap() +} + +/// Extracts a mutable subarray from an array, slice, or vector of any type. +/// Changes to the subarray will be reflected in the original array. +/// +/// # Parameters +/// - `array`: Input array, slice, or vector of values. +/// - `start`: Starting index of the subarray. +/// +/// # Returns +/// A mutable reference to a fixed-size subarray of type `[U; LEN]`. +/// +/// # Panics +/// Panics if `start + LEN > array.as_mut().len()`. +#[inline] +pub(crate) fn subarray_mut + ?Sized, U, const LEN: usize>( + array: &mut T, + start: usize, +) -> &mut [U; LEN] { + (&mut array.as_mut()[start..start + LEN]) + .try_into() + .unwrap() +} + +#[cfg(test)] +pub(crate) fn hex_to_array(hex: &str) -> [u8; N] { + assert_eq!( + hex.len(), + N * 2, + "Hex string length must be twice the array size" + ); + hex::decode(hex).unwrap().try_into().unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_subarray() { + let array = [1_u8, 2, 3, 4, 5]; + let sub: &[u8; 3] = subarray(&array, 1); + assert_eq!(sub, &[2, 3, 4]); + assert!(std::ptr::eq(&array[1], &sub[0])); // same memory, not copy + } + + #[test] + fn test_subarray_copy() { + let mut array = [1_u8, 2, 3, 4, 5]; + let sub_copied: [u8; 3] = subarray_copy(&array, 1); + assert_eq!(sub_copied, [2, 3, 4]); + array[1] = 10; + assert_eq!(sub_copied, [2, 3, 4]); // copy, not affected + } + + #[test] + fn test_subarray_mut() { + let mut array = [1_u8, 2, 3, 4, 5]; + let sub: &mut [u8; 2] = subarray_mut(&mut array, 1); + assert_eq!(sub, &[2_u8, 3]); + sub[0] = 10; + assert_eq!(array, [1_u8, 10, 3, 4, 5]); // original array modified + } + #[test] + #[should_panic(expected = "range end index 4 out of range for slice of length 1")] + fn subarray_panic() { + let array = [1_u8]; + let _: &[u8; 3] = subarray(&array, 1); + } + + #[test] + #[should_panic(expected = "range end index 4 out of range for slice of length 1")] + fn subarray_copy_panic() { + let array = [1_u8]; + let _: [u8; 3] = subarray_copy(&array, 1); + } + + #[test] + #[should_panic(expected = "range end index 4 out of range for slice of length 1")] + fn subarray_mut_panic() { + let mut array = [1_u8]; + let _: &mut [u8; 3] = subarray_mut(&mut array, 1); + } +} diff --git a/deny.toml b/deny.toml index 85e7da2..e54d116 100644 --- a/deny.toml +++ b/deny.toml @@ -81,6 +81,9 @@ ignore = [ #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, + + # TODO: check this is sorted before a beta release. + { id = "RUSTSEC-2024-0370", reason = "unmaintained crate, not necessarily vulnerable yet." } ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. @@ -110,6 +113,7 @@ allow = [ "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained + "Zlib", # https://spdx.org/licenses/Zlib.html # OpenSSL 3.0+ uses Apache-2.0 # OpenSSL 1.x.x uses https://www.openssl.org/source/license-openssl-ssleay.txt @@ -133,7 +137,7 @@ confidence-threshold = 0.8 # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Cuprate (AGPL-3.0) - # { allow = ["AGPL-3.0"], name = "cuprated", version = "*" } + { allow = ["AGPL-3.0"], name = "cuprated", version = "*" } # Each entry is the crate and version constraint, and its specific allow # list diff --git a/helper/Cargo.toml b/helper/Cargo.toml index 59e4e71..716beb3 100644 --- a/helper/Cargo.toml +++ b/helper/Cargo.toml @@ -9,25 +9,33 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/consensus" [features] -# All features on by default. -default = ["std", "atomic", "asynch", "fs", "num", "map", "time", "thread", "constants"] +# All features off by default. +default = [] std = [] atomic = ["dep:crossbeam"] asynch = ["dep:futures", "dep:rayon"] +cast = [] constants = [] -fs = ["dep:dirs"] +crypto = ["dep:curve25519-dalek", "dep:monero-serai", "std"] +fs = ["dep:dirs", "std"] num = [] -map = ["dep:monero-serai"] +map = ["cast", "dep:monero-serai", "dep:cuprate-constants"] time = ["dep:chrono", "std"] thread = ["std", "dep:target_os_lib"] +tx = ["dep:monero-serai"] [dependencies] -crossbeam = { workspace = true, optional = true } -chrono = { workspace = true, optional = true, features = ["std", "clock"] } -dirs = { workspace = true, optional = true } -futures = { workspace = true, optional = true, features = ["std"] } -monero-serai = { workspace = true, optional = true } -rayon = { workspace = true, optional = true } +cuprate-constants = { workspace = true, optional = true, features = ["block"] } + +chrono = { workspace = true, optional = true, features = ["std", "clock"] } +crossbeam = { workspace = true, optional = true } +curve25519-dalek = { workspace = true, optional = true } +dirs = { workspace = true, optional = true } +futures = { workspace = true, optional = true, features = ["std"] } +monero-serai = { workspace = true, optional = true } +rayon = { workspace = true, optional = true } + +serde = { workspace = true, optional = true, features = ["derive"] } # This is kinda a stupid work around. # [thread] needs to activate one of these libs (windows|libc) @@ -35,7 +43,11 @@ rayon = { workspace = true, optional = true } [target.'cfg(windows)'.dependencies] target_os_lib = { package = "windows", version = ">=0.51", features = ["Win32_System_Threading", "Win32_Foundation"], optional = true } [target.'cfg(unix)'.dependencies] -target_os_lib = { package = "libc", version = "0.2.151", optional = true } +target_os_lib = { package = "libc", version = "0.2.158", optional = true } [dev-dependencies] -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["full"] } +curve25519-dalek = { workspace = true } + +[lints] +workspace = true diff --git a/helper/README.md b/helper/README.md index 42c633a..05d98f6 100644 --- a/helper/README.md +++ b/helper/README.md @@ -6,12 +6,16 @@ This allows all workspace crates to share, and aids compile times. If a 3rd party's crate/functions/types are small enough, it could be moved here to trim dependencies and allow easy modifications. ## Features -Code can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`. +Modules can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`. -All features on by default. +All features are off by default. See [`Cargo.toml`](Cargo.toml)'s `[features]` table to see what features there are and what they enable. +Special non-module related features: +- `serde`: Enables serde implementations on applicable types +- `std`: Enables usage of `std` + ## `#[no_std]` Each modules documents whether it requires `std` or not. diff --git a/helper/src/asynch.rs b/helper/src/asynch.rs index ea89dd7..9868191 100644 --- a/helper/src/asynch.rs +++ b/helper/src/asynch.rs @@ -19,7 +19,7 @@ pub struct InfallibleOneshotReceiver(oneshot::Receiver); impl From> for InfallibleOneshotReceiver { fn from(value: oneshot::Receiver) -> Self { - InfallibleOneshotReceiver(value) + Self(value) } } @@ -43,7 +43,7 @@ where { let (tx, rx) = oneshot::channel(); rayon::spawn(move || { - let _ = tx.send(f()); + drop(tx.send(f())); }); rx.await.expect("The sender must not be dropped") } @@ -62,7 +62,7 @@ mod test { #[tokio::test] // Assert that basic channel operations work. async fn infallible_oneshot_receiver() { - let (tx, rx) = futures::channel::oneshot::channel::(); + let (tx, rx) = oneshot::channel::(); let msg = "hello world!".to_string(); tx.send(msg.clone()).unwrap(); @@ -84,7 +84,7 @@ mod test { let barrier = Arc::new(Barrier::new(2)); let task = |barrier: &Barrier| barrier.wait(); - let b_2 = barrier.clone(); + let b_2 = Arc::clone(&barrier); let (tx, rx) = std::sync::mpsc::channel(); diff --git a/helper/src/atomic.rs b/helper/src/atomic.rs index f253737..aa66c0c 100644 --- a/helper/src/atomic.rs +++ b/helper/src/atomic.rs @@ -5,9 +5,6 @@ //---------------------------------------------------------------------------------------------------- Use use crossbeam::atomic::AtomicCell; -#[allow(unused_imports)] // docs -use std::sync::atomic::{Ordering, Ordering::Acquire, Ordering::Release}; - //---------------------------------------------------------------------------------------------------- Atomic Float /// Compile-time assertion that our floats are /// lock-free for the target we're building for. @@ -31,9 +28,13 @@ const _: () = { /// This is an alias for /// [`crossbeam::atomic::AtomicCell`](https://docs.rs/crossbeam/latest/crossbeam/atomic/struct.AtomicCell.html). /// -/// Note that there are no [`Ordering`] parameters, -/// atomic loads use [`Acquire`], -/// and atomic stores use [`Release`]. +/// Note that there are no [Ordering] parameters, +/// atomic loads use [Acquire], +/// and atomic stores use [Release]. +/// +/// [Ordering]: std::sync::atomic::Ordering +/// [Acquire]: std::sync::atomic::Ordering::Acquire +/// [Release]: std::sync::atomic::Ordering::Release pub type AtomicF32 = AtomicCell; /// An atomic [`f64`]. @@ -41,14 +42,20 @@ pub type AtomicF32 = AtomicCell; /// This is an alias for /// [`crossbeam::atomic::AtomicCell`](https://docs.rs/crossbeam/latest/crossbeam/atomic/struct.AtomicCell.html). /// -/// Note that there are no [`Ordering`] parameters, -/// atomic loads use [`Acquire`], -/// and atomic stores use [`Release`]. +/// Note that there are no [Ordering] parameters, +/// atomic loads use [Acquire], +/// and atomic stores use [Release]. +/// +/// [Ordering]: std::sync::atomic::Ordering +/// [Acquire]: std::sync::atomic::Ordering::Acquire +/// [Release]: std::sync::atomic::Ordering::Release pub type AtomicF64 = AtomicCell; //---------------------------------------------------------------------------------------------------- TESTS #[cfg(test)] mod tests { + #![allow(clippy::float_cmp)] + use super::*; #[test] diff --git a/helper/src/cast.rs b/helper/src/cast.rs new file mode 100644 index 0000000..5628d7d --- /dev/null +++ b/helper/src/cast.rs @@ -0,0 +1,85 @@ +//! Casting. +//! +//! This modules provides utilities for casting between types. +//! +//! `#[no_std]` compatible. + +#![allow(clippy::cast_possible_truncation)] + +#[rustfmt::skip] +//============================ SAFETY: DO NOT REMOVE ===========================// +// // +// // +// Only allow building 64-bit targets. // +// This allows us to assume 64-bit invariants in this file. // + #[cfg(not(target_pointer_width = "64"))] + compile_error!("Cuprate is only compatible with 64-bit CPUs"); +// // +// // +//============================ SAFETY: DO NOT REMOVE ===========================// + +/// Cast [`u64`] to [`usize`]. +#[inline(always)] +pub const fn u64_to_usize(u: u64) -> usize { + u as usize +} + +/// Cast [`u32`] to [`usize`]. +#[inline(always)] +pub const fn u32_to_usize(u: u32) -> usize { + u as usize +} + +/// Cast [`usize`] to [`u64`]. +#[inline(always)] +pub const fn usize_to_u64(u: usize) -> u64 { + u as u64 +} + +/// Cast [`i64`] to [`isize`]. +#[inline(always)] +pub const fn i64_to_isize(i: i64) -> isize { + i as isize +} + +/// Cast [`i32`] to [`isize`]. +#[inline(always)] +pub const fn i32_to_isize(i: i32) -> isize { + i as isize +} + +/// Cast [`isize`] to [`i64`]. +#[inline(always)] +pub const fn isize_to_i64(i: isize) -> i64 { + i as i64 +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn max_unsigned() { + assert_eq!(u32_to_usize(u32::MAX), usize::try_from(u32::MAX).unwrap()); + assert_eq!(usize_to_u64(u32_to_usize(u32::MAX)), u64::from(u32::MAX)); + + assert_eq!(u64_to_usize(u64::MAX), usize::MAX); + assert_eq!(usize_to_u64(u64_to_usize(u64::MAX)), u64::MAX); + + assert_eq!(usize_to_u64(usize::MAX), u64::MAX); + assert_eq!(u64_to_usize(usize_to_u64(usize::MAX)), usize::MAX); + } + + #[test] + fn max_signed() { + assert_eq!(i32_to_isize(i32::MAX), isize::try_from(i32::MAX).unwrap()); + assert_eq!(isize_to_i64(i32_to_isize(i32::MAX)), i64::from(i32::MAX)); + + assert_eq!(i64_to_isize(i64::MAX), isize::MAX); + assert_eq!(isize_to_i64(i64_to_isize(i64::MAX)), i64::MAX); + + assert_eq!(isize_to_i64(isize::MAX), i64::MAX); + assert_eq!(i64_to_isize(isize_to_i64(isize::MAX)), isize::MAX); + } +} diff --git a/helper/src/constants.rs b/helper/src/constants.rs deleted file mode 100644 index b77fad1..0000000 --- a/helper/src/constants.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! General `const`ants and `static`s. -//! -//! `#[no_std]` compatible. - -//---------------------------------------------------------------------------------------------------- Commit -/// The current commit hash of the root Cuprate repository. -/// -/// # Case & length -/// It is guaranteed that `COMMIT` will be: -/// - Lowercase -/// - 40 characters long (no newline) -/// -/// ```rust -/// # use cuprate_helper::constants::*; -/// assert_eq!(COMMIT.as_bytes().len(), 40); -/// assert_eq!(COMMIT.to_lowercase(), COMMIT); -/// ``` -pub const COMMIT: &str = core::env!("COMMIT"); // Set in `helper/build.rs`. - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test {} diff --git a/helper/src/crypto.rs b/helper/src/crypto.rs new file mode 100644 index 0000000..1a27cd3 --- /dev/null +++ b/helper/src/crypto.rs @@ -0,0 +1,122 @@ +//! Crypto related functions and runtime initialized constants + +//---------------------------------------------------------------------------------------------------- Use +use std::sync::LazyLock; + +use curve25519_dalek::{ + constants::ED25519_BASEPOINT_POINT, edwards::VartimeEdwardsPrecomputation, + traits::VartimePrecomputedMultiscalarMul, EdwardsPoint, Scalar, +}; +use monero_serai::generators::H; + +//---------------------------------------------------------------------------------------------------- Pre-computation + +/// This is the decomposed amount table containing the mandatory Pre-RCT amounts. It is used to pre-compute +/// zero commitments at runtime. +/// +/// Defined at: +/// - +#[rustfmt::skip] +pub const ZERO_COMMITMENT_DECOMPOSED_AMOUNT: [u64; 172] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 20, 30, 40, 50, 60, 70, 80, 90, + 100, 200, 300, 400, 500, 600, 700, 800, 900, + 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, + 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, + 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, + 1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000, + 10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000, + 100000000, 200000000, 300000000, 400000000, 500000000, 600000000, 700000000, 800000000, 900000000, + 1000000000, 2000000000, 3000000000, 4000000000, 5000000000, 6000000000, 7000000000, 8000000000, 9000000000, + 10000000000, 20000000000, 30000000000, 40000000000, 50000000000, 60000000000, 70000000000, 80000000000, 90000000000, + 100000000000, 200000000000, 300000000000, 400000000000, 500000000000, 600000000000, 700000000000, 800000000000, 900000000000, + 1000000000000, 2000000000000, 3000000000000, 4000000000000, 5000000000000, 6000000000000, 7000000000000, 8000000000000, 9000000000000, + 10000000000000, 20000000000000, 30000000000000, 40000000000000, 50000000000000, 60000000000000, 70000000000000, 80000000000000, 90000000000000, + 100000000000000, 200000000000000, 300000000000000, 400000000000000, 500000000000000, 600000000000000, 700000000000000, 800000000000000, 900000000000000, + 1000000000000000, 2000000000000000, 3000000000000000, 4000000000000000, 5000000000000000, 6000000000000000, 7000000000000000, 8000000000000000, 9000000000000000, + 10000000000000000, 20000000000000000, 30000000000000000, 40000000000000000, 50000000000000000, 60000000000000000, 70000000000000000, 80000000000000000, 90000000000000000, + 100000000000000000, 200000000000000000, 300000000000000000, 400000000000000000, 500000000000000000, 600000000000000000, 700000000000000000, 800000000000000000, 900000000000000000, + 1000000000000000000, 2000000000000000000, 3000000000000000000, 4000000000000000000, 5000000000000000000, 6000000000000000000, 7000000000000000000, 8000000000000000000, 9000000000000000000, + 10000000000000000000 +]; + +/// Runtime initialized [`H`] generator. +static H_PRECOMP: LazyLock = + LazyLock::new(|| VartimeEdwardsPrecomputation::new([*H, ED25519_BASEPOINT_POINT])); + +/// Runtime initialized zero commitment lookup table +/// +/// # Invariant +/// This function assumes that the [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`] +/// table is sorted. +pub static ZERO_COMMITMENT_LOOKUP_TABLE: LazyLock<[EdwardsPoint; 172]> = LazyLock::new(|| { + let mut lookup_table: [EdwardsPoint; 172] = [ED25519_BASEPOINT_POINT; 172]; + + for (i, amount) in ZERO_COMMITMENT_DECOMPOSED_AMOUNT.into_iter().enumerate() { + lookup_table[i] = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount); + } + + lookup_table +}); + +//---------------------------------------------------------------------------------------------------- Free functions + +/// This function computes the zero commitment given a specific amount. +/// +/// It will first attempt to lookup into the table of known Pre-RCT value. +/// Compute it otherwise. +#[expect(clippy::cast_possible_truncation)] +pub fn compute_zero_commitment(amount: u64) -> EdwardsPoint { + // OPTIMIZATION: Unlike monerod which execute a linear search across its lookup + // table (O(n)). Cuprate is making use of an arithmetic based constant time + // version (O(1)). It has been benchmarked in both hit and miss scenarios against + // a binary search lookup (O(log2(n))). To understand the following algorithm it + // is important to observe the pattern that follows the values of + // [`ZERO_COMMITMENT_DECOMPOSED_AMOUNT`]. + + // First obtain the logarithm base 10 of the amount. and extend it back to obtain + // the amount without its most significant digit. + let Some(log) = amount.checked_ilog10() else { + // amount = 0 so H component is 0. + return ED25519_BASEPOINT_POINT; + }; + let div = 10_u64.pow(log); + + // Extract the most significant digit. + let most_significant_digit = amount / div; + + // If the *rounded* version is different than the exact amount. Then + // there aren't only trailing zeroes behind the most significant digit. + // The amount is not part of the table and can calculated apart. + if most_significant_digit * div != amount { + return H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]); + } + + // Calculating the index back by progressing within the powers of 10. + // The index of the first value in the cached amount's row. + let row_start = u64::from(log) * 9; + // The index of the cached amount + let index = (most_significant_digit - 1 + row_start) as usize; + + ZERO_COMMITMENT_LOOKUP_TABLE[index] +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use curve25519_dalek::{traits::VartimePrecomputedMultiscalarMul, Scalar}; + + use crate::crypto::{compute_zero_commitment, H_PRECOMP, ZERO_COMMITMENT_DECOMPOSED_AMOUNT}; + + #[test] + /// Compare the output of `compute_zero_commitment` for all + /// preRCT decomposed amounts against their actual computation. + /// + /// Assert that the lookup table returns the correct commitments + fn compare_lookup_with_computation() { + for amount in ZERO_COMMITMENT_DECOMPOSED_AMOUNT { + let commitment = H_PRECOMP.vartime_multiscalar_mul([Scalar::from(amount), Scalar::ONE]); + assert!(commitment == compute_zero_commitment(amount)); + } + } +} diff --git a/helper/src/fs.rs b/helper/src/fs.rs index 1efb20c..f694f62 100644 --- a/helper/src/fs.rs +++ b/helper/src/fs.rs @@ -4,7 +4,7 @@ //! Note that this module's functions uses [`dirs`], //! which adheres to the XDG standard on Linux. //! -//! This means that the values returned by these functions +//! This means that the values returned by these statics //! may change at runtime depending on environment variables, //! for example: //! @@ -17,7 +17,7 @@ //! # if cfg!(target_os = "linux") { //! std::env::set_var("XDG_CONFIG_HOME", "/custom/path"); //! assert_eq!( -//! cuprate_config_dir().to_string_lossy(), +//! CUPRATE_CONFIG_DIR.to_string_lossy(), //! "/custom/path/cuprate" //! ); //! # } @@ -30,9 +30,11 @@ //---------------------------------------------------------------------------------------------------- Use use std::{ path::{Path, PathBuf}, - sync::OnceLock, + sync::LazyLock, }; +use crate::network::Network; + //---------------------------------------------------------------------------------------------------- Const /// Cuprate's main directory. /// @@ -61,72 +63,63 @@ pub const CUPRATE_DIR: &str = { } }; +/// The default name of Cuprate's config file. +pub const DEFAULT_CONFIG_FILE_NAME: &str = "Cuprated.toml"; + //---------------------------------------------------------------------------------------------------- Directories -/// Create a (private) `OnceLock` and accessor function for common PATHs used by Cuprate. +/// Create a `LazyLock` for common PATHs used by Cuprate. /// /// This currently creates these directories: -/// - [`cuprate_cache_dir()`] -/// - [`cuprate_config_dir()`] -/// - [`cuprate_data_dir()`] -/// - [`cuprate_blockchain_dir()`] -/// -/// FIXME: Use `LazyLock` when stabilized. -/// . -/// . -macro_rules! impl_path_oncelock_and_fn { +/// - [`CUPRATE_CACHE_DIR`] +/// - [`CUPRATE_CONFIG_DIR`] +/// - [`CUPRATE_DATA_DIR`] +/// - [`CUPRATE_BLOCKCHAIN_DIR`] +macro_rules! impl_path_lazylock { ($( $(#[$attr:meta])* // Documentation and any `derive`'s. - $fn:ident, // Name of the corresponding access function. + $name:ident, // Name of the corresponding `LazyLock`. $dirs_fn:ident, // Name of the `dirs` function to use, the PATH prefix. $sub_dirs:literal // Any sub-directories to add onto the PATH. ),* $(,)?) => {$( - // Create the `OnceLock` if needed, append + // Create the `LazyLock` if needed, append // the Cuprate directory string and return. $(#[$attr])* - pub fn $fn() -> &'static Path { - /// Local `OnceLock` containing the Path. - static ONCE_LOCK: OnceLock = OnceLock::new(); + pub static $name: LazyLock = LazyLock::new(|| { + // There's nothing we can do but panic if + // we cannot acquire critical system directories. + // + // Although, this realistically won't panic on + // normal systems for all OS's supported by `dirs`. + let mut path = dirs::$dirs_fn().unwrap(); - ONCE_LOCK.get_or_init(|| { - // There's nothing we can do but panic if - // we cannot acquire critical system directories. - // - // Although, this realistically won't panic on - // normal systems for all OS's supported by `dirs`. - let mut path = dirs::$dirs_fn().unwrap(); + // FIXME: + // Consider a user who does `HOME=/ ./cuprated` + // + // Should we say "that's stupid" and panic here? + // Or should it be respected? + // We really don't want a `rm -rf /` type of situation... + assert!( + path.parent().is_some(), + "SAFETY: returned OS PATH was either root or empty, aborting" + ); - // FIXME: - // Consider a user who does `HOME=/ ./cuprated` - // - // Should we say "that's stupid" and panic here? - // Or should it be respected? - // We really don't want a `rm -rf /` type of situation... - assert!( - path.parent().is_some(), - "SAFETY: returned OS PATH was either root or empty, aborting" - ); + // Returned OS PATH should be absolute, not relative. + assert!(path.is_absolute(), "SAFETY: returned OS PATH was not absolute"); - // Returned OS PATH should be absolute, not relative. - assert!(path.is_absolute(), "SAFETY: returned OS PATH was not absolute"); + // Unconditionally prefix with the top-level Cuprate directory. + path.push(CUPRATE_DIR); - // Unconditionally prefix with the top-level Cuprate directory. - path.push(CUPRATE_DIR); + // Add any sub directories if specified in the macro. + if !$sub_dirs.is_empty() { + path.push($sub_dirs); + } - // Add any sub directories if specified in the macro. - if !$sub_dirs.is_empty() { - path.push($sub_dirs); - } - - path - }) - } + path + }); )*}; } -// Note that the `OnceLock`'s are prefixed with `__` to indicate: -// 1. They're not really to be used directly -// 2. To avoid name conflicts -impl_path_oncelock_and_fn! { +impl_path_lazylock! { /// Cuprate's cache directory. /// /// This is the PATH used for any Cuprate cache files. @@ -136,7 +129,7 @@ impl_path_oncelock_and_fn! { /// | Windows | `C:\Users\Alice\AppData\Local\Cuprate\` | /// | macOS | `/Users/Alice/Library/Caches/Cuprate/` | /// | Linux | `/home/alice/.cache/cuprate/` | - cuprate_cache_dir, + CUPRATE_CACHE_DIR, cache_dir, "", @@ -149,7 +142,7 @@ impl_path_oncelock_and_fn! { /// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | /// | macOS | `/Users/Alice/Library/Application Support/Cuprate/` | /// | Linux | `/home/alice/.config/cuprate/` | - cuprate_config_dir, + CUPRATE_CONFIG_DIR, config_dir, "", @@ -162,22 +155,64 @@ impl_path_oncelock_and_fn! { /// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\` | /// | macOS | `/Users/Alice/Library/Application Support/Cuprate/` | /// | Linux | `/home/alice/.local/share/cuprate/` | - cuprate_data_dir, + CUPRATE_DATA_DIR, data_dir, "", +} - /// Cuprate's blockchain directory. - /// - /// This is the PATH used for any Cuprate blockchain files. - /// - /// | OS | PATH | - /// |---------|----------------------------------------------------------------| - /// | Windows | `C:\Users\Alice\AppData\Roaming\Cuprate\blockchain\` | - /// | macOS | `/Users/Alice/Library/Application Support/Cuprate/blockchain/` | - /// | Linux | `/home/alice/.local/share/cuprate/blockchain/` | - cuprate_blockchain_dir, - data_dir, - "blockchain", +/// Joins the [`Network`] to the [`Path`]. +/// +/// This will keep the path the same for [`Network::Mainnet`]. +fn path_with_network(path: &Path, network: Network) -> PathBuf { + match network { + Network::Mainnet => path.to_path_buf(), + network => path.join(network.to_string()), + } +} + +/// Cuprate's blockchain directory. +/// +/// This is the PATH used for any Cuprate blockchain files. +/// +/// ```rust +/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, blockchain_path}}; +/// +/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("blockchain")); +/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("blockchain")); +/// assert_eq!(blockchain_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("blockchain")); +/// ``` +pub fn blockchain_path(data_dir: &Path, network: Network) -> PathBuf { + path_with_network(data_dir, network).join("blockchain") +} + +/// Cuprate's txpool directory. +/// +/// This is the PATH used for any Cuprate txpool files. +/// +/// ```rust +/// use cuprate_helper::{network::Network, fs::{CUPRATE_DATA_DIR, txpool_path}}; +/// +/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Mainnet).as_path(), CUPRATE_DATA_DIR.join("txpool")); +/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Stagenet).as_path(), CUPRATE_DATA_DIR.join(Network::Stagenet.to_string()).join("txpool")); +/// assert_eq!(txpool_path(&**CUPRATE_DATA_DIR, Network::Testnet).as_path(), CUPRATE_DATA_DIR.join(Network::Testnet.to_string()).join("txpool")); +/// ``` +pub fn txpool_path(data_dir: &Path, network: Network) -> PathBuf { + path_with_network(data_dir, network).join("txpool") +} + +/// Cuprate's address-book directory. +/// +/// This is the PATH used for any Cuprate address-book files. +/// +/// ```rust +/// use cuprate_helper::{network::Network, fs::{CUPRATE_CACHE_DIR, address_book_path}}; +/// +/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Mainnet).as_path(), CUPRATE_CACHE_DIR.join("addressbook")); +/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Stagenet).as_path(), CUPRATE_CACHE_DIR.join(Network::Stagenet.to_string()).join("addressbook")); +/// assert_eq!(address_book_path(&**CUPRATE_CACHE_DIR, Network::Testnet).as_path(), CUPRATE_CACHE_DIR.join(Network::Testnet.to_string()).join("addressbook")); +/// ``` +pub fn address_book_path(cache_dir: &Path, network: Network) -> PathBuf { + path_with_network(cache_dir, network).join("addressbook") } //---------------------------------------------------------------------------------------------------- Tests @@ -192,60 +227,33 @@ mod test { // - It must `ends_with()` the expected end PATH for the OS #[test] fn path_sanity_check() { - assert!(cuprate_cache_dir().is_absolute()); - assert!(cuprate_config_dir().is_absolute()); - assert!(cuprate_data_dir().is_absolute()); - assert!(cuprate_blockchain_dir().is_absolute()); + // Array of (PATH, expected_path_as_string). + // + // The different OS's will set the expected path below. + let mut array = [ + (&*CUPRATE_CACHE_DIR, ""), + (&*CUPRATE_CONFIG_DIR, ""), + (&*CUPRATE_DATA_DIR, ""), + ]; if cfg!(target_os = "windows") { - let dir = cuprate_cache_dir(); - println!("cuprate_cache_dir: {dir:?}"); - assert!(dir.ends_with(r"AppData\Local\Cuprate")); - - let dir = cuprate_config_dir(); - println!("cuprate_config_dir: {dir:?}"); - assert!(dir.ends_with(r"AppData\Roaming\Cuprate")); - - let dir = cuprate_data_dir(); - println!("cuprate_data_dir: {dir:?}"); - assert!(dir.ends_with(r"AppData\Roaming\Cuprate")); - - let dir = cuprate_blockchain_dir(); - println!("cuprate_blockchain_dir: {dir:?}"); - assert!(dir.ends_with(r"AppData\Roaming\Cuprate\blockchain")); + array[0].1 = r"AppData\Local\Cuprate"; + array[1].1 = r"AppData\Roaming\Cuprate"; + array[2].1 = r"AppData\Roaming\Cuprate"; } else if cfg!(target_os = "macos") { - let dir = cuprate_cache_dir(); - println!("cuprate_cache_dir: {dir:?}"); - assert!(dir.ends_with("Library/Caches/Cuprate")); - - let dir = cuprate_config_dir(); - println!("cuprate_config_dir: {dir:?}"); - assert!(dir.ends_with("Library/Application Support/Cuprate")); - - let dir = cuprate_data_dir(); - println!("cuprate_data_dir: {dir:?}"); - assert!(dir.ends_with("Library/Application Support/Cuprate")); - - let dir = cuprate_blockchain_dir(); - println!("cuprate_blockchain_dir: {dir:?}"); - assert!(dir.ends_with("Library/Application Support/Cuprate/blockchain")); + array[0].1 = "Library/Caches/Cuprate"; + array[1].1 = "Library/Application Support/Cuprate"; + array[2].1 = "Library/Application Support/Cuprate"; } else { // Assumes Linux. - let dir = cuprate_cache_dir(); - println!("cuprate_cache_dir: {dir:?}"); - assert!(dir.ends_with(".cache/cuprate")); + array[0].1 = ".cache/cuprate"; + array[1].1 = ".config/cuprate"; + array[2].1 = ".local/share/cuprate"; + }; - let dir = cuprate_config_dir(); - println!("cuprate_config_dir: {dir:?}"); - assert!(dir.ends_with(".config/cuprate")); - - let dir = cuprate_data_dir(); - println!("cuprate_data_dir: {dir:?}"); - assert!(dir.ends_with(".local/share/cuprate")); - - let dir = cuprate_blockchain_dir(); - println!("cuprate_blockchain_dir: {dir:?}"); - assert!(dir.ends_with(".local/share/cuprate/blockchain")); + for (path, expected) in array { + assert!(path.is_absolute()); + assert!(path.ends_with(expected)); } } } diff --git a/helper/src/lib.rs b/helper/src/lib.rs index 90f420d..9bd64fa 100644 --- a/helper/src/lib.rs +++ b/helper/src/lib.rs @@ -1,36 +1,4 @@ #![doc = include_str!("../README.md")] -//---------------------------------------------------------------------------------------------------- Lints -#![allow(clippy::len_zero, clippy::type_complexity, clippy::module_inception)] -#![deny(nonstandard_style, deprecated, missing_docs, unused_mut)] -#![forbid( - unused_unsafe, - future_incompatible, - break_with_label_and_loop, - coherence_leak_check, - duplicate_macro_attributes, - exported_private_dependencies, - for_loops_over_fallibles, - large_assignments, - overlapping_range_endpoints, - // private_in_public, - semicolon_in_expressions_from_macros, - redundant_semicolons, - unconditional_recursion, - unreachable_patterns, - unused_allocation, - unused_braces, - unused_comparisons, - unused_doc_comments, - unused_parens, - unused_labels, - while_true, - keyword_idents, - non_ascii_idents, - noop_method_call, - unreachable_pub, - single_use_lifetimes, - // variant_size_differences, -)] #![cfg_attr(not(feature = "std"), no_std)] //---------------------------------------------------------------------------------------------------- Public API @@ -40,10 +8,10 @@ pub mod asynch; // async collides #[cfg(feature = "atomic")] pub mod atomic; -#[cfg(feature = "constants")] -pub mod constants; +#[cfg(feature = "cast")] +pub mod cast; -#[cfg(feature = "fs")] +#[cfg(all(feature = "fs", feature = "std"))] pub mod fs; pub mod network; @@ -60,6 +28,11 @@ pub mod thread; #[cfg(feature = "time")] pub mod time; +#[cfg(feature = "tx")] +pub mod tx; + +#[cfg(feature = "crypto")] +pub mod crypto; //---------------------------------------------------------------------------------------------------- Private Usage //---------------------------------------------------------------------------------------------------- diff --git a/helper/src/map.rs b/helper/src/map.rs index 96d9f61..b719f8f 100644 --- a/helper/src/map.rs +++ b/helper/src/map.rs @@ -7,6 +7,10 @@ //---------------------------------------------------------------------------------------------------- Use use monero_serai::transaction::Timelock; +use cuprate_constants::block::MAX_BLOCK_HEIGHT; + +use crate::cast::{u64_to_usize, usize_to_u64}; + //---------------------------------------------------------------------------------------------------- `(u64, u64) <-> u128` /// Split a [`u128`] value into 2 64-bit values. /// @@ -27,6 +31,7 @@ use monero_serai::transaction::Timelock; /// ``` #[inline] pub const fn split_u128_into_low_high_bits(value: u128) -> (u64, u64) { + #[expect(clippy::cast_possible_truncation)] (value as u64, (value >> 64) as u64) } @@ -58,7 +63,7 @@ pub const fn combine_low_high_bits_to_u128(low_bits: u64, high_bits: u64) -> u12 /// Map a [`u64`] to a [`Timelock`]. /// /// Height/time is not differentiated via type, but rather: -/// "height is any value less than 500_000_000 and timestamp is any value above" +/// "height is any value less than [`MAX_BLOCK_HEIGHT`] and timestamp is any value above" /// so the `u64/usize` is stored without any tag. /// /// See [`timelock_to_u64`] for the inverse function. @@ -69,15 +74,16 @@ pub const fn combine_low_high_bits_to_u128(low_bits: u64, high_bits: u64) -> u12 /// ```rust /// # use cuprate_helper::map::*; /// # use monero_serai::transaction::*; +/// use cuprate_constants::block::{MAX_BLOCK_HEIGHT, MAX_BLOCK_HEIGHT_USIZE}; /// assert_eq!(u64_to_timelock(0), Timelock::None); -/// assert_eq!(u64_to_timelock(499_999_999), Timelock::Block(499_999_999)); -/// assert_eq!(u64_to_timelock(500_000_000), Timelock::Time(500_000_000)); +/// assert_eq!(u64_to_timelock(MAX_BLOCK_HEIGHT-1), Timelock::Block(MAX_BLOCK_HEIGHT_USIZE-1)); +/// assert_eq!(u64_to_timelock(MAX_BLOCK_HEIGHT), Timelock::Time(MAX_BLOCK_HEIGHT)); /// ``` -pub fn u64_to_timelock(u: u64) -> Timelock { +pub const fn u64_to_timelock(u: u64) -> Timelock { if u == 0 { Timelock::None - } else if u < 500_000_000 { - Timelock::Block(usize::try_from(u).unwrap()) + } else if u < MAX_BLOCK_HEIGHT { + Timelock::Block(u64_to_usize(u)) } else { Timelock::Time(u) } @@ -90,14 +96,15 @@ pub fn u64_to_timelock(u: u64) -> Timelock { /// ```rust /// # use cuprate_helper::map::*; /// # use monero_serai::transaction::*; +/// use cuprate_constants::block::{MAX_BLOCK_HEIGHT, MAX_BLOCK_HEIGHT_USIZE}; /// assert_eq!(timelock_to_u64(Timelock::None), 0); -/// assert_eq!(timelock_to_u64(Timelock::Block(499_999_999)), 499_999_999); -/// assert_eq!(timelock_to_u64(Timelock::Time(500_000_000)), 500_000_000); +/// assert_eq!(timelock_to_u64(Timelock::Block(MAX_BLOCK_HEIGHT_USIZE-1)), MAX_BLOCK_HEIGHT-1); +/// assert_eq!(timelock_to_u64(Timelock::Time(MAX_BLOCK_HEIGHT)), MAX_BLOCK_HEIGHT); /// ``` -pub fn timelock_to_u64(timelock: Timelock) -> u64 { +pub const fn timelock_to_u64(timelock: Timelock) -> u64 { match timelock { Timelock::None => 0, - Timelock::Block(u) => u64::try_from(u).unwrap(), + Timelock::Block(u) => usize_to_u64(u), Timelock::Time(u) => u, } } diff --git a/helper/src/network.rs b/helper/src/network.rs index 684e71a..d296117 100644 --- a/helper/src/network.rs +++ b/helper/src/network.rs @@ -5,6 +5,12 @@ //! into it's own crate. //! //! `#[no_std]` compatible. +// TODO: move to types crate. + +use core::{ + fmt::{Display, Formatter}, + str::FromStr, +}; const MAINNET_NETWORK_ID: [u8; 16] = [ 0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10, @@ -17,7 +23,8 @@ const STAGENET_NETWORK_ID: [u8; 16] = [ ]; /// An enum representing every Monero network. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, Default, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum Network { /// Mainnet #[default] @@ -30,11 +37,36 @@ pub enum Network { impl Network { /// Returns the network ID for the current network. - pub fn network_id(&self) -> [u8; 16] { + pub const fn network_id(&self) -> [u8; 16] { match self { - Network::Mainnet => MAINNET_NETWORK_ID, - Network::Testnet => TESTNET_NETWORK_ID, - Network::Stagenet => STAGENET_NETWORK_ID, + Self::Mainnet => MAINNET_NETWORK_ID, + Self::Testnet => TESTNET_NETWORK_ID, + Self::Stagenet => STAGENET_NETWORK_ID, } } } + +#[derive(Debug, PartialEq, Eq)] +pub struct ParseNetworkError; + +impl FromStr for Network { + type Err = ParseNetworkError; + + fn from_str(s: &str) -> Result { + match s { + "mainnet" | "Mainnet" => Ok(Self::Mainnet), + "testnet" | "Testnet" => Ok(Self::Testnet), + "stagenet" | "Stagenet" => Ok(Self::Stagenet), + _ => Err(ParseNetworkError), + } + } +} +impl Display for Network { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str(match self { + Self::Mainnet => "mainnet", + Self::Testnet => "testnet", + Self::Stagenet => "stagenet", + }) + } +} diff --git a/helper/src/num.rs b/helper/src/num.rs index cc1feb1..399c38d 100644 --- a/helper/src/num.rs +++ b/helper/src/num.rs @@ -8,6 +8,9 @@ use core::{ ops::{Add, Div, Mul, Sub}, }; +#[cfg(feature = "std")] +mod rolling_median; + //---------------------------------------------------------------------------------------------------- Types // INVARIANT: must be private. // Protects against outside-crate implementations. @@ -15,6 +18,9 @@ mod private { pub trait Sealed: Copy + PartialOrd + core::fmt::Display {} } +#[cfg(feature = "std")] +pub use rolling_median::RollingMedian; + /// Non-floating point numbers /// /// This trait is sealed and is only implemented on: @@ -83,8 +89,9 @@ where /// assert_eq!(median(vec), 5); /// ``` /// -/// # Safety +/// # Invariant /// If not sorted the output will be invalid. +#[expect(clippy::debug_assert_with_mut_call)] pub fn median(array: impl AsRef<[T]>) -> T where T: Add diff --git a/helper/src/num/rolling_median.rs b/helper/src/num/rolling_median.rs new file mode 100644 index 0000000..2babda2 --- /dev/null +++ b/helper/src/num/rolling_median.rs @@ -0,0 +1,150 @@ +use std::{ + collections::VecDeque, + ops::{Add, Div, Mul, Sub}, +}; + +use crate::num::median; + +/// A rolling median type. +/// +/// This keeps track of a window of items and allows calculating the [`RollingMedian::median`] of them. +/// +/// Example: +/// ```rust +/// # use cuprate_helper::num::RollingMedian; +/// let mut rolling_median = RollingMedian::new(2); +/// +/// rolling_median.push(1); +/// assert_eq!(rolling_median.median(), 1); +/// assert_eq!(rolling_median.window_len(), 1); +/// +/// rolling_median.push(3); +/// assert_eq!(rolling_median.median(), 2); +/// assert_eq!(rolling_median.window_len(), 2); +/// +/// rolling_median.push(5); +/// assert_eq!(rolling_median.median(), 4); +/// assert_eq!(rolling_median.window_len(), 2); +/// ``` +/// +// TODO: a more efficient structure is probably possible. +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)] +pub struct RollingMedian { + /// The window of items, in order of insertion. + window: VecDeque, + /// The window of items, sorted. + sorted_window: Vec, + + /// The target window length. + target_window: usize, +} + +impl RollingMedian +where + T: Ord + + PartialOrd + + Add + + Sub + + Div + + Mul + + Copy + + From, +{ + /// Creates a new [`RollingMedian`] with a certain target window length. + /// + /// `target_window` is the maximum amount of items to keep in the rolling window. + pub fn new(target_window: usize) -> Self { + Self { + window: VecDeque::with_capacity(target_window), + sorted_window: Vec::with_capacity(target_window), + target_window, + } + } + + /// Creates a new [`RollingMedian`] from a [`Vec`] with a certain target window length. + /// + /// `target_window` is the maximum amount of items to keep in the rolling window. + /// + /// # Panics + /// This function panics if `vec.len() > target_window`. + pub fn from_vec(vec: Vec, target_window: usize) -> Self { + assert!(vec.len() <= target_window); + + let mut sorted_window = vec.clone(); + sorted_window.sort_unstable(); + + Self { + window: vec.into(), + sorted_window, + target_window, + } + } + + /// Pops the front of the window, i.e. the oldest item. + /// + /// This is often not needed as [`RollingMedian::push`] will handle popping old values when they fall + /// out of the window. + pub fn pop_front(&mut self) { + if let Some(item) = self.window.pop_front() { + match self.sorted_window.binary_search(&item) { + Ok(idx) => { + self.sorted_window.remove(idx); + } + Err(_) => panic!("Value expected to be in sorted_window was not there"), + } + } + } + + /// Pops the back of the window, i.e. the youngest item. + pub fn pop_back(&mut self) { + if let Some(item) = self.window.pop_back() { + match self.sorted_window.binary_search(&item) { + Ok(idx) => { + self.sorted_window.remove(idx); + } + Err(_) => panic!("Value expected to be in sorted_window was not there"), + } + } + } + + /// Push an item to the _back_ of the window. + /// + /// This will pop the oldest item in the window if the target length has been exceeded. + pub fn push(&mut self, item: T) { + if self.window.len() >= self.target_window { + self.pop_front(); + } + + self.window.push_back(item); + match self.sorted_window.binary_search(&item) { + Ok(idx) | Err(idx) => self.sorted_window.insert(idx, item), + } + } + + /// Append some values to the _front_ of the window. + /// + /// These new values will be the oldest items in the window. The order of the inputted items will be + /// kept, i.e. the first item in the [`Vec`] will be the oldest item in the queue. + pub fn append_front(&mut self, items: Vec) { + for item in items.into_iter().rev() { + self.window.push_front(item); + match self.sorted_window.binary_search(&item) { + Ok(idx) | Err(idx) => self.sorted_window.insert(idx, item), + } + + if self.window.len() > self.target_window { + self.pop_back(); + } + } + } + + /// Returns the number of items currently in the [`RollingMedian`]. + pub fn window_len(&self) -> usize { + self.window.len() + } + + /// Calculates the median of the values currently in the [`RollingMedian`]. + pub fn median(&self) -> T { + median(&self.sorted_window) + } +} diff --git a/helper/src/thread.rs b/helper/src/thread.rs index 96958ff..8ba025d 100644 --- a/helper/src/thread.rs +++ b/helper/src/thread.rs @@ -6,7 +6,6 @@ use std::{cmp::max, num::NonZeroUsize}; //---------------------------------------------------------------------------------------------------- Thread Count & Percent -#[allow(non_snake_case)] /// Get the total amount of system threads. /// /// ```rust @@ -28,10 +27,15 @@ macro_rules! impl_thread_percent { $( $(#[$doc])* pub fn $fn_name() -> NonZeroUsize { - // SAFETY: - // unwrap here is okay because: - // - THREADS().get() is always non-zero - // - max() guards against 0 + // unwrap here is okay because: + // - THREADS().get() is always non-zero + // - max() guards against 0 + #[expect( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_precision_loss, + reason = "we need to round integers" + )] NonZeroUsize::new(max(1, (threads().get() as f64 * $percent).floor() as usize)).unwrap() } )* @@ -58,10 +62,10 @@ impl_thread_percent! { /// Originally from . /// /// # Windows -/// Uses SetThreadPriority() with THREAD_PRIORITY_IDLE (-15). +/// Uses `SetThreadPriority()` with `THREAD_PRIORITY_IDLE` (-15). /// /// # Unix -/// Uses libc::nice() with the max nice level. +/// Uses `libc::nice()` with the max nice level. /// /// On macOS and *BSD: +20 /// On Linux: +19 @@ -74,7 +78,7 @@ pub fn low_priority_thread() { // SAFETY: calling C. // We are _lowering_ our priority, not increasing, so this function should never fail. unsafe { - let _ = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE); + drop(SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE)); } } @@ -87,7 +91,7 @@ pub fn low_priority_thread() { // SAFETY: calling C. // We are _lowering_ our priority, not increasing, so this function should never fail. unsafe { - let _ = libc::nice(NICE_MAX); + libc::nice(NICE_MAX); } } } diff --git a/helper/src/time.rs b/helper/src/time.rs index 7bc155f..c7b12c2 100644 --- a/helper/src/time.rs +++ b/helper/src/time.rs @@ -55,7 +55,7 @@ pub const fn unix_clock(seconds_after_unix_epoch: u64) -> u32 { /// - The seconds returned is guaranteed to be `0..=59` /// - The minutes returned is guaranteed to be `0..=59` /// - The hours returned can be over `23`, as this is not a clock function, -/// see [`secs_to_clock`] for clock-like behavior that wraps around on `24` +/// see [`secs_to_clock`] for clock-like behavior that wraps around on `24` /// /// ```rust /// # use cuprate_helper::time::*; @@ -129,6 +129,7 @@ pub const fn secs_to_clock(seconds: u32) -> (u8, u8, u8) { debug_assert!(m < 60); debug_assert!(s < 60); + #[expect(clippy::cast_possible_truncation, reason = "checked above")] (h as u8, m, s) } @@ -153,6 +154,7 @@ pub fn time() -> u32 { /// /// This is guaranteed to return a value between `0..=86399` pub fn time_utc() -> u32 { + #[expect(clippy::cast_sign_loss, reason = "checked in function calls")] unix_clock(chrono::offset::Local::now().timestamp() as u64) } diff --git a/helper/src/tx.rs b/helper/src/tx.rs new file mode 100644 index 0000000..53706ec --- /dev/null +++ b/helper/src/tx.rs @@ -0,0 +1,70 @@ +//! Utils for working with [`Transaction`] + +use monero_serai::transaction::{Input, Transaction}; + +/// Calculates the fee of the [`Transaction`]. +/// +/// # Panics +/// This will panic if the inputs overflow or the transaction outputs too much, so should only +/// be used on known to be valid txs. +pub fn tx_fee(tx: &Transaction) -> u64 { + let mut fee = 0_u64; + + match &tx { + Transaction::V1 { prefix, .. } => { + for input in &prefix.inputs { + match input { + Input::Gen(_) => return 0, + Input::ToKey { amount, .. } => { + fee = fee.checked_add(amount.unwrap_or(0)).unwrap(); + } + } + } + + for output in &prefix.outputs { + fee = fee.checked_sub(output.amount.unwrap_or(0)).unwrap(); + } + } + Transaction::V2 { proofs, .. } => { + fee = proofs.as_ref().unwrap().base.fee; + } + }; + + fee +} + +#[cfg(test)] +mod test { + use curve25519_dalek::{edwards::CompressedEdwardsY, EdwardsPoint}; + use monero_serai::transaction::{NotPruned, Output, Timelock, TransactionPrefix}; + + use super::*; + + #[test] + #[should_panic(expected = "called `Option::unwrap()` on a `None` value")] + fn tx_fee_panic() { + let input = Input::ToKey { + amount: Some(u64::MAX), + key_offsets: vec![], + key_image: EdwardsPoint::default(), + }; + + let output = Output { + amount: Some(u64::MAX), + key: CompressedEdwardsY::default(), + view_tag: None, + }; + + let tx = Transaction::::V1 { + prefix: TransactionPrefix { + additional_timelock: Timelock::None, + inputs: vec![input; 2], + outputs: vec![output], + extra: vec![], + }, + signatures: vec![], + }; + + tx_fee(&tx); + } +} diff --git a/misc/ENVIRONMENT-ADVICE.md b/misc/ENVIRONMENT-ADVICE.md index 295fabd..b3358b9 100644 --- a/misc/ENVIRONMENT-ADVICE.md +++ b/misc/ENVIRONMENT-ADVICE.md @@ -87,4 +87,4 @@ On Rust-analyzer's VSCode plugin, you can add the following configuration if you If you still deal with lags on VSCode or Neovim, you could try the following IDE: - RustRover: It have been reported to have excellent performance at managing huge workspace. It use its own fine-tuned plugins by jetbrains. -- Zed: Rust-written IDE focused on performance. Still in beta and macOS only. \ No newline at end of file +- Zed: Rust-written IDE focused on performance. Stable on MacOS and Linux (requires Vulkan driver, therefore unable in virtual machines). diff --git a/net/epee-encoding/Cargo.toml b/net/epee-encoding/Cargo.toml index 7feac00..4724e2d 100644 --- a/net/epee-encoding/Cargo.toml +++ b/net/epee-encoding/Cargo.toml @@ -15,12 +15,16 @@ default = ["std"] std = ["dep:thiserror", "bytes/std", "cuprate-fixed-bytes/std"] [dependencies] -cuprate-fixed-bytes = { path = "../fixed-bytes", default-features = false } +cuprate-helper = { workspace = true, default-features = false, features = ["cast"] } +cuprate-fixed-bytes = { workspace = true, default-features = false } -paste = "1.0.14" -ref-cast = "1.0.22" +paste = "1.0.15" +ref-cast = "1.0.23" bytes = { workspace = true } thiserror = { workspace = true, optional = true} [dev-dependencies] hex = { workspace = true, features = ["default"] } + +[lints] +workspace = true diff --git a/net/epee-encoding/src/container_as_blob.rs b/net/epee-encoding/src/container_as_blob.rs index a4e8808..363e157 100644 --- a/net/epee-encoding/src/container_as_blob.rs +++ b/net/epee-encoding/src/container_as_blob.rs @@ -1,3 +1,5 @@ +use alloc::{string::ToString, vec, vec::Vec}; + use bytes::{Buf, BufMut, Bytes, BytesMut}; use ref_cast::RefCast; @@ -9,7 +11,7 @@ pub struct ContainerAsBlob(Vec); impl From> for ContainerAsBlob { fn from(value: Vec) -> Self { - ContainerAsBlob(value) + Self(value) } } @@ -36,9 +38,7 @@ impl EpeeValue for ContainerAsBlob { )); } - Ok(ContainerAsBlob( - bytes.chunks(T::SIZE).map(T::from_bytes).collect(), - )) + Ok(Self(bytes.chunks(T::SIZE).map(T::from_bytes).collect())) } fn should_write(&self) -> bool { @@ -46,10 +46,10 @@ impl EpeeValue for ContainerAsBlob { } fn epee_default_value() -> Option { - Some(ContainerAsBlob(vec![])) + Some(Self(vec![])) } - fn write(self, w: &mut B) -> crate::Result<()> { + fn write(self, w: &mut B) -> Result<()> { let mut buf = BytesMut::with_capacity(self.0.len() * T::SIZE); self.0.iter().for_each(|tt| tt.push_bytes(&mut buf)); buf.write(w) @@ -70,7 +70,7 @@ pub trait Containerable { macro_rules! int_container_able { ($int:ty ) => { impl Containerable for $int { - const SIZE: usize = std::mem::size_of::<$int>(); + const SIZE: usize = size_of::<$int>(); fn from_bytes(bytes: &[u8]) -> Self { <$int>::from_le_bytes(bytes.try_into().unwrap()) diff --git a/net/epee-encoding/src/error.rs b/net/epee-encoding/src/error.rs index 4b3c7b0..7206189 100644 --- a/net/epee-encoding/src/error.rs +++ b/net/epee-encoding/src/error.rs @@ -1,3 +1,4 @@ +use alloc::string::{String, ToString}; use core::{ fmt::{Debug, Formatter}, num::TryFromIntError, @@ -7,6 +8,7 @@ use core::{ pub type Result = core::result::Result; #[cfg_attr(feature = "std", derive(thiserror::Error))] +#[expect(clippy::error_impl_error, reason = "FIXME: rename this type")] pub enum Error { #[cfg_attr(feature = "std", error("IO error: {0}"))] IO(&'static str), @@ -17,19 +19,18 @@ pub enum Error { } impl Error { - fn field_name(&self) -> &'static str { + const fn field_name(&self) -> &'static str { match self { - Error::IO(_) => "io", - Error::Format(_) => "format", - Error::Value(_) => "value", + Self::IO(_) => "io", + Self::Format(_) => "format", + Self::Value(_) => "value", } } fn field_data(&self) -> &str { match self { - Error::IO(data) => data, - Error::Format(data) => data, - Error::Value(data) => data, + Self::IO(data) | Self::Format(data) => data, + Self::Value(data) => data, } } } @@ -44,12 +45,12 @@ impl Debug for Error { impl From for Error { fn from(_: TryFromIntError) -> Self { - Error::Value("Int is too large".to_string()) + Self::Value("Int is too large".to_string()) } } impl From for Error { fn from(_: Utf8Error) -> Self { - Error::Value("Invalid utf8 str".to_string()) + Self::Value("Invalid utf8 str".to_string()) } } diff --git a/net/epee-encoding/src/io.rs b/net/epee-encoding/src/io.rs index 4f4240d..c118145 100644 --- a/net/epee-encoding/src/io.rs +++ b/net/epee-encoding/src/io.rs @@ -3,42 +3,46 @@ use bytes::{Buf, BufMut}; use crate::error::*; #[inline] -pub fn checked_read_primitive( +pub(crate) fn checked_read_primitive( b: &mut B, read: impl Fn(&mut B) -> R, ) -> Result { - checked_read(b, read, core::mem::size_of::()) + checked_read(b, read, size_of::()) } #[inline] -pub fn checked_read(b: &mut B, read: impl Fn(&mut B) -> R, size: usize) -> Result { +pub(crate) fn checked_read( + b: &mut B, + read: impl Fn(&mut B) -> R, + size: usize, +) -> Result { if b.remaining() < size { - Err(Error::IO("Not enough bytes in buffer to build object."))?; + Err(Error::IO("Not enough bytes in buffer to build object.")) + } else { + Ok(read(b)) } - - Ok(read(b)) } #[inline] -pub fn checked_write_primitive( +pub(crate) fn checked_write_primitive( b: &mut B, write: impl Fn(&mut B, T), t: T, ) -> Result<()> { - checked_write(b, write, t, core::mem::size_of::()) + checked_write(b, write, t, size_of::()) } #[inline] -pub fn checked_write( +pub(crate) fn checked_write( b: &mut B, write: impl Fn(&mut B, T), t: T, size: usize, ) -> Result<()> { if b.remaining_mut() < size { - Err(Error::IO("Not enough capacity to write object."))?; + Err(Error::IO("Not enough capacity to write object.")) + } else { + write(b, t); + Ok(()) } - - write(b, t); - Ok(()) } diff --git a/net/epee-encoding/src/lib.rs b/net/epee-encoding/src/lib.rs index 5b64315..a6ff1b0 100644 --- a/net/epee-encoding/src/lib.rs +++ b/net/epee-encoding/src/lib.rs @@ -59,12 +59,18 @@ //! //! ``` +#[cfg(test)] +use hex as _; + extern crate alloc; -use core::{ops::Deref, str::from_utf8 as str_from_utf8}; +use alloc::string::ToString; +use core::str::from_utf8 as str_from_utf8; use bytes::{Buf, BufMut, Bytes, BytesMut}; +use cuprate_helper::cast::{u64_to_usize, usize_to_u64}; + pub mod container_as_blob; pub mod error; mod io; @@ -128,7 +134,7 @@ pub fn to_bytes(val: T) -> Result { fn read_header(r: &mut B) -> Result<()> { let buf = checked_read(r, |b: &mut B| b.copy_to_bytes(HEADER.len()), HEADER.len())?; - if buf.deref() != HEADER { + if &*buf != HEADER { return Err(Error::Format("Data does not contain header")); } Ok(()) @@ -183,7 +189,7 @@ fn read_object(r: &mut B, skipped_objects: &mut u8) -> Re for _ in 0..number_o_field { let field_name_bytes = read_field_name_bytes(r)?; - let field_name = str_from_utf8(field_name_bytes.deref())?; + let field_name = str_from_utf8(&field_name_bytes)?; if !object_builder.add_field(field_name, r)? { skip_epee_value(r, skipped_objects)?; @@ -242,7 +248,7 @@ pub fn write_bytes, B: BufMut>(t: T, w: &mut B) -> Result<()> { let bytes = t.as_ref(); let len = bytes.len(); - write_varint(len.try_into()?, w)?; + write_varint(usize_to_u64(len), w)?; if w.remaining_mut() < len { return Err(Error::IO("Not enough capacity to write bytes")); @@ -286,8 +292,8 @@ where I: Iterator + ExactSizeIterator, B: BufMut, { - write_varint(iterator.len().try_into()?, w)?; - for item in iterator.into_iter() { + write_varint(usize_to_u64(iterator.len()), w)?; + for item in iterator { item.write(w)?; } Ok(()) @@ -327,14 +333,11 @@ impl EpeeObject for SkipObject { fn skip_epee_value(r: &mut B, skipped_objects: &mut u8) -> Result<()> { let marker = read_marker(r)?; - let mut len = 1; - if marker.is_seq { - len = read_varint(r)?; - } + let len = if marker.is_seq { read_varint(r)? } else { 1 }; if let Some(size) = marker.inner_marker.size() { let bytes_to_skip = size - .checked_mul(len.try_into()?) + .checked_mul(u64_to_usize(len)) .ok_or(Error::Value("List is too big".to_string()))?; return advance(bytes_to_skip, r); }; @@ -352,8 +355,8 @@ fn skip_epee_value(r: &mut B, skipped_objects: &mut u8) -> Result<()> { | InnerMarker::U8 | InnerMarker::Bool => unreachable!("These types are constant size."), InnerMarker::String => { - let len = read_varint(r)?; - advance(len.try_into()?, r)?; + let len = u64_to_usize(read_varint(r)?); + advance(len, r)?; } InnerMarker::Object => { *skipped_objects += 1; diff --git a/net/epee-encoding/src/macros.rs b/net/epee-encoding/src/macros.rs index 38dcc45..bb1afef 100644 --- a/net/epee-encoding/src/macros.rs +++ b/net/epee-encoding/src/macros.rs @@ -76,14 +76,14 @@ macro_rules! epee_object { // All this does is return the second (right) arg if present otherwise the left is returned. ( @internal_try_right_then_left - $a:expr, $b:expr + $a:expr_2021, $b:expr_2021 ) => { $b }; ( @internal_try_right_then_left - $a:expr, + $a:expr_2021, ) => { $a }; @@ -122,7 +122,7 @@ macro_rules! epee_object { // ------------------------------------------------------------------------ Entry Point ( $obj:ident, - $($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr)? $(=> $read_fn:expr, $write_fn:expr, $should_write_fn:expr)?, )* + $($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr_2021)? $(=> $read_fn:expr_2021, $write_fn:expr_2021, $should_write_fn:expr_2021)?, )* $(!flatten: $flat_field: ident: $flat_ty:ty ,)* ) => { diff --git a/net/epee-encoding/src/marker.rs b/net/epee-encoding/src/marker.rs index d8ffc4b..16eaa6a 100644 --- a/net/epee-encoding/src/marker.rs +++ b/net/epee-encoding/src/marker.rs @@ -19,13 +19,13 @@ pub enum InnerMarker { } impl InnerMarker { - pub fn size(&self) -> Option { + pub const fn size(&self) -> Option { Some(match self { - InnerMarker::I64 | InnerMarker::U64 | InnerMarker::F64 => 8, - InnerMarker::I32 | InnerMarker::U32 => 4, - InnerMarker::I16 | InnerMarker::U16 => 2, - InnerMarker::I8 | InnerMarker::U8 | InnerMarker::Bool => 1, - InnerMarker::String | InnerMarker::Object => return None, + Self::I64 | Self::U64 | Self::F64 => 8, + Self::I32 | Self::U32 => 4, + Self::I16 | Self::U16 => 2, + Self::I8 | Self::U8 | Self::Bool => 1, + Self::String | Self::Object => return None, }) } } @@ -40,23 +40,23 @@ pub struct Marker { impl Marker { pub(crate) const fn new(inner_marker: InnerMarker) -> Self { - Marker { + Self { inner_marker, is_seq: false, } } + + #[must_use] pub const fn into_seq(self) -> Self { - if self.is_seq { - panic!("Sequence of sequence not allowed!"); - } + assert!(!self.is_seq, "Sequence of sequence not allowed!"); if matches!(self.inner_marker, InnerMarker::U8) { - return Marker { + return Self { inner_marker: InnerMarker::String, is_seq: false, }; } - Marker { + Self { inner_marker: self.inner_marker, is_seq: true, } @@ -112,7 +112,7 @@ impl TryFrom for Marker { _ => return Err(Error::Format("Unknown value Marker")), }; - Ok(Marker { + Ok(Self { inner_marker, is_seq, }) diff --git a/net/epee-encoding/src/value.rs b/net/epee-encoding/src/value.rs index 094f0ef..4762c96 100644 --- a/net/epee-encoding/src/value.rs +++ b/net/epee-encoding/src/value.rs @@ -1,12 +1,13 @@ //! This module contains a [`EpeeValue`] trait and //! impls for some possible base epee values. -use alloc::{string::String, vec::Vec}; +use alloc::{string::String, vec, vec::Vec}; use core::fmt::Debug; use bytes::{Buf, BufMut, Bytes, BytesMut}; use cuprate_fixed_bytes::{ByteArray, ByteArrayVec}; +use cuprate_helper::cast::u64_to_usize; use crate::{ io::{checked_read_primitive, checked_write_primitive}, @@ -66,11 +67,11 @@ impl EpeeValue for Vec { "Marker is not sequence when a sequence was expected", )); } - let len = read_varint(r)?; + let len = u64_to_usize(read_varint(r)?); let individual_marker = Marker::new(marker.inner_marker); - let mut res = Vec::with_capacity(len.try_into()?); + let mut res = Self::with_capacity(len); for _ in 0..len { res.push(T::read(r, &individual_marker)?); } @@ -82,7 +83,7 @@ impl EpeeValue for Vec { } fn epee_default_value() -> Option { - Some(Vec::new()) + Some(Self::new()) } fn write(self, w: &mut B) -> Result<()> { @@ -167,18 +168,20 @@ impl EpeeValue for Vec { return Err(Error::Format("Byte array exceeded max length")); } - if r.remaining() < len.try_into()? { + let len = u64_to_usize(len); + + if r.remaining() < len { return Err(Error::IO("Not enough bytes to fill object")); } - let mut res = vec![0; len.try_into()?]; + let mut res = vec![0; len]; r.copy_to_slice(&mut res); Ok(res) } fn epee_default_value() -> Option { - Some(Vec::new()) + Some(Self::new()) } fn should_write(&self) -> bool { @@ -203,15 +206,17 @@ impl EpeeValue for Bytes { return Err(Error::Format("Byte array exceeded max length")); } - if r.remaining() < len.try_into()? { + let len = u64_to_usize(len); + + if r.remaining() < len { return Err(Error::IO("Not enough bytes to fill object")); } - Ok(r.copy_to_bytes(len.try_into()?)) + Ok(r.copy_to_bytes(len)) } fn epee_default_value() -> Option { - Some(Bytes::new()) + Some(Self::new()) } fn should_write(&self) -> bool { @@ -236,18 +241,20 @@ impl EpeeValue for BytesMut { return Err(Error::Format("Byte array exceeded max length")); } - if r.remaining() < len.try_into()? { + let len = u64_to_usize(len); + + if r.remaining() < len { return Err(Error::IO("Not enough bytes to fill object")); } - let mut bytes = BytesMut::zeroed(len.try_into()?); + let mut bytes = Self::zeroed(len); r.copy_to_slice(&mut bytes); Ok(bytes) } fn epee_default_value() -> Option { - Some(BytesMut::new()) + Some(Self::new()) } fn should_write(&self) -> bool { @@ -272,16 +279,17 @@ impl EpeeValue for ByteArrayVec { return Err(Error::Format("Byte array exceeded max length")); } - if r.remaining() < usize::try_from(len)? { + let len = u64_to_usize(len); + + if r.remaining() < len { return Err(Error::IO("Not enough bytes to fill object")); } - ByteArrayVec::try_from(r.copy_to_bytes(usize::try_from(len)?)) - .map_err(|_| Error::Format("Field has invalid length")) + Self::try_from(r.copy_to_bytes(len)).map_err(|_| Error::Format("Field has invalid length")) } fn epee_default_value() -> Option { - Some(ByteArrayVec::try_from(Bytes::new()).unwrap()) + Some(Self::try_from(Bytes::new()).unwrap()) } fn should_write(&self) -> bool { @@ -302,7 +310,7 @@ impl EpeeValue for ByteArray { return Err(Error::Format("Marker does not match expected Marker")); } - let len: usize = read_varint(r)?.try_into()?; + let len = u64_to_usize(read_varint(r)?); if len != N { return Err(Error::Format("Byte array has incorrect length")); } @@ -311,8 +319,7 @@ impl EpeeValue for ByteArray { return Err(Error::IO("Not enough bytes to fill object")); } - ByteArray::try_from(r.copy_to_bytes(N)) - .map_err(|_| Error::Format("Field has invalid length")) + Self::try_from(r.copy_to_bytes(N)).map_err(|_| Error::Format("Field has invalid length")) } fn write(self, w: &mut B) -> Result<()> { @@ -326,7 +333,7 @@ impl EpeeValue for String { fn read(r: &mut B, marker: &Marker) -> Result { let bytes = Vec::::read(r, marker)?; - String::from_utf8(bytes).map_err(|_| Error::Format("Invalid string")) + Self::from_utf8(bytes).map_err(|_| Error::Format("Invalid string")) } fn should_write(&self) -> bool { @@ -334,7 +341,7 @@ impl EpeeValue for String { } fn epee_default_value() -> Option { - Some(String::new()) + Some(Self::new()) } fn write(self, w: &mut B) -> Result<()> { @@ -370,11 +377,11 @@ impl EpeeValue for Vec<[u8; N]> { )); } - let len = read_varint(r)?; + let len = u64_to_usize(read_varint(r)?); let individual_marker = Marker::new(marker.inner_marker); - let mut res = Vec::with_capacity(len.try_into()?); + let mut res = Self::with_capacity(len); for _ in 0..len { res.push(<[u8; N]>::read(r, &individual_marker)?); } @@ -386,7 +393,7 @@ impl EpeeValue for Vec<[u8; N]> { } fn epee_default_value() -> Option { - Some(Vec::new()) + Some(Self::new()) } fn write(self, w: &mut B) -> Result<()> { @@ -406,11 +413,11 @@ macro_rules! epee_seq { )); } - let len = read_varint(r)?; + let len = u64_to_usize(read_varint(r)?); let individual_marker = Marker::new(marker.inner_marker.clone()); - let mut res = Vec::with_capacity(len.try_into()?); + let mut res = Vec::with_capacity(len); for _ in 0..len { res.push(<$val>::read(r, &individual_marker)?); } diff --git a/net/epee-encoding/src/varint.rs b/net/epee-encoding/src/varint.rs index ae9c569..3f191dc 100644 --- a/net/epee-encoding/src/varint.rs +++ b/net/epee-encoding/src/varint.rs @@ -21,14 +21,14 @@ const FITS_IN_FOUR_BYTES: u64 = 2_u64.pow(32 - SIZE_OF_SIZE_MARKER) - 1; /// ``` pub fn read_varint(r: &mut B) -> Result { if !r.has_remaining() { - Err(Error::IO("Not enough bytes to build VarInt"))? + return Err(Error::IO("Not enough bytes to build VarInt")); } let vi_start = r.get_u8(); let len = 1 << (vi_start & 0b11); if r.remaining() < len - 1 { - Err(Error::IO("Not enough bytes to build VarInt"))? + return Err(Error::IO("Not enough bytes to build VarInt")); } let mut vi = u64::from(vi_start >> 2); @@ -67,12 +67,15 @@ pub fn write_varint(number: u64, w: &mut B) -> Result<()> { }; if w.remaining_mut() < 1 << size_marker { - Err(Error::IO("Not enough capacity to write VarInt"))?; + return Err(Error::IO("Not enough capacity to write VarInt")); } let number = (number << 2) | size_marker; - // Although `as` is unsafe we just checked the length. + #[expect( + clippy::cast_possible_truncation, + reason = "Although `as` is unsafe we just checked the length." + )] match size_marker { 0 => w.put_u8(number as u8), 1 => w.put_u16_le(number as u16), diff --git a/net/epee-encoding/tests/alt_name.rs b/net/epee-encoding/tests/alt_name.rs index 8a9bc6f..3ddd1ef 100644 --- a/net/epee-encoding/tests/alt_name.rs +++ b/net/epee-encoding/tests/alt_name.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes}; struct AltName { diff --git a/net/epee-encoding/tests/duplicate_key.rs b/net/epee-encoding/tests/duplicate_key.rs index c1b3148..fd8ccc9 100644 --- a/net/epee-encoding/tests/duplicate_key.rs +++ b/net/epee-encoding/tests/duplicate_key.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes}; struct T { @@ -9,12 +11,12 @@ epee_object!( a: u8, ); -struct TT { +struct T2 { a: u8, } epee_object!( - TT, + T2, a: u8 = 0, ); @@ -35,5 +37,5 @@ fn duplicate_key_with_default() { b'a', 0x0B, 0x00, ]; - assert!(from_bytes::(&mut &data[..]).is_err()); + assert!(from_bytes::(&mut &data[..]).is_err()); } diff --git a/net/epee-encoding/tests/epee_default.rs b/net/epee-encoding/tests/epee_default.rs index c221b28..778bbc0 100644 --- a/net/epee-encoding/tests/epee_default.rs +++ b/net/epee-encoding/tests/epee_default.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes}; pub struct Optional { @@ -58,7 +60,7 @@ fn epee_non_default_does_encode() { let val: Optional = from_bytes(&mut bytes).unwrap(); assert_eq!(val.optional_val, -3); - assert_eq!(val.val, 8) + assert_eq!(val.val, 8); } #[test] @@ -70,5 +72,5 @@ fn epee_value_not_present_with_default() { let val: Optional = from_bytes(&mut bytes).unwrap(); assert_eq!(val.optional_val, -4); - assert_eq!(val.val, 76) + assert_eq!(val.val, 76); } diff --git a/net/epee-encoding/tests/flattened.rs b/net/epee-encoding/tests/flattened.rs index a737370..dfb951f 100644 --- a/net/epee-encoding/tests/flattened.rs +++ b/net/epee-encoding/tests/flattened.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes}; struct Child { @@ -37,6 +39,7 @@ epee_object!( ); #[test] +#[expect(clippy::float_cmp)] fn epee_flatten() { let val2 = ParentChild { h: 38.9, diff --git a/net/epee-encoding/tests/options.rs b/net/epee-encoding/tests/options.rs index 5bae9a9..d242124 100644 --- a/net/epee-encoding/tests/options.rs +++ b/net/epee-encoding/tests/options.rs @@ -1,5 +1,6 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes}; -use std::ops::Deref; #[derive(Clone)] struct T { @@ -28,6 +29,6 @@ fn optional_val_in_data() { ]; let t: T = from_bytes(&mut &bytes[..]).unwrap(); let bytes2 = to_bytes(t.clone()).unwrap(); - assert_eq!(bytes.as_slice(), bytes2.deref()); + assert_eq!(bytes.as_slice(), &*bytes2); assert_eq!(t.val.unwrap(), 21); } diff --git a/net/epee-encoding/tests/p2p.rs b/net/epee-encoding/tests/p2p.rs index 2f74ef6..ba17386 100644 --- a/net/epee-encoding/tests/p2p.rs +++ b/net/epee-encoding/tests/p2p.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes}; #[derive(Eq, PartialEq, Debug, Clone)] @@ -5,7 +7,7 @@ pub struct SupportFlags(u32); impl From for SupportFlags { fn from(value: u32) -> Self { - SupportFlags(value) + Self(value) } } diff --git a/net/epee-encoding/tests/rpc.rs b/net/epee-encoding/tests/rpc.rs index 973498e..b366854 100644 --- a/net/epee-encoding/tests/rpc.rs +++ b/net/epee-encoding/tests/rpc.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes, to_bytes}; #[derive(Clone, Debug, PartialEq)] diff --git a/net/epee-encoding/tests/seq.rs b/net/epee-encoding/tests/seq.rs index a4685d0..b4ae788 100644 --- a/net/epee-encoding/tests/seq.rs +++ b/net/epee-encoding/tests/seq.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes}; struct ObjSeq { diff --git a/net/epee-encoding/tests/stack_overflow.rs b/net/epee-encoding/tests/stack_overflow.rs index c53420a..78a1120 100644 --- a/net/epee-encoding/tests/stack_overflow.rs +++ b/net/epee-encoding/tests/stack_overflow.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "outer test module")] + use cuprate_epee_encoding::{epee_object, from_bytes}; struct D { @@ -737,5 +739,5 @@ fn stack_overflow() { let obj: Result = from_bytes(&mut bytes.as_slice()); - assert!(obj.is_err()) + assert!(obj.is_err()); } diff --git a/net/fixed-bytes/Cargo.toml b/net/fixed-bytes/Cargo.toml index b592a09..7844570 100644 --- a/net/fixed-bytes/Cargo.toml +++ b/net/fixed-bytes/Cargo.toml @@ -6,9 +6,17 @@ license = "MIT" authors = ["Boog900"] [features] -default = ["std"] +default = ["std", "serde"] std = ["bytes/std", "dep:thiserror"] +serde = ["bytes/serde", "dep:serde"] [dependencies] thiserror = { workspace = true, optional = true } -bytes = { workspace = true } \ No newline at end of file +bytes = { workspace = true } +serde = { workspace = true, features = ["derive"], optional = true } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } + +[lints] +workspace = true \ No newline at end of file diff --git a/net/fixed-bytes/README.md b/net/fixed-bytes/README.md new file mode 100644 index 0000000..b96c9fc --- /dev/null +++ b/net/fixed-bytes/README.md @@ -0,0 +1,10 @@ +# `cuprate-fixed-bytes` +TODO + +# Feature flags +| Feature flag | Does what | +|--------------|-----------| +| `std` | TODO +| `serde` | Enables `serde` on applicable types + +`serde` is enabled by default. \ No newline at end of file diff --git a/net/fixed-bytes/src/lib.rs b/net/fixed-bytes/src/lib.rs index 8776d30..b1b064b 100644 --- a/net/fixed-bytes/src/lib.rs +++ b/net/fixed-bytes/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + use core::{ fmt::{Debug, Formatter}, ops::{Deref, Index}, @@ -5,7 +7,12 @@ use core::{ use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize}; + #[cfg_attr(feature = "std", derive(thiserror::Error))] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum FixedByteError { #[cfg_attr( feature = "std", @@ -15,17 +22,15 @@ pub enum FixedByteError { } impl FixedByteError { - fn field_name(&self) -> &'static str { + const fn field_name(&self) -> &'static str { match self { - FixedByteError::InvalidLength => "input", + Self::InvalidLength => "input", } } - fn field_data(&self) -> &'static str { + const fn field_data(&self) -> &'static str { match self { - FixedByteError::InvalidLength => { - "Cannot create fix byte array, input has invalid length." - } + Self::InvalidLength => "Cannot create fix byte array, input has invalid length.", } } } @@ -42,9 +47,31 @@ impl Debug for FixedByteError { /// /// Internally this is just a wrapper around [`Bytes`], with the constructors checking that the length is equal to `N`. /// This implements [`Deref`] with the target being `[u8; N]`. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[repr(transparent)] pub struct ByteArray(Bytes); +#[cfg(feature = "serde")] +impl<'de, const N: usize> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Bytes::deserialize(deserializer)?; + let len = bytes.len(); + if len == N { + Ok(Self(bytes)) + } else { + Err(serde::de::Error::invalid_length( + len, + &N.to_string().as_str(), + )) + } + } +} + impl ByteArray { pub fn take_bytes(self) -> Bytes { self.0 @@ -53,7 +80,7 @@ impl ByteArray { impl From<[u8; N]> for ByteArray { fn from(value: [u8; N]) -> Self { - ByteArray(Bytes::copy_from_slice(&value)) + Self(Bytes::copy_from_slice(&value)) } } @@ -72,7 +99,7 @@ impl TryFrom for ByteArray { if value.len() != N { return Err(FixedByteError::InvalidLength); } - Ok(ByteArray(value)) + Ok(Self(value)) } } @@ -83,19 +110,41 @@ impl TryFrom> for ByteArray { if value.len() != N { return Err(FixedByteError::InvalidLength); } - Ok(ByteArray(Bytes::from(value))) + Ok(Self(Bytes::from(value))) } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[repr(transparent)] pub struct ByteArrayVec(Bytes); +#[cfg(feature = "serde")] +impl<'de, const N: usize> Deserialize<'de> for ByteArrayVec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Bytes::deserialize(deserializer)?; + let len = bytes.len(); + if len % N == 0 { + Ok(Self(bytes)) + } else { + Err(serde::de::Error::invalid_length( + len, + &N.to_string().as_str(), + )) + } + } +} + impl ByteArrayVec { - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.0.len() / N } - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.len() == 0 } @@ -111,6 +160,7 @@ impl ByteArrayVec { /// /// # Panics /// Panics if at > len. + #[must_use] pub fn split_off(&mut self, at: usize) -> Self { Self(self.0.split_off(at * N)) } @@ -118,9 +168,9 @@ impl ByteArrayVec { impl From<&ByteArrayVec> for Vec<[u8; N]> { fn from(value: &ByteArrayVec) -> Self { - let mut out = Vec::with_capacity(value.len()); + let mut out = Self::with_capacity(value.len()); for i in 0..value.len() { - out.push(value[i]) + out.push(value[i]); } out @@ -130,11 +180,11 @@ impl From<&ByteArrayVec> for Vec<[u8; N]> { impl From> for ByteArrayVec { fn from(value: Vec<[u8; N]>) -> Self { let mut bytes = BytesMut::with_capacity(N * value.len()); - for i in value.into_iter() { - bytes.extend_from_slice(&i) + for i in value { + bytes.extend_from_slice(&i); } - ByteArrayVec(bytes.freeze()) + Self(bytes.freeze()) } } @@ -146,13 +196,13 @@ impl TryFrom for ByteArrayVec { return Err(FixedByteError::InvalidLength); } - Ok(ByteArrayVec(value)) + Ok(Self(value)) } } impl From<[u8; N]> for ByteArrayVec { fn from(value: [u8; N]) -> Self { - ByteArrayVec(Bytes::copy_from_slice(value.as_slice())) + Self(Bytes::copy_from_slice(value.as_slice())) } } @@ -160,11 +210,11 @@ impl From<[[u8; N]; LEN]> for ByteArrayVec fn from(value: [[u8; N]; LEN]) -> Self { let mut bytes = BytesMut::with_capacity(N * LEN); - for val in value.into_iter() { + for val in value { bytes.put_slice(val.as_slice()); } - ByteArrayVec(bytes.freeze()) + Self(bytes.freeze()) } } @@ -176,7 +226,7 @@ impl TryFrom> for ByteArrayVec { return Err(FixedByteError::InvalidLength); } - Ok(ByteArrayVec(Bytes::from(value))) + Ok(Self(Bytes::from(value))) } } @@ -184,9 +234,12 @@ impl Index for ByteArrayVec { type Output = [u8; N]; fn index(&self, index: usize) -> &Self::Output { - if (index + 1) * N > self.0.len() { - panic!("Index out of range, idx: {}, length: {}", index, self.len()); - } + assert!( + (index + 1) * N <= self.0.len(), + "Index out of range, idx: {}, length: {}", + index, + self.len() + ); self.0[index * N..(index + 1) * N] .as_ref() @@ -197,6 +250,8 @@ impl Index for ByteArrayVec { #[cfg(test)] mod tests { + use serde_json::{from_str, to_string}; + use super::*; #[test] @@ -207,4 +262,46 @@ mod tests { assert_eq!(bytes.len(), 100); let _ = bytes[99]; } + + /// Tests that `serde` works on [`ByteArray`]. + #[test] + #[cfg(feature = "serde")] + fn byte_array_serde() { + let b = ByteArray::from([1, 0, 0, 0, 1]); + let string = to_string(&b).unwrap(); + assert_eq!(string, "[1,0,0,0,1]"); + let b2 = from_str::>(&string).unwrap(); + assert_eq!(b, b2); + } + + /// Tests that `serde` works on [`ByteArrayVec`]. + #[test] + #[cfg(feature = "serde")] + fn byte_array_vec_serde() { + let b = ByteArrayVec::from([1, 0, 0, 0, 1]); + let string = to_string(&b).unwrap(); + assert_eq!(string, "[1,0,0,0,1]"); + let b2 = from_str::>(&string).unwrap(); + assert_eq!(b, b2); + } + + /// Tests that bad input `serde` fails on [`ByteArray`]. + #[test] + #[cfg(feature = "serde")] + #[should_panic( + expected = r#"called `Result::unwrap()` on an `Err` value: Error("invalid length 4, expected 5", line: 0, column: 0)"# + )] + fn byte_array_bad_deserialize() { + from_str::>("[1,0,0,0]").unwrap(); + } + + /// Tests that bad input `serde` fails on [`ByteArrayVec`]. + #[test] + #[cfg(feature = "serde")] + #[should_panic( + expected = r#"called `Result::unwrap()` on an `Err` value: Error("invalid length 4, expected 5", line: 0, column: 0)"# + )] + fn byte_array_vec_bad_deserialize() { + from_str::>("[1,0,0,0]").unwrap(); + } } diff --git a/net/levin/Cargo.toml b/net/levin/Cargo.toml index 13deabe..a9f3c1f 100644 --- a/net/levin/Cargo.toml +++ b/net/levin/Cargo.toml @@ -12,6 +12,9 @@ default = [] tracing = ["dep:tracing", "tokio-util/tracing"] [dependencies] +cuprate-helper = { workspace = true, default-features = false, features = ["cast"] } + +cfg-if = { workspace = true } thiserror = { workspace = true } bytes = { workspace = true, features = ["std"] } bitflags = { workspace = true } @@ -24,4 +27,7 @@ proptest = { workspace = true } rand = { workspace = true, features = ["std", "std_rng"] } tokio-util = { workspace = true, features = ["io-util"]} tokio = { workspace = true, features = ["full"] } -futures = { workspace = true, features = ["std"] } \ No newline at end of file +futures = { workspace = true, features = ["std"] } + +[lints] +workspace = true diff --git a/net/levin/src/codec.rs b/net/levin/src/codec.rs index 3718d8c..4c7695e 100644 --- a/net/levin/src/codec.rs +++ b/net/levin/src/codec.rs @@ -20,6 +20,8 @@ use std::{fmt::Debug, marker::PhantomData}; use bytes::{Buf, BufMut, BytesMut}; use tokio_util::codec::{Decoder, Encoder}; +use cuprate_helper::cast::u64_to_usize; + use crate::{ header::{Flags, HEADER_SIZE}, message::{make_dummy_message, LevinMessage}, @@ -45,7 +47,7 @@ pub struct LevinBucketCodec { impl Default for LevinBucketCodec { fn default() -> Self { - LevinBucketCodec { + Self { state: LevinBucketState::WaitingForHeader, protocol: Protocol::default(), handshake_message_seen: false, @@ -54,8 +56,8 @@ impl Default for LevinBucketCodec { } impl LevinBucketCodec { - pub fn new(protocol: Protocol) -> Self { - LevinBucketCodec { + pub const fn new(protocol: Protocol) -> Self { + Self { state: LevinBucketState::WaitingForHeader, protocol, handshake_message_seen: false, @@ -110,14 +112,13 @@ impl Decoder for LevinBucketCodec { } } - let _ = - std::mem::replace(&mut self.state, LevinBucketState::WaitingForBody(head)); + drop(std::mem::replace( + &mut self.state, + LevinBucketState::WaitingForBody(head), + )); } LevinBucketState::WaitingForBody(head) => { - let body_len = head - .size - .try_into() - .map_err(|_| BucketError::BucketExceededMaxSize)?; + let body_len = u64_to_usize(head.size); if src.len() < body_len { src.reserve(body_len - src.len()); return Ok(None); @@ -146,7 +147,7 @@ impl Encoder> for LevinBucketCodec { type Error = BucketError; fn encode(&mut self, item: Bucket, dst: &mut BytesMut) -> Result<(), Self::Error> { if let Some(additional) = (HEADER_SIZE + item.body.len()).checked_sub(dst.capacity()) { - dst.reserve(additional) + dst.reserve(additional); } item.header.write_bytes_into(dst); @@ -255,13 +256,11 @@ impl Decoder for LevinMessageCodec { continue; }; - let max_size = if self.bucket_codec.handshake_message_seen { + let max_size = u64_to_usize(if self.bucket_codec.handshake_message_seen { self.bucket_codec.protocol.max_packet_size } else { self.bucket_codec.protocol.max_packet_size_before_handshake - } - .try_into() - .expect("Levin max message size is too large, does not fit into a usize."); + }); if bytes.len().saturating_add(bucket.body.len()) > max_size { return Err(BucketError::InvalidFragmentedMessage( @@ -300,12 +299,7 @@ impl Decoder for LevinMessageCodec { } // Check the fragmented message contains enough bytes to build the message. - if bytes.len().saturating_sub(HEADER_SIZE) - < header - .size - .try_into() - .map_err(|_| BucketError::BucketExceededMaxSize)? - { + if bytes.len().saturating_sub(HEADER_SIZE) < u64_to_usize(header.size) { return Err(BucketError::InvalidFragmentedMessage( "Fragmented message does not have enough bytes to fill bucket body", )); diff --git a/net/levin/src/header.rs b/net/levin/src/header.rs index 7acd085..057eee8 100644 --- a/net/levin/src/header.rs +++ b/net/levin/src/header.rs @@ -13,7 +13,7 @@ // copies or substantial portions of the Software. // -//! This module provides a struct BucketHead for the header of a levin protocol +//! This module provides a struct `BucketHead` for the header of a levin protocol //! message. use bitflags::bitflags; @@ -62,7 +62,7 @@ bitflags! { impl From for Flags { fn from(value: u32) -> Self { - Flags(value) + Self(value) } } @@ -99,9 +99,9 @@ impl BucketHead { /// /// # Panics /// This function will panic if there aren't enough bytes to fill the header. - /// Currently [HEADER_SIZE] - pub fn from_bytes(buf: &mut BytesMut) -> BucketHead { - BucketHead { + /// Currently [`HEADER_SIZE`] + pub fn from_bytes(buf: &mut BytesMut) -> Self { + Self { signature: buf.get_u64_le(), size: buf.get_u64_le(), have_to_return_data: buf.get_u8() != 0, diff --git a/net/levin/src/lib.rs b/net/levin/src/lib.rs index 0a247f7..a3f4b69 100644 --- a/net/levin/src/lib.rs +++ b/net/levin/src/lib.rs @@ -33,11 +33,23 @@ #![deny(unused_mut)] //#![deny(missing_docs)] +cfg_if::cfg_if! { + // Used in `tests/`. + if #[cfg(test)] { + use futures as _; + use proptest as _; + use rand as _; + use tokio as _; + } +} + use std::fmt::Debug; use bytes::{Buf, Bytes}; use thiserror::Error; +use cuprate_helper::cast::usize_to_u64; + pub mod codec; pub mod header; pub mod message; @@ -97,7 +109,7 @@ pub struct Protocol { impl Default for Protocol { fn default() -> Self { - Protocol { + Self { version: MONERO_PROTOCOL_VERSION, signature: MONERO_LEVIN_SIGNATURE, max_packet_size_before_handshake: MONERO_MAX_PACKET_SIZE_BEFORE_HANDSHAKE, @@ -128,22 +140,22 @@ pub enum MessageType { impl MessageType { /// Returns if the message requires a response - pub fn have_to_return_data(&self) -> bool { + pub const fn have_to_return_data(&self) -> bool { match self { - MessageType::Request => true, - MessageType::Response | MessageType::Notification => false, + Self::Request => true, + Self::Response | Self::Notification => false, } } - /// Returns the `MessageType` given the flags and have_to_return_data fields - pub fn from_flags_and_have_to_return( + /// Returns the `MessageType` given the flags and `have_to_return_data` fields + pub const fn from_flags_and_have_to_return( flags: Flags, have_to_return: bool, ) -> Result { Ok(match (flags, have_to_return) { - (Flags::REQUEST, true) => MessageType::Request, - (Flags::REQUEST, false) => MessageType::Notification, - (Flags::RESPONSE, false) => MessageType::Response, + (Flags::REQUEST, true) => Self::Request, + (Flags::REQUEST, false) => Self::Notification, + (Flags::RESPONSE, false) => Self::Response, _ => { return Err(BucketError::InvalidHeaderFlags( "Unable to assign a message type to this bucket", @@ -152,10 +164,10 @@ impl MessageType { }) } - pub fn as_flags(&self) -> header::Flags { + pub const fn as_flags(&self) -> Flags { match self { - MessageType::Request | MessageType::Notification => header::Flags::REQUEST, - MessageType::Response => header::Flags::RESPONSE, + Self::Request | Self::Notification => Flags::REQUEST, + Self::Response => Flags::RESPONSE, } } } @@ -171,7 +183,7 @@ pub struct BucketBuilder { } impl BucketBuilder { - pub fn new(protocol: &Protocol) -> Self { + pub const fn new(protocol: &Protocol) -> Self { Self { signature: Some(protocol.signature), ty: None, @@ -183,27 +195,27 @@ impl BucketBuilder { } pub fn set_signature(&mut self, sig: u64) { - self.signature = Some(sig) + self.signature = Some(sig); } pub fn set_message_type(&mut self, ty: MessageType) { - self.ty = Some(ty) + self.ty = Some(ty); } pub fn set_command(&mut self, command: C) { - self.command = Some(command) + self.command = Some(command); } pub fn set_return_code(&mut self, code: i32) { - self.return_code = Some(code) + self.return_code = Some(code); } pub fn set_protocol_version(&mut self, version: u32) { - self.protocol_version = Some(version) + self.protocol_version = Some(version); } pub fn set_body(&mut self, body: Bytes) { - self.body = Some(body) + self.body = Some(body); } pub fn finish(self) -> Bucket { @@ -212,7 +224,7 @@ impl BucketBuilder { Bucket { header: BucketHead { signature: self.signature.unwrap(), - size: body.len().try_into().unwrap(), + size: usize_to_u64(body.len()), have_to_return_data: ty.have_to_return_data(), command: self.command.unwrap(), return_code: self.return_code.unwrap(), diff --git a/net/levin/src/message.rs b/net/levin/src/message.rs index af8227d..32be653 100644 --- a/net/levin/src/message.rs +++ b/net/levin/src/message.rs @@ -5,6 +5,8 @@ //! for more control over what is actually sent over the wire at certain times. use bytes::{Bytes, BytesMut}; +use cuprate_helper::cast::usize_to_u64; + use crate::{ header::{Flags, HEADER_SIZE}, Bucket, BucketBuilder, BucketError, BucketHead, LevinBody, LevinCommand, Protocol, @@ -31,13 +33,13 @@ pub enum LevinMessage { impl From for LevinMessage { fn from(value: T) -> Self { - LevinMessage::Body(value) + Self::Body(value) } } impl From> for LevinMessage { fn from(value: Bucket) -> Self { - LevinMessage::Bucket(value) + Self::Bucket(value) } } @@ -56,7 +58,7 @@ pub struct Dummy(pub usize); impl From for LevinMessage { fn from(value: Dummy) -> Self { - LevinMessage::Dummy(value.0) + Self::Dummy(value.0) } } @@ -74,12 +76,11 @@ pub fn make_fragmented_messages( fragment_size: usize, message: T, ) -> Result>, BucketError> { - if fragment_size * 2 < HEADER_SIZE { - panic!( - "Fragment size: {fragment_size}, is too small, must be at least {}", - 2 * HEADER_SIZE - ); - } + assert!( + fragment_size * 2 >= HEADER_SIZE, + "Fragment size: {fragment_size}, is too small, must be at least {}", + 2 * HEADER_SIZE + ); let mut builder = BucketBuilder::new(protocol); message.encode(&mut builder)?; @@ -106,9 +107,7 @@ pub fn make_fragmented_messages( new_body.resize(fragment_size - HEADER_SIZE, 0); bucket.body = new_body.freeze(); - bucket.header.size = (fragment_size - HEADER_SIZE) - .try_into() - .expect("Bucket size does not fit into u64"); + bucket.header.size = usize_to_u64(fragment_size - HEADER_SIZE); } return Ok(vec![bucket]); @@ -118,9 +117,7 @@ pub fn make_fragmented_messages( // The first fragment will set the START flag, the last will set the END flag. let fragment_head = BucketHead { signature: protocol.signature, - size: (fragment_size - HEADER_SIZE) - .try_into() - .expect("Bucket size does not fit into u64"), + size: usize_to_u64(fragment_size - HEADER_SIZE), have_to_return_data: false, // Just use a default command. command: T::Command::from(0), @@ -191,7 +188,7 @@ pub(crate) fn make_dummy_message(protocol: &Protocol, size: usi // A header to put on the dummy message. let header = BucketHead { signature: protocol.signature, - size: size.try_into().expect("Bucket size does not fit into u64"), + size: usize_to_u64(size), have_to_return_data: false, // Just use a default command. command: T::from(0), diff --git a/net/levin/tests/fragmented_message.rs b/net/levin/tests/fragmented_message.rs index 7799a71..f34b145 100644 --- a/net/levin/tests/fragmented_message.rs +++ b/net/levin/tests/fragmented_message.rs @@ -1,3 +1,9 @@ +#![expect( + clippy::tests_outside_test_module, + unused_crate_dependencies, + reason = "outer test module" +)] + use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::{SinkExt, StreamExt}; use proptest::{prelude::any_with, prop_assert_eq, proptest, sample::size_range}; @@ -8,6 +14,8 @@ use tokio::{ }; use tokio_util::codec::{FramedRead, FramedWrite}; +use cuprate_helper::cast::u64_to_usize; + use cuprate_levin::{ message::make_fragmented_messages, BucketBuilder, BucketError, LevinBody, LevinCommand, LevinMessageCodec, MessageType, Protocol, @@ -54,14 +62,14 @@ impl LevinBody for TestBody { _: MessageType, _: Self::Command, ) -> Result { - let size = body.get_u64_le().try_into().unwrap(); + let size = u64_to_usize(body.get_u64_le()); // bucket - Ok(TestBody::Bytes(size, body.copy_to_bytes(size))) + Ok(Self::Bytes(size, body.copy_to_bytes(size))) } fn encode(self, builder: &mut BucketBuilder) -> Result<(), BucketError> { match self { - TestBody::Bytes(len, bytes) => { + Self::Bytes(len, bytes) => { let mut buf = BytesMut::new(); buf.put_u64_le(len as u64); buf.extend_from_slice(bytes.as_ref()); @@ -139,12 +147,12 @@ proptest! { message2.extend_from_slice(&fragments[0].body[(33 + 8)..]); for frag in fragments.iter().skip(1) { - message2.extend_from_slice(frag.body.as_ref()) + message2.extend_from_slice(frag.body.as_ref()); } prop_assert_eq!(message.as_slice(), &message2[0..message.len()], "numb_fragments: {}", fragments.len()); - for byte in message2[message.len()..].iter(){ + for byte in &message2[message.len()..]{ prop_assert_eq!(*byte, 0); } } diff --git a/net/wire/Cargo.toml b/net/wire/Cargo.toml index c71a77b..b500a28 100644 --- a/net/wire/Cargo.toml +++ b/net/wire/Cargo.toml @@ -11,9 +11,11 @@ default = [] tracing = ["cuprate-levin/tracing"] [dependencies] -cuprate-levin = { path = "../levin" } -cuprate-epee-encoding = { path = "../epee-encoding" } -cuprate-fixed-bytes = { path = "../fixed-bytes" } +cuprate-levin = { workspace = true } +cuprate-epee-encoding = { workspace = true } +cuprate-fixed-bytes = { workspace = true } +cuprate-types = { workspace = true, default-features = false, features = ["epee"] } +cuprate-helper = { workspace = true, default-features = false, features = ["map"] } bitflags = { workspace = true, features = ["std"] } bytes = { workspace = true, features = ["std"] } @@ -22,3 +24,5 @@ thiserror = { workspace = true } [dev-dependencies] hex = { workspace = true, features = ["std"]} +[lints] +workspace = true diff --git a/net/wire/src/lib.rs b/net/wire/src/lib.rs index 45a2405..674a2e9 100644 --- a/net/wire/src/lib.rs +++ b/net/wire/src/lib.rs @@ -13,10 +13,10 @@ // copies or substantial portions of the Software. // -//! # Monero Wire +//! # Cuprate Wire //! //! A crate defining Monero network messages and network addresses, -//! built on top of the levin-cuprate crate. +//! built on top of the [`cuprate_levin`] crate. //! //! ## License //! diff --git a/net/wire/src/network_address.rs b/net/wire/src/network_address.rs index 632739a..3e15c46 100644 --- a/net/wire/src/network_address.rs +++ b/net/wire/src/network_address.rs @@ -17,10 +17,12 @@ //! Monero network. Core Monero has 4 main addresses: IPv4, IPv6, Tor, //! I2p. Currently this module only has IPv(4/6). //! -use bytes::BufMut; -use cuprate_epee_encoding::EpeeObject; use std::{hash::Hash, net, net::SocketAddr}; +use bytes::BufMut; + +use cuprate_epee_encoding::EpeeObject; + mod epee_builder; use epee_builder::*; @@ -51,38 +53,38 @@ impl EpeeObject for NetworkAddress { } impl NetworkAddress { - pub fn get_zone(&self) -> NetZone { + pub const fn get_zone(&self) -> NetZone { match self { - NetworkAddress::Clear(_) => NetZone::Public, + Self::Clear(_) => NetZone::Public, } } - pub fn is_loopback(&self) -> bool { + pub const fn is_loopback(&self) -> bool { // TODO false } - pub fn is_local(&self) -> bool { + pub const fn is_local(&self) -> bool { // TODO false } - pub fn port(&self) -> u16 { + pub const fn port(&self) -> u16 { match self { - NetworkAddress::Clear(ip) => ip.port(), + Self::Clear(ip) => ip.port(), } } } impl From for NetworkAddress { fn from(value: net::SocketAddrV4) -> Self { - NetworkAddress::Clear(value.into()) + Self::Clear(value.into()) } } impl From for NetworkAddress { fn from(value: net::SocketAddrV6) -> Self { - NetworkAddress::Clear(value.into()) + Self::Clear(value.into()) } } diff --git a/net/wire/src/network_address/epee_builder.rs b/net/wire/src/network_address/epee_builder.rs index 36db824..bd481a5 100644 --- a/net/wire/src/network_address/epee_builder.rs +++ b/net/wire/src/network_address/epee_builder.rs @@ -1,9 +1,10 @@ -use bytes::Buf; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; -use cuprate_epee_encoding::{epee_object, EpeeObjectBuilder}; +use bytes::Buf; use thiserror::Error; +use cuprate_epee_encoding::{epee_object, EpeeObjectBuilder}; + use crate::NetworkAddress; #[derive(Default)] @@ -74,15 +75,15 @@ impl From for TaggedNetworkAddress { fn from(value: NetworkAddress) -> Self { match value { NetworkAddress::Clear(addr) => match addr { - SocketAddr::V4(addr) => TaggedNetworkAddress { + SocketAddr::V4(addr) => Self { ty: Some(1), addr: Some(AllFieldsNetworkAddress { - m_ip: Some(u32::from_be_bytes(addr.ip().octets())), + m_ip: Some(u32::from_le_bytes(addr.ip().octets())), m_port: Some(addr.port()), addr: None, }), }, - SocketAddr::V6(addr) => TaggedNetworkAddress { + SocketAddr::V6(addr) => Self { ty: Some(2), addr: Some(AllFieldsNetworkAddress { addr: Some(addr.ip().octets()), @@ -112,7 +113,10 @@ epee_object!( impl AllFieldsNetworkAddress { fn try_into_network_address(self, ty: u8) -> Option { Some(match ty { - 1 => NetworkAddress::from(SocketAddrV4::new(Ipv4Addr::from(self.m_ip?), self.m_port?)), + 1 => NetworkAddress::from(SocketAddrV4::new( + Ipv4Addr::from(self.m_ip?.to_le_bytes()), + self.m_port?, + )), 2 => NetworkAddress::from(SocketAddrV6::new( Ipv6Addr::from(self.addr?), self.m_port?, diff --git a/net/wire/src/p2p.rs b/net/wire/src/p2p.rs index 0d448e4..a7cd784 100644 --- a/net/wire/src/p2p.rs +++ b/net/wire/src/p2p.rs @@ -55,27 +55,27 @@ pub enum LevinCommand { impl std::fmt::Display for LevinCommand { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if let LevinCommand::Unknown(id) = self { - return f.write_str(&format!("unknown id: {}", id)); + if let Self::Unknown(id) = self { + return f.write_str(&format!("unknown id: {id}")); } f.write_str(match self { - LevinCommand::Handshake => "handshake", - LevinCommand::TimedSync => "timed sync", - LevinCommand::Ping => "ping", - LevinCommand::SupportFlags => "support flags", + Self::Handshake => "handshake", + Self::TimedSync => "timed sync", + Self::Ping => "ping", + Self::SupportFlags => "support flags", - LevinCommand::NewBlock => "new block", - LevinCommand::NewTransactions => "new transactions", - LevinCommand::GetObjectsRequest => "get objects request", - LevinCommand::GetObjectsResponse => "get objects response", - LevinCommand::ChainRequest => "chain request", - LevinCommand::ChainResponse => "chain response", - LevinCommand::NewFluffyBlock => "new fluffy block", - LevinCommand::FluffyMissingTxsRequest => "fluffy missing transaction request", - LevinCommand::GetTxPoolCompliment => "get transaction pool compliment", + Self::NewBlock => "new block", + Self::NewTransactions => "new transactions", + Self::GetObjectsRequest => "get objects request", + Self::GetObjectsResponse => "get objects response", + Self::ChainRequest => "chain request", + Self::ChainResponse => "chain response", + Self::NewFluffyBlock => "new fluffy block", + Self::FluffyMissingTxsRequest => "fluffy missing transaction request", + Self::GetTxPoolCompliment => "get transaction pool compliment", - LevinCommand::Unknown(_) => unreachable!(), + Self::Unknown(_) => unreachable!(), }) } } @@ -83,50 +83,51 @@ impl std::fmt::Display for LevinCommand { impl LevinCommandTrait for LevinCommand { fn bucket_size_limit(&self) -> u64 { // https://github.com/monero-project/monero/blob/00fd416a99686f0956361d1cd0337fe56e58d4a7/src/cryptonote_basic/connection_context.cpp#L37 + #[expect(clippy::match_same_arms, reason = "formatting is more clear")] match self { - LevinCommand::Handshake => 65536, - LevinCommand::TimedSync => 65536, - LevinCommand::Ping => 4096, - LevinCommand::SupportFlags => 4096, + Self::Handshake => 65536, + Self::TimedSync => 65536, + Self::Ping => 4096, + Self::SupportFlags => 4096, - LevinCommand::NewBlock => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though) - LevinCommand::NewTransactions => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though) - LevinCommand::GetObjectsRequest => 1024 * 1024 * 2, // 2 MB - LevinCommand::GetObjectsResponse => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though) - LevinCommand::ChainRequest => 512 * 1024, // 512 kB - LevinCommand::ChainResponse => 1024 * 1024 * 4, // 4 MB - LevinCommand::NewFluffyBlock => 1024 * 1024 * 4, // 4 MB - LevinCommand::FluffyMissingTxsRequest => 1024 * 1024, // 1 MB - LevinCommand::GetTxPoolCompliment => 1024 * 1024 * 4, // 4 MB + Self::NewBlock => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though) + Self::NewTransactions => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though) + Self::GetObjectsRequest => 1024 * 1024 * 2, // 2 MB + Self::GetObjectsResponse => 1024 * 1024 * 128, // 128 MB (max packet is a bit less than 100 MB though) + Self::ChainRequest => 512 * 1024, // 512 kB + Self::ChainResponse => 1024 * 1024 * 4, // 4 MB + Self::NewFluffyBlock => 1024 * 1024 * 4, // 4 MB + Self::FluffyMissingTxsRequest => 1024 * 1024, // 1 MB + Self::GetTxPoolCompliment => 1024 * 1024 * 4, // 4 MB - LevinCommand::Unknown(_) => usize::MAX.try_into().unwrap_or(u64::MAX), + Self::Unknown(_) => u64::MAX, } } fn is_handshake(&self) -> bool { - matches!(self, LevinCommand::Handshake) + matches!(self, Self::Handshake) } } impl From for LevinCommand { fn from(value: u32) -> Self { match value { - 1001 => LevinCommand::Handshake, - 1002 => LevinCommand::TimedSync, - 1003 => LevinCommand::Ping, - 1007 => LevinCommand::SupportFlags, + 1001 => Self::Handshake, + 1002 => Self::TimedSync, + 1003 => Self::Ping, + 1007 => Self::SupportFlags, - 2001 => LevinCommand::NewBlock, - 2002 => LevinCommand::NewTransactions, - 2003 => LevinCommand::GetObjectsRequest, - 2004 => LevinCommand::GetObjectsResponse, - 2006 => LevinCommand::ChainRequest, - 2007 => LevinCommand::ChainResponse, - 2008 => LevinCommand::NewFluffyBlock, - 2009 => LevinCommand::FluffyMissingTxsRequest, - 2010 => LevinCommand::GetTxPoolCompliment, + 2001 => Self::NewBlock, + 2002 => Self::NewTransactions, + 2003 => Self::GetObjectsRequest, + 2004 => Self::GetObjectsResponse, + 2006 => Self::ChainRequest, + 2007 => Self::ChainResponse, + 2008 => Self::NewFluffyBlock, + 2009 => Self::FluffyMissingTxsRequest, + 2010 => Self::GetTxPoolCompliment, - x => LevinCommand::Unknown(x), + x => Self::Unknown(x), } } } @@ -177,6 +178,7 @@ fn build_message( Ok(()) } +#[derive(Debug, Clone)] pub enum ProtocolMessage { NewBlock(NewBlock), NewFluffyBlock(NewFluffyBlock), @@ -190,19 +192,19 @@ pub enum ProtocolMessage { } impl ProtocolMessage { - pub fn command(&self) -> LevinCommand { + pub const fn command(&self) -> LevinCommand { use LevinCommand as C; match self { - ProtocolMessage::NewBlock(_) => C::NewBlock, - ProtocolMessage::NewFluffyBlock(_) => C::NewFluffyBlock, - ProtocolMessage::GetObjectsRequest(_) => C::GetObjectsRequest, - ProtocolMessage::GetObjectsResponse(_) => C::GetObjectsResponse, - ProtocolMessage::ChainRequest(_) => C::ChainRequest, - ProtocolMessage::ChainEntryResponse(_) => C::ChainResponse, - ProtocolMessage::NewTransactions(_) => C::NewTransactions, - ProtocolMessage::FluffyMissingTransactionsRequest(_) => C::FluffyMissingTxsRequest, - ProtocolMessage::GetTxPoolCompliment(_) => C::GetTxPoolCompliment, + Self::NewBlock(_) => C::NewBlock, + Self::NewFluffyBlock(_) => C::NewFluffyBlock, + Self::GetObjectsRequest(_) => C::GetObjectsRequest, + Self::GetObjectsResponse(_) => C::GetObjectsResponse, + Self::ChainRequest(_) => C::ChainRequest, + Self::ChainEntryResponse(_) => C::ChainResponse, + Self::NewTransactions(_) => C::NewTransactions, + Self::FluffyMissingTransactionsRequest(_) => C::FluffyMissingTxsRequest, + Self::GetTxPoolCompliment(_) => C::GetTxPoolCompliment, } } @@ -229,48 +231,49 @@ impl ProtocolMessage { use LevinCommand as C; match self { - ProtocolMessage::NewBlock(val) => build_message(C::NewBlock, val, builder)?, - ProtocolMessage::NewTransactions(val) => { - build_message(C::NewTransactions, val, builder)? + Self::NewBlock(val) => build_message(C::NewBlock, val, builder)?, + Self::NewTransactions(val) => { + build_message(C::NewTransactions, val, builder)?; } - ProtocolMessage::GetObjectsRequest(val) => { - build_message(C::GetObjectsRequest, val, builder)? + Self::GetObjectsRequest(val) => { + build_message(C::GetObjectsRequest, val, builder)?; } - ProtocolMessage::GetObjectsResponse(val) => { - build_message(C::GetObjectsResponse, val, builder)? + Self::GetObjectsResponse(val) => { + build_message(C::GetObjectsResponse, val, builder)?; } - ProtocolMessage::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?, - ProtocolMessage::ChainEntryResponse(val) => { - build_message(C::ChainResponse, val, builder)? + Self::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?, + Self::ChainEntryResponse(val) => { + build_message(C::ChainResponse, val, builder)?; } - ProtocolMessage::NewFluffyBlock(val) => build_message(C::NewFluffyBlock, val, builder)?, - ProtocolMessage::FluffyMissingTransactionsRequest(val) => { - build_message(C::FluffyMissingTxsRequest, val, builder)? + Self::NewFluffyBlock(val) => build_message(C::NewFluffyBlock, val, builder)?, + Self::FluffyMissingTransactionsRequest(val) => { + build_message(C::FluffyMissingTxsRequest, val, builder)?; } - ProtocolMessage::GetTxPoolCompliment(val) => { - build_message(C::GetTxPoolCompliment, val, builder)? + Self::GetTxPoolCompliment(val) => { + build_message(C::GetTxPoolCompliment, val, builder)?; } } Ok(()) } } -pub enum RequestMessage { +#[derive(Debug, Clone)] +pub enum AdminRequestMessage { Handshake(HandshakeRequest), Ping, SupportFlags, TimedSync(TimedSyncRequest), } -impl RequestMessage { - pub fn command(&self) -> LevinCommand { +impl AdminRequestMessage { + pub const fn command(&self) -> LevinCommand { use LevinCommand as C; match self { - RequestMessage::Handshake(_) => C::Handshake, - RequestMessage::Ping => C::Ping, - RequestMessage::SupportFlags => C::SupportFlags, - RequestMessage::TimedSync(_) => C::TimedSync, + Self::Handshake(_) => C::Handshake, + Self::Ping => C::Ping, + Self::SupportFlags => C::SupportFlags, + Self::TimedSync(_) => C::TimedSync, } } @@ -278,19 +281,19 @@ impl RequestMessage { use LevinCommand as C; Ok(match command { - C::Handshake => decode_message(RequestMessage::Handshake, buf)?, - C::TimedSync => decode_message(RequestMessage::TimedSync, buf)?, + C::Handshake => decode_message(AdminRequestMessage::Handshake, buf)?, + C::TimedSync => decode_message(AdminRequestMessage::TimedSync, buf)?, C::Ping => { cuprate_epee_encoding::from_bytes::(buf) .map_err(|e| BucketError::BodyDecodingError(e.into()))?; - RequestMessage::Ping + Self::Ping } C::SupportFlags => { cuprate_epee_encoding::from_bytes::(buf) .map_err(|e| BucketError::BodyDecodingError(e.into()))?; - RequestMessage::SupportFlags + Self::SupportFlags } _ => return Err(BucketError::UnknownCommand), }) @@ -300,31 +303,34 @@ impl RequestMessage { use LevinCommand as C; match self { - RequestMessage::Handshake(val) => build_message(C::Handshake, val, builder)?, - RequestMessage::TimedSync(val) => build_message(C::TimedSync, val, builder)?, - RequestMessage::Ping => build_message(C::Ping, EmptyMessage, builder)?, - RequestMessage::SupportFlags => build_message(C::SupportFlags, EmptyMessage, builder)?, + Self::Handshake(val) => build_message(C::Handshake, val, builder)?, + Self::TimedSync(val) => build_message(C::TimedSync, val, builder)?, + Self::Ping => build_message(C::Ping, EmptyMessage, builder)?, + Self::SupportFlags => { + build_message(C::SupportFlags, EmptyMessage, builder)?; + } } Ok(()) } } -pub enum ResponseMessage { +#[derive(Debug, Clone)] +pub enum AdminResponseMessage { Handshake(HandshakeResponse), Ping(PingResponse), SupportFlags(SupportFlagsResponse), TimedSync(TimedSyncResponse), } -impl ResponseMessage { - pub fn command(&self) -> LevinCommand { +impl AdminResponseMessage { + pub const fn command(&self) -> LevinCommand { use LevinCommand as C; match self { - ResponseMessage::Handshake(_) => C::Handshake, - ResponseMessage::Ping(_) => C::Ping, - ResponseMessage::SupportFlags(_) => C::SupportFlags, - ResponseMessage::TimedSync(_) => C::TimedSync, + Self::Handshake(_) => C::Handshake, + Self::Ping(_) => C::Ping, + Self::SupportFlags(_) => C::SupportFlags, + Self::TimedSync(_) => C::TimedSync, } } @@ -332,10 +338,10 @@ impl ResponseMessage { use LevinCommand as C; Ok(match command { - C::Handshake => decode_message(ResponseMessage::Handshake, buf)?, - C::TimedSync => decode_message(ResponseMessage::TimedSync, buf)?, - C::Ping => decode_message(ResponseMessage::Ping, buf)?, - C::SupportFlags => decode_message(ResponseMessage::SupportFlags, buf)?, + C::Handshake => decode_message(AdminResponseMessage::Handshake, buf)?, + C::TimedSync => decode_message(AdminResponseMessage::TimedSync, buf)?, + C::Ping => decode_message(AdminResponseMessage::Ping, buf)?, + C::SupportFlags => decode_message(AdminResponseMessage::SupportFlags, buf)?, _ => return Err(BucketError::UnknownCommand), }) } @@ -344,39 +350,42 @@ impl ResponseMessage { use LevinCommand as C; match self { - ResponseMessage::Handshake(val) => build_message(C::Handshake, val, builder)?, - ResponseMessage::TimedSync(val) => build_message(C::TimedSync, val, builder)?, - ResponseMessage::Ping(val) => build_message(C::Ping, val, builder)?, - ResponseMessage::SupportFlags(val) => build_message(C::SupportFlags, val, builder)?, + Self::Handshake(val) => build_message(C::Handshake, val, builder)?, + Self::TimedSync(val) => build_message(C::TimedSync, val, builder)?, + Self::Ping(val) => build_message(C::Ping, val, builder)?, + Self::SupportFlags(val) => { + build_message(C::SupportFlags, val, builder)?; + } } Ok(()) } } +#[derive(Debug, Clone)] pub enum Message { - Request(RequestMessage), - Response(ResponseMessage), + Request(AdminRequestMessage), + Response(AdminResponseMessage), Protocol(ProtocolMessage), } impl Message { - pub fn is_request(&self) -> bool { - matches!(self, Message::Request(_)) + pub const fn is_request(&self) -> bool { + matches!(self, Self::Request(_)) } - pub fn is_response(&self) -> bool { - matches!(self, Message::Response(_)) + pub const fn is_response(&self) -> bool { + matches!(self, Self::Response(_)) } - pub fn is_protocol(&self) -> bool { - matches!(self, Message::Protocol(_)) + pub const fn is_protocol(&self) -> bool { + matches!(self, Self::Protocol(_)) } - pub fn command(&self) -> LevinCommand { + pub const fn command(&self) -> LevinCommand { match self { - Message::Request(mes) => mes.command(), - Message::Response(mes) => mes.command(), - Message::Protocol(mes) => mes.command(), + Self::Request(mes) => mes.command(), + Self::Response(mes) => mes.command(), + Self::Protocol(mes) => mes.command(), } } } @@ -390,25 +399,25 @@ impl LevinBody for Message { command: LevinCommand, ) -> Result { Ok(match typ { - MessageType::Request => Message::Request(RequestMessage::decode(body, command)?), - MessageType::Response => Message::Response(ResponseMessage::decode(body, command)?), - MessageType::Notification => Message::Protocol(ProtocolMessage::decode(body, command)?), + MessageType::Request => Self::Request(AdminRequestMessage::decode(body, command)?), + MessageType::Response => Self::Response(AdminResponseMessage::decode(body, command)?), + MessageType::Notification => Self::Protocol(ProtocolMessage::decode(body, command)?), }) } fn encode(self, builder: &mut BucketBuilder) -> Result<(), BucketError> { match self { - Message::Protocol(pro) => { + Self::Protocol(pro) => { builder.set_message_type(MessageType::Notification); builder.set_return_code(0); pro.build(builder) } - Message::Request(req) => { + Self::Request(req) => { builder.set_message_type(MessageType::Request); builder.set_return_code(0); req.build(builder) } - Message::Response(res) => { + Self::Response(res) => { builder.set_message_type(MessageType::Response); builder.set_return_code(1); res.build(builder) diff --git a/net/wire/src/p2p/admin.rs b/net/wire/src/p2p/admin.rs index 173c293..67a8e21 100644 --- a/net/wire/src/p2p/admin.rs +++ b/net/wire/src/p2p/admin.rs @@ -45,7 +45,7 @@ pub struct HandshakeResponse { pub node_data: BasicNodeData, /// Core Sync Data pub payload_data: CoreSyncData, - /// PeerList + /// `PeerList` pub local_peerlist_new: Vec, } @@ -56,7 +56,7 @@ epee_object!( local_peerlist_new: Vec, ); -/// A TimedSync Request +/// A `TimedSync` Request #[derive(Debug, Clone, PartialEq, Eq)] pub struct TimedSyncRequest { /// Core Sync Data @@ -68,12 +68,12 @@ epee_object!( payload_data: CoreSyncData, ); -/// A TimedSync Response +/// A `TimedSync` Response #[derive(Debug, Clone, PartialEq, Eq)] pub struct TimedSyncResponse { /// Core Sync Data pub payload_data: CoreSyncData, - /// PeerList + /// `PeerList` pub local_peerlist_new: Vec, } diff --git a/net/wire/src/p2p/common.rs b/net/wire/src/p2p/common.rs index 91adb90..d95a620 100644 --- a/net/wire/src/p2p/common.rs +++ b/net/wire/src/p2p/common.rs @@ -16,10 +16,10 @@ //! Common types that are used across multiple messages. use bitflags::bitflags; -use bytes::{Buf, BufMut, Bytes}; -use cuprate_epee_encoding::{epee_object, EpeeValue, InnerMarker}; -use cuprate_fixed_bytes::ByteArray; +use cuprate_epee_encoding::epee_object; +use cuprate_helper::map::split_u128_into_low_high_bits; +pub use cuprate_types::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; use crate::NetworkAddress; @@ -35,7 +35,7 @@ bitflags! { impl From for PeerSupportFlags { fn from(value: u32) -> Self { - PeerSupportFlags(value) + Self(value) } } @@ -114,16 +114,17 @@ epee_object! { } impl CoreSyncData { - pub fn new( + pub const fn new( cumulative_difficulty_128: u128, current_height: u64, pruning_seed: u32, top_id: [u8; 32], top_version: u8, - ) -> CoreSyncData { - let cumulative_difficulty = cumulative_difficulty_128 as u64; - let cumulative_difficulty_top64 = (cumulative_difficulty_128 >> 64) as u64; - CoreSyncData { + ) -> Self { + let (cumulative_difficulty, cumulative_difficulty_top64) = + split_u128_into_low_high_bits(cumulative_difficulty_128); + + Self { cumulative_difficulty, cumulative_difficulty_top64, current_height, @@ -140,7 +141,7 @@ impl CoreSyncData { } } -/// PeerListEntryBase, information kept on a peer which will be entered +/// `PeerListEntryBase`, information kept on a peer which will be entered /// in a peer list/store. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PeerListEntryBase { @@ -168,113 +169,6 @@ epee_object! { rpc_credits_per_hash: u32 = 0_u32, } -/// A pruned tx with the hash of the missing prunable data -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PrunedTxBlobEntry { - /// The Tx - pub tx: Bytes, - /// The Prunable Tx Hash - pub prunable_hash: ByteArray<32>, -} - -epee_object!( - PrunedTxBlobEntry, - tx: Bytes, - prunable_hash: ByteArray<32>, -); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TransactionBlobs { - Pruned(Vec), - Normal(Vec), - None, -} - -impl TransactionBlobs { - pub fn take_pruned(self) -> Option> { - match self { - TransactionBlobs::Normal(_) => None, - TransactionBlobs::Pruned(txs) => Some(txs), - TransactionBlobs::None => Some(vec![]), - } - } - - pub fn take_normal(self) -> Option> { - match self { - TransactionBlobs::Normal(txs) => Some(txs), - TransactionBlobs::Pruned(_) => None, - TransactionBlobs::None => Some(vec![]), - } - } - - pub fn len(&self) -> usize { - match self { - TransactionBlobs::Normal(txs) => txs.len(), - TransactionBlobs::Pruned(txs) => txs.len(), - TransactionBlobs::None => 0, - } - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -/// A Block that can contain transactions -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BlockCompleteEntry { - /// True if tx data is pruned - pub pruned: bool, - /// The Block - pub block: Bytes, - /// The Block Weight/Size - pub block_weight: u64, - /// The blocks txs - pub txs: TransactionBlobs, -} - -epee_object!( - BlockCompleteEntry, - pruned: bool = false, - block: Bytes, - block_weight: u64 = 0_u64, - txs: TransactionBlobs = TransactionBlobs::None => tx_blob_read, tx_blob_write, should_write_tx_blobs, -); - -fn tx_blob_read(b: &mut B) -> cuprate_epee_encoding::Result { - let marker = cuprate_epee_encoding::read_marker(b)?; - match marker.inner_marker { - InnerMarker::Object => Ok(TransactionBlobs::Pruned(Vec::read(b, &marker)?)), - InnerMarker::String => Ok(TransactionBlobs::Normal(Vec::read(b, &marker)?)), - _ => Err(cuprate_epee_encoding::Error::Value( - "Invalid marker for tx blobs".to_string(), - )), - } -} - -fn tx_blob_write( - val: TransactionBlobs, - field_name: &str, - w: &mut B, -) -> cuprate_epee_encoding::Result<()> { - if should_write_tx_blobs(&val) { - match val { - TransactionBlobs::Normal(bytes) => { - cuprate_epee_encoding::write_field(bytes, field_name, w)? - } - TransactionBlobs::Pruned(obj) => { - cuprate_epee_encoding::write_field(obj, field_name, w)? - } - TransactionBlobs::None => (), - } - } - Ok(()) -} - -fn should_write_tx_blobs(val: &TransactionBlobs) -> bool { - !val.is_empty() -} - #[cfg(test)] mod tests { diff --git a/net/wire/src/p2p/protocol.rs b/net/wire/src/p2p/protocol.rs index 5e95a4f..cc4b49d 100644 --- a/net/wire/src/p2p/protocol.rs +++ b/net/wire/src/p2p/protocol.rs @@ -16,14 +16,14 @@ //! This module defines Monero protocol messages //! //! Protocol message requests don't have to be responded to in order unlike -//! admin messages. +//! admin messages. use bytes::Bytes; use cuprate_epee_encoding::{container_as_blob::ContainerAsBlob, epee_object}; use cuprate_fixed_bytes::{ByteArray, ByteArrayVec}; -use super::common::BlockCompleteEntry; +use crate::p2p::common::BlockCompleteEntry; /// A block that SHOULD have transactions #[derive(Debug, Clone, PartialEq, Eq)] @@ -61,7 +61,7 @@ epee_object!( /// A Request For Blocks #[derive(Debug, Clone, PartialEq, Eq)] pub struct GetObjectsRequest { - /// Block hashes we want + /// Block hashes wanted. pub blocks: ByteArrayVec<32>, /// Pruned pub pruned: bool, @@ -127,7 +127,7 @@ pub struct ChainResponse { impl ChainResponse { #[inline] - pub fn cumulative_difficulty(&self) -> u128 { + pub const fn cumulative_difficulty(&self) -> u128 { let cumulative_difficulty = self.cumulative_difficulty_top64 as u128; cumulative_difficulty << 64 | self.cumulative_difficulty_low64 as u128 } @@ -159,7 +159,7 @@ epee_object!( current_blockchain_height: u64, ); -/// A request for Txs we are missing from our TxPool +/// A request for txs we are missing from an incoming block. #[derive(Debug, Clone, PartialEq, Eq)] pub struct FluffyMissingTransactionsRequest { /// The Block we are missing the Txs in @@ -177,7 +177,7 @@ epee_object!( missing_tx_indices: Vec as ContainerAsBlob, ); -/// TxPoolCompliment +/// `TxPoolCompliment` #[derive(Debug, Clone, PartialEq, Eq)] pub struct GetTxPoolCompliment { /// Tx Hashes diff --git a/p2p/address-book/Cargo.toml b/p2p/address-book/Cargo.toml index 9cff78a..d57cfde 100644 --- a/p2p/address-book/Cargo.toml +++ b/p2p/address-book/Cargo.toml @@ -7,9 +7,9 @@ authors = ["Boog900"] [dependencies] -cuprate-pruning = { path = "../../pruning" } -cuprate-wire = { path= "../../net/wire" } -cuprate-p2p-core = { path = "../p2p-core" } +cuprate-constants = { workspace = true } +cuprate-pruning = { workspace = true } +cuprate-p2p-core = { workspace = true, features = ["borsh"] } tower = { workspace = true, features = ["util"] } tokio = { workspace = true, features = ["time", "fs", "rt"]} @@ -23,9 +23,12 @@ indexmap = { workspace = true, features = ["std"] } rand = { workspace = true, features = ["std", "std_rng"] } -borsh = { workspace = true, features = ["derive", "std"]} +borsh = { workspace = true, features = ["derive", "std"] } [dev-dependencies] -cuprate-test-utils = {path = "../../test-utils"} +cuprate-test-utils = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"]} + +[lints] +workspace = true diff --git a/p2p/address-book/src/book.rs b/p2p/address-book/src/book.rs index b6ab07a..3e5269f 100644 --- a/p2p/address-book/src/book.rs +++ b/p2p/address-book/src/book.rs @@ -27,13 +27,16 @@ use cuprate_p2p_core::{ }; use cuprate_pruning::PruningSeed; -use crate::{peer_list::PeerList, store::save_peers_to_disk, AddressBookConfig, AddressBookError}; +use crate::{ + peer_list::PeerList, store::save_peers_to_disk, AddressBookConfig, AddressBookError, + BorshNetworkZone, +}; #[cfg(test)] mod tests; /// An entry in the connected list. -pub struct ConnectionPeerEntry { +pub(crate) struct ConnectionPeerEntry { addr: Option, id: u64, handle: ConnectionHandle, @@ -45,7 +48,7 @@ pub struct ConnectionPeerEntry { rpc_credits_per_hash: u32, } -pub struct AddressBook { +pub struct AddressBook { /// Our white peers - the peers we have previously connected to. white_list: PeerList, /// Our gray peers - the peers we have been told about but haven't connected to. @@ -66,7 +69,7 @@ pub struct AddressBook { cfg: AddressBookConfig, } -impl AddressBook { +impl AddressBook { pub fn new( cfg: AddressBookConfig, white_peers: Vec>, @@ -106,14 +109,14 @@ impl AddressBook { match handle.poll_unpin(cx) { Poll::Pending => return, Poll::Ready(Ok(Err(e))) => { - tracing::error!("Could not save peer list to disk, got error: {}", e) + tracing::error!("Could not save peer list to disk, got error: {e}"); } Poll::Ready(Err(e)) => { if e.is_panic() { panic::resume_unwind(e.into_panic()) } } - _ => (), + Poll::Ready(_) => (), } } // the task is finished. @@ -141,6 +144,7 @@ impl AddressBook { let mut internal_addr_disconnected = Vec::new(); let mut addrs_to_ban = Vec::new(); + #[expect(clippy::iter_over_hash_type, reason = "ordering doesn't matter here")] for (internal_addr, peer) in &mut self.connected_peers { if let Some(time) = peer.handle.check_should_ban() { match internal_addr { @@ -155,7 +159,7 @@ impl AddressBook { } } - for (addr, time) in addrs_to_ban.into_iter() { + for (addr, time) in addrs_to_ban { self.ban_peer(addr, time); } @@ -169,12 +173,7 @@ impl AddressBook { .remove(&addr); // If the amount of peers with this ban id is 0 remove the whole set. - if self - .connected_peers_ban_id - .get(&addr.ban_id()) - .unwrap() - .is_empty() - { + if self.connected_peers_ban_id[&addr.ban_id()].is_empty() { self.connected_peers_ban_id.remove(&addr.ban_id()); } // remove the peer from the anchor list. @@ -185,7 +184,7 @@ impl AddressBook { fn ban_peer(&mut self, addr: Z::Addr, time: Duration) { if self.banned_peers.contains_key(&addr.ban_id()) { - tracing::error!("Tried to ban peer twice, this shouldn't happen.") + tracing::error!("Tried to ban peer twice, this shouldn't happen."); } if let Some(connected_peers_with_ban_id) = self.connected_peers_ban_id.get(&addr.ban_id()) { @@ -230,6 +229,15 @@ impl AddressBook { self.banned_peers.contains_key(&peer.ban_id()) } + /// Checks when a peer will be unbanned. + /// + /// - If the peer is banned, this returns [`Some`] containing + /// the [`Instant`] the peer will be unbanned + /// - If the peer is not banned, this returns [`None`] + fn peer_unban_instant(&self, peer: &Z::Addr) -> Option { + self.banned_peers.get(&peer.ban_id()).copied() + } + fn handle_incoming_peer_list( &mut self, mut peer_list: Vec>, @@ -239,10 +247,10 @@ impl AddressBook { peer_list.retain_mut(|peer| { peer.adr.make_canonical(); - if !peer.adr.should_add_to_peer_list() { - false - } else { + if peer.adr.should_add_to_peer_list() { !self.is_peer_banned(&peer.adr) + } else { + false } // TODO: check rpc/ p2p ports not the same }); @@ -257,7 +265,7 @@ impl AddressBook { fn take_random_white_peer( &mut self, - block_needed: Option, + block_needed: Option, ) -> Option> { tracing::debug!("Retrieving random white peer"); self.white_list @@ -266,7 +274,7 @@ impl AddressBook { fn take_random_gray_peer( &mut self, - block_needed: Option, + block_needed: Option, ) -> Option> { tracing::debug!("Retrieving random gray peer"); self.gray_list @@ -351,7 +359,7 @@ impl AddressBook { } } -impl Service> for AddressBook { +impl Service> for AddressBook { type Response = AddressBookResponse; type Error = AddressBookError; type Future = Ready>; @@ -388,7 +396,7 @@ impl Service> for AddressBook { rpc_credits_per_hash, }, ) - .map(|_| AddressBookResponse::Ok), + .map(|()| AddressBookResponse::Ok), AddressBookRequest::IncomingPeerList(peer_list) => { self.handle_incoming_peer_list(peer_list); Ok(AddressBookResponse::Ok) @@ -409,9 +417,16 @@ impl Service> for AddressBook { AddressBookRequest::GetWhitePeers(len) => { Ok(AddressBookResponse::Peers(self.get_white_peers(len))) } - AddressBookRequest::IsPeerBanned(addr) => Ok(AddressBookResponse::IsPeerBanned( - self.is_peer_banned(&addr), - )), + AddressBookRequest::GetBan(addr) => Ok(AddressBookResponse::GetBan { + unban_instant: self.peer_unban_instant(&addr).map(Instant::into_std), + }), + AddressBookRequest::PeerlistSize + | AddressBookRequest::ConnectionCount + | AddressBookRequest::SetBan(_) + | AddressBookRequest::GetBans + | AddressBookRequest::ConnectionInfo => { + todo!("finish https://github.com/Cuprate/cuprate/pull/297") + } }; ready(response) diff --git a/p2p/address-book/src/book/tests.rs b/p2p/address-book/src/book/tests.rs index 11f3186..b2c4c49 100644 --- a/p2p/address-book/src/book/tests.rs +++ b/p2p/address-book/src/book/tests.rs @@ -1,7 +1,7 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{path::PathBuf, time::Duration}; use futures::StreamExt; -use tokio::{sync::Semaphore, time::interval}; +use tokio::time::interval; use cuprate_p2p_core::handles::HandleBuilder; use cuprate_pruning::PruningSeed; @@ -15,15 +15,12 @@ fn test_cfg() -> AddressBookConfig { AddressBookConfig { max_white_list_length: 100, max_gray_list_length: 500, - peer_store_file: PathBuf::new(), + peer_store_directory: PathBuf::new(), peer_save_period: Duration::from_secs(60), } } -fn make_fake_address_book( - numb_white: u32, - numb_gray: u32, -) -> AddressBook> { +fn make_fake_address_book(numb_white: u32, numb_gray: u32) -> AddressBook> { let white_list = make_fake_peer_list(0, numb_white); let gray_list = make_fake_peer_list(numb_white, numb_gray); @@ -78,11 +75,7 @@ async fn get_white_peers() { async fn add_new_peer_already_connected() { let mut address_book = make_fake_address_book(0, 0); - let semaphore = Arc::new(Semaphore::new(10)); - - let (_, handle) = HandleBuilder::default() - .with_permit(semaphore.clone().try_acquire_owned().unwrap()) - .build(); + let (_, handle) = HandleBuilder::default().build(); address_book .handle_new_connection( @@ -98,9 +91,7 @@ async fn add_new_peer_already_connected() { ) .unwrap(); - let (_, handle) = HandleBuilder::default() - .with_permit(semaphore.try_acquire_owned().unwrap()) - .build(); + let (_, handle) = HandleBuilder::default().build(); assert_eq!( address_book.handle_new_connection( @@ -115,7 +106,7 @@ async fn add_new_peer_already_connected() { }, ), Err(AddressBookError::PeerAlreadyConnected) - ) + ); } #[tokio::test] @@ -149,5 +140,5 @@ async fn banned_peer_removed_from_peer_lists() { .unwrap() .into_inner(), TestNetZoneAddr(1) - ) + ); } diff --git a/p2p/address-book/src/lib.rs b/p2p/address-book/src/lib.rs index 1ce659f..054be46 100644 --- a/p2p/address-book/src/lib.rs +++ b/p2p/address-book/src/lib.rs @@ -10,10 +10,9 @@ //! clear net peers getting linked to their dark counterparts //! and so peers will only get told about peers they can //! connect to. -//! use std::{io::ErrorKind, path::PathBuf, time::Duration}; -use cuprate_p2p_core::NetworkZone; +use cuprate_p2p_core::{NetZoneAddress, NetworkZone}; mod book; mod peer_list; @@ -30,8 +29,8 @@ pub struct AddressBookConfig { /// /// Gray peers are peers we are yet to make a connection to. pub max_gray_list_length: usize, - /// The location to store the address book. - pub peer_store_file: PathBuf, + /// The location to store the peer store files. + pub peer_store_directory: PathBuf, /// The amount of time between saving the address book to disk. pub peer_save_period: Duration, } @@ -61,14 +60,9 @@ pub enum AddressBookError { } /// Initializes the P2P address book for a specific network zone. -pub async fn init_address_book( +pub async fn init_address_book( cfg: AddressBookConfig, ) -> Result, std::io::Error> { - tracing::info!( - "Loading peers from file: {} ", - cfg.peer_store_file.display() - ); - let (white_list, gray_list) = match store::read_peers_from_disk::(&cfg).await { Ok(res) => res, Err(e) if e.kind() == ErrorKind::NotFound => (vec![], vec![]), @@ -82,3 +76,21 @@ pub async fn init_address_book( Ok(address_book) } + +use sealed::BorshNetworkZone; +mod sealed { + use super::*; + + /// An internal trait for the address book for a [`NetworkZone`] that adds the requirement of [`borsh`] traits + /// onto the network address. + pub trait BorshNetworkZone: NetworkZone { + type BorshAddr: NetZoneAddress + borsh::BorshDeserialize + borsh::BorshSerialize; + } + + impl BorshNetworkZone for T + where + T::Addr: borsh::BorshDeserialize + borsh::BorshSerialize, + { + type BorshAddr = T::Addr; + } +} diff --git a/p2p/address-book/src/peer_list.rs b/p2p/address-book/src/peer_list.rs index e2a15d8..fdaf336 100644 --- a/p2p/address-book/src/peer_list.rs +++ b/p2p/address-book/src/peer_list.rs @@ -3,35 +3,36 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use indexmap::IndexMap; use rand::prelude::*; +use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone}; -use cuprate_pruning::{PruningSeed, CRYPTONOTE_MAX_BLOCK_HEIGHT}; +use cuprate_pruning::PruningSeed; #[cfg(test)] -pub mod tests; +pub(crate) mod tests; /// A Peer list in the address book. /// /// This could either be the white list or gray list. #[derive(Debug)] -pub struct PeerList { +pub(crate) struct PeerList { /// The peers with their peer data. pub peers: IndexMap>, /// An index of Pruning seed to address, so can quickly grab peers with the blocks /// we want. /// - /// Pruning seeds are sorted by first their log_stripes and then their stripe. + /// Pruning seeds are sorted by first their `log_stripes` and then their stripe. /// This means the first peers in this list will store more blocks than peers /// later on. So when we need a peer with a certain block we look at the peers /// storing more blocks first then work our way to the peers storing less. /// pruning_seeds: BTreeMap>, - /// A hashmap linking ban_ids to addresses. + /// A hashmap linking `ban_ids` to addresses. ban_ids: HashMap<::BanID, Vec>, } impl PeerList { /// Creates a new peer list. - pub fn new(list: Vec>) -> PeerList { + pub(crate) fn new(list: Vec>) -> Self { let mut peers = IndexMap::with_capacity(list.len()); let mut pruning_seeds = BTreeMap::new(); let mut ban_ids = HashMap::with_capacity(list.len()); @@ -49,7 +50,7 @@ impl PeerList { peers.insert(peer.adr, peer); } - PeerList { + Self { peers, pruning_seeds, ban_ids, @@ -57,21 +58,20 @@ impl PeerList { } /// Gets the length of the peer list - pub fn len(&self) -> usize { + pub(crate) fn len(&self) -> usize { self.peers.len() } /// Adds a new peer to the peer list - pub fn add_new_peer(&mut self, peer: ZoneSpecificPeerListEntryBase) { + pub(crate) fn add_new_peer(&mut self, peer: ZoneSpecificPeerListEntryBase) { if self.peers.insert(peer.adr, peer).is_none() { - // It's more clear with this - #[allow(clippy::unwrap_or_default)] + #[expect(clippy::unwrap_or_default, reason = "It's more clear with this")] self.pruning_seeds .entry(peer.pruning_seed) .or_insert_with(Vec::new) .push(peer.adr); - #[allow(clippy::unwrap_or_default)] + #[expect(clippy::unwrap_or_default)] self.ban_ids .entry(peer.adr.ban_id()) .or_insert_with(Vec::new) @@ -85,10 +85,10 @@ impl PeerList { /// list. /// /// The given peer will be removed from the peer list. - pub fn take_random_peer( + pub(crate) fn take_random_peer( &mut self, r: &mut R, - block_needed: Option, + block_needed: Option, must_keep_peers: &HashSet, ) -> Option> { // Take a random peer and see if it's in the list of must_keep_peers, if it is try again. @@ -98,7 +98,7 @@ impl PeerList { if let Some(needed_height) = block_needed { let (_, addresses_with_block) = self.pruning_seeds.iter().find(|(seed, _)| { // TODO: factor in peer blockchain height? - seed.get_next_unpruned_block(needed_height, CRYPTONOTE_MAX_BLOCK_HEIGHT) + seed.get_next_unpruned_block(needed_height, MAX_BLOCK_HEIGHT_USIZE) .expect("Block needed is higher than max block allowed.") == needed_height })?; @@ -127,7 +127,7 @@ impl PeerList { None } - pub fn get_random_peers( + pub(crate) fn get_random_peers( &self, r: &mut R, len: usize, @@ -142,7 +142,7 @@ impl PeerList { } /// Returns a mutable reference to a peer. - pub fn get_peer_mut( + pub(crate) fn get_peer_mut( &mut self, peer: &Z::Addr, ) -> Option<&mut ZoneSpecificPeerListEntryBase> { @@ -150,7 +150,7 @@ impl PeerList { } /// Returns true if the list contains this peer. - pub fn contains_peer(&self, peer: &Z::Addr) -> bool { + pub(crate) fn contains_peer(&self, peer: &Z::Addr) -> bool { self.peers.contains_key(peer) } @@ -189,11 +189,11 @@ impl PeerList { /// MUST NOT BE USED ALONE fn remove_peer_from_all_idxs(&mut self, peer: &ZoneSpecificPeerListEntryBase) { self.remove_peer_pruning_idx(peer); - self.remove_peer_ban_idx(peer) + self.remove_peer_ban_idx(peer); } /// Removes a peer from the peer list - pub fn remove_peer( + pub(crate) fn remove_peer( &mut self, peer: &Z::Addr, ) -> Option> { @@ -203,7 +203,7 @@ impl PeerList { } /// Removes all peers with a specific ban id. - pub fn remove_peers_with_ban_id(&mut self, ban_id: &::BanID) { + pub(crate) fn remove_peers_with_ban_id(&mut self, ban_id: &::BanID) { let Some(addresses) = self.ban_ids.get(ban_id) else { // No peers to ban return; @@ -217,8 +217,8 @@ impl PeerList { /// Tries to reduce the peer list to `new_len`. /// /// This function could keep the list bigger than `new_len` if `must_keep_peers`s length - /// is larger than new_len, in that case we will remove as much as we can. - pub fn reduce_list(&mut self, must_keep_peers: &HashSet, new_len: usize) { + /// is larger than `new_len`, in that case we will remove as much as we can. + pub(crate) fn reduce_list(&mut self, must_keep_peers: &HashSet, new_len: usize) { if new_len >= self.len() { return; } diff --git a/p2p/address-book/src/peer_list/tests.rs b/p2p/address-book/src/peer_list/tests.rs index 8d2d220..352e305 100644 --- a/p2p/address-book/src/peer_list/tests.rs +++ b/p2p/address-book/src/peer_list/tests.rs @@ -14,7 +14,7 @@ fn make_fake_peer( ) -> ZoneSpecificPeerListEntryBase { ZoneSpecificPeerListEntryBase { adr: TestNetZoneAddr(id), - id: id as u64, + id: u64::from(id), last_seen: 0, pruning_seed: PruningSeed::decompress(pruning_seed.unwrap_or(0)).unwrap(), rpc_port: 0, @@ -22,22 +22,20 @@ fn make_fake_peer( } } -pub fn make_fake_peer_list( +pub(crate) fn make_fake_peer_list( start_idx: u32, numb_o_peers: u32, -) -> PeerList> { +) -> PeerList> { let mut peer_list = Vec::with_capacity(numb_o_peers as usize); for idx in start_idx..(start_idx + numb_o_peers) { - peer_list.push(make_fake_peer(idx, None)) + peer_list.push(make_fake_peer(idx, None)); } PeerList::new(peer_list) } -fn make_fake_peer_list_with_random_pruning_seeds( - numb_o_peers: u32, -) -> PeerList> { +fn make_fake_peer_list_with_random_pruning_seeds(numb_o_peers: u32) -> PeerList> { let mut r = rand::thread_rng(); let mut peer_list = Vec::with_capacity(numb_o_peers as usize); @@ -50,7 +48,7 @@ fn make_fake_peer_list_with_random_pruning_seeds( } else { r.gen_range(384..=391) }), - )) + )); } PeerList::new(peer_list) } @@ -70,7 +68,7 @@ fn peer_list_reduce_length() { #[test] fn peer_list_reduce_length_with_peers_we_need() { let mut peer_list = make_fake_peer_list(0, 500); - let must_keep_peers = HashSet::from_iter(peer_list.peers.keys().copied()); + let must_keep_peers = peer_list.peers.keys().copied().collect::>(); let target_len = 49; @@ -92,7 +90,7 @@ fn peer_list_remove_specific_peer() { let peers = peer_list.peers; for (_, addrs) in pruning_idxs { - addrs.iter().for_each(|adr| assert_ne!(adr, &peer.adr)) + addrs.iter().for_each(|adr| assert_ne!(adr, &peer.adr)); } assert!(!peers.contains_key(&peer.adr)); @@ -104,13 +102,13 @@ fn peer_list_pruning_idxs_are_correct() { let mut total_len = 0; for (seed, list) in peer_list.pruning_seeds { - for peer in list.iter() { + for peer in &list { assert_eq!(peer_list.peers.get(peer).unwrap().pruning_seed, seed); total_len += 1; } } - assert_eq!(total_len, peer_list.peers.len()) + assert_eq!(total_len, peer_list.peers.len()); } #[test] @@ -122,11 +120,7 @@ fn peer_list_add_new_peer() { assert_eq!(peer_list.len(), 11); assert_eq!(peer_list.peers.get(&new_peer.adr), Some(&new_peer)); - assert!(peer_list - .pruning_seeds - .get(&new_peer.pruning_seed) - .unwrap() - .contains(&new_peer.adr)); + assert!(peer_list.pruning_seeds[&new_peer.pruning_seed].contains(&new_peer.adr)); } #[test] @@ -164,7 +158,7 @@ fn peer_list_get_peer_with_block() { assert!(peer .pruning_seed .get_next_unpruned_block(1, 1_000_000) - .is_ok()) + .is_ok()); } #[test] diff --git a/p2p/address-book/src/store.rs b/p2p/address-book/src/store.rs index 94b0ec2..47994ae 100644 --- a/p2p/address-book/src/store.rs +++ b/p2p/address-book/src/store.rs @@ -1,11 +1,16 @@ +#![expect( + single_use_lifetimes, + reason = "false positive on generated derive code on `SerPeerDataV1`" +)] + use std::fs; use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize}; use tokio::task::{spawn_blocking, JoinHandle}; -use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress, NetworkZone}; +use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress}; -use crate::{peer_list::PeerList, AddressBookConfig}; +use crate::{peer_list::PeerList, AddressBookConfig, BorshNetworkZone}; // TODO: store anchor and ban list. @@ -21,7 +26,7 @@ struct DeserPeerDataV1 { gray_list: Vec>, } -pub fn save_peers_to_disk( +pub(crate) fn save_peers_to_disk( cfg: &AddressBookConfig, white_list: &PeerList, gray_list: &PeerList, @@ -34,11 +39,13 @@ pub fn save_peers_to_disk( }) .unwrap(); - let file = cfg.peer_store_file.clone(); + let file = cfg + .peer_store_directory + .join(format!("{}_p2p_state", Z::NAME)); spawn_blocking(move || fs::write(&file, &data)) } -pub async fn read_peers_from_disk( +pub(crate) async fn read_peers_from_disk( cfg: &AddressBookConfig, ) -> Result< ( @@ -47,7 +54,12 @@ pub async fn read_peers_from_disk( ), std::io::Error, > { - let file = cfg.peer_store_file.clone(); + let file = cfg + .peer_store_directory + .join(format!("{}_p2p_state", Z::NAME)); + + tracing::info!("Loading peers from file: {} ", file.display()); + let data = spawn_blocking(move || fs::read(file)).await.unwrap()?; let de_ser: DeserPeerDataV1 = from_slice(&data)?; @@ -74,9 +86,8 @@ mod tests { let de_ser: DeserPeerDataV1 = from_slice(&data).unwrap(); - let white_list_2: PeerList> = - PeerList::new(de_ser.white_list); - let gray_list_2: PeerList> = PeerList::new(de_ser.gray_list); + let white_list_2: PeerList> = PeerList::new(de_ser.white_list); + let gray_list_2: PeerList> = PeerList::new(de_ser.gray_list); assert_eq!(white_list.peers.len(), white_list_2.peers.len()); assert_eq!(gray_list.peers.len(), gray_list_2.peers.len()); diff --git a/p2p/async-buffer/src/lib.rs b/p2p/async-buffer/src/lib.rs index 0e2ced2..8174481 100644 --- a/p2p/async-buffer/src/lib.rs +++ b/p2p/async-buffer/src/lib.rs @@ -157,7 +157,7 @@ pub struct BufferSinkSend<'a, T> { item: Option, } -impl<'a, T> Future for BufferSinkSend<'a, T> { +impl Future for BufferSinkSend<'_, T> { type Output = Result<(), BufferError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { @@ -183,7 +183,7 @@ pub struct BufferSinkReady<'a, T> { size_needed: usize, } -impl<'a, T> Future for BufferSinkReady<'a, T> { +impl Future for BufferSinkReady<'_, T> { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/p2p/bucket/Cargo.toml b/p2p/bucket/Cargo.toml new file mode 100644 index 0000000..1a53e85 --- /dev/null +++ b/p2p/bucket/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cuprate-p2p-bucket" +version = "0.1.0" +edition = "2021" +license = "MIT" +authors = ["SyntheticBird"] + +[dependencies] +arrayvec = { workspace = true } +rand = { workspace = true, features = ["std", "std_rng"]} + +[lints] +workspace = true diff --git a/p2p/bucket/src/lib.rs b/p2p/bucket/src/lib.rs new file mode 100644 index 0000000..0f73eea --- /dev/null +++ b/p2p/bucket/src/lib.rs @@ -0,0 +1,172 @@ +//! Bucket data structure +//! +//! A collection data structure that discriminates its unique items and place them into "buckets". +//! +//! The item must implement the [`Bucketable`] trait that defines how to create the discriminant +//! from the item type. The data structure will internally contain any item into "buckets" or vectors +//! of sized capacity `N` that regroup all the stored items with this specific discriminant. +//! +//! A practical example of this data structure is for storing `N` amount of IP discriminated by their subnets. +//! You can store in each "buckets" corresponding to a `/16` subnet up to `N` IPs of that subnet. +//! +//! # Example +//! +//! ``` +//! use cuprate_p2p_bucket::Bucket; +//! use std::net::Ipv4Addr; +//! +//! // Create a new bucket that can store at most 2 IPs in a particular `/16` subnet. +//! let mut bucket = Bucket::<2,Ipv4Addr>::new(); +//! +//! // Fulfill the `96.96.0.0/16` bucket. +//! bucket.push("96.96.0.1".parse().unwrap()); +//! bucket.push("96.96.0.2".parse().unwrap()); +//! assert_eq!(2, bucket.len()); +//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap()); +//! +//! // Push a new IP from another subnet +//! bucket.push("127.0.0.1".parse().unwrap()); +//! assert_eq!(3, bucket.len()); +//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap()); +//! assert_eq!(1, bucket.len_bucket(&[127_u8,0_u8]).unwrap()); +//! +//! // Attempting to push a new IP within `96.96.0.0/16` bucket will return the IP back +//! // as this subnet is already full. +//! let pushed = bucket.push("96.96.0.3".parse().unwrap()); +//! assert!(pushed.is_some()); +//! assert_eq!(2, bucket.len_bucket(&[96_u8,96_u8]).unwrap()); +//! +//! ``` + +use arrayvec::{ArrayVec, CapacityError}; +use rand::random; + +use std::{collections::BTreeMap, net::Ipv4Addr}; + +/// A discriminant that can be computed from the type. +pub trait Bucketable: Sized + Eq + Clone { + /// The type of the discriminant being used in the Binary tree. + type Discriminant: Ord + AsRef<[u8]>; + + /// Method that can compute the discriminant from the item. + fn discriminant(&self) -> Self::Discriminant; +} + +/// A collection data structure discriminating its unique items +/// with a specified method. Limiting the amount of items stored +/// with that discriminant to the const `N`. +pub struct Bucket { + /// The storage of the bucket + storage: BTreeMap>, +} + +impl Bucket { + /// Create a new Bucket + pub const fn new() -> Self { + Self { + storage: BTreeMap::new(), + } + } + + /// Push a new element into the Bucket + /// + /// Will internally create a new vector for each new discriminant being + /// generated from an item. + /// + /// This function WILL NOT push the element if it already exists. + /// + /// Return `None` if the item has been pushed or ignored. `Some(I)` if + /// the vector is full. + /// + /// # Example + /// + /// ``` + /// use cuprate_p2p_bucket::Bucket; + /// use std::net::Ipv4Addr; + /// + /// let mut bucket = Bucket::<8,Ipv4Addr>::new(); + /// + /// // Push a first IP address. + /// bucket.push("127.0.0.1".parse().unwrap()); + /// assert_eq!(1, bucket.len()); + /// + /// // Push the same IP address a second time. + /// bucket.push("127.0.0.1".parse().unwrap()); + /// assert_eq!(1, bucket.len()); + /// ``` + pub fn push(&mut self, item: I) -> Option { + let discriminant = item.discriminant(); + + if let Some(vec) = self.storage.get_mut(&discriminant) { + // Push the item if it doesn't exist. + if !vec.contains(&item) { + return vec.try_push(item).err().map(CapacityError::element); + } + } else { + // Initialize the vector if not found. + let mut vec = ArrayVec::::new(); + vec.push(item); + self.storage.insert(discriminant, vec); + } + + None + } + + /// Will attempt to remove an item from the bucket. + pub fn remove(&mut self, item: &I) -> Option { + self.storage.get_mut(&item.discriminant()).and_then(|vec| { + vec.iter() + .enumerate() + .find_map(|(i, v)| (item == v).then_some(i)) + .map(|index| vec.swap_remove(index)) + }) + } + + /// Return the number of item stored within the storage + pub fn len(&self) -> usize { + self.storage.values().map(ArrayVec::len).sum() + } + + /// Return the number of item stored with a specific discriminant. + /// + /// This method returns None if the bucket with this discriminant + /// doesn't exist. + pub fn len_bucket(&self, discriminant: &I::Discriminant) -> Option { + self.storage.get(discriminant).map(ArrayVec::len) + } + + /// Return `true` if the storage contains no items + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Return a reference to an item chosen at random. + /// + /// Repeated use of this function will provide a normal distribution of + /// items based on their discriminants. + pub fn get_random(&mut self) -> Option<&I> { + // Get the total amount of discriminants to explore. + let len = self.storage.len(); + + // Get a random bucket. + let (_, vec) = self.storage.iter().nth(random::() / len).unwrap(); + + // Return a reference chose at random. + vec.get(random::() / vec.len()) + } +} + +impl Default for Bucket { + fn default() -> Self { + Self::new() + } +} + +impl Bucketable for Ipv4Addr { + /// We are discriminating by `/16` subnets. + type Discriminant = [u8; 2]; + + fn discriminant(&self) -> Self::Discriminant { + [self.octets()[0], self.octets()[1]] + } +} diff --git a/p2p/dandelion-tower/Cargo.toml b/p2p/dandelion-tower/Cargo.toml index 976dad6..92e4915 100644 --- a/p2p/dandelion-tower/Cargo.toml +++ b/p2p/dandelion-tower/Cargo.toml @@ -24,4 +24,7 @@ thiserror = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync"] } -proptest = { workspace = true, features = ["default"] } \ No newline at end of file +proptest = { workspace = true, features = ["default"] } + +[lints] +workspace = true \ No newline at end of file diff --git a/p2p/dandelion-tower/src/config.rs b/p2p/dandelion-tower/src/config.rs index 71a4e5b..46c780a 100644 --- a/p2p/dandelion-tower/src/config.rs +++ b/p2p/dandelion-tower/src/config.rs @@ -8,7 +8,7 @@ use std::{ /// (1 - ep) is the probability that a transaction travels for `k` hops before a nodes embargo timeout fires, this constant is (1 - ep). const EMBARGO_FULL_TRAVEL_PROBABILITY: f64 = 0.90; -/// The graph type to use for dandelion routing, the dandelion paper recommends [Graph::FourRegular]. +/// The graph type to use for dandelion routing, the dandelion paper recommends [`Graph::FourRegular`]. /// /// The decision between line graphs and 4-regular graphs depend on the priorities of the system, if /// linkability of transactions is a first order concern then line graphs may be better, however 4-regular graphs @@ -42,7 +42,6 @@ pub enum Graph { /// `(-k*(k-1)*hop)/(2*log(1-ep))` /// /// Where `k` is calculated from the fluff probability, `hop` is `time_between_hop` and `ep` is fixed at `0.1`. -/// #[derive(Debug, Clone, Copy)] pub struct DandelionConfig { /// The time it takes for a stem transaction to pass through a node, including network latency. @@ -67,7 +66,7 @@ impl DandelionConfig { /// Returns the number of outbound peers to use to stem transactions. /// /// This value depends on the [`Graph`] chosen. - pub fn number_of_stems(&self) -> usize { + pub const fn number_of_stems(&self) -> usize { match self.graph { Graph::Line => 1, Graph::FourRegular => 2, diff --git a/p2p/dandelion-tower/src/lib.rs b/p2p/dandelion-tower/src/lib.rs index aa622f3..2c8de71 100644 --- a/p2p/dandelion-tower/src/lib.rs +++ b/p2p/dandelion-tower/src/lib.rs @@ -2,17 +2,17 @@ //! //! This crate implements [dandelion++](https://arxiv.org/pdf/1805.11060.pdf), using [`tower`]. //! -//! This crate provides 2 [`tower::Service`]s, a [`DandelionRouter`] and a [`DandelionPool`](pool::DandelionPool). +//! This crate provides 2 [`tower::Service`]s, a [`DandelionRouter`] and a [`DandelionPoolManager`](pool::DandelionPoolManager). //! The router is pretty minimal and only handles the absolute necessary data to route transactions, whereas the //! pool keeps track of all data necessary for dandelion++ but requires you to provide a backing tx-pool. //! -//! This split was done not because the [`DandelionPool`](pool::DandelionPool) is unnecessary but because it is hard -//! to cover a wide range of projects when abstracting over the tx-pool. Not using the [`DandelionPool`](pool::DandelionPool) +//! This split was done not because the [`DandelionPoolManager`](pool::DandelionPoolManager) is unnecessary but because it is hard +//! to cover a wide range of projects when abstracting over the tx-pool. Not using the [`DandelionPoolManager`](pool::DandelionPoolManager) //! requires you to implement part of the paper yourself. //! //! # Features //! -//! This crate only has one feature `txpool` which enables [`DandelionPool`](pool::DandelionPool). +//! This crate only has one feature `txpool` which enables [`DandelionPoolManager`](pool::DandelionPoolManager). //! //! # Needed Services //! @@ -26,7 +26,7 @@ //! The diffuse service should have a request of [`DiffuseRequest`](traits::DiffuseRequest) and it's error //! should be [`tower::BoxError`]. //! -//! ## Outbound Peer TryStream +//! ## Outbound Peer `TryStream` //! //! The outbound peer [`TryStream`](futures::TryStream) should provide a stream of randomly selected outbound //! peers, these peers will then be used to route stem txs to. @@ -37,7 +37,7 @@ //! ## Peer Service //! //! This service represents a connection to an individual peer, this should be returned from the Outbound Peer -//! TryStream. This should immediately send the transaction to the peer when requested, it should _not_ set +//! `TryStream`. This should immediately send the transaction to the peer when requested, it should _not_ set //! a timer. //! //! The peer service should have a request of [`StemRequest`](traits::StemRequest) and its error @@ -45,7 +45,7 @@ //! //! ## Backing Pool //! -//! ([`DandelionPool`](pool::DandelionPool) only) +//! ([`DandelionPoolManager`](pool::DandelionPoolManager) only) //! //! This service is a backing tx-pool, in memory or on disk. //! The backing pool should have a request of [`TxStoreRequest`](traits::TxStoreRequest) and a response of diff --git a/p2p/dandelion-tower/src/pool.rs b/p2p/dandelion-tower/src/pool.rs deleted file mode 100644 index eddcc67..0000000 --- a/p2p/dandelion-tower/src/pool.rs +++ /dev/null @@ -1,510 +0,0 @@ -//! # Dandelion++ Pool -//! -//! This module contains [`DandelionPool`] which is a thin wrapper around a backing transaction store, -//! which fully implements the dandelion++ protocol. -//! -//! ### How To Get Txs From [`DandelionPool`]. -//! -//! [`DandelionPool`] does not provide a full tx-pool API. You cannot retrieve transactions from it or -//! check what transactions are in it, to do this you must keep a handle to the backing transaction store -//! yourself. -//! -//! The reason for this is, the [`DandelionPool`] will only itself be passing these requests onto the backing -//! pool, so it makes sense to remove the "middle man". -//! -//! ### Keep Stem Transactions Hidden -//! -//! When using your handle to the backing store it must be remembered to keep transactions in the stem pool hidden. -//! So handle any requests to the tx-pool like the stem side of the pool does not exist. -//! -use std::{ - collections::{HashMap, HashSet}, - future::Future, - hash::Hash, - marker::PhantomData, - pin::Pin, - task::{Context, Poll}, - time::Duration, -}; - -use futures::{FutureExt, StreamExt}; -use rand::prelude::*; -use rand_distr::Exp; -use tokio::{ - sync::{mpsc, oneshot}, - task::JoinSet, -}; -use tokio_util::{sync::PollSender, time::DelayQueue}; -use tower::{Service, ServiceExt}; -use tracing::Instrument; - -use crate::{ - traits::{TxStoreRequest, TxStoreResponse}, - DandelionConfig, DandelionRouteReq, DandelionRouterError, State, TxState, -}; - -/// Start the [`DandelionPool`]. -/// -/// This function spawns the [`DandelionPool`] and returns [`DandelionPoolService`] which can be used to send -/// requests to the pool. -/// -/// ### Args -/// -/// - `buffer_size` is the size of the channel's buffer between the [`DandelionPoolService`] and [`DandelionPool`]. -/// - `dandelion_router` is the router service, kept generic instead of [`DandelionRouter`](crate::DandelionRouter) to allow -/// user to customise routing functionality. -/// - `backing_pool` is the backing transaction storage service -/// - `config` is [`DandelionConfig`]. -pub fn start_dandelion_pool( - buffer_size: usize, - dandelion_router: R, - backing_pool: P, - config: DandelionConfig, -) -> DandelionPoolService -where - Tx: Clone + Send + 'static, - TxID: Hash + Eq + Clone + Send + 'static, - PID: Hash + Eq + Clone + Send + 'static, - P: Service< - TxStoreRequest, - Response = TxStoreResponse, - Error = tower::BoxError, - > + Send - + 'static, - P::Future: Send + 'static, - R: Service, Response = State, Error = DandelionRouterError> - + Send - + 'static, - R::Future: Send + 'static, -{ - let (tx, rx) = mpsc::channel(buffer_size); - - let pool = DandelionPool { - dandelion_router, - backing_pool, - routing_set: JoinSet::new(), - stem_origins: HashMap::new(), - embargo_timers: DelayQueue::new(), - embargo_dist: Exp::new(1.0 / config.average_embargo_timeout().as_secs_f64()).unwrap(), - config, - _tx: PhantomData, - }; - - let span = tracing::debug_span!("dandelion_pool"); - - tokio::spawn(pool.run(rx).instrument(span)); - - DandelionPoolService { - tx: PollSender::new(tx), - } -} - -#[derive(Copy, Clone, Debug, thiserror::Error)] -#[error("The dandelion pool was shutdown")] -pub struct DandelionPoolShutDown; - -/// An incoming transaction for the [`DandelionPool`] to handle. -/// -/// Users may notice there is no way to check if the dandelion-pool wants a tx according to an inventory message like seen -/// in Bitcoin, only having a request for a full tx. Users should look in the *public* backing pool to handle inv messages, -/// and request txs even if they are in the stem pool. -pub struct IncomingTx { - /// The transaction. - /// - /// It is recommended to put this in an [`Arc`](std::sync::Arc) as it needs to be cloned to send to the backing - /// tx pool and [`DandelionRouter`](crate::DandelionRouter) - pub tx: Tx, - /// The transaction ID. - pub tx_id: TxID, - /// The routing state of this transaction. - pub tx_state: TxState, -} - -/// The dandelion tx pool service. -#[derive(Clone)] -pub struct DandelionPoolService { - /// The channel to [`DandelionPool`]. - tx: PollSender<(IncomingTx, oneshot::Sender<()>)>, -} - -impl Service> for DandelionPoolService -where - Tx: Clone + Send, - TxID: Hash + Eq + Clone + Send + 'static, - PID: Hash + Eq + Clone + Send + 'static, -{ - type Response = (); - type Error = DandelionPoolShutDown; - type Future = - Pin> + Send + 'static>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.tx.poll_reserve(cx).map_err(|_| DandelionPoolShutDown) - } - - fn call(&mut self, req: IncomingTx) -> Self::Future { - // although the channel isn't sending anything we want to wait for the request to be handled before continuing. - let (tx, rx) = oneshot::channel(); - - let res = self - .tx - .send_item((req, tx)) - .map_err(|_| DandelionPoolShutDown); - - async move { - res?; - rx.await.expect("Oneshot dropped before response!"); - - Ok(()) - } - .boxed() - } -} - -/// The dandelion++ tx pool. -/// -/// See the [module docs](self) for more. -pub struct DandelionPool { - /// The dandelion++ router - dandelion_router: R, - /// The backing tx storage. - backing_pool: P, - /// The set of tasks that are running the future returned from `dandelion_router`. - routing_set: JoinSet<(TxID, Result>)>, - - /// The origin of stem transactions. - stem_origins: HashMap>, - - /// Current stem pool embargo timers. - embargo_timers: DelayQueue, - /// The distrobution to sample to get embargo timers. - embargo_dist: Exp, - - /// The d++ config. - config: DandelionConfig, - - _tx: PhantomData, -} - -impl DandelionPool -where - Tx: Clone + Send, - TxID: Hash + Eq + Clone + Send + 'static, - PID: Hash + Eq + Clone + Send + 'static, - P: Service< - TxStoreRequest, - Response = TxStoreResponse, - Error = tower::BoxError, - >, - P::Future: Send + 'static, - R: Service, Response = State, Error = DandelionRouterError>, - R::Future: Send + 'static, -{ - /// Stores the tx in the backing pools stem pool, setting the embargo timer, stem origin and steming the tx. - async fn store_tx_and_stem( - &mut self, - tx: Tx, - tx_id: TxID, - from: Option, - ) -> Result<(), tower::BoxError> { - self.backing_pool - .ready() - .await? - .call(TxStoreRequest::Store( - tx.clone(), - tx_id.clone(), - State::Stem, - )) - .await?; - - let embargo_timer = self.embargo_dist.sample(&mut thread_rng()); - tracing::debug!( - "Setting embargo timer for stem tx: {} seconds.", - embargo_timer - ); - self.embargo_timers - .insert(tx_id.clone(), Duration::from_secs_f64(embargo_timer)); - - self.stem_tx(tx, tx_id, from).await - } - - /// Stems the tx, setting the stem origin, if it wasn't already set. - /// - /// This function does not add the tx to the backing pool. - async fn stem_tx( - &mut self, - tx: Tx, - tx_id: TxID, - from: Option, - ) -> Result<(), tower::BoxError> { - if let Some(peer) = &from { - self.stem_origins - .entry(tx_id.clone()) - .or_default() - .insert(peer.clone()); - } - - let state = from - .map(|from| TxState::Stem { from }) - .unwrap_or(TxState::Local); - - let fut = self - .dandelion_router - .ready() - .await? - .call(DandelionRouteReq { - tx, - state: state.clone(), - }); - - self.routing_set - .spawn(fut.map(|res| (tx_id, res.map_err(|_| state)))); - Ok(()) - } - - /// Stores the tx in the backing pool and fluffs the tx, removing the stem data for this tx. - async fn store_and_fluff_tx(&mut self, tx: Tx, tx_id: TxID) -> Result<(), tower::BoxError> { - // fluffs the tx first to prevent timing attacks where we could fluff at different average times - // depending on if the tx was in the stem pool already or not. - // Massively overkill but this is a minimal change. - self.fluff_tx(tx.clone(), tx_id.clone()).await?; - - // Remove the tx from the maps used during the stem phase. - self.stem_origins.remove(&tx_id); - - self.backing_pool - .ready() - .await? - .call(TxStoreRequest::Store(tx, tx_id, State::Fluff)) - .await?; - - // The key for this is *Not* the tx_id, it is given on insert, so just keep the timer in the - // map. These timers should be relatively short, so it shouldn't be a problem. - //self.embargo_timers.try_remove(&tx_id); - - Ok(()) - } - - /// Fluffs a tx, does not add the tx to the tx pool. - async fn fluff_tx(&mut self, tx: Tx, tx_id: TxID) -> Result<(), tower::BoxError> { - let fut = self - .dandelion_router - .ready() - .await? - .call(DandelionRouteReq { - tx, - state: TxState::Fluff, - }); - - self.routing_set - .spawn(fut.map(|res| (tx_id, res.map_err(|_| TxState::Fluff)))); - Ok(()) - } - - /// Function to handle an incoming [`DandelionPoolRequest::IncomingTx`]. - async fn handle_incoming_tx( - &mut self, - tx: Tx, - tx_state: TxState, - tx_id: TxID, - ) -> Result<(), tower::BoxError> { - let TxStoreResponse::Contains(have_tx) = self - .backing_pool - .ready() - .await? - .call(TxStoreRequest::Contains(tx_id.clone())) - .await? - else { - panic!("Backing tx pool responded with wrong response for request."); - }; - // If we have already fluffed this tx then we don't need to do anything. - if have_tx == Some(State::Fluff) { - tracing::debug!("Already fluffed incoming tx, ignoring."); - return Ok(()); - } - - match tx_state { - TxState::Stem { from } => { - if self - .stem_origins - .get(&tx_id) - .is_some_and(|peers| peers.contains(&from)) - { - tracing::debug!("Received stem tx twice from same peer, fluffing it"); - // The same peer sent us a tx twice, fluff it. - self.promote_and_fluff_tx(tx_id).await - } else { - // This could be a new tx or it could have already been stemed, but we still stem it again - // unless the same peer sends us a tx twice. - tracing::debug!("Steming incoming tx"); - self.store_tx_and_stem(tx, tx_id, Some(from)).await - } - } - TxState::Fluff => { - tracing::debug!("Fluffing incoming tx"); - self.store_and_fluff_tx(tx, tx_id).await - } - TxState::Local => { - // If we have already stemed this tx then nothing to do. - if have_tx.is_some() { - tracing::debug!("Received a local tx that we already have, skipping"); - return Ok(()); - } - tracing::debug!("Steming local transaction"); - self.store_tx_and_stem(tx, tx_id, None).await - } - } - } - - /// Promotes a tx to the clear pool. - async fn promote_tx(&mut self, tx_id: TxID) -> Result<(), tower::BoxError> { - // Remove the tx from the maps used during the stem phase. - self.stem_origins.remove(&tx_id); - - // The key for this is *Not* the tx_id, it is given on insert, so just keep the timer in the - // map. These timers should be relatively short, so it shouldn't be a problem. - //self.embargo_timers.try_remove(&tx_id); - - self.backing_pool - .ready() - .await? - .call(TxStoreRequest::Promote(tx_id)) - .await?; - - Ok(()) - } - - /// Promotes a tx to the public fluff pool and fluffs the tx. - async fn promote_and_fluff_tx(&mut self, tx_id: TxID) -> Result<(), tower::BoxError> { - tracing::debug!("Promoting transaction to public pool and fluffing it."); - - let TxStoreResponse::Transaction(tx) = self - .backing_pool - .ready() - .await? - .call(TxStoreRequest::Get(tx_id.clone())) - .await? - else { - panic!("Backing tx pool responded with wrong response for request."); - }; - - let Some((tx, state)) = tx else { - tracing::debug!("Could not find tx, skipping."); - return Ok(()); - }; - - if state == State::Fluff { - tracing::debug!("Transaction already fluffed, skipping."); - return Ok(()); - } - - self.promote_tx(tx_id.clone()).await?; - self.fluff_tx(tx, tx_id).await - } - - /// Returns a tx stored in the fluff _OR_ stem pool. - async fn get_tx_from_pool(&mut self, tx_id: TxID) -> Result, tower::BoxError> { - let TxStoreResponse::Transaction(tx) = self - .backing_pool - .ready() - .await? - .call(TxStoreRequest::Get(tx_id)) - .await? - else { - panic!("Backing tx pool responded with wrong response for request."); - }; - - Ok(tx.map(|tx| tx.0)) - } - - /// Starts the [`DandelionPool`]. - async fn run( - mut self, - mut rx: mpsc::Receiver<(IncomingTx, oneshot::Sender<()>)>, - ) { - tracing::debug!("Starting dandelion++ tx-pool, config: {:?}", self.config); - - // On start up we just fluff all txs left in the stem pool. - let Ok(TxStoreResponse::IDs(ids)) = (&mut self.backing_pool) - .oneshot(TxStoreRequest::IDsInStemPool) - .await - else { - tracing::error!("Failed to get transactions in stem pool."); - return; - }; - - tracing::debug!( - "Fluffing {} txs that are currently in the stem pool", - ids.len() - ); - - for id in ids { - if let Err(e) = self.promote_and_fluff_tx(id).await { - tracing::error!("Failed to fluff tx in the stem pool at start up, {e}."); - return; - } - } - - loop { - tracing::trace!("Waiting for next event."); - tokio::select! { - // biased to handle current txs before routing new ones. - biased; - Some(fired) = self.embargo_timers.next() => { - tracing::debug!("Embargo timer fired, did not see stem tx in time."); - - let tx_id = fired.into_inner(); - if let Err(e) = self.promote_and_fluff_tx(tx_id).await { - tracing::error!("Error handling fired embargo timer: {e}"); - return; - } - } - Some(Ok((tx_id, res))) = self.routing_set.join_next() => { - tracing::trace!("Received d++ routing result."); - - let res = match res { - Ok(State::Fluff) => { - tracing::debug!("Transaction was fluffed upgrading it to the public pool."); - self.promote_tx(tx_id).await - } - Err(tx_state) => { - tracing::debug!("Error routing transaction, trying again."); - - match self.get_tx_from_pool(tx_id.clone()).await { - Ok(Some(tx)) => match tx_state { - TxState::Fluff => self.fluff_tx(tx, tx_id).await, - TxState::Stem { from } => self.stem_tx(tx, tx_id, Some(from)).await, - TxState::Local => self.stem_tx(tx, tx_id, None).await, - } - Err(e) => Err(e), - _ => continue, - } - } - Ok(State::Stem) => continue, - }; - - if let Err(e) = res { - tracing::error!("Error handling transaction routing return: {e}"); - return; - } - } - req = rx.recv() => { - tracing::debug!("Received new tx to route."); - - let Some((IncomingTx { tx, tx_state, tx_id }, res_tx)) = req else { - return; - }; - - if let Err(e) = self.handle_incoming_tx(tx, tx_state, tx_id).await { - let _ = res_tx.send(()); - - tracing::error!("Error handling transaction in dandelion pool: {e}"); - return; - } - let _ = res_tx.send(()); - - } - } - } - } -} diff --git a/p2p/dandelion-tower/src/pool/incoming_tx.rs b/p2p/dandelion-tower/src/pool/incoming_tx.rs new file mode 100644 index 0000000..13cdffe --- /dev/null +++ b/p2p/dandelion-tower/src/pool/incoming_tx.rs @@ -0,0 +1,113 @@ +//! Contains [`IncomingTx`] and [`IncomingTxBuilder`] +use crate::{State, TxState}; + +/// An incoming transaction that has gone through the preprocessing stage. +pub struct IncomingTx { + /// The transaction. + pub(crate) tx: Tx, + /// The transaction ID. + pub(crate) tx_id: TxId, + /// The routing state of the transaction. + pub(crate) routing_state: TxState, +} + +/// An [`IncomingTx`] builder. +/// +/// The const generics here are used to restrict what methods can be called. +/// +/// - `RS`: routing state; a `bool` for if the routing state is set +/// - `DBS`: database state; a `bool` for if the state in the DB is set +pub struct IncomingTxBuilder { + /// The transaction. + tx: Tx, + /// The transaction ID. + tx_id: TxId, + /// The routing state of the transaction. + routing_state: Option>, + /// The state of this transaction in the DB. + state_in_db: Option, +} + +impl IncomingTxBuilder { + /// Creates a new [`IncomingTxBuilder`]. + pub const fn new(tx: Tx, tx_id: TxId) -> Self { + Self { + tx, + tx_id, + routing_state: None, + state_in_db: None, + } + } +} + +impl IncomingTxBuilder { + /// Adds the routing state to the builder. + /// + /// The routing state is the origin of this transaction from our perspective. + pub fn with_routing_state( + self, + state: TxState, + ) -> IncomingTxBuilder { + IncomingTxBuilder { + tx: self.tx, + tx_id: self.tx_id, + routing_state: Some(state), + state_in_db: self.state_in_db, + } + } +} + +impl IncomingTxBuilder { + /// Adds the database state to the builder. + /// + /// If the transaction is not in the DB already then the state should be [`None`]. + pub fn with_state_in_db( + self, + state: Option, + ) -> IncomingTxBuilder { + IncomingTxBuilder { + tx: self.tx, + tx_id: self.tx_id, + routing_state: self.routing_state, + state_in_db: state, + } + } +} + +impl IncomingTxBuilder { + /// Builds the [`IncomingTx`]. + /// + /// If this returns [`None`] then the transaction does not need to be given to the dandelion pool + /// manager. + pub fn build(self) -> Option> { + let routing_state = self.routing_state.unwrap(); + + if self.state_in_db == Some(State::Fluff) { + return None; + } + + Some(IncomingTx { + tx: self.tx, + tx_id: self.tx_id, + routing_state, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_builder() { + IncomingTxBuilder::new(1, 2) + .with_routing_state(TxState::Stem { from: 3 }) + .with_state_in_db(None) + .build(); + + IncomingTxBuilder::new(1, 2) + .with_state_in_db(None) + .with_routing_state(TxState::Stem { from: 3 }) + .build(); + } +} diff --git a/p2p/dandelion-tower/src/pool/manager.rs b/p2p/dandelion-tower/src/pool/manager.rs new file mode 100644 index 0000000..2ac3302 --- /dev/null +++ b/p2p/dandelion-tower/src/pool/manager.rs @@ -0,0 +1,294 @@ +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, + marker::PhantomData, + time::Duration, +}; + +use futures::{FutureExt, StreamExt}; +use rand::prelude::*; +use rand_distr::Exp; +use tokio::{ + sync::{mpsc, oneshot}, + task::JoinSet, +}; +use tokio_util::time::DelayQueue; +use tower::{Service, ServiceExt}; + +use crate::{ + pool::IncomingTx, + traits::{TxStoreRequest, TxStoreResponse}, + DandelionConfig, DandelionRouteReq, DandelionRouterError, State, TxState, +}; + +#[derive(Copy, Clone, Debug, thiserror::Error)] +#[error("The dandelion pool was shutdown")] +pub struct DandelionPoolShutDown; + +/// The dandelion++ pool manager. +/// +/// See the [module docs](super) for more. +pub struct DandelionPoolManager { + /// The dandelion++ router + pub(crate) dandelion_router: R, + /// The backing tx storage. + pub(crate) backing_pool: P, + /// The set of tasks that are running the future returned from `dandelion_router`. + pub(crate) routing_set: JoinSet<(TxId, Result>)>, + + /// The origin of stem transactions. + pub(crate) stem_origins: HashMap>, + + /// Current stem pool embargo timers. + pub(crate) embargo_timers: DelayQueue, + /// The distrobution to sample to get embargo timers. + pub(crate) embargo_dist: Exp, + + /// The d++ config. + pub(crate) config: DandelionConfig, + + pub(crate) _tx: PhantomData, +} + +impl DandelionPoolManager +where + Tx: Clone + Send, + TxId: Hash + Eq + Clone + Send + 'static, + PeerId: Hash + Eq + Clone + Send + 'static, + P: Service, Response = TxStoreResponse, Error = tower::BoxError>, + P::Future: Send + 'static, + R: Service, Response = State, Error = DandelionRouterError>, + R::Future: Send + 'static, +{ + /// Adds a new embargo timer to the running timers, with a duration pulled from [`Self::embargo_dist`] + fn add_embargo_timer_for_tx(&mut self, tx_id: TxId) { + let embargo_timer = self.embargo_dist.sample(&mut thread_rng()); + tracing::debug!( + "Setting embargo timer for stem tx: {} seconds.", + embargo_timer + ); + + self.embargo_timers + .insert(tx_id, Duration::from_secs_f64(embargo_timer)); + } + + /// Stems the tx, setting the stem origin, if it wasn't already set. + /// + /// This function does not add the tx to the backing pool. + async fn stem_tx( + &mut self, + tx: Tx, + tx_id: TxId, + from: Option, + ) -> Result<(), tower::BoxError> { + if let Some(peer) = &from { + self.stem_origins + .entry(tx_id.clone()) + .or_default() + .insert(peer.clone()); + } + + let state = from.map_or(TxState::Local, |from| TxState::Stem { from }); + + let fut = self + .dandelion_router + .ready() + .await? + .call(DandelionRouteReq { + tx, + state: state.clone(), + }); + + self.routing_set + .spawn(fut.map(|res| (tx_id, res.map_err(|_| state)))); + Ok(()) + } + + /// Fluffs a tx, does not add the tx to the tx pool. + async fn fluff_tx(&mut self, tx: Tx, tx_id: TxId) -> Result<(), tower::BoxError> { + let fut = self + .dandelion_router + .ready() + .await? + .call(DandelionRouteReq { + tx, + state: TxState::Fluff, + }); + + self.routing_set + .spawn(fut.map(|res| (tx_id, res.map_err(|_| TxState::Fluff)))); + Ok(()) + } + + /// Function to handle an [`IncomingTx`]. + async fn handle_incoming_tx( + &mut self, + tx: Tx, + tx_state: TxState, + tx_id: TxId, + ) -> Result<(), tower::BoxError> { + match tx_state { + TxState::Stem { from } => { + if self + .stem_origins + .get(&tx_id) + .is_some_and(|peers| peers.contains(&from)) + { + tracing::debug!("Received stem tx twice from same peer, fluffing it"); + // The same peer sent us a tx twice, fluff it. + self.promote_and_fluff_tx(tx_id).await?; + } else { + // This could be a new tx or it could have already been stemed, but we still stem it again + // unless the same peer sends us a tx twice. + tracing::debug!("Steming incoming tx"); + self.stem_tx(tx, tx_id.clone(), Some(from)).await?; + self.add_embargo_timer_for_tx(tx_id); + } + } + TxState::Fluff => { + tracing::debug!("Fluffing incoming tx"); + self.fluff_tx(tx, tx_id).await?; + } + TxState::Local => { + tracing::debug!("Steming local transaction"); + self.stem_tx(tx, tx_id.clone(), None).await?; + self.add_embargo_timer_for_tx(tx_id); + } + } + + Ok(()) + } + + /// Promotes a tx to the clear pool. + async fn promote_tx(&mut self, tx_id: TxId) -> Result<(), tower::BoxError> { + // Remove the tx from the maps used during the stem phase. + self.stem_origins.remove(&tx_id); + + // The key for this is *Not* the tx_id, it is given on insert, so just keep the timer in the + // map. These timers should be relatively short, so it shouldn't be a problem. + //self.embargo_timers.try_remove(&tx_id); + + self.backing_pool + .ready() + .await? + .call(TxStoreRequest::Promote(tx_id)) + .await?; + + Ok(()) + } + + /// Promotes a tx to the public fluff pool and fluffs the tx. + async fn promote_and_fluff_tx(&mut self, tx_id: TxId) -> Result<(), tower::BoxError> { + tracing::debug!("Promoting transaction to public pool and fluffing it."); + + let TxStoreResponse::Transaction(tx) = self + .backing_pool + .ready() + .await? + .call(TxStoreRequest::Get(tx_id.clone())) + .await? + else { + panic!("Backing tx pool responded with wrong response for request."); + }; + + let Some((tx, state)) = tx else { + tracing::debug!("Could not find tx, skipping."); + return Ok(()); + }; + + if state == State::Fluff { + tracing::debug!("Transaction already fluffed, skipping."); + return Ok(()); + } + + self.promote_tx(tx_id.clone()).await?; + self.fluff_tx(tx, tx_id).await + } + + /// Returns a tx stored in the fluff _OR_ stem pool. + async fn get_tx_from_pool(&mut self, tx_id: TxId) -> Result, tower::BoxError> { + let TxStoreResponse::Transaction(tx) = self + .backing_pool + .ready() + .await? + .call(TxStoreRequest::Get(tx_id)) + .await? + else { + panic!("Backing tx pool responded with wrong response for request."); + }; + + Ok(tx.map(|tx| tx.0)) + } + + /// Starts the [`DandelionPoolManager`]. + pub(crate) async fn run( + mut self, + mut rx: mpsc::Receiver<(IncomingTx, oneshot::Sender<()>)>, + ) { + tracing::debug!("Starting dandelion++ tx-pool, config: {:?}", self.config); + + loop { + tracing::trace!("Waiting for next event."); + tokio::select! { + // biased to handle current txs before routing new ones. + biased; + Some(fired) = self.embargo_timers.next() => { + tracing::debug!("Embargo timer fired, did not see stem tx in time."); + + let tx_id = fired.into_inner(); + if let Err(e) = self.promote_and_fluff_tx(tx_id).await { + tracing::error!("Error handling fired embargo timer: {e}"); + return; + } + } + Some(Ok((tx_id, res))) = self.routing_set.join_next() => { + tracing::trace!("Received d++ routing result."); + + let res = match res { + Ok(State::Fluff) => { + tracing::debug!("Transaction was fluffed upgrading it to the public pool."); + self.promote_tx(tx_id).await + } + Err(tx_state) => { + tracing::debug!("Error routing transaction, trying again."); + + match self.get_tx_from_pool(tx_id.clone()).await { + Ok(Some(tx)) => match tx_state { + TxState::Fluff => self.fluff_tx(tx, tx_id).await, + TxState::Stem { from } => self.stem_tx(tx, tx_id, Some(from)).await, + TxState::Local => self.stem_tx(tx, tx_id, None).await, + } + Err(e) => Err(e), + _ => continue, + } + } + Ok(State::Stem) => continue, + }; + + if let Err(e) = res { + tracing::error!("Error handling transaction routing return: {e}"); + return; + } + } + req = rx.recv() => { + tracing::debug!("Received new tx to route."); + + let Some((IncomingTx { tx, tx_id, routing_state }, res_tx)) = req else { + return; + }; + + if let Err(e) = self.handle_incoming_tx(tx, routing_state, tx_id).await { + #[expect(clippy::let_underscore_must_use, reason = "dropped receivers can be ignored")] + let _ = res_tx.send(()); + + tracing::error!("Error handling transaction in dandelion pool: {e}"); + return; + } + + #[expect(clippy::let_underscore_must_use)] + let _ = res_tx.send(()); + } + } + } + } +} diff --git a/p2p/dandelion-tower/src/pool/mod.rs b/p2p/dandelion-tower/src/pool/mod.rs new file mode 100644 index 0000000..40a3617 --- /dev/null +++ b/p2p/dandelion-tower/src/pool/mod.rs @@ -0,0 +1,145 @@ +//! # Dandelion++ Pool +//! +//! This module contains [`DandelionPoolManager`] which is a wrapper around a backing transaction store, +//! which fully implements the dandelion++ protocol. +//! +//! The [`DandelionPoolManager`] is a middle man between a [preprocessing stage](#preprocessing-stage) and a dandelion router. +//! It handles promoting transactions in the stem state to the fluff state and setting embargo timers on stem state transactions. +//! +//! ### Preprocessing stage +//! +//! The preprocessing stage (not handled in this crate) before giving the transaction to the [`DandelionPoolManager`] +//! should handle: +//! +//! - verifying the tx. +//! - checking if we have the tx in the pool already and giving that information to the [`IncomingTxBuilder`]. +//! - storing the tx in the pool, if it isn't there already. +//! +//! ### Keep Stem Transactions Hidden +//! +//! When using your handle to the backing store it must be remembered to keep transactions in the stem pool hidden. +//! So handle any requests to the tx-pool like the stem side of the pool does not exist. +use std::{ + collections::HashMap, + hash::Hash, + marker::PhantomData, + task::{Context, Poll}, +}; + +use futures::{future::BoxFuture, FutureExt}; +use rand_distr::Exp; +use tokio::{ + sync::{mpsc, oneshot}, + task::JoinSet, +}; +use tokio_util::{sync::PollSender, time::DelayQueue}; +use tower::Service; +use tracing::Instrument; + +use crate::{ + pool::manager::DandelionPoolShutDown, + traits::{TxStoreRequest, TxStoreResponse}, + DandelionConfig, DandelionRouteReq, DandelionRouterError, State, +}; + +mod incoming_tx; +mod manager; + +pub use incoming_tx::{IncomingTx, IncomingTxBuilder}; +pub use manager::DandelionPoolManager; + +/// Start the [`DandelionPoolManager`]. +/// +/// This function spawns the [`DandelionPoolManager`] and returns [`DandelionPoolService`] which can be used to send +/// requests to the pool. +/// +/// ### Args +/// +/// - `buffer_size` is the size of the channel's buffer between the [`DandelionPoolService`] and [`DandelionPoolManager`]. +/// - `dandelion_router` is the router service, kept generic instead of [`DandelionRouter`](crate::DandelionRouter) to allow +/// user to customise routing functionality. +/// - `backing_pool` is the backing transaction storage service +/// - `config` is [`DandelionConfig`]. +pub fn start_dandelion_pool_manager( + buffer_size: usize, + dandelion_router: R, + backing_pool: P, + config: DandelionConfig, +) -> DandelionPoolService +where + Tx: Clone + Send + 'static, + TxId: Hash + Eq + Clone + Send + 'static, + PeerId: Hash + Eq + Clone + Send + 'static, + P: Service, Response = TxStoreResponse, Error = tower::BoxError> + + Send + + 'static, + P::Future: Send + 'static, + R: Service, Response = State, Error = DandelionRouterError> + + Send + + 'static, + R::Future: Send + 'static, +{ + let (tx, rx) = mpsc::channel(buffer_size); + + let pool = DandelionPoolManager { + dandelion_router, + backing_pool, + routing_set: JoinSet::new(), + stem_origins: HashMap::new(), + embargo_timers: DelayQueue::new(), + embargo_dist: Exp::new(1.0 / config.average_embargo_timeout().as_secs_f64()).unwrap(), + config, + _tx: PhantomData, + }; + + let span = tracing::debug_span!("dandelion_pool"); + + tokio::spawn(pool.run(rx).instrument(span)); + + DandelionPoolService { + tx: PollSender::new(tx), + } +} + +/// The dandelion pool manager service. +/// +/// Used to send [`IncomingTx`]s to the [`DandelionPoolManager`] +#[derive(Clone)] +pub struct DandelionPoolService { + /// The channel to [`DandelionPoolManager`]. + tx: PollSender<(IncomingTx, oneshot::Sender<()>)>, +} + +impl Service> + for DandelionPoolService +where + Tx: Clone + Send, + TxId: Hash + Eq + Clone + Send + 'static, + PeerId: Hash + Eq + Clone + Send + 'static, +{ + type Response = (); + type Error = DandelionPoolShutDown; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.tx.poll_reserve(cx).map_err(|_| DandelionPoolShutDown) + } + + fn call(&mut self, req: IncomingTx) -> Self::Future { + // although the channel isn't sending anything we want to wait for the request to be handled before continuing. + let (tx, rx) = oneshot::channel(); + + let res = self + .tx + .send_item((req, tx)) + .map_err(|_| DandelionPoolShutDown); + + async move { + res?; + rx.await.expect("Oneshot dropped before response!"); + + Ok(()) + } + .boxed() + } +} diff --git a/p2p/dandelion-tower/src/router.rs b/p2p/dandelion-tower/src/router.rs index a64819a..7ca0598 100644 --- a/p2p/dandelion-tower/src/router.rs +++ b/p2p/dandelion-tower/src/router.rs @@ -6,8 +6,7 @@ //! ### What The Router Does Not Do //! //! It does not handle anything to do with keeping transactions long term, i.e. embargo timers and handling -//! loops in the stem. It is up to implementers to do this if they decide not to use [`DandelionPool`](crate::pool::DandelionPool) -//! +//! loops in the stem. It is up to implementers to do this if they decide not to use [`DandelionPool`](crate::pool::DandelionPoolManager) use std::{ collections::HashMap, hash::Hash, @@ -44,9 +43,9 @@ pub enum DandelionRouterError { } /// A response from an attempt to retrieve an outbound peer. -pub enum OutboundPeer { +pub enum OutboundPeer { /// A peer. - Peer(ID, T), + Peer(Id, T), /// The peer store is exhausted and has no more to return. Exhausted, } @@ -62,28 +61,37 @@ pub enum State { /// The routing state of a transaction. #[derive(Debug, Clone, Eq, PartialEq)] -pub enum TxState { +pub enum TxState { /// Fluff state. Fluff, /// Stem state. Stem { - /// The peer who sent us this transaction's ID. - from: ID, + /// The peer who sent us this transaction's Id. + from: Id, }, /// Local - the transaction originated from our node. Local, } +impl TxState { + /// Returns `true` if the tx is in the stem stage. + /// + /// [`TxState::Local`] & [`TxState::Stem`] are the 2 stem stage states. + pub const fn is_stem_stage(&self) -> bool { + matches!(self, Self::Local | Self::Stem { .. }) + } +} + /// A request to route a transaction. -pub struct DandelionRouteReq { +pub struct DandelionRouteReq { /// The transaction. pub tx: Tx, /// The transaction state. - pub state: TxState, + pub state: TxState, } /// The dandelion router service. -pub struct DandelionRouter { +pub struct DandelionRouter { // pub(crate) is for tests /// A [`Discover`] where we can get outbound peers from. outbound_peer_discover: Pin>, @@ -96,14 +104,14 @@ pub struct DandelionRouter { epoch_start: Instant, /// The stem our local transactions will be sent to. - local_route: Option, - /// A [`HashMap`] linking peer's IDs to IDs in `stem_peers`. - stem_routes: HashMap, + local_route: Option, + /// A [`HashMap`] linking peer's Ids to Ids in `stem_peers`. + stem_routes: HashMap, /// Peers we are using for stemming. /// /// This will contain peers, even in [`State::Fluff`] to allow us to stem [`TxState::Local`] /// transactions. - pub(crate) stem_peers: HashMap, + pub(crate) stem_peers: HashMap, /// The distribution to sample to get the [`State`], true is [`State::Fluff`]. state_dist: Bernoulli, @@ -117,10 +125,10 @@ pub struct DandelionRouter { _tx: PhantomData, } -impl DandelionRouter +impl DandelionRouter where - ID: Hash + Eq + Clone, - P: TryStream, Error = tower::BoxError>, + Id: Hash + Eq + Clone, + P: TryStream, Error = tower::BoxError>, B: Service, Error = tower::BoxError>, B::Future: Send + 'static, S: Service, Error = tower::BoxError>, @@ -141,7 +149,7 @@ where State::Stem }; - DandelionRouter { + Self { outbound_peer_discover: Box::pin(outbound_peer_discover), broadcast_svc, current_state, @@ -199,7 +207,7 @@ where fn stem_tx( &mut self, tx: Tx, - from: ID, + from: &Id, ) -> BoxFuture<'static, Result> { if self.stem_peers.is_empty() { tracing::debug!("Stem peers are empty, fluffing stem transaction."); @@ -217,7 +225,7 @@ where }); let Some(peer) = self.stem_peers.get_mut(stem_route) else { - self.stem_routes.remove(&from); + self.stem_routes.remove(from); continue; }; @@ -259,19 +267,10 @@ where } } -/* -## Generics ## - -Tx: The tx type -ID: Peer Id type - unique identifier for nodes. -P: Peer Set discover - where we can get outbound peers from -B: Broadcast service - where we send txs to get diffused. -S: The Peer service - handles routing messages to a single node. - */ -impl Service> for DandelionRouter +impl Service> for DandelionRouter where - ID: Hash + Eq + Clone, - P: TryStream, Error = tower::BoxError>, + Id: Hash + Eq + Clone, + P: TryStream, Error = tower::BoxError>, B: Service, Error = tower::BoxError>, B::Future: Send + 'static, S: Service, Error = tower::BoxError>, @@ -312,7 +311,7 @@ where tracing::debug!( parent: span, "Peer returned an error on `poll_ready`: {e}, removing from router.", - ) + ); }) .is_ok(), Poll::Pending => { @@ -337,7 +336,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, req: DandelionRouteReq) -> Self::Future { + fn call(&mut self, req: DandelionRouteReq) -> Self::Future { tracing::trace!(parent: &self.span, "Handling route request."); match req.state { @@ -351,7 +350,7 @@ where State::Stem => { tracing::trace!(parent: &self.span, "Steming transaction"); - self.stem_tx(req.tx, from) + self.stem_tx(req.tx, &from) } }, TxState::Local => { diff --git a/p2p/dandelion-tower/src/tests/mod.rs b/p2p/dandelion-tower/src/tests/mod.rs index d868a99..ae9bee1 100644 --- a/p2p/dandelion-tower/src/tests/mod.rs +++ b/p2p/dandelion-tower/src/tests/mod.rs @@ -12,7 +12,8 @@ use crate::{ OutboundPeer, State, }; -pub fn mock_discover_svc() -> ( +#[expect(clippy::type_complexity)] +pub(crate) fn mock_discover_svc() -> ( impl Stream< Item = Result< OutboundPeer< @@ -49,7 +50,7 @@ pub fn mock_discover_svc() -> ( (discover, rx) } -pub fn mock_broadcast_svc() -> ( +pub(crate) fn mock_broadcast_svc() -> ( impl Service< Req, Future = impl Future> + Send + 'static, @@ -70,53 +71,32 @@ pub fn mock_broadcast_svc() -> ( ) } -#[allow(clippy::type_complexity)] // just test code. -pub fn mock_in_memory_backing_pool< +#[expect(clippy::type_complexity, reason = "just test code.")] +pub(crate) fn mock_in_memory_backing_pool< Tx: Clone + Send + 'static, TxID: Clone + Hash + Eq + Send + 'static, >() -> ( impl Service< - TxStoreRequest, - Response = TxStoreResponse, - Future = impl Future, tower::BoxError>> - + Send - + 'static, + TxStoreRequest, + Response = TxStoreResponse, + Future = impl Future, tower::BoxError>> + Send + 'static, Error = tower::BoxError, > + Send + 'static, Arc>>, ) { let txs = Arc::new(std::sync::Mutex::new(HashMap::new())); - let txs_2 = txs.clone(); + let txs_2 = Arc::clone(&txs); ( - service_fn(move |req: TxStoreRequest| { - let txs = txs.clone(); + service_fn(move |req: TxStoreRequest| { + let txs = Arc::clone(&txs); async move { match req { - TxStoreRequest::Store(tx, tx_id, state) => { - txs.lock().unwrap().insert(tx_id, (tx, state)); - Ok(TxStoreResponse::Ok) - } TxStoreRequest::Get(tx_id) => { let tx_state = txs.lock().unwrap().get(&tx_id).cloned(); Ok(TxStoreResponse::Transaction(tx_state)) } - TxStoreRequest::Contains(tx_id) => Ok(TxStoreResponse::Contains( - txs.lock().unwrap().get(&tx_id).map(|res| res.1), - )), - TxStoreRequest::IDsInStemPool => { - // horribly inefficient, but it's test code :) - let ids = txs - .lock() - .unwrap() - .iter() - .filter(|(_, (_, state))| matches!(state, State::Stem)) - .map(|tx| tx.0.clone()) - .collect::>(); - - Ok(TxStoreResponse::IDs(ids)) - } TxStoreRequest::Promote(tx_id) => { let _ = txs .lock() diff --git a/p2p/dandelion-tower/src/tests/pool.rs b/p2p/dandelion-tower/src/tests/pool.rs index 4a7c87d..70f642a 100644 --- a/p2p/dandelion-tower/src/tests/pool.rs +++ b/p2p/dandelion-tower/src/tests/pool.rs @@ -1,12 +1,11 @@ use std::time::Duration; +use super::*; use crate::{ - pool::{start_dandelion_pool, IncomingTx}, + pool::{start_dandelion_pool_manager, IncomingTx}, DandelionConfig, DandelionRouter, Graph, TxState, }; -use super::*; - #[tokio::test] async fn basic_functionality() { let config = DandelionConfig { @@ -21,9 +20,9 @@ async fn basic_functionality() { let router = DandelionRouter::new(broadcast_svc, outbound_peer_svc, config); - let (pool_svc, pool) = mock_in_memory_backing_pool(); + let (pool_svc, _pool) = mock_in_memory_backing_pool(); - let mut pool_svc = start_dandelion_pool(15, router, pool_svc, config); + let mut pool_svc = start_dandelion_pool_manager(15, router, pool_svc, config); pool_svc .ready() @@ -32,11 +31,13 @@ async fn basic_functionality() { .call(IncomingTx { tx: 0_usize, tx_id: 1_usize, - tx_state: TxState::Fluff, + routing_state: TxState::Fluff, }) .await .unwrap(); - assert!(pool.lock().unwrap().contains_key(&1)); - assert!(broadcast_rx.try_recv().is_ok()) + // TODO: the DandelionPoolManager doesn't handle adding txs to the pool, add more tests here to test + // all functionality. + //assert!(pool.lock().unwrap().contains_key(&1)); + assert!(broadcast_rx.try_recv().is_ok()); } diff --git a/p2p/dandelion-tower/src/traits.rs b/p2p/dandelion-tower/src/traits.rs index c84ecf0..bbf6086 100644 --- a/p2p/dandelion-tower/src/traits.rs +++ b/p2p/dandelion-tower/src/traits.rs @@ -8,42 +8,24 @@ pub struct StemRequest(pub Tx); #[cfg(feature = "txpool")] /// A request sent to the backing transaction pool storage. -pub enum TxStoreRequest { - /// A request to store a transaction with the ID to store it under and the pool to store it in. - /// - /// If the tx is already in the pool then do nothing, unless the tx is in the stem pool then move it - /// to the fluff pool, _if this request state is fluff_. - Store(Tx, TxID, crate::State), - /// A request to retrieve a `Tx` with the given ID from the pool, should not remove that tx from the pool. +pub enum TxStoreRequest { + /// A request to retrieve a `Tx` with the given Id from the pool, should not remove that tx from the pool. /// /// Must return [`TxStoreResponse::Transaction`] - Get(TxID), + Get(TxId), /// Promote a transaction from the stem pool to the public pool. /// /// If the tx is already in the fluff pool do nothing. /// /// This should not error if the tx isn't in the pool at all. - Promote(TxID), - /// A request to check if a translation is in the pool. - /// - /// Must return [`TxStoreResponse::Contains`] - Contains(TxID), - /// Returns the IDs of all the transaction in the stem pool. - /// - /// Must return [`TxStoreResponse::IDs`] - IDsInStemPool, + Promote(TxId), } #[cfg(feature = "txpool")] /// A response sent back from the backing transaction pool. -pub enum TxStoreResponse { +pub enum TxStoreResponse { /// A generic ok response. Ok, - /// A response containing a [`Option`] for if the transaction is in the pool (Some) or not (None) and in which pool - /// the tx is in. - Contains(Option), /// A response containing a requested transaction. Transaction(Option<(Tx, crate::State)>), - /// A list of transaction IDs. - IDs(Vec), } diff --git a/p2p/p2p-core/Cargo.toml b/p2p/p2p-core/Cargo.toml index f434d51..bc6c833 100644 --- a/p2p/p2p-core/Cargo.toml +++ b/p2p/p2p-core/Cargo.toml @@ -10,25 +10,30 @@ default = ["borsh"] borsh = ["dep:borsh", "cuprate-pruning/borsh"] [dependencies] -cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } -cuprate-wire = { path = "../../net/wire", features = ["tracing"] } -cuprate-pruning = { path = "../../pruning" } +cuprate-helper = { workspace = true, features = ["asynch"], default-features = false } +cuprate-wire = { workspace = true, features = ["tracing"] } +cuprate-pruning = { workspace = true } +cuprate-types = { workspace = true } -tokio = { workspace = true, features = ["net", "sync", "macros", "time"]} +tokio = { workspace = true, features = ["net", "sync", "macros", "time", "rt", "rt-multi-thread"]} tokio-util = { workspace = true, features = ["codec"] } tokio-stream = { workspace = true, features = ["sync"]} futures = { workspace = true, features = ["std"] } async-trait = { workspace = true } -tower = { workspace = true, features = ["util", "tracing"] } +tower = { workspace = true, features = ["util", "tracing", "make"] } +cfg-if = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, features = ["std", "attributes"] } +hex-literal = { workspace = true } borsh = { workspace = true, features = ["derive", "std"], optional = true } [dev-dependencies] -cuprate-test-utils = {path = "../../test-utils"} +cuprate-test-utils = { workspace = true } hex = { workspace = true, features = ["std"] } -tokio = { workspace = true, features = ["net", "rt-multi-thread", "rt", "macros"]} -tracing-subscriber = { workspace = true } +tokio-test = { workspace = true } + +[lints] +workspace = true diff --git a/p2p/p2p-core/src/client.rs b/p2p/p2p-core/src/client.rs index 0e81d96..f2fde67 100644 --- a/p2p/p2p-core/src/client.rs +++ b/p2p/p2p-core/src/client.rs @@ -1,6 +1,6 @@ use std::{ fmt::{Debug, Display, Formatter}, - sync::Arc, + sync::{Arc, Mutex}, task::{ready, Context, Poll}, }; @@ -15,6 +15,7 @@ use tracing::Instrument; use cuprate_helper::asynch::InfallibleOneshotReceiver; use cuprate_pruning::PruningSeed; +use cuprate_wire::CoreSyncData; use crate::{ handles::{ConnectionGuard, ConnectionHandle}, @@ -24,10 +25,13 @@ use crate::{ mod connection; mod connector; pub mod handshaker; +mod request_handler; mod timeout_monitor; +mod weak; pub use connector::{ConnectRequest, Connector}; -pub use handshaker::{DoHandshakeRequest, HandShaker, HandshakeError}; +pub use handshaker::{DoHandshakeRequest, HandshakeError, HandshakerBuilder}; +pub use weak::WeakClient; /// An internal identifier for a given peer, will be their address if known /// or a random u128 if not. @@ -42,8 +46,8 @@ pub enum InternalPeerID { impl Display for InternalPeerID { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - InternalPeerID::KnownAddr(addr) => addr.fmt(f), - InternalPeerID::Unknown(id) => f.write_str(&format!("Unknown, ID: {id}")), + Self::KnownAddr(addr) => addr.fmt(f), + Self::Unknown(id) => f.write_str(&format!("Unknown, ID: {id}")), } } } @@ -58,8 +62,17 @@ pub struct PeerInformation { pub handle: ConnectionHandle, /// The direction of this connection (inbound|outbound). pub direction: ConnectionDirection, - /// The peers pruning seed. + /// The peer's [`PruningSeed`]. pub pruning_seed: PruningSeed, + /// The [`CoreSyncData`] of this peer. + /// + /// Data across fields are not necessarily related, so [`CoreSyncData::top_id`] is not always the + /// block hash for the block at height one below [`CoreSyncData::current_height`]. + /// + /// This value is behind a [`Mutex`] and is updated whenever the peer sends new information related + /// to their sync state. It is publicly accessible to anyone who has a peers [`Client`] handle. You + /// probably should not mutate this value unless you are creating a custom [`ProtocolRequestHandler`](crate::ProtocolRequestHandler). + pub core_sync_data: Arc>, } /// This represents a connection to a peer. @@ -112,11 +125,22 @@ impl Client { fn set_err(&self, err: PeerError) -> tower::BoxError { let err_str = err.to_string(); match self.error.try_insert_err(err) { - Ok(_) => err_str, + Ok(()) => err_str, Err(e) => e.to_string(), } .into() } + + /// Create a [`WeakClient`] for this [`Client`]. + pub fn downgrade(&self) -> WeakClient { + WeakClient { + info: self.info.clone(), + connection_tx: self.connection_tx.downgrade(), + semaphore: self.semaphore.clone(), + permit: None, + error: self.error.clone(), + } + } } impl Service for Client { @@ -168,9 +192,8 @@ impl Service for Client { TrySendError::Closed(req) | TrySendError::Full(req) => { self.set_err(PeerError::ClientChannelClosed); - let _ = req - .response_channel - .send(Err(PeerError::ClientChannelClosed.into())); + let resp = Err(PeerError::ClientChannelClosed.into()); + drop(req.response_channel.send(resp)); } } } @@ -188,7 +211,8 @@ pub fn mock_client( mut request_handler: S, ) -> Client where - S: crate::PeerRequestHandler, + S: Service + Send + 'static, + S::Future: Send + 'static, { let (tx, mut rx) = mpsc::channel(1); @@ -214,7 +238,7 @@ where tracing::debug!("Sending back response"); - let _ = req.response_channel.send(Ok(res)); + drop(req.response_channel.send(Ok(res))); } } .instrument(task_span), diff --git a/p2p/p2p-core/src/client/connection.rs b/p2p/p2p-core/src/client/connection.rs index 341d8c0..892fa64 100644 --- a/p2p/p2p-core/src/client/connection.rs +++ b/p2p/p2p-core/src/client/connection.rs @@ -2,7 +2,6 @@ //! //! This module handles routing requests from a [`Client`](crate::client::Client) or a broadcast channel to //! a peer. This module also handles routing requests from the connected peer to a request handler. -//! use std::pin::Pin; use futures::{ @@ -15,19 +14,19 @@ use tokio::{ time::{sleep, timeout, Sleep}, }; use tokio_stream::wrappers::ReceiverStream; -use tower::ServiceExt; use cuprate_wire::{LevinCommand, Message, ProtocolMessage}; +use crate::client::request_handler::PeerRequestHandler; use crate::{ constants::{REQUEST_TIMEOUT, SENDING_TIMEOUT}, handles::ConnectionGuard, - BroadcastMessage, MessageID, NetworkZone, PeerError, PeerRequest, PeerRequestHandler, - PeerResponse, SharedError, + AddressBook, BroadcastMessage, CoreSyncSvc, MessageID, NetworkZone, PeerError, PeerRequest, + PeerResponse, ProtocolRequestHandler, ProtocolResponse, SharedError, }; /// A request to the connection task from a [`Client`](crate::client::Client). -pub struct ConnectionTaskRequest { +pub(crate) struct ConnectionTaskRequest { /// The request. pub request: PeerRequest, /// The response channel. @@ -37,7 +36,7 @@ pub struct ConnectionTaskRequest { } /// The connection state. -pub enum State { +pub(crate) enum State { /// Waiting for a request from Cuprate or the connected peer. WaitingForRequest, /// Waiting for a response from the peer. @@ -54,7 +53,7 @@ pub enum State { /// Returns if the [`LevinCommand`] is the correct response message for our request. /// /// e.g. that we didn't get a block for a txs request. -fn levin_command_response(message_id: &MessageID, command: LevinCommand) -> bool { +const fn levin_command_response(message_id: MessageID, command: LevinCommand) -> bool { matches!( (message_id, command), (MessageID::Handshake, LevinCommand::Handshake) @@ -72,7 +71,7 @@ fn levin_command_response(message_id: &MessageID, command: LevinCommand) -> bool } /// This represents a connection to a peer. -pub struct Connection { +pub(crate) struct Connection { /// The peer sink - where we send messages to the peer. peer_sink: Z::Sink, @@ -87,7 +86,7 @@ pub struct Connection { broadcast_stream: Pin>, /// The inner handler for any requests that come from the requested peer. - peer_request_handler: ReqHndlr, + peer_request_handler: PeerRequestHandler, /// The connection guard which will send signals to other parts of Cuprate when this connection is dropped. connection_guard: ConnectionGuard, @@ -95,21 +94,24 @@ pub struct Connection { error: SharedError, } -impl Connection +impl Connection where - ReqHndlr: PeerRequestHandler, + Z: NetworkZone, + A: AddressBook, + CS: CoreSyncSvc, + PR: ProtocolRequestHandler, BrdcstStrm: Stream + Send + 'static, { /// Create a new connection struct. - pub fn new( + pub(crate) fn new( peer_sink: Z::Sink, client_rx: mpsc::Receiver, broadcast_stream: BrdcstStrm, - peer_request_handler: ReqHndlr, + peer_request_handler: PeerRequestHandler, connection_guard: ConnectionGuard, error: SharedError, - ) -> Connection { - Connection { + ) -> Self { + Self { peer_sink, state: State::WaitingForRequest, request_timeout: None, @@ -171,13 +173,14 @@ where if let Err(e) = res { // can't clone the error so turn it to a string first, hacky but oh well. let err_str = e.to_string(); - let _ = req.response_channel.send(Err(err_str.clone().into())); + drop(req.response_channel.send(Err(err_str.into()))); return Err(e); - } else { - // We still need to respond even if the response is this. - let _ = req.response_channel.send(Ok(PeerResponse::NA)); } + // We still need to respond even if the response is this. + let resp = Ok(PeerResponse::Protocol(ProtocolResponse::NA)); + drop(req.response_channel.send(resp)); + Ok(()) } @@ -185,17 +188,14 @@ where async fn handle_peer_request(&mut self, req: PeerRequest) -> Result<(), PeerError> { tracing::debug!("Received peer request: {:?}", req.id()); - let ready_svc = self.peer_request_handler.ready().await?; - let res = ready_svc.call(req).await?; - if matches!(res, PeerResponse::NA) { - return Ok(()); + let res = self.peer_request_handler.handle_peer_request(req).await?; + + // This will be an error if a response does not need to be sent + if let Ok(res) = res.try_into() { + self.send_message_to_peer(res).await?; } - self.send_message_to_peer( - res.try_into() - .expect("We just checked if the response was `NA`"), - ) - .await + Ok(()) } /// Handles a message from a peer when we are in [`State::WaitingForResponse`]. @@ -213,7 +213,7 @@ where }; // Check if the message is a response to our request. - if levin_command_response(request_id, mes.command()) { + if levin_command_response(*request_id, mes.command()) { // TODO: Do more checks before returning response. let State::WaitingForResponse { tx, .. } = @@ -222,9 +222,11 @@ where panic!("Not in correct state, can't receive response!") }; - let _ = tx.send(Ok(mes + let resp = Ok(mes .try_into() - .map_err(|_| PeerError::PeerSentInvalidMessage)?)); + .map_err(|_| PeerError::PeerSentInvalidMessage)?); + + drop(tx.send(resp)); self.request_timeout = None; @@ -280,7 +282,7 @@ where tokio::select! { biased; - _ = self.request_timeout.as_mut().expect("Request timeout was not set!") => { + () = self.request_timeout.as_mut().expect("Request timeout was not set!") => { Err(PeerError::ClientChannelClosed) } broadcast_req = self.broadcast_stream.next() => { @@ -304,8 +306,11 @@ where /// Runs the Connection handler logic, this should be put in a separate task. /// /// `eager_protocol_messages` are protocol messages that we received during a handshake. - pub async fn run(mut self, mut stream: Str, eager_protocol_messages: Vec) - where + pub(crate) async fn run( + mut self, + mut stream: Str, + eager_protocol_messages: Vec, + ) where Str: FusedStream> + Unpin, { tracing::debug!( @@ -346,6 +351,7 @@ where /// Shutdowns the connection, flushing pending requests and setting the error slot, if it hasn't been /// set already. + #[expect(clippy::significant_drop_tightening)] fn shutdown(mut self, err: PeerError) { tracing::debug!("Connection task shutting down: {}", err); @@ -360,11 +366,11 @@ where if let State::WaitingForResponse { tx, .. } = std::mem::replace(&mut self.state, State::WaitingForRequest) { - let _ = tx.send(Err(err_str.clone().into())); + drop(tx.send(Err(err_str.clone().into()))); } while let Ok(req) = client_rx.try_recv() { - let _ = req.response_channel.send(Err(err_str.clone().into())); + drop(req.response_channel.send(Err(err_str.clone().into()))); } self.connection_guard.connection_closed(); diff --git a/p2p/p2p-core/src/client/connector.rs b/p2p/p2p-core/src/client/connector.rs index 278d740..abe7e13 100644 --- a/p2p/p2p-core/src/client/connector.rs +++ b/p2p/p2p-core/src/client/connector.rs @@ -4,7 +4,6 @@ //! perform a handshake and create a [`Client`]. //! //! This is where outbound connections are created. -//! use std::{ future::Future, pin::Pin, @@ -16,9 +15,9 @@ use tokio::sync::OwnedSemaphorePermit; use tower::{Service, ServiceExt}; use crate::{ - client::{Client, DoHandshakeRequest, HandShaker, HandshakeError, InternalPeerID}, + client::{handshaker::HandShaker, Client, DoHandshakeRequest, HandshakeError, InternalPeerID}, AddressBook, BroadcastMessage, ConnectionDirection, CoreSyncSvc, NetworkZone, - PeerRequestHandler, PeerSyncSvc, + ProtocolRequestHandlerMaker, }; /// A request to connect to a peer. @@ -27,30 +26,33 @@ pub struct ConnectRequest { pub addr: Z::Addr, /// A permit which will be held be the connection allowing you to set limits on the number of /// connections. - pub permit: OwnedSemaphorePermit, + /// + /// This doesn't have to be set. + pub permit: Option, } /// The connector service, this service connects to peer and returns the [`Client`]. -pub struct Connector { - handshaker: HandShaker, +pub struct Connector { + handshaker: HandShaker, } -impl - Connector +impl + Connector { /// Create a new connector from a handshaker. - pub fn new(handshaker: HandShaker) -> Self { + pub const fn new( + handshaker: HandShaker, + ) -> Self { Self { handshaker } } } -impl - Service> for Connector +impl + Service> for Connector where AdrBook: AddressBook + Clone, CSync: CoreSyncSvc + Clone, - PSync: PeerSyncSvc + Clone, - ReqHdlr: PeerRequestHandler + Clone, + ProtoHdlrMkr: ProtocolRequestHandlerMaker + Clone, BrdcstStrm: Stream + Send + 'static, BrdcstStrmMkr: Fn(InternalPeerID) -> BrdcstStrm + Clone + Send + 'static, { @@ -74,7 +76,7 @@ where permit: req.permit, peer_stream, peer_sink, - direction: ConnectionDirection::OutBound, + direction: ConnectionDirection::Outbound, }; handshaker.ready().await?.call(req).await } diff --git a/p2p/p2p-core/src/client/handshaker.rs b/p2p/p2p-core/src/client/handshaker.rs index 1071b33..66acb5b 100644 --- a/p2p/p2p-core/src/client/handshaker.rs +++ b/p2p/p2p-core/src/client/handshaker.rs @@ -8,7 +8,7 @@ use std::{ future::Future, marker::PhantomData, pin::Pin, - sync::Arc, + sync::{Arc, Mutex}, task::{Context, Poll}, }; @@ -18,7 +18,7 @@ use tokio::{ time::{error::Elapsed, timeout}, }; use tower::{Service, ServiceExt}; -use tracing::{info_span, Instrument}; +use tracing::{info_span, Instrument, Span}; use cuprate_pruning::{PruningError, PruningSeed}; use cuprate_wire::{ @@ -27,25 +27,27 @@ use cuprate_wire::{ PING_OK_RESPONSE_STATUS_TEXT, }, common::PeerSupportFlags, - BasicNodeData, BucketError, LevinCommand, Message, RequestMessage, ResponseMessage, + AdminRequestMessage, AdminResponseMessage, BasicNodeData, BucketError, LevinCommand, Message, }; use crate::{ client::{ - connection::Connection, timeout_monitor::connection_timeout_monitor_task, Client, - InternalPeerID, PeerInformation, + connection::Connection, request_handler::PeerRequestHandler, + timeout_monitor::connection_timeout_monitor_task, Client, InternalPeerID, PeerInformation, }, constants::{ HANDSHAKE_TIMEOUT, MAX_EAGER_PROTOCOL_MESSAGES, MAX_PEERS_IN_PEER_LIST_MESSAGE, PING_TIMEOUT, }, handles::HandleBuilder, - services::PeerSyncRequest, AddressBook, AddressBookRequest, AddressBookResponse, BroadcastMessage, ConnectionDirection, CoreSyncDataRequest, CoreSyncDataResponse, CoreSyncSvc, NetZoneAddress, NetworkZone, - PeerRequestHandler, PeerSyncSvc, SharedError, + ProtocolRequestHandlerMaker, SharedError, }; +pub mod builder; +pub use builder::HandshakerBuilder; + #[derive(Debug, thiserror::Error)] pub enum HandshakeError { #[error("The handshake timed out")] @@ -78,21 +80,19 @@ pub struct DoHandshakeRequest { pub peer_sink: Z::Sink, /// The direction of the connection. pub direction: ConnectionDirection, - /// A permit for this connection. - pub permit: OwnedSemaphorePermit, + /// An [`Option`]al permit for this connection. + pub permit: Option, } /// The peer handshaking service. #[derive(Debug, Clone)] -pub struct HandShaker { +pub struct HandShaker { /// The address book service. address_book: AdrBook, /// The core sync data service. core_sync_svc: CSync, - /// The peer sync service. - peer_sync_svc: PSync, - /// The peer request handler service. - peer_request_svc: ReqHdlr, + /// The protocol request handler service. + protocol_request_svc_maker: ProtoHdlrMkr, /// Our [`BasicNodeData`] our_basic_node_data: BasicNodeData, @@ -100,42 +100,42 @@ pub struct HandShaker, } -impl - HandShaker +impl + HandShaker { /// Creates a new handshaker. - pub fn new( + const fn new( address_book: AdrBook, - peer_sync_svc: PSync, core_sync_svc: CSync, - peer_request_svc: ReqHdlr, + protocol_request_svc_maker: ProtoHdlrMkr, broadcast_stream_maker: BrdcstStrmMkr, - our_basic_node_data: BasicNodeData, + connection_parent_span: Span, ) -> Self { Self { address_book, - peer_sync_svc, core_sync_svc, - peer_request_svc, + protocol_request_svc_maker, broadcast_stream_maker, our_basic_node_data, + connection_parent_span, _zone: PhantomData, } } } -impl - Service> for HandShaker +impl + Service> for HandShaker where AdrBook: AddressBook + Clone, CSync: CoreSyncSvc + Clone, - PSync: PeerSyncSvc + Clone, - ReqHdlr: PeerRequestHandler + Clone, + ProtoHdlrMkr: ProtocolRequestHandlerMaker + Clone, BrdcstStrm: Stream + Send + 'static, BrdcstStrmMkr: Fn(InternalPeerID) -> BrdcstStrm + Clone + Send + 'static, { @@ -152,12 +152,13 @@ where let broadcast_stream_maker = self.broadcast_stream_maker.clone(); let address_book = self.address_book.clone(); - let peer_request_svc = self.peer_request_svc.clone(); + let protocol_request_svc_maker = self.protocol_request_svc_maker.clone(); let core_sync_svc = self.core_sync_svc.clone(); - let peer_sync_svc = self.peer_sync_svc.clone(); let our_basic_node_data = self.our_basic_node_data.clone(); - let span = info_span!(parent: &tracing::Span::current(), "handshaker", addr=%req.addr); + let connection_parent_span = self.connection_parent_span.clone(); + + let span = info_span!(parent: &Span::current(), "handshaker", addr=%req.addr); async move { timeout( @@ -167,9 +168,9 @@ where broadcast_stream_maker, address_book, core_sync_svc, - peer_sync_svc, - peer_request_svc, + protocol_request_svc_maker, our_basic_node_data, + connection_parent_span, ), ) .await? @@ -190,11 +191,11 @@ pub async fn ping(addr: N::Addr) -> Result tracing::debug!("Made outbound connection to peer, sending ping."); peer_sink - .send(Message::Request(RequestMessage::Ping).into()) + .send(Message::Request(AdminRequestMessage::Ping).into()) .await?; if let Some(res) = peer_stream.next().await { - if let Message::Response(ResponseMessage::Ping(ping)) = res? { + if let Message::Response(AdminResponseMessage::Ping(ping)) = res? { if ping.status == PING_OK_RESPONSE_STATUS_TEXT { tracing::debug!("Ping successful."); return Ok(ping.peer_id); @@ -216,26 +217,26 @@ pub async fn ping(addr: N::Addr) -> Result Err(BucketError::IO(std::io::Error::new( std::io::ErrorKind::ConnectionAborted, "The peer stream returned None", - )))? + )) + .into()) } /// This function completes a handshake with the requested peer. -async fn handshake( +async fn handshake( req: DoHandshakeRequest, broadcast_stream_maker: BrdcstStrmMkr, mut address_book: AdrBook, mut core_sync_svc: CSync, - mut peer_sync_svc: PSync, - peer_request_svc: ReqHdlr, + mut protocol_request_svc_maker: ProtoHdlrMkr, our_basic_node_data: BasicNodeData, + connection_parent_span: Span, ) -> Result, HandshakeError> where - AdrBook: AddressBook, - CSync: CoreSyncSvc, - PSync: PeerSyncSvc, - ReqHdlr: PeerRequestHandler, + AdrBook: AddressBook + Clone, + CSync: CoreSyncSvc + Clone, + ProtoHdlrMkr: ProtocolRequestHandlerMaker, BrdcstStrm: Stream + Send + 'static, BrdcstStrmMkr: Fn(InternalPeerID) -> BrdcstStrm + Send + 'static, { @@ -252,19 +253,20 @@ where let mut eager_protocol_messages = Vec::new(); let (peer_core_sync, peer_node_data) = match direction { - ConnectionDirection::InBound => { + ConnectionDirection::Inbound => { // Inbound handshake the peer sends the request. tracing::debug!("waiting for handshake request."); - let Message::Request(RequestMessage::Handshake(handshake_req)) = wait_for_message::( - LevinCommand::Handshake, - true, - &mut peer_sink, - &mut peer_stream, - &mut eager_protocol_messages, - &our_basic_node_data, - ) - .await? + let Message::Request(AdminRequestMessage::Handshake(handshake_req)) = + wait_for_message::( + LevinCommand::Handshake, + true, + &mut peer_sink, + &mut peer_stream, + &mut eager_protocol_messages, + &our_basic_node_data, + ) + .await? else { panic!("wait_for_message returned ok with wrong message."); }; @@ -273,7 +275,7 @@ where // We will respond to the handshake request later. (handshake_req.payload_data, handshake_req.node_data) } - ConnectionDirection::OutBound => { + ConnectionDirection::Outbound => { // Outbound handshake, we send the request. send_hs_request::( &mut peer_sink, @@ -283,7 +285,7 @@ where .await?; // Wait for the handshake response. - let Message::Response(ResponseMessage::Handshake(handshake_res)) = + let Message::Response(AdminResponseMessage::Handshake(handshake_res)) = wait_for_message::( LevinCommand::Handshake, false, @@ -373,13 +375,13 @@ where // public_address, if Some, is the reachable address of the node. let public_address = 'check_out_addr: { match direction { - ConnectionDirection::InBound => { + ConnectionDirection::Inbound => { // First send the handshake response. send_hs_response::( &mut peer_sink, &mut core_sync_svc, &mut address_book, - our_basic_node_data, + our_basic_node_data.clone(), ) .await?; @@ -390,7 +392,10 @@ where break 'check_out_addr None; }; - // u32 does not make sense as a port so just truncate it. + #[expect( + clippy::cast_possible_truncation, + reason = "u32 does not make sense as a port so just truncate it." + )] outbound_address.set_port(peer_node_data.my_port as u16); let Ok(Ok(ping_peer_id)) = timeout( @@ -411,7 +416,7 @@ where // The peer did not specify a reachable port or the ping was not successful. None } - ConnectionDirection::OutBound => { + ConnectionDirection::Outbound => { let InternalPeerID::KnownAddr(outbound_addr) = addr else { unreachable!("How could we make an outbound connection to an unknown address"); }; @@ -424,37 +429,7 @@ where tracing::debug!("Handshake complete."); - // Set up the connection data. - let error_slot = SharedError::new(); let (connection_guard, handle) = HandleBuilder::new().with_permit(permit).build(); - let (connection_tx, client_rx) = mpsc::channel(1); - - let connection = Connection::::new( - peer_sink, - client_rx, - broadcast_stream_maker(addr), - peer_request_svc, - connection_guard, - error_slot.clone(), - ); - - let connection_span = tracing::error_span!(parent: &tracing::Span::none(), "connection", %addr); - let connection_handle = tokio::spawn( - connection - .run(peer_stream.fuse(), eager_protocol_messages) - .instrument(connection_span), - ); - - // Tell the core sync service about the new peer. - peer_sync_svc - .ready() - .await? - .call(PeerSyncRequest::IncomingCoreSyncData( - addr, - handle.clone(), - peer_core_sync, - )) - .await?; // Tell the address book about the new connection. address_book @@ -471,23 +446,58 @@ where }) .await?; + // Set up the connection data. + let error_slot = SharedError::new(); + let (connection_tx, client_rx) = mpsc::channel(1); + let info = PeerInformation { id: addr, handle, direction, pruning_seed, + core_sync_data: Arc::new(Mutex::new(peer_core_sync)), }; + let protocol_request_handler = protocol_request_svc_maker + .as_service() + .ready() + .await? + .call(info.clone()) + .await?; + + let request_handler = PeerRequestHandler { + address_book_svc: address_book.clone(), + our_sync_svc: core_sync_svc.clone(), + protocol_request_handler, + our_basic_node_data, + peer_info: info.clone(), + }; + + let connection = Connection::::new( + peer_sink, + client_rx, + broadcast_stream_maker(addr), + request_handler, + connection_guard, + error_slot.clone(), + ); + + let connection_span = + tracing::error_span!(parent: &connection_parent_span, "connection", %addr); + let connection_handle = tokio::spawn( + connection + .run(peer_stream.fuse(), eager_protocol_messages) + .instrument(connection_span), + ); + let semaphore = Arc::new(Semaphore::new(1)); let timeout_handle = tokio::spawn(connection_timeout_monitor_task( - info.id, - info.handle.clone(), + info.clone(), connection_tx.clone(), - semaphore.clone(), + Arc::clone(&semaphore), address_book, core_sync_svc, - peer_sync_svc, )); let client = Client::::new( @@ -502,7 +512,7 @@ where Ok(client) } -/// Sends a [`RequestMessage::Handshake`] down the peer sink. +/// Sends a [`AdminRequestMessage::Handshake`] down the peer sink. async fn send_hs_request( peer_sink: &mut Z::Sink, core_sync_svc: &mut CSync, @@ -525,13 +535,13 @@ where tracing::debug!("Sending handshake request."); peer_sink - .send(Message::Request(RequestMessage::Handshake(req)).into()) + .send(Message::Request(AdminRequestMessage::Handshake(req)).into()) .await?; Ok(()) } -/// Sends a [`ResponseMessage::Handshake`] down the peer sink. +/// Sends a [`AdminResponseMessage::Handshake`] down the peer sink. async fn send_hs_response( peer_sink: &mut Z::Sink, core_sync_svc: &mut CSync, @@ -568,7 +578,7 @@ where tracing::debug!("Sending handshake response."); peer_sink - .send(Message::Response(ResponseMessage::Handshake(res)).into()) + .send(Message::Response(AdminResponseMessage::Handshake(res)).into()) .await?; Ok(()) @@ -619,7 +629,7 @@ async fn wait_for_message( } match req_message { - RequestMessage::SupportFlags => { + AdminRequestMessage::SupportFlags => { if !allow_support_flag_req { return Err(HandshakeError::PeerSentInvalidMessage( "Peer sent 2 support flag requests", @@ -631,7 +641,7 @@ async fn wait_for_message( allow_support_flag_req = false; continue; } - RequestMessage::Ping => { + AdminRequestMessage::Ping => { if !allow_ping { return Err(HandshakeError::PeerSentInvalidMessage( "Peer sent 2 ping requests", @@ -647,7 +657,7 @@ async fn wait_for_message( _ => { return Err(HandshakeError::PeerSentInvalidMessage( "Peer sent an admin request before responding to the handshake", - )) + )); } } } @@ -662,19 +672,20 @@ async fn wait_for_message( )); } - _ => Err(HandshakeError::PeerSentInvalidMessage( + Message::Response(_) => Err(HandshakeError::PeerSentInvalidMessage( "Peer sent an incorrect message", )), - }? + }?; } Err(BucketError::IO(std::io::Error::new( std::io::ErrorKind::ConnectionAborted, "The peer stream returned None", - )))? + )) + .into()) } -/// Sends a [`ResponseMessage::SupportFlags`] down the peer sink. +/// Sends a [`AdminResponseMessage::SupportFlags`] down the peer sink. async fn send_support_flags( peer_sink: &mut Z::Sink, support_flags: PeerSupportFlags, @@ -682,7 +693,7 @@ async fn send_support_flags( tracing::debug!("Sending support flag response."); Ok(peer_sink .send( - Message::Response(ResponseMessage::SupportFlags(SupportFlagsResponse { + Message::Response(AdminResponseMessage::SupportFlags(SupportFlagsResponse { support_flags, })) .into(), @@ -690,7 +701,7 @@ async fn send_support_flags( .await?) } -/// Sends a [`ResponseMessage::Ping`] down the peer sink. +/// Sends a [`AdminResponseMessage::Ping`] down the peer sink. async fn send_ping_response( peer_sink: &mut Z::Sink, peer_id: u64, @@ -698,7 +709,7 @@ async fn send_ping_response( tracing::debug!("Sending ping response."); Ok(peer_sink .send( - Message::Response(ResponseMessage::Ping(PingResponse { + Message::Response(AdminResponseMessage::Ping(PingResponse { status: PING_OK_RESPONSE_STATUS_TEXT, peer_id, })) diff --git a/p2p/p2p-core/src/client/handshaker/builder.rs b/p2p/p2p-core/src/client/handshaker/builder.rs new file mode 100644 index 0000000..c1c3f3f --- /dev/null +++ b/p2p/p2p-core/src/client/handshaker/builder.rs @@ -0,0 +1,242 @@ +use std::{convert::Infallible, marker::PhantomData}; + +use futures::{stream, Stream}; +use tower::{make::Shared, util::MapErr}; +use tracing::Span; + +use cuprate_wire::BasicNodeData; + +use crate::{ + client::{handshaker::HandShaker, InternalPeerID}, + AddressBook, BroadcastMessage, CoreSyncSvc, NetworkZone, ProtocolRequestHandlerMaker, +}; + +mod dummy; +pub use dummy::{DummyAddressBook, DummyCoreSyncSvc, DummyProtocolRequestHandler}; + +/// A [`HandShaker`] [`Service`](tower::Service) builder. +/// +/// This builder applies default values to make usage easier, behaviour and drawbacks of the defaults are documented +/// on the `with_*` method to change it, for example [`HandshakerBuilder::with_protocol_request_handler_maker`]. +/// +/// If you want to use any network other than [`Mainnet`](crate::Network::Mainnet) +/// you will need to change the core sync service with [`HandshakerBuilder::with_core_sync_svc`], +/// see that method for details. +#[derive(Debug, Clone)] +pub struct HandshakerBuilder< + N: NetworkZone, + AdrBook = DummyAddressBook, + CSync = DummyCoreSyncSvc, + ProtoHdlrMkr = MapErr, fn(Infallible) -> tower::BoxError>, + BrdcstStrmMkr = fn( + InternalPeerID<::Addr>, + ) -> stream::Pending, +> { + /// The address book service. + address_book: AdrBook, + /// The core sync data service. + core_sync_svc: CSync, + /// The protocol request service. + protocol_request_svc_maker: ProtoHdlrMkr, + /// Our [`BasicNodeData`] + our_basic_node_data: BasicNodeData, + /// A function that returns a stream that will give items to be broadcast by a connection. + broadcast_stream_maker: BrdcstStrmMkr, + /// The [`Span`] that will set as the parent to the connection [`Span`]. + connection_parent_span: Option, + + /// The network zone. + _zone: PhantomData, +} + +impl HandshakerBuilder { + /// Creates a new builder with our node's basic node data. + pub fn new(our_basic_node_data: BasicNodeData) -> Self { + Self { + address_book: DummyAddressBook, + core_sync_svc: DummyCoreSyncSvc::static_mainnet_genesis(), + protocol_request_svc_maker: MapErr::new( + Shared::new(DummyProtocolRequestHandler), + tower::BoxError::from, + ), + our_basic_node_data, + broadcast_stream_maker: |_| stream::pending(), + connection_parent_span: None, + _zone: PhantomData, + } + } +} + +impl + HandshakerBuilder +{ + /// Changes the address book to the provided one. + /// + /// ## Default Address Book + /// + /// The default address book is used if this function is not called. + /// + /// The default address book's only drawback is that it does not keep track of peers and therefore + /// bans. + pub fn with_address_book( + self, + new_address_book: NAdrBook, + ) -> HandshakerBuilder + where + NAdrBook: AddressBook + Clone, + { + let Self { + core_sync_svc, + protocol_request_svc_maker, + our_basic_node_data, + broadcast_stream_maker, + connection_parent_span, + .. + } = self; + + HandshakerBuilder { + address_book: new_address_book, + core_sync_svc, + protocol_request_svc_maker, + our_basic_node_data, + broadcast_stream_maker, + connection_parent_span, + _zone: PhantomData, + } + } + + /// Changes the core sync service to the provided one. + /// + /// The core sync service should keep track of our nodes core sync data. + /// + /// ## Default Core Sync Service + /// + /// The default core sync service is used if this method is not called. + /// + /// The default core sync service will just use the mainnet genesis block, to use other network's + /// genesis see [`DummyCoreSyncSvc::static_stagenet_genesis`] and [`DummyCoreSyncSvc::static_testnet_genesis`]. + /// The drawbacks to keeping this the default is that it will always return the mainnet genesis as our nodes + /// sync info, which means peers won't know our actual chain height, this may or may not be a problem for + /// different use cases. + pub fn with_core_sync_svc( + self, + new_core_sync_svc: NCSync, + ) -> HandshakerBuilder + where + NCSync: CoreSyncSvc + Clone, + { + let Self { + address_book, + protocol_request_svc_maker, + our_basic_node_data, + broadcast_stream_maker, + connection_parent_span, + .. + } = self; + + HandshakerBuilder { + address_book, + core_sync_svc: new_core_sync_svc, + protocol_request_svc_maker, + our_basic_node_data, + broadcast_stream_maker, + connection_parent_span, + _zone: PhantomData, + } + } + + /// Changes the protocol request handler maker, which creates the service that handles [`ProtocolRequest`](crate::ProtocolRequest)s + /// to our node. + /// + /// ## Default Protocol Request Handler + /// + /// The default service maker will create services that will not respond to any protocol requests, this should not + /// be an issue as long as peers do not think we are ahead of them, if they do they will send requests + /// for our blocks, and we won't respond which will cause them to disconnect. + pub fn with_protocol_request_handler_maker( + self, + new_protocol_request_svc_maker: NProtoHdlrMkr, + ) -> HandshakerBuilder + where + NProtoHdlrMkr: ProtocolRequestHandlerMaker + Clone, + { + let Self { + address_book, + core_sync_svc, + our_basic_node_data, + broadcast_stream_maker, + connection_parent_span, + .. + } = self; + + HandshakerBuilder { + address_book, + core_sync_svc, + protocol_request_svc_maker: new_protocol_request_svc_maker, + our_basic_node_data, + broadcast_stream_maker, + connection_parent_span, + _zone: PhantomData, + } + } + + /// Changes the broadcast stream maker, which is used to create streams that yield messages to broadcast. + /// + /// ## Default Broadcast Stream Maker + /// + /// The default broadcast stream maker just returns [`stream::Pending`], i.e. the returned stream will not + /// produce any messages to broadcast, this is not a problem if your use case does not require broadcasting + /// messages. + pub fn with_broadcast_stream_maker( + self, + new_broadcast_stream_maker: NBrdcstStrmMkr, + ) -> HandshakerBuilder + where + BrdcstStrm: Stream + Send + 'static, + NBrdcstStrmMkr: Fn(InternalPeerID) -> BrdcstStrm + Clone + Send + 'static, + { + let Self { + address_book, + core_sync_svc, + protocol_request_svc_maker, + our_basic_node_data, + connection_parent_span, + .. + } = self; + + HandshakerBuilder { + address_book, + core_sync_svc, + protocol_request_svc_maker, + our_basic_node_data, + broadcast_stream_maker: new_broadcast_stream_maker, + connection_parent_span, + _zone: PhantomData, + } + } + + /// Changes the parent [`Span`] of the connection task to the one provided. + /// + /// ## Default Connection Parent Span + /// + /// The default connection span will be [`Span::none`]. + #[must_use] + pub fn with_connection_parent_span(self, connection_parent_span: Span) -> Self { + Self { + connection_parent_span: Some(connection_parent_span), + ..self + } + } + + /// Builds the [`HandShaker`]. + pub fn build(self) -> HandShaker { + HandShaker::new( + self.address_book, + self.core_sync_svc, + self.protocol_request_svc_maker, + self.broadcast_stream_maker, + self.our_basic_node_data, + self.connection_parent_span.unwrap_or(Span::none()), + ) + } +} diff --git a/p2p/p2p-core/src/client/handshaker/builder/dummy.rs b/p2p/p2p-core/src/client/handshaker/builder/dummy.rs new file mode 100644 index 0000000..48b3daf --- /dev/null +++ b/p2p/p2p-core/src/client/handshaker/builder/dummy.rs @@ -0,0 +1,138 @@ +use std::{ + future::{ready, Ready}, + task::{Context, Poll}, +}; + +use tower::Service; + +use cuprate_wire::CoreSyncData; + +use crate::{ + services::{ + AddressBookRequest, AddressBookResponse, CoreSyncDataRequest, CoreSyncDataResponse, + }, + NetworkZone, ProtocolRequest, ProtocolResponse, +}; + +/// A dummy core sync service that just returns static [`CoreSyncData`]. +#[derive(Debug, Clone)] +pub struct DummyCoreSyncSvc(CoreSyncData); + +impl DummyCoreSyncSvc { + /// Returns a [`DummyCoreSyncSvc`] that will just return the mainnet genesis [`CoreSyncData`]. + pub const fn static_mainnet_genesis() -> Self { + Self(CoreSyncData { + cumulative_difficulty: 1, + cumulative_difficulty_top64: 0, + current_height: 1, + pruning_seed: 0, + top_id: hex_literal::hex!( + "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3" + ), + top_version: 1, + }) + } + + /// Returns a [`DummyCoreSyncSvc`] that will just return the testnet genesis [`CoreSyncData`]. + pub const fn static_testnet_genesis() -> Self { + Self(CoreSyncData { + cumulative_difficulty: 1, + cumulative_difficulty_top64: 0, + current_height: 1, + pruning_seed: 0, + top_id: hex_literal::hex!( + "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b" + ), + top_version: 1, + }) + } + + /// Returns a [`DummyCoreSyncSvc`] that will just return the stagenet genesis [`CoreSyncData`]. + pub const fn static_stagenet_genesis() -> Self { + Self(CoreSyncData { + cumulative_difficulty: 1, + cumulative_difficulty_top64: 0, + current_height: 1, + pruning_seed: 0, + top_id: hex_literal::hex!( + "76ee3cc98646292206cd3e86f74d88b4dcc1d937088645e9b0cbca84b7ce74eb" + ), + top_version: 1, + }) + } + + /// Returns a [`DummyCoreSyncSvc`] that will return the provided [`CoreSyncData`]. + pub const fn static_custom(data: CoreSyncData) -> Self { + Self(data) + } +} + +impl Service for DummyCoreSyncSvc { + type Response = CoreSyncDataResponse; + type Error = tower::BoxError; + type Future = Ready>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _: CoreSyncDataRequest) -> Self::Future { + ready(Ok(CoreSyncDataResponse(self.0.clone()))) + } +} + +/// A dummy address book that doesn't actually keep track of peers. +#[derive(Debug, Clone)] +pub struct DummyAddressBook; + +impl Service> for DummyAddressBook { + type Response = AddressBookResponse; + type Error = tower::BoxError; + type Future = Ready>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: AddressBookRequest) -> Self::Future { + ready(Ok(match req { + AddressBookRequest::GetWhitePeers(_) => AddressBookResponse::Peers(vec![]), + AddressBookRequest::TakeRandomGrayPeer { .. } + | AddressBookRequest::TakeRandomPeer { .. } + | AddressBookRequest::TakeRandomWhitePeer { .. } => { + return ready(Err("dummy address book does not hold peers".into())); + } + AddressBookRequest::NewConnection { .. } | AddressBookRequest::IncomingPeerList(_) => { + AddressBookResponse::Ok + } + AddressBookRequest::GetBan(_) => AddressBookResponse::GetBan { + unban_instant: None, + }, + AddressBookRequest::PeerlistSize + | AddressBookRequest::ConnectionCount + | AddressBookRequest::SetBan(_) + | AddressBookRequest::GetBans + | AddressBookRequest::ConnectionInfo => { + todo!("finish https://github.com/Cuprate/cuprate/pull/297") + } + })) + } +} + +/// A dummy protocol request handler. +#[derive(Debug, Clone)] +pub struct DummyProtocolRequestHandler; + +impl Service for DummyProtocolRequestHandler { + type Response = ProtocolResponse; + type Error = tower::BoxError; + type Future = Ready>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _: ProtocolRequest) -> Self::Future { + ready(Ok(ProtocolResponse::NA)) + } +} diff --git a/p2p/p2p-core/src/client/request_handler.rs b/p2p/p2p-core/src/client/request_handler.rs new file mode 100644 index 0000000..c2f3b8e --- /dev/null +++ b/p2p/p2p-core/src/client/request_handler.rs @@ -0,0 +1,128 @@ +use futures::TryFutureExt; +use tower::ServiceExt; + +use cuprate_wire::{ + admin::{ + PingResponse, SupportFlagsResponse, TimedSyncRequest, TimedSyncResponse, + PING_OK_RESPONSE_STATUS_TEXT, + }, + AdminRequestMessage, AdminResponseMessage, BasicNodeData, +}; + +use crate::{ + client::PeerInformation, + constants::MAX_PEERS_IN_PEER_LIST_MESSAGE, + services::{ + AddressBookRequest, AddressBookResponse, CoreSyncDataRequest, CoreSyncDataResponse, + }, + AddressBook, CoreSyncSvc, NetworkZone, PeerRequest, PeerResponse, ProtocolRequestHandler, +}; + +#[derive(thiserror::Error, Debug, Copy, Clone, Eq, PartialEq)] +enum PeerRequestHandlerError { + #[error("Received a handshake request during a connection.")] + ReceivedHandshakeDuringConnection, +} + +/// The peer request handler, handles incoming [`PeerRequest`]s to our node. +#[derive(Debug, Clone)] +pub(crate) struct PeerRequestHandler { + /// The address book service. + pub address_book_svc: A, + /// Our core sync service. + pub our_sync_svc: CS, + + /// The handler for [`ProtocolRequest`](crate::ProtocolRequest)s to our node. + pub protocol_request_handler: PR, + + /// The basic node data of our node. + pub our_basic_node_data: BasicNodeData, + + /// The information on the connected peer. + pub peer_info: PeerInformation, +} + +impl PeerRequestHandler +where + Z: NetworkZone, + A: AddressBook, + CS: CoreSyncSvc, + PR: ProtocolRequestHandler, +{ + /// Handles an incoming [`PeerRequest`] to our node. + pub(crate) async fn handle_peer_request( + &mut self, + req: PeerRequest, + ) -> Result { + match req { + PeerRequest::Admin(admin_req) => match admin_req { + AdminRequestMessage::Handshake(_) => { + Err(PeerRequestHandlerError::ReceivedHandshakeDuringConnection.into()) + } + AdminRequestMessage::SupportFlags => { + let support_flags = self.our_basic_node_data.support_flags; + + Ok(PeerResponse::Admin(AdminResponseMessage::SupportFlags( + SupportFlagsResponse { support_flags }, + ))) + } + AdminRequestMessage::Ping => Ok(PeerResponse::Admin(AdminResponseMessage::Ping( + PingResponse { + peer_id: self.our_basic_node_data.peer_id, + status: PING_OK_RESPONSE_STATUS_TEXT, + }, + ))), + AdminRequestMessage::TimedSync(timed_sync_req) => { + let res = self.handle_timed_sync_request(timed_sync_req).await?; + + Ok(PeerResponse::Admin(AdminResponseMessage::TimedSync(res))) + } + }, + + PeerRequest::Protocol(protocol_req) => { + // TODO: add limits here + + self.protocol_request_handler + .ready() + .await? + .call(protocol_req) + .map_ok(PeerResponse::Protocol) + .await + } + } + } + + /// Handles a [`TimedSyncRequest`] to our node. + async fn handle_timed_sync_request( + &mut self, + req: TimedSyncRequest, + ) -> Result { + // TODO: add a limit on the amount of these requests in a certain time period. + + *self.peer_info.core_sync_data.lock().unwrap() = req.payload_data; + + let AddressBookResponse::Peers(peers) = self + .address_book_svc + .ready() + .await? + .call(AddressBookRequest::GetWhitePeers( + MAX_PEERS_IN_PEER_LIST_MESSAGE, + )) + .await? + else { + panic!("Address book sent incorrect response!"); + }; + + let CoreSyncDataResponse(core_sync_data) = self + .our_sync_svc + .ready() + .await? + .call(CoreSyncDataRequest) + .await?; + + Ok(TimedSyncResponse { + payload_data: core_sync_data, + local_peerlist_new: peers.into_iter().map(Into::into).collect(), + }) + } +} diff --git a/p2p/p2p-core/src/client/timeout_monitor.rs b/p2p/p2p-core/src/client/timeout_monitor.rs index db261b4..d9703d6 100644 --- a/p2p/p2p-core/src/client/timeout_monitor.rs +++ b/p2p/p2p-core/src/client/timeout_monitor.rs @@ -1,6 +1,6 @@ //! Timeout Monitor //! -//! This module holds the task that sends periodic [TimedSync](PeerRequest::TimedSync) requests to a peer to make +//! This module holds the task that sends periodic [`TimedSync`](PeerRequest::TimedSync) requests to a peer to make //! sure the connection is still active. use std::sync::Arc; @@ -12,39 +12,38 @@ use tokio::{ use tower::ServiceExt; use tracing::instrument; -use cuprate_wire::admin::TimedSyncRequest; +use cuprate_wire::{admin::TimedSyncRequest, AdminRequestMessage, AdminResponseMessage}; use crate::{ - client::{connection::ConnectionTaskRequest, InternalPeerID}, + client::{connection::ConnectionTaskRequest, PeerInformation}, constants::{MAX_PEERS_IN_PEER_LIST_MESSAGE, TIMEOUT_INTERVAL}, - handles::ConnectionHandle, - services::{AddressBookRequest, CoreSyncDataRequest, CoreSyncDataResponse, PeerSyncRequest}, - AddressBook, CoreSyncSvc, NetworkZone, PeerRequest, PeerResponse, PeerSyncSvc, + services::{AddressBookRequest, CoreSyncDataRequest, CoreSyncDataResponse}, + AddressBook, CoreSyncSvc, NetworkZone, PeerRequest, PeerResponse, }; /// The timeout monitor task, this task will send periodic timed sync requests to the peer to make sure it is still active. #[instrument( name = "timeout_monitor", level = "debug", - fields(addr = %id), + fields(addr = %peer_information.id), skip_all, )] -pub async fn connection_timeout_monitor_task( - id: InternalPeerID, - handle: ConnectionHandle, +pub async fn connection_timeout_monitor_task( + peer_information: PeerInformation, connection_tx: mpsc::Sender, semaphore: Arc, mut address_book_svc: AdrBook, mut core_sync_svc: CSync, - mut peer_core_sync_svc: PSync, ) -> Result<(), tower::BoxError> where AdrBook: AddressBook, CSync: CoreSyncSvc, - PSync: PeerSyncSvc, { + let connection_tx_weak = connection_tx.downgrade(); + drop(connection_tx); + // Instead of tracking the time from last message from the peer and sending a timed sync if this value is too high, // we just send a timed sync every [TIMEOUT_INTERVAL] seconds. let mut interval = interval(TIMEOUT_INTERVAL); @@ -59,12 +58,12 @@ where tracing::trace!("timeout monitor tick."); - if connection_tx.is_closed() { + let Some(connection_tx) = connection_tx_weak.upgrade() else { tracing::debug!("Closing timeout monitor, connection disconnected."); return Ok(()); - } + }; - let Ok(permit) = semaphore.clone().try_acquire_owned() else { + let Ok(permit) = Arc::clone(&semaphore).try_acquire_owned() else { // If we can't get a permit the connection is currently waiting for a response, so no need to // do a timed sync. continue; @@ -87,15 +86,15 @@ where tracing::debug!(parent: &ping_span, "Sending timed sync to peer"); connection_tx .send(ConnectionTaskRequest { - request: PeerRequest::TimedSync(TimedSyncRequest { + request: PeerRequest::Admin(AdminRequestMessage::TimedSync(TimedSyncRequest { payload_data: core_sync_data, - }), + })), response_channel: tx, permit: Some(permit), }) .await?; - let PeerResponse::TimedSync(timed_sync) = rx.await?? else { + let PeerResponse::Admin(AdminResponseMessage::TimedSync(timed_sync)) = rx.await?? else { panic!("Connection task returned wrong response!"); }; @@ -122,15 +121,6 @@ where )) .await?; - // Tell the peer sync service about the peers core sync data - peer_core_sync_svc - .ready() - .await? - .call(PeerSyncRequest::IncomingCoreSyncData( - id, - handle.clone(), - timed_sync.payload_data, - )) - .await?; + *peer_information.core_sync_data.lock().unwrap() = timed_sync.payload_data; } } diff --git a/p2p/p2p-core/src/client/weak.rs b/p2p/p2p-core/src/client/weak.rs new file mode 100644 index 0000000..90f25dd --- /dev/null +++ b/p2p/p2p-core/src/client/weak.rs @@ -0,0 +1,114 @@ +use std::task::{ready, Context, Poll}; + +use futures::channel::oneshot; +use tokio::sync::{mpsc, OwnedSemaphorePermit}; +use tokio_util::sync::PollSemaphore; +use tower::Service; + +use cuprate_helper::asynch::InfallibleOneshotReceiver; + +use crate::{ + client::{connection, PeerInformation}, + NetworkZone, PeerError, PeerRequest, PeerResponse, SharedError, +}; + +/// A weak handle to a [`Client`](super::Client). +/// +/// When this is dropped the peer will not be disconnected. +pub struct WeakClient { + /// Information on the connected peer. + pub info: PeerInformation, + + /// The channel to the [`Connection`](connection::Connection) task. + pub(super) connection_tx: mpsc::WeakSender, + + /// The semaphore that limits the requests sent to the peer. + pub(super) semaphore: PollSemaphore, + /// A permit for the semaphore, will be [`Some`] after `poll_ready` returns ready. + pub(super) permit: Option, + + /// The error slot shared between the [`Client`] and [`Connection`](connection::Connection). + pub(super) error: SharedError, +} + +impl WeakClient { + /// Internal function to set an error on the [`SharedError`]. + fn set_err(&self, err: PeerError) -> tower::BoxError { + let err_str = err.to_string(); + match self.error.try_insert_err(err) { + Ok(()) => err_str, + Err(e) => e.to_string(), + } + .into() + } +} + +impl Service for WeakClient { + type Response = PeerResponse; + type Error = tower::BoxError; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + if let Some(err) = self.error.try_get_err() { + return Poll::Ready(Err(err.to_string().into())); + } + + if self.connection_tx.strong_count() == 0 { + let err = self.set_err(PeerError::ClientChannelClosed); + return Poll::Ready(Err(err)); + } + + if self.permit.is_some() { + return Poll::Ready(Ok(())); + } + + let permit = ready!(self.semaphore.poll_acquire(cx)) + .expect("Client semaphore should not be closed!"); + + self.permit = Some(permit); + + Poll::Ready(Ok(())) + } + + #[expect(clippy::significant_drop_tightening)] + fn call(&mut self, request: PeerRequest) -> Self::Future { + let permit = self + .permit + .take() + .expect("poll_ready did not return ready before call to call"); + + let (tx, rx) = oneshot::channel(); + let req = connection::ConnectionTaskRequest { + response_channel: tx, + request, + permit: Some(permit), + }; + + match self.connection_tx.upgrade() { + None => { + self.set_err(PeerError::ClientChannelClosed); + + let resp = Err(PeerError::ClientChannelClosed.into()); + drop(req.response_channel.send(resp)); + } + Some(sender) => { + if let Err(e) = sender.try_send(req) { + // The connection task could have closed between a call to `poll_ready` and the call to + // `call`, which means if we don't handle the error here the receiver would panic. + use mpsc::error::TrySendError; + + match e { + TrySendError::Closed(req) | TrySendError::Full(req) => { + self.set_err(PeerError::ClientChannelClosed); + + let resp = Err(PeerError::ClientChannelClosed.into()); + drop(req.response_channel.send(resp)); + } + } + } + } + } + + rx.into() + } +} diff --git a/p2p/p2p-core/src/error.rs b/p2p/p2p-core/src/error.rs index 65303ad..d0de923 100644 --- a/p2p/p2p-core/src/error.rs +++ b/p2p/p2p-core/src/error.rs @@ -4,7 +4,7 @@ pub struct SharedError(Arc>); impl Clone for SharedError { fn clone(&self) -> Self { - Self(self.0.clone()) + Self(Arc::clone(&self.0)) } } diff --git a/p2p/p2p-core/src/handles.rs b/p2p/p2p-core/src/handles.rs index f383170..06dc212 100644 --- a/p2p/p2p-core/src/handles.rs +++ b/p2p/p2p-core/src/handles.rs @@ -18,15 +18,14 @@ pub struct HandleBuilder { impl HandleBuilder { /// Create a new builder. - pub fn new() -> Self { + pub const fn new() -> Self { Self { permit: None } } /// Sets the permit for this connection. - /// - /// This must be called at least once. - pub fn with_permit(mut self, permit: OwnedSemaphorePermit) -> Self { - self.permit = Some(permit); + #[must_use] + pub fn with_permit(mut self, permit: Option) -> Self { + self.permit = permit; self } @@ -39,10 +38,10 @@ impl HandleBuilder { ( ConnectionGuard { token: token.clone(), - _permit: self.permit.expect("connection permit was not set!"), + _permit: self.permit, }, ConnectionHandle { - token: token.clone(), + token, ban: Arc::new(OnceLock::new()), }, ) @@ -56,7 +55,7 @@ pub struct BanPeer(pub Duration); /// A struct given to the connection task. pub struct ConnectionGuard { token: CancellationToken, - _permit: OwnedSemaphorePermit, + _permit: Option, } impl ConnectionGuard { @@ -68,13 +67,13 @@ impl ConnectionGuard { /// /// This will be called on [`Drop::drop`]. pub fn connection_closed(&self) { - self.token.cancel() + self.token.cancel(); } } impl Drop for ConnectionGuard { fn drop(&mut self) { - self.token.cancel() + self.token.cancel(); } } @@ -92,6 +91,10 @@ impl ConnectionHandle { } /// Bans the peer for the given `duration`. pub fn ban_peer(&self, duration: Duration) { + #[expect( + clippy::let_underscore_must_use, + reason = "error means peer is already banned; fine to ignore" + )] let _ = self.ban.set(BanPeer(duration)); self.token.cancel(); } @@ -105,6 +108,6 @@ impl ConnectionHandle { } /// Sends the signal to the connection task to disconnect. pub fn send_close_signal(&self) { - self.token.cancel() + self.token.cancel(); } } diff --git a/p2p/p2p-core/src/lib.rs b/p2p/p2p-core/src/lib.rs index 8703d59..e574693 100644 --- a/p2p/p2p-core/src/lib.rs +++ b/p2p/p2p-core/src/lib.rs @@ -1,4 +1,4 @@ -//! # Monero P2P +//! # Cuprate P2P Core //! //! This crate is general purpose P2P networking library for working with Monero. This is a low level //! crate, which means it may seem verbose for a lot of use cases, if you want a crate that handles @@ -6,13 +6,67 @@ //! //! # Network Zones //! -//! This crate abstracts over network zones, Tor/I2p/clearnet with the [NetworkZone] trait. Currently only clearnet is implemented: [ClearNet](network_zones::ClearNet). +//! This crate abstracts over network zones, Tor/I2p/clearnet with the [`NetworkZone`] trait. Currently only clearnet is implemented: [`ClearNet`]. //! //! # Usage //! -//! TODO +//! ## Connecting to a peer //! -use std::{fmt::Debug, future::Future, hash::Hash, pin::Pin}; +//! ```rust +//! # use std::{net::SocketAddr, str::FromStr}; +//! # +//! # use tower::ServiceExt; +//! # +//! # use cuprate_p2p_core::{ +//! # client::{ConnectRequest, Connector, HandshakerBuilder}, +//! # ClearNet, Network, +//! # }; +//! # use cuprate_wire::{common::PeerSupportFlags, BasicNodeData}; +//! # use cuprate_test_utils::monerod::monerod; +//! # +//! # tokio_test::block_on(async move { +//! # +//! # let _monerod = monerod::<&str>([]).await; +//! # let addr = _monerod.p2p_addr(); +//! # +//! // The information about our local node. +//! let our_basic_node_data = BasicNodeData { +//! my_port: 0, +//! network_id: Network::Mainnet.network_id(), +//! peer_id: 0, +//! support_flags: PeerSupportFlags::FLUFFY_BLOCKS, +//! rpc_port: 0, +//! rpc_credits_per_hash: 0, +//! }; +//! +//! // See [`HandshakerBuilder`] for information about the default values set, they may not be +//! // appropriate for every use case. +//! let handshaker = HandshakerBuilder::::new(our_basic_node_data).build(); +//! +//! // The outbound connector. +//! let mut connector = Connector::new(handshaker); +//! +//! // The connection. +//! let connection = connector +//! .oneshot(ConnectRequest { +//! addr, +//! permit: None, +//! }) +//! .await +//! .unwrap(); +//! # }); +//! ``` + +cfg_if::cfg_if! { + // Used in `tests/` + if #[cfg(test)] { + use cuprate_test_utils as _; + use tokio_test as _; + use hex as _; + } +} + +use std::{fmt::Debug, hash::Hash}; use futures::{Sink, Stream}; @@ -25,21 +79,29 @@ pub mod client; mod constants; pub mod error; pub mod handles; -pub mod network_zones; +mod network_zones; pub mod protocol; pub mod services; +pub mod types; pub use error::*; +pub use network_zones::{ClearNet, ClearNetServerCfg}; pub use protocol::*; use services::*; +//re-export +pub use cuprate_helper::network::Network; +pub use cuprate_wire::CoreSyncData; +/// The direction of a connection. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ConnectionDirection { - InBound, - OutBound, + /// An inbound connection to our node. + Inbound, + /// An outbound connection from our node. + Outbound, } -#[cfg(not(feature = "borsh"))] +/// An address on a specific [`NetworkZone`]. pub trait NetZoneAddress: TryFrom + Into @@ -52,50 +114,22 @@ pub trait NetZoneAddress: + Unpin + 'static { - /// Cuprate needs to be able to ban peers by IP addresses and not just by SocketAddr as + /// Cuprate needs to be able to ban peers by IP addresses and not just by `SocketAddr` as /// that include the port, to be able to facilitate this network addresses must have a ban ID /// which for hidden services could just be the address it self but for clear net addresses will /// be the IP address. - /// TODO: IP zone banning? - type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static; - - /// Changes the port of this address to `port`. - fn set_port(&mut self, port: u16); - - fn make_canonical(&mut self); - - fn ban_id(&self) -> Self::BanID; - - fn should_add_to_peer_list(&self) -> bool; -} - -#[cfg(feature = "borsh")] -pub trait NetZoneAddress: - TryFrom - + Into - + std::fmt::Display - + borsh::BorshSerialize - + borsh::BorshDeserialize - + Hash - + Eq - + Copy - + Send - + Sync - + Unpin - + 'static -{ - /// Cuprate needs to be able to ban peers by IP addresses and not just by SocketAddr as - /// that include the port, to be able to facilitate this network addresses must have a ban ID - /// which for hidden services could just be the address it self but for clear net addresses will - /// be the IP address. - /// TODO: IP zone banning? + /// + /// - TODO: IP zone banning? + /// - TODO: rename this to Host. type BanID: Debug + Hash + Eq + Clone + Copy + Send + 'static; /// Changes the port of this address to `port`. fn set_port(&mut self, port: u16); + /// Turns this address into its canonical form. fn make_canonical(&mut self); + /// Returns the [`Self::BanID`] for this address. fn ban_id(&self) -> Self::BanID; fn should_add_to_peer_list(&self) -> bool; @@ -106,21 +140,11 @@ pub trait NetZoneAddress: pub trait NetworkZone: Clone + Copy + Send + 'static { /// The network name. const NAME: &'static str; - /// Allow syncing over this network. - /// - /// Not recommended for anonymity networks. - const ALLOW_SYNC: bool; - /// Enable dandelion++ for this network. - /// - /// This is unneeded on anonymity networks. - const DANDELION_PP: bool; /// Check if our node ID matches the incoming peers node ID for this network. /// /// This has privacy implications on an anonymity network if true so should be set /// to false. const CHECK_NODE_ID: bool; - /// Fixed seed nodes for this network. - const SEEDS: &'static [Self::Addr]; /// The address type of this network. type Addr: NetZoneAddress; @@ -136,6 +160,15 @@ pub trait NetworkZone: Clone + Copy + Send + 'static { /// Config used to start a server which listens for incoming connections. type ServerCfg: Clone + Debug + Send + 'static; + /// Connects to a peer with the given address. + /// + ///
+ /// + /// This does not complete a handshake with the peer, to do that see the [crate](crate) docs. + /// + ///
+ /// + /// Returns the [`Self::Stream`] and [`Self::Sink`] to send messages to the peer. async fn connect_to_peer( addr: Self::Addr, ) -> Result<(Self::Stream, Self::Sink), std::io::Error>; @@ -150,55 +183,26 @@ pub trait NetworkZone: Clone + Copy + Send + 'static { // Below here is just helper traits, so we don't have to type out tower::Service bounds // everywhere but still get to use tower. -pub trait PeerSyncSvc: - tower::Service< - PeerSyncRequest, - Response = PeerSyncResponse, - Error = tower::BoxError, - Future = Self::Future2, - > + Send - + 'static -{ - // This allows us to put more restrictive bounds on the future without defining the future here - // explicitly. - type Future2: Future> + Send + 'static; -} - -impl PeerSyncSvc for T -where - T: tower::Service, Response = PeerSyncResponse, Error = tower::BoxError> - + Send - + 'static, - T::Future: Future> + Send + 'static, -{ - type Future2 = T::Future; -} - pub trait AddressBook: tower::Service< AddressBookRequest, Response = AddressBookResponse, Error = tower::BoxError, - Future = Self::Future2, + Future: Send + 'static, > + Send + 'static { - // This allows us to put more restrictive bounds on the future without defining the future here - // explicitly. - type Future2: Future> + Send + 'static; } -impl AddressBook for T -where +impl AddressBook for T where T: tower::Service< AddressBookRequest, Response = AddressBookResponse, Error = tower::BoxError, + Future: Send + 'static, > + Send - + 'static, - T::Future: Future> + Send + 'static, + + 'static { - type Future2 = T::Future; } pub trait CoreSyncSvc: @@ -206,11 +210,7 @@ pub trait CoreSyncSvc: CoreSyncDataRequest, Response = CoreSyncDataResponse, Error = tower::BoxError, - Future = Pin< - Box< - dyn Future> + Send + 'static, - >, - >, + Future: Send + 'static, > + Send + 'static { @@ -221,39 +221,53 @@ impl CoreSyncSvc for T where CoreSyncDataRequest, Response = CoreSyncDataResponse, Error = tower::BoxError, - Future = Pin< - Box< - dyn Future> - + Send - + 'static, - >, - >, + Future: Send + 'static, > + Send + 'static { } -pub trait PeerRequestHandler: +pub trait ProtocolRequestHandler: tower::Service< - PeerRequest, - Response = PeerResponse, + ProtocolRequest, + Response = ProtocolResponse, Error = tower::BoxError, - Future = Pin< - Box> + Send + 'static>, - >, + Future: Send + 'static, > + Send + 'static { } -impl PeerRequestHandler for T where +impl ProtocolRequestHandler for T where T: tower::Service< - PeerRequest, - Response = PeerResponse, + ProtocolRequest, + Response = ProtocolResponse, Error = tower::BoxError, - Future = Pin< - Box> + Send + 'static>, - >, + Future: Send + 'static, + > + Send + + 'static +{ +} + +pub trait ProtocolRequestHandlerMaker: + tower::MakeService< + client::PeerInformation, + ProtocolRequest, + MakeError = tower::BoxError, + Service: ProtocolRequestHandler, + Future: Send + 'static, + > + Send + + 'static +{ +} + +impl ProtocolRequestHandlerMaker for T where + T: tower::MakeService< + client::PeerInformation, + ProtocolRequest, + MakeError = tower::BoxError, + Service: ProtocolRequestHandler, + Future: Send + 'static, > + Send + 'static { diff --git a/p2p/p2p-core/src/network_zones/clear.rs b/p2p/p2p-core/src/network_zones/clear.rs index 192e363..cb3c559 100644 --- a/p2p/p2p-core/src/network_zones/clear.rs +++ b/p2p/p2p-core/src/network_zones/clear.rs @@ -1,5 +1,5 @@ use std::{ - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, SocketAddr}, pin::Pin, task::{Context, Poll}, }; @@ -19,7 +19,7 @@ impl NetZoneAddress for SocketAddr { type BanID = IpAddr; fn set_port(&mut self, port: u16) { - SocketAddr::set_port(self, port) + Self::set_port(self, port); } fn ban_id(&self) -> Self::BanID { @@ -45,21 +45,10 @@ pub struct ClearNetServerCfg { #[derive(Clone, Copy)] pub enum ClearNet {} -const fn ip_v4(a: u8, b: u8, c: u8, d: u8, port: u16) -> SocketAddr { - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(a, b, c, d)), port) -} - #[async_trait::async_trait] impl NetworkZone for ClearNet { const NAME: &'static str = "ClearNet"; - const SEEDS: &'static [Self::Addr] = &[ - ip_v4(37, 187, 74, 171, 18080), - ip_v4(192, 99, 8, 110, 18080), - ]; - - const ALLOW_SYNC: bool = true; - const DANDELION_PP: bool = true; const CHECK_NODE_ID: bool = true; type Addr = SocketAddr; diff --git a/p2p/p2p-core/src/protocol.rs b/p2p/p2p-core/src/protocol.rs index 172038f..82aac82 100644 --- a/p2p/p2p-core/src/protocol.rs +++ b/p2p/p2p-core/src/protocol.rs @@ -1,13 +1,16 @@ -//! This module defines InternalRequests and InternalResponses. Cuprate's P2P works by translating network messages into an internal -//! request/ response, this is easy for levin "requests" and "responses" (admin messages) but takes a bit more work with "notifications" +//! This module defines [`PeerRequest`] and [`PeerResponse`]. Cuprate's P2P crates works by translating network messages into an internal +//! request/response enums, this is easy for levin "requests" and "responses" (admin messages) but takes a bit more work with "notifications" //! (protocol messages). //! -//! Some notifications are easy to translate, like `GetObjectsRequest` is obviously a request but others like `NewFluffyBlock` are a -//! bit tri cker. To translate a `NewFluffyBlock` into a request/ response we will have to look to see if we asked for `FluffyMissingTransactionsRequest` -//! if we have we interpret `NewFluffyBlock` as a response if not its a request that doesn't require a response. +//! Some notifications are easy to translate, like [`GetObjectsRequest`] is obviously a request but others like [`NewFluffyBlock`] are a +//! bit tricker. To translate a [`NewFluffyBlock`] into a request/ response we will have to look to see if we asked for [`FluffyMissingTransactionsRequest`], +//! if we have, we interpret [`NewFluffyBlock`] as a response, if not, it's a request that doesn't require a response. //! -//! Here is every P2P request/ response. *note admin messages are already request/ response so "Handshake" is actually made of a HandshakeRequest & HandshakeResponse +//! Here is every P2P request/response. //! +//! *note admin messages are already request/response so "Handshake" is actually made of a `HandshakeRequest` & `HandshakeResponse` +//! +//! ```md //! Admin: //! Handshake, //! TimedSync, @@ -21,16 +24,14 @@ //! Request: NewBlock, Response: None, //! Request: NewFluffyBlock, Response: None, //! Request: NewTransactions, Response: None +//!``` //! use cuprate_wire::{ - admin::{ - HandshakeRequest, HandshakeResponse, PingResponse, SupportFlagsResponse, TimedSyncRequest, - TimedSyncResponse, - }, protocol::{ ChainRequest, ChainResponse, FluffyMissingTransactionsRequest, GetObjectsRequest, GetObjectsResponse, GetTxPoolCompliment, NewBlock, NewFluffyBlock, NewTransactions, }, + AdminRequestMessage, AdminResponseMessage, }; mod try_from; @@ -60,12 +61,7 @@ pub enum BroadcastMessage { } #[derive(Debug, Clone)] -pub enum PeerRequest { - Handshake(HandshakeRequest), - TimedSync(TimedSyncRequest), - Ping, - SupportFlags, - +pub enum ProtocolRequest { GetObjects(GetObjectsRequest), GetChain(ChainRequest), FluffyMissingTxs(FluffyMissingTransactionsRequest), @@ -75,62 +71,81 @@ pub enum PeerRequest { NewTransactions(NewTransactions), } -impl PeerRequest { - pub fn id(&self) -> MessageID { - match self { - PeerRequest::Handshake(_) => MessageID::Handshake, - PeerRequest::TimedSync(_) => MessageID::TimedSync, - PeerRequest::Ping => MessageID::Ping, - PeerRequest::SupportFlags => MessageID::SupportFlags, +#[derive(Debug, Clone)] +pub enum PeerRequest { + Admin(AdminRequestMessage), + Protocol(ProtocolRequest), +} - PeerRequest::GetObjects(_) => MessageID::GetObjects, - PeerRequest::GetChain(_) => MessageID::GetChain, - PeerRequest::FluffyMissingTxs(_) => MessageID::FluffyMissingTxs, - PeerRequest::GetTxPoolCompliment(_) => MessageID::GetTxPoolCompliment, - PeerRequest::NewBlock(_) => MessageID::NewBlock, - PeerRequest::NewFluffyBlock(_) => MessageID::NewFluffyBlock, - PeerRequest::NewTransactions(_) => MessageID::NewTransactions, +impl PeerRequest { + pub const fn id(&self) -> MessageID { + match self { + Self::Admin(admin_req) => match admin_req { + AdminRequestMessage::Handshake(_) => MessageID::Handshake, + AdminRequestMessage::TimedSync(_) => MessageID::TimedSync, + AdminRequestMessage::Ping => MessageID::Ping, + AdminRequestMessage::SupportFlags => MessageID::SupportFlags, + }, + Self::Protocol(protocol_request) => match protocol_request { + ProtocolRequest::GetObjects(_) => MessageID::GetObjects, + ProtocolRequest::GetChain(_) => MessageID::GetChain, + ProtocolRequest::FluffyMissingTxs(_) => MessageID::FluffyMissingTxs, + ProtocolRequest::GetTxPoolCompliment(_) => MessageID::GetTxPoolCompliment, + ProtocolRequest::NewBlock(_) => MessageID::NewBlock, + ProtocolRequest::NewFluffyBlock(_) => MessageID::NewFluffyBlock, + ProtocolRequest::NewTransactions(_) => MessageID::NewTransactions, + }, } } - pub fn needs_response(&self) -> bool { + pub const fn needs_response(&self) -> bool { !matches!( self, - PeerRequest::NewBlock(_) - | PeerRequest::NewFluffyBlock(_) - | PeerRequest::NewTransactions(_) + Self::Protocol( + ProtocolRequest::NewBlock(_) + | ProtocolRequest::NewFluffyBlock(_) + | ProtocolRequest::NewTransactions(_) + ) ) } } #[derive(Debug, Clone)] -pub enum PeerResponse { - Handshake(HandshakeResponse), - TimedSync(TimedSyncResponse), - Ping(PingResponse), - SupportFlags(SupportFlagsResponse), - +pub enum ProtocolResponse { GetObjects(GetObjectsResponse), GetChain(ChainResponse), NewFluffyBlock(NewFluffyBlock), NewTransactions(NewTransactions), + FluffyMissingTransactionsRequest(FluffyMissingTransactionsRequest), NA, } +#[derive(Debug, Clone)] +pub enum PeerResponse { + Admin(AdminResponseMessage), + Protocol(ProtocolResponse), +} + impl PeerResponse { - pub fn id(&self) -> MessageID { - match self { - PeerResponse::Handshake(_) => MessageID::Handshake, - PeerResponse::TimedSync(_) => MessageID::TimedSync, - PeerResponse::Ping(_) => MessageID::Ping, - PeerResponse::SupportFlags(_) => MessageID::SupportFlags, + pub const fn id(&self) -> Option { + Some(match self { + Self::Admin(admin_res) => match admin_res { + AdminResponseMessage::Handshake(_) => MessageID::Handshake, + AdminResponseMessage::TimedSync(_) => MessageID::TimedSync, + AdminResponseMessage::Ping(_) => MessageID::Ping, + AdminResponseMessage::SupportFlags(_) => MessageID::SupportFlags, + }, + Self::Protocol(protocol_res) => match protocol_res { + ProtocolResponse::GetObjects(_) => MessageID::GetObjects, + ProtocolResponse::GetChain(_) => MessageID::GetChain, + ProtocolResponse::NewFluffyBlock(_) => MessageID::NewBlock, + ProtocolResponse::NewTransactions(_) => MessageID::NewFluffyBlock, + ProtocolResponse::FluffyMissingTransactionsRequest(_) => { + MessageID::FluffyMissingTxs + } - PeerResponse::GetObjects(_) => MessageID::GetObjects, - PeerResponse::GetChain(_) => MessageID::GetChain, - PeerResponse::NewFluffyBlock(_) => MessageID::NewBlock, - PeerResponse::NewTransactions(_) => MessageID::NewFluffyBlock, - - PeerResponse::NA => panic!("Can't get message ID for a non existent response"), - } + ProtocolResponse::NA => return None, + }, + }) } } diff --git a/p2p/p2p-core/src/protocol/try_from.rs b/p2p/p2p-core/src/protocol/try_from.rs index 8e3d026..2dfc41d 100644 --- a/p2p/p2p-core/src/protocol/try_from.rs +++ b/p2p/p2p-core/src/protocol/try_from.rs @@ -1,150 +1,110 @@ //! This module contains the implementations of [`TryFrom`] and [`From`] to convert between //! [`Message`], [`PeerRequest`] and [`PeerResponse`]. -use cuprate_wire::{Message, ProtocolMessage, RequestMessage, ResponseMessage}; +use cuprate_wire::{Message, ProtocolMessage}; -use super::{PeerRequest, PeerResponse}; +use crate::{PeerRequest, PeerResponse, ProtocolRequest, ProtocolResponse}; #[derive(Debug)] pub struct MessageConversionError; -macro_rules! match_body { - (match $value: ident {$($body:tt)*} ($left:pat => $right_ty:expr) $($todo:tt)*) => { - match_body!( match $value { - $left => $right_ty, - $($body)* - } $($todo)* ) - }; - (match $value: ident {$($body:tt)*}) => { - match $value { - $($body)* +impl From for ProtocolMessage { + fn from(value: ProtocolRequest) -> Self { + match value { + ProtocolRequest::GetObjects(val) => Self::GetObjectsRequest(val), + ProtocolRequest::GetChain(val) => Self::ChainRequest(val), + ProtocolRequest::FluffyMissingTxs(val) => Self::FluffyMissingTransactionsRequest(val), + ProtocolRequest::GetTxPoolCompliment(val) => Self::GetTxPoolCompliment(val), + ProtocolRequest::NewBlock(val) => Self::NewBlock(val), + ProtocolRequest::NewFluffyBlock(val) => Self::NewFluffyBlock(val), + ProtocolRequest::NewTransactions(val) => Self::NewTransactions(val), } - }; + } } -macro_rules! from { - ($left_ty:ident, $right_ty:ident, {$($left:ident $(($val: ident))? = $right:ident $(($vall: ident))?,)+}) => { - impl From<$left_ty> for $right_ty { - fn from(value: $left_ty) -> Self { - match_body!( match value {} - $(($left_ty::$left$(($val))? => $right_ty::$right$(($vall))?))+ - ) - } - } - }; -} - -macro_rules! try_from { - ($left_ty:ident, $right_ty:ident, {$($left:ident $(($val: ident))? = $right:ident $(($vall: ident))?,)+}) => { - impl TryFrom<$left_ty> for $right_ty { - type Error = MessageConversionError; - - fn try_from(value: $left_ty) -> Result { - Ok(match_body!( match value { - _ => return Err(MessageConversionError) - } - $(($left_ty::$left$(($val))? => $right_ty::$right$(($vall))?))+ - )) - } - } - }; -} - -macro_rules! from_try_from { - ($left_ty:ident, $right_ty:ident, {$($left:ident $(($val: ident))? = $right:ident $(($vall: ident))?,)+}) => { - try_from!($left_ty, $right_ty, {$($left $(($val))? = $right $(($vall))?,)+}); - from!($right_ty, $left_ty, {$($right $(($val))? = $left $(($vall))?,)+}); - }; -} - -macro_rules! try_from_try_from { - ($left_ty:ident, $right_ty:ident, {$($left:ident $(($val: ident))? = $right:ident $(($vall: ident))?,)+}) => { - try_from!($left_ty, $right_ty, {$($left $(($val))? = $right $(($vall))?,)+}); - try_from!($right_ty, $left_ty, {$($right $(($val))? = $left $(($val))?,)+}); - }; -} - -from_try_from!(PeerRequest, RequestMessage,{ - Handshake(val) = Handshake(val), - Ping = Ping, - SupportFlags = SupportFlags, - TimedSync(val) = TimedSync(val), -}); - -try_from_try_from!(PeerRequest, ProtocolMessage,{ - NewBlock(val) = NewBlock(val), - NewFluffyBlock(val) = NewFluffyBlock(val), - GetObjects(val) = GetObjectsRequest(val), - GetChain(val) = ChainRequest(val), - NewTransactions(val) = NewTransactions(val), - FluffyMissingTxs(val) = FluffyMissingTransactionsRequest(val), - GetTxPoolCompliment(val) = GetTxPoolCompliment(val), -}); - -impl TryFrom for PeerRequest { +impl TryFrom for ProtocolRequest { type Error = MessageConversionError; - fn try_from(value: Message) -> Result { - match value { - Message::Request(req) => Ok(req.into()), - Message::Protocol(pro) => pro.try_into(), - _ => Err(MessageConversionError), - } + fn try_from(value: ProtocolMessage) -> Result { + Ok(match value { + ProtocolMessage::GetObjectsRequest(val) => Self::GetObjects(val), + ProtocolMessage::ChainRequest(val) => Self::GetChain(val), + ProtocolMessage::FluffyMissingTransactionsRequest(val) => Self::FluffyMissingTxs(val), + ProtocolMessage::GetTxPoolCompliment(val) => Self::GetTxPoolCompliment(val), + ProtocolMessage::NewBlock(val) => Self::NewBlock(val), + ProtocolMessage::NewFluffyBlock(val) => Self::NewFluffyBlock(val), + ProtocolMessage::NewTransactions(val) => Self::NewTransactions(val), + ProtocolMessage::GetObjectsResponse(_) | ProtocolMessage::ChainEntryResponse(_) => { + return Err(MessageConversionError) + } + }) } } impl From for Message { fn from(value: PeerRequest) -> Self { match value { - PeerRequest::Handshake(val) => Message::Request(RequestMessage::Handshake(val)), - PeerRequest::Ping => Message::Request(RequestMessage::Ping), - PeerRequest::SupportFlags => Message::Request(RequestMessage::SupportFlags), - PeerRequest::TimedSync(val) => Message::Request(RequestMessage::TimedSync(val)), - - PeerRequest::NewBlock(val) => Message::Protocol(ProtocolMessage::NewBlock(val)), - PeerRequest::NewFluffyBlock(val) => { - Message::Protocol(ProtocolMessage::NewFluffyBlock(val)) - } - PeerRequest::GetObjects(val) => { - Message::Protocol(ProtocolMessage::GetObjectsRequest(val)) - } - PeerRequest::GetChain(val) => Message::Protocol(ProtocolMessage::ChainRequest(val)), - PeerRequest::NewTransactions(val) => { - Message::Protocol(ProtocolMessage::NewTransactions(val)) - } - PeerRequest::FluffyMissingTxs(val) => { - Message::Protocol(ProtocolMessage::FluffyMissingTransactionsRequest(val)) - } - PeerRequest::GetTxPoolCompliment(val) => { - Message::Protocol(ProtocolMessage::GetTxPoolCompliment(val)) - } + PeerRequest::Admin(val) => Self::Request(val), + PeerRequest::Protocol(val) => Self::Protocol(val.into()), } } } -from_try_from!(PeerResponse, ResponseMessage,{ - Handshake(val) = Handshake(val), - Ping(val) = Ping(val), - SupportFlags(val) = SupportFlags(val), - TimedSync(val) = TimedSync(val), -}); +impl TryFrom for PeerRequest { + type Error = MessageConversionError; -try_from_try_from!(PeerResponse, ProtocolMessage,{ - NewFluffyBlock(val) = NewFluffyBlock(val), - GetObjects(val) = GetObjectsResponse(val), - GetChain(val) = ChainEntryResponse(val), - NewTransactions(val) = NewTransactions(val), + fn try_from(value: Message) -> Result { + match value { + Message::Request(req) => Ok(Self::Admin(req)), + Message::Protocol(pro) => Ok(Self::Protocol(pro.try_into()?)), + Message::Response(_) => Err(MessageConversionError), + } + } +} -}); +impl TryFrom for ProtocolMessage { + type Error = MessageConversionError; + + fn try_from(value: ProtocolResponse) -> Result { + Ok(match value { + ProtocolResponse::NewTransactions(val) => Self::NewTransactions(val), + ProtocolResponse::NewFluffyBlock(val) => Self::NewFluffyBlock(val), + ProtocolResponse::GetChain(val) => Self::ChainEntryResponse(val), + ProtocolResponse::GetObjects(val) => Self::GetObjectsResponse(val), + ProtocolResponse::FluffyMissingTransactionsRequest(val) => { + Self::FluffyMissingTransactionsRequest(val) + } + ProtocolResponse::NA => return Err(MessageConversionError), + }) + } +} + +impl TryFrom for ProtocolResponse { + type Error = MessageConversionError; + + fn try_from(value: ProtocolMessage) -> Result { + Ok(match value { + ProtocolMessage::NewTransactions(val) => Self::NewTransactions(val), + ProtocolMessage::NewFluffyBlock(val) => Self::NewFluffyBlock(val), + ProtocolMessage::ChainEntryResponse(val) => Self::GetChain(val), + ProtocolMessage::GetObjectsResponse(val) => Self::GetObjects(val), + ProtocolMessage::ChainRequest(_) + | ProtocolMessage::FluffyMissingTransactionsRequest(_) + | ProtocolMessage::GetObjectsRequest(_) + | ProtocolMessage::GetTxPoolCompliment(_) + | ProtocolMessage::NewBlock(_) => return Err(MessageConversionError), + }) + } +} impl TryFrom for PeerResponse { type Error = MessageConversionError; fn try_from(value: Message) -> Result { match value { - Message::Response(res) => Ok(res.into()), - Message::Protocol(pro) => pro.try_into(), - _ => Err(MessageConversionError), + Message::Response(res) => Ok(Self::Admin(res)), + Message::Protocol(pro) => Ok(Self::Protocol(pro.try_into()?)), + Message::Request(_) => Err(MessageConversionError), } } } @@ -154,27 +114,8 @@ impl TryFrom for Message { fn try_from(value: PeerResponse) -> Result { Ok(match value { - PeerResponse::Handshake(val) => Message::Response(ResponseMessage::Handshake(val)), - PeerResponse::Ping(val) => Message::Response(ResponseMessage::Ping(val)), - PeerResponse::SupportFlags(val) => { - Message::Response(ResponseMessage::SupportFlags(val)) - } - PeerResponse::TimedSync(val) => Message::Response(ResponseMessage::TimedSync(val)), - - PeerResponse::NewFluffyBlock(val) => { - Message::Protocol(ProtocolMessage::NewFluffyBlock(val)) - } - PeerResponse::GetObjects(val) => { - Message::Protocol(ProtocolMessage::GetObjectsResponse(val)) - } - PeerResponse::GetChain(val) => { - Message::Protocol(ProtocolMessage::ChainEntryResponse(val)) - } - PeerResponse::NewTransactions(val) => { - Message::Protocol(ProtocolMessage::NewTransactions(val)) - } - - PeerResponse::NA => return Err(MessageConversionError), + PeerResponse::Admin(val) => Self::Response(val), + PeerResponse::Protocol(val) => Self::Protocol(val.try_into()?), }) } } diff --git a/p2p/p2p-core/src/services.rs b/p2p/p2p-core/src/services.rs index 6fd6c15..6d1089c 100644 --- a/p2p/p2p-core/src/services.rs +++ b/p2p/p2p-core/src/services.rs @@ -1,35 +1,25 @@ +use std::time::Instant; + use cuprate_pruning::{PruningError, PruningSeed}; use cuprate_wire::{CoreSyncData, PeerListEntryBase}; use crate::{ - client::InternalPeerID, handles::ConnectionHandle, NetZoneAddress, NetworkAddressIncorrectZone, - NetworkZone, + client::InternalPeerID, + handles::ConnectionHandle, + types::{BanState, ConnectionInfo, SetBan}, + NetZoneAddress, NetworkAddressIncorrectZone, NetworkZone, }; -pub enum PeerSyncRequest { - /// Request some peers to sync from. - /// - /// This takes in the current cumulative difficulty of our chain and will return peers that - /// claim to have a higher cumulative difficulty. - PeersToSyncFrom { - current_cumulative_difficulty: u128, - block_needed: Option, - }, - /// Add/update a peers core sync data to the sync state service. - IncomingCoreSyncData(InternalPeerID, ConnectionHandle, CoreSyncData), -} - -pub enum PeerSyncResponse { - /// The return value of [`PeerSyncRequest::PeersToSyncFrom`]. - PeersToSyncFrom(Vec>), - /// A generic ok response. - Ok, -} - +/// A request to the core sync service for our node's [`CoreSyncData`]. pub struct CoreSyncDataRequest; +/// A response from the core sync service containing our [`CoreSyncData`]. pub struct CoreSyncDataResponse(pub CoreSyncData); +/// A [`NetworkZone`] specific [`PeerListEntryBase`]. +/// +/// Using this type instead of [`PeerListEntryBase`] in the address book makes +/// usage easier for the rest of the P2P code as we can guarantee only the correct addresses will be stored and returned. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr( feature = "borsh", @@ -44,7 +34,7 @@ pub struct ZoneSpecificPeerListEntryBase { pub rpc_credits_per_hash: u32, } -impl From> for cuprate_wire::PeerListEntryBase { +impl From> for PeerListEntryBase { fn from(value: ZoneSpecificPeerListEntryBase
) -> Self { Self { adr: value.adr.into(), @@ -57,6 +47,7 @@ impl From> for cuprate_wire: } } +/// An error converting a [`PeerListEntryBase`] into a [`ZoneSpecificPeerListEntryBase`]. #[derive(Debug, thiserror::Error)] pub enum PeerListConversionError { #[error("Address is in incorrect zone")] @@ -65,9 +56,7 @@ pub enum PeerListConversionError { PruningSeed(#[from] PruningError), } -impl TryFrom - for ZoneSpecificPeerListEntryBase -{ +impl TryFrom for ZoneSpecificPeerListEntryBase { type Error = PeerListConversionError; fn try_from(value: PeerListEntryBase) -> Result { @@ -82,6 +71,7 @@ impl TryFrom } } +/// A request to the address book service. pub enum AddressBookRequest { /// Tells the address book that we have connected or received a connection from a peer. NewConnection { @@ -100,33 +90,83 @@ pub enum AddressBookRequest { /// The peers rpc credits per hash rpc_credits_per_hash: u32, }, + /// Tells the address book about a peer list received from a peer. IncomingPeerList(Vec>), + /// Takes a random white peer from the peer list. If height is specified /// then the peer list should retrieve a peer that should have a full /// block at that height according to it's pruning seed - TakeRandomWhitePeer { height: Option }, + TakeRandomWhitePeer { height: Option }, + /// Takes a random gray peer from the peer list. If height is specified /// then the peer list should retrieve a peer that should have a full /// block at that height according to it's pruning seed - TakeRandomGrayPeer { height: Option }, + TakeRandomGrayPeer { height: Option }, + /// Takes a random peer from the peer list. If height is specified /// then the peer list should retrieve a peer that should have a full /// block at that height according to it's pruning seed. /// /// The address book will look in the white peer list first, then the gray /// one if no peer is found. - TakeRandomPeer { height: Option }, + TakeRandomPeer { height: Option }, + /// Gets the specified number of white peers, or less if we don't have enough. GetWhitePeers(usize), + + /// Get the amount of white & grey peers. + PeerlistSize, + + /// Get information on all connections. + ConnectionInfo, + + /// Get the amount of incoming & outgoing connections. + ConnectionCount, + + /// (Un)ban a peer. + SetBan(SetBan), + /// Checks if the given peer is banned. - IsPeerBanned(Z::Addr), + GetBan(Z::Addr), + + /// Get the state of all bans. + GetBans, } +/// A response from the address book service. pub enum AddressBookResponse { + /// Generic OK response. + /// + /// Response to: + /// - [`AddressBookRequest::NewConnection`] + /// - [`AddressBookRequest::IncomingPeerList`] Ok, + + /// Response to: + /// - [`AddressBookRequest::TakeRandomWhitePeer`] + /// - [`AddressBookRequest::TakeRandomGrayPeer`] + /// - [`AddressBookRequest::TakeRandomPeer`] Peer(ZoneSpecificPeerListEntryBase), + + /// Response to [`AddressBookRequest::GetWhitePeers`]. Peers(Vec>), - /// Contains `true` if the peer is banned. - IsPeerBanned(bool), + + /// Response to [`AddressBookRequest::PeerlistSize`]. + PeerlistSize { white: usize, grey: usize }, + + /// Response to [`AddressBookRequest::ConnectionInfo`]. + ConnectionInfo(Vec>), + + /// Response to [`AddressBookRequest::ConnectionCount`]. + ConnectionCount { incoming: usize, outgoing: usize }, + + /// Response to [`AddressBookRequest::GetBan`]. + /// + /// This returns [`None`] if the peer is not banned, + /// else it returns how long the peer is banned for. + GetBan { unban_instant: Option }, + + /// Response to [`AddressBookRequest::GetBans`]. + GetBans(Vec>), } diff --git a/p2p/p2p-core/src/types.rs b/p2p/p2p-core/src/types.rs new file mode 100644 index 0000000..ca56055 --- /dev/null +++ b/p2p/p2p-core/src/types.rs @@ -0,0 +1,96 @@ +//! General data structures. + +use std::time::{Duration, Instant}; + +use cuprate_pruning::PruningSeed; +use cuprate_types::{AddressType, ConnectionState}; + +use crate::NetZoneAddress; + +/// Data within [`crate::services::AddressBookRequest::SetBan`]. +pub struct SetBan { + /// Address of the peer. + pub address: A, + /// - If [`Some`], how long this peer should be banned for + /// - If [`None`], the peer will be unbanned + pub ban: Option, +} + +/// Data within [`crate::services::AddressBookResponse::GetBans`]. +pub struct BanState { + /// Address of the peer. + pub address: A, + /// - If [`Some`], the peer is banned until this [`Instant`] + /// - If [`None`], the peer is not currently banned + pub unban_instant: Option, +} + +/// Data within [`crate::services::AddressBookResponse::ConnectionInfo`]. +pub struct ConnectionInfo { + // The following fields are mostly the same as `monerod`. + pub address: A, + pub address_type: AddressType, + pub avg_download: u64, + pub avg_upload: u64, + pub current_download: u64, + pub current_upload: u64, + pub height: u64, + /// Either a domain or an IP without the port. + pub host: String, + pub incoming: bool, + pub live_time: u64, + pub localhost: bool, + pub local_ip: bool, + pub peer_id: u64, + pub pruning_seed: PruningSeed, + pub recv_count: u64, + pub recv_idle_time: u64, + pub rpc_credits_per_hash: u32, + pub rpc_port: u16, + pub send_count: u64, + pub send_idle_time: u64, + pub state: ConnectionState, + pub support_flags: u32, + + // The following fields are slightly different than `monerod`. + + // + /// [`None`] if Tor/i2p or unknown. + pub socket_addr: Option, + + /// This field does not exist for `cuprated`'s RPC, this is just a marker type: + /// - + /// - + /// + /// [`ConnectionId::DEFAULT_STR`] is used when mapping to the RPC type. + pub connection_id: ConnectionId, +} + +/// Marker type for `monerod`'s connection ID. +/// +/// `connection_id` is a 128-bit `uuid` in `monerod`. +/// `cuprated` does not support this field so it returns +/// the default value in the RPC interface, an all 0-bit UUID. +/// +/// This default value in string form is [`ConnectionId::DEFAULT_STR`]. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ConnectionId; + +impl ConnectionId { + /// [`str`] representation of a default connection ID. + pub const DEFAULT_STR: &str = "00000000000000000000000000000000"; +} + +/// Used in RPC's `sync_info`. +/// +// TODO: fix docs after +// Data within [`crate::services::AddressBookResponse::Spans`]. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Span { + pub nblocks: u64, + pub rate: u32, + pub remote_address: A, + pub size: u64, + pub speed: u32, + pub start_block_height: u64, +} diff --git a/p2p/p2p-core/tests/fragmented_handshake.rs b/p2p/p2p-core/tests/fragmented_handshake.rs index 2e96574..6df7232 100644 --- a/p2p/p2p-core/tests/fragmented_handshake.rs +++ b/p2p/p2p-core/tests/fragmented_handshake.rs @@ -1,8 +1,10 @@ //! This file contains a test for a handshake with monerod but uses fragmented messages. + +#![expect(unused_crate_dependencies, reason = "external test module")] + use std::{ net::SocketAddr, pin::Pin, - sync::Arc, task::{Context, Poll}, time::Duration, }; @@ -13,7 +15,6 @@ use tokio::{ tcp::{OwnedReadHalf, OwnedWriteHalf}, TcpListener, TcpStream, }, - sync::Semaphore, time::timeout, }; use tokio_util::{ @@ -23,21 +24,20 @@ use tokio_util::{ use tower::{Service, ServiceExt}; use cuprate_helper::network::Network; -use cuprate_p2p_core::{ - client::{ConnectRequest, Connector, DoHandshakeRequest, HandShaker, InternalPeerID}, - network_zones::ClearNetServerCfg, - ConnectionDirection, NetworkZone, -}; +use cuprate_test_utils::monerod::monerod; use cuprate_wire::{ common::PeerSupportFlags, levin::{message::make_fragmented_messages, LevinMessage, Protocol}, BasicNodeData, Message, MoneroWireCodec, }; -use cuprate_test_utils::monerod::monerod; - -mod utils; -use utils::*; +use cuprate_p2p_core::{ + client::{ + handshaker::HandshakerBuilder, ConnectRequest, Connector, DoHandshakeRequest, + InternalPeerID, + }, + ClearNetServerCfg, ConnectionDirection, NetworkZone, +}; /// A network zone equal to clear net where every message sent is turned into a fragmented message. /// Does not support sending fragmented or dummy messages manually. @@ -47,9 +47,6 @@ pub enum FragNet {} #[async_trait::async_trait] impl NetworkZone for FragNet { const NAME: &'static str = "FragNet"; - const SEEDS: &'static [Self::Addr] = &[]; - const ALLOW_SYNC: bool = true; - const DANDELION_PP: bool = true; const CHECK_NODE_ID: bool = true; type Addr = SocketAddr; @@ -135,9 +132,6 @@ impl Encoder> for FragmentCodec { #[tokio::test] async fn fragmented_handshake_cuprate_to_monerod() { - let semaphore = Arc::new(Semaphore::new(10)); - let permit = semaphore.acquire_owned().await.unwrap(); - let monerod = monerod(["--fixed-difficulty=1", "--out-peers=0"]).await; let our_basic_node_data = BasicNodeData { @@ -149,14 +143,7 @@ async fn fragmented_handshake_cuprate_to_monerod() { rpc_credits_per_hash: 0, }; - let handshaker = HandShaker::::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data, - ); + let handshaker = HandshakerBuilder::::new(our_basic_node_data).build(); let mut connector = Connector::new(handshaker); @@ -166,7 +153,7 @@ async fn fragmented_handshake_cuprate_to_monerod() { .unwrap() .call(ConnectRequest { addr: monerod.p2p_addr(), - permit, + permit: None, }) .await .unwrap(); @@ -174,9 +161,6 @@ async fn fragmented_handshake_cuprate_to_monerod() { #[tokio::test] async fn fragmented_handshake_monerod_to_cuprate() { - let semaphore = Arc::new(Semaphore::new(10)); - let permit = semaphore.acquire_owned().await.unwrap(); - let our_basic_node_data = BasicNodeData { my_port: 18081, network_id: Network::Mainnet.network_id(), @@ -186,14 +170,7 @@ async fn fragmented_handshake_monerod_to_cuprate() { rpc_credits_per_hash: 0, }; - let mut handshaker = HandShaker::::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data, - ); + let mut handshaker = HandshakerBuilder::::new(our_basic_node_data).build(); let ip = "127.0.0.1".parse().unwrap(); @@ -207,7 +184,7 @@ async fn fragmented_handshake_monerod_to_cuprate() { let next_connection_fut = timeout(Duration::from_secs(30), listener.next()); if let Some(Ok((addr, stream, sink))) = next_connection_fut.await.unwrap() { - let _ = handshaker + handshaker .ready() .await .unwrap() @@ -215,8 +192,8 @@ async fn fragmented_handshake_monerod_to_cuprate() { addr: InternalPeerID::KnownAddr(addr.unwrap()), // This is clear net all addresses are known. peer_stream: stream, peer_sink: sink, - direction: ConnectionDirection::InBound, - permit, + direction: ConnectionDirection::Inbound, + permit: None, }) .await .unwrap(); diff --git a/p2p/p2p-core/tests/handles.rs b/p2p/p2p-core/tests/handles.rs index e98cd2d..2a2e2be 100644 --- a/p2p/p2p-core/tests/handles.rs +++ b/p2p/p2p-core/tests/handles.rs @@ -1,3 +1,5 @@ +#![expect(unused_crate_dependencies, reason = "external test module")] + use std::{sync::Arc, time::Duration}; use tokio::sync::Semaphore; @@ -6,10 +8,7 @@ use cuprate_p2p_core::handles::HandleBuilder; #[test] fn send_ban_signal() { - let semaphore = Arc::new(Semaphore::new(5)); - let (guard, mut connection_handle) = HandleBuilder::default() - .with_permit(semaphore.try_acquire_owned().unwrap()) - .build(); + let (guard, mut connection_handle) = HandleBuilder::default().build(); connection_handle.ban_peer(Duration::from_secs(300)); @@ -28,10 +27,7 @@ fn send_ban_signal() { #[test] fn multiple_ban_signals() { - let semaphore = Arc::new(Semaphore::new(5)); - let (guard, mut connection_handle) = HandleBuilder::default() - .with_permit(semaphore.try_acquire_owned().unwrap()) - .build(); + let (guard, mut connection_handle) = HandleBuilder::default().build(); connection_handle.ban_peer(Duration::from_secs(300)); connection_handle.ban_peer(Duration::from_secs(301)); @@ -55,7 +51,7 @@ fn multiple_ban_signals() { fn dropped_guard_sends_disconnect_signal() { let semaphore = Arc::new(Semaphore::new(5)); let (guard, connection_handle) = HandleBuilder::default() - .with_permit(semaphore.try_acquire_owned().unwrap()) + .with_permit(Some(semaphore.try_acquire_owned().unwrap())) .build(); assert!(!connection_handle.is_closed()); diff --git a/p2p/p2p-core/tests/handshake.rs b/p2p/p2p-core/tests/handshake.rs index f979248..cf08086 100644 --- a/p2p/p2p-core/tests/handshake.rs +++ b/p2p/p2p-core/tests/handshake.rs @@ -1,40 +1,35 @@ -use std::{sync::Arc, time::Duration}; +#![expect(unused_crate_dependencies, reason = "external test module")] + +use std::time::Duration; use futures::StreamExt; use tokio::{ io::{duplex, split}, - sync::Semaphore, time::timeout, }; use tokio_util::codec::{FramedRead, FramedWrite}; use tower::{Service, ServiceExt}; use cuprate_helper::network::Network; -use cuprate_wire::{common::PeerSupportFlags, BasicNodeData, MoneroWireCodec}; - -use cuprate_p2p_core::{ - client::{ConnectRequest, Connector, DoHandshakeRequest, HandShaker, InternalPeerID}, - network_zones::{ClearNet, ClearNetServerCfg}, - ConnectionDirection, NetworkZone, -}; - use cuprate_test_utils::{ monerod::monerod, test_netzone::{TestNetZone, TestNetZoneAddr}, }; +use cuprate_wire::{common::PeerSupportFlags, BasicNodeData, MoneroWireCodec}; -mod utils; -use utils::*; +use cuprate_p2p_core::{ + client::{ + handshaker::HandshakerBuilder, ConnectRequest, Connector, DoHandshakeRequest, + InternalPeerID, + }, + ClearNet, ClearNetServerCfg, ConnectionDirection, NetworkZone, +}; #[tokio::test] +#[expect(clippy::significant_drop_tightening)] async fn handshake_cuprate_to_cuprate() { // Tests a Cuprate <-> Cuprate handshake by making 2 handshake services and making them talk to // each other. - - let semaphore = Arc::new(Semaphore::new(10)); - let permit_1 = semaphore.clone().acquire_owned().await.unwrap(); - let permit_2 = semaphore.acquire_owned().await.unwrap(); - let our_basic_node_data_1 = BasicNodeData { my_port: 0, network_id: Network::Mainnet.network_id(), @@ -48,23 +43,11 @@ async fn handshake_cuprate_to_cuprate() { let mut our_basic_node_data_2 = our_basic_node_data_1.clone(); our_basic_node_data_2.peer_id = 2344; - let mut handshaker_1 = HandShaker::, _, _, _, _, _>::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data_1, - ); + let mut handshaker_1 = + HandshakerBuilder::>::new(our_basic_node_data_1).build(); - let mut handshaker_2 = HandShaker::, _, _, _, _, _>::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data_2, - ); + let mut handshaker_2 = + HandshakerBuilder::>::new(our_basic_node_data_2).build(); let (p1, p2) = duplex(50_000); @@ -75,16 +58,16 @@ async fn handshake_cuprate_to_cuprate() { addr: InternalPeerID::KnownAddr(TestNetZoneAddr(888)), peer_stream: FramedRead::new(p2_receiver, MoneroWireCodec::default()), peer_sink: FramedWrite::new(p2_sender, MoneroWireCodec::default()), - direction: ConnectionDirection::OutBound, - permit: permit_1, + direction: ConnectionDirection::Outbound, + permit: None, }; let p2_handshake_req = DoHandshakeRequest { addr: InternalPeerID::KnownAddr(TestNetZoneAddr(444)), peer_stream: FramedRead::new(p1_receiver, MoneroWireCodec::default()), peer_sink: FramedWrite::new(p1_sender, MoneroWireCodec::default()), - direction: ConnectionDirection::InBound, - permit: permit_2, + direction: ConnectionDirection::Inbound, + permit: None, }; let p1 = tokio::spawn(async move { @@ -114,9 +97,6 @@ async fn handshake_cuprate_to_cuprate() { #[tokio::test] async fn handshake_cuprate_to_monerod() { - let semaphore = Arc::new(Semaphore::new(10)); - let permit = semaphore.acquire_owned().await.unwrap(); - let monerod = monerod(["--fixed-difficulty=1", "--out-peers=0"]).await; let our_basic_node_data = BasicNodeData { @@ -128,14 +108,7 @@ async fn handshake_cuprate_to_monerod() { rpc_credits_per_hash: 0, }; - let handshaker = HandShaker::::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data, - ); + let handshaker = HandshakerBuilder::::new(our_basic_node_data).build(); let mut connector = Connector::new(handshaker); @@ -145,7 +118,7 @@ async fn handshake_cuprate_to_monerod() { .unwrap() .call(ConnectRequest { addr: monerod.p2p_addr(), - permit, + permit: None, }) .await .unwrap(); @@ -153,9 +126,6 @@ async fn handshake_cuprate_to_monerod() { #[tokio::test] async fn handshake_monerod_to_cuprate() { - let semaphore = Arc::new(Semaphore::new(10)); - let permit = semaphore.acquire_owned().await.unwrap(); - let our_basic_node_data = BasicNodeData { my_port: 18081, network_id: Network::Mainnet.network_id(), @@ -165,14 +135,7 @@ async fn handshake_monerod_to_cuprate() { rpc_credits_per_hash: 0, }; - let mut handshaker = HandShaker::::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data, - ); + let mut handshaker = HandshakerBuilder::::new(our_basic_node_data).build(); let ip = "127.0.0.1".parse().unwrap(); @@ -186,7 +149,7 @@ async fn handshake_monerod_to_cuprate() { let next_connection_fut = timeout(Duration::from_secs(30), listener.next()); if let Some(Ok((addr, stream, sink))) = next_connection_fut.await.unwrap() { - let _ = handshaker + handshaker .ready() .await .unwrap() @@ -194,8 +157,8 @@ async fn handshake_monerod_to_cuprate() { addr: InternalPeerID::KnownAddr(addr.unwrap()), // This is clear net all addresses are known. peer_stream: stream, peer_sink: sink, - direction: ConnectionDirection::InBound, - permit, + direction: ConnectionDirection::Inbound, + permit: None, }) .await .unwrap(); diff --git a/p2p/p2p-core/tests/sending_receiving.rs b/p2p/p2p-core/tests/sending_receiving.rs index b4c42e2..8c90c83 100644 --- a/p2p/p2p-core/tests/sending_receiving.rs +++ b/p2p/p2p-core/tests/sending_receiving.rs @@ -1,27 +1,19 @@ -use std::sync::Arc; +#![expect(unused_crate_dependencies, reason = "external test module")] -use tokio::sync::Semaphore; use tower::{Service, ServiceExt}; use cuprate_helper::network::Network; +use cuprate_test_utils::monerod::monerod; use cuprate_wire::{common::PeerSupportFlags, protocol::GetObjectsRequest, BasicNodeData}; use cuprate_p2p_core::{ - client::{ConnectRequest, Connector, HandShaker}, - network_zones::ClearNet, + client::{handshaker::HandshakerBuilder, ConnectRequest, Connector}, protocol::{PeerRequest, PeerResponse}, + ClearNet, ProtocolRequest, ProtocolResponse, }; -use cuprate_test_utils::monerod::monerod; - -mod utils; -use utils::*; - #[tokio::test] async fn get_single_block_from_monerod() { - let semaphore = Arc::new(Semaphore::new(10)); - let permit = semaphore.acquire_owned().await.unwrap(); - let monerod = monerod(["--out-peers=0"]).await; let our_basic_node_data = BasicNodeData { @@ -33,14 +25,7 @@ async fn get_single_block_from_monerod() { rpc_credits_per_hash: 0, }; - let handshaker = HandShaker::::new( - DummyAddressBook, - DummyPeerSyncSvc, - DummyCoreSyncSvc, - DummyPeerRequestHandlerSvc, - |_| futures::stream::pending(), - our_basic_node_data, - ); + let handshaker = HandshakerBuilder::::new(our_basic_node_data).build(); let mut connector = Connector::new(handshaker); @@ -50,22 +35,26 @@ async fn get_single_block_from_monerod() { .unwrap() .call(ConnectRequest { addr: monerod.p2p_addr(), - permit, + permit: None, }) .await .unwrap(); - let PeerResponse::GetObjects(obj) = connected_peer + let PeerResponse::Protocol(ProtocolResponse::GetObjects(obj)) = connected_peer .ready() .await .unwrap() - .call(PeerRequest::GetObjects(GetObjectsRequest { - blocks: hex::decode("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3") + .call(PeerRequest::Protocol(ProtocolRequest::GetObjects( + GetObjectsRequest { + blocks: hex::decode( + "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3", + ) .unwrap() .try_into() .unwrap(), - pruned: false, - })) + pruned: false, + }, + ))) .await .unwrap() else { diff --git a/p2p/p2p-core/tests/utils.rs b/p2p/p2p-core/tests/utils.rs deleted file mode 100644 index 9587bb5..0000000 --- a/p2p/p2p-core/tests/utils.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - -use futures::FutureExt; -use tower::Service; - -use cuprate_p2p_core::{ - services::{ - AddressBookRequest, AddressBookResponse, CoreSyncDataRequest, CoreSyncDataResponse, - PeerSyncRequest, PeerSyncResponse, - }, - NetworkZone, PeerRequest, PeerResponse, -}; - -#[derive(Clone)] -pub struct DummyAddressBook; - -impl Service> for DummyAddressBook { - type Response = AddressBookResponse; - type Error = tower::BoxError; - type Future = - Pin> + Send + 'static>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: AddressBookRequest) -> Self::Future { - async move { - Ok(match req { - AddressBookRequest::GetWhitePeers(_) => AddressBookResponse::Peers(vec![]), - _ => AddressBookResponse::Ok, - }) - } - .boxed() - } -} - -#[derive(Clone)] -pub struct DummyCoreSyncSvc; - -impl Service for DummyCoreSyncSvc { - type Response = CoreSyncDataResponse; - type Error = tower::BoxError; - type Future = - Pin> + Send + 'static>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: CoreSyncDataRequest) -> Self::Future { - async move { - Ok(CoreSyncDataResponse(cuprate_wire::CoreSyncData { - cumulative_difficulty: 1, - cumulative_difficulty_top64: 0, - current_height: 1, - pruning_seed: 0, - top_id: hex::decode( - "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3", - ) - .unwrap() - .try_into() - .unwrap(), - top_version: 1, - })) - } - .boxed() - } -} - -#[derive(Clone)] -pub struct DummyPeerSyncSvc; - -impl Service> for DummyPeerSyncSvc { - type Error = tower::BoxError; - type Future = - Pin> + Send + 'static>>; - - type Response = PeerSyncResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: PeerSyncRequest) -> Self::Future { - async { Ok(PeerSyncResponse::Ok) }.boxed() - } -} - -#[derive(Clone)] -pub struct DummyPeerRequestHandlerSvc; - -impl Service for DummyPeerRequestHandlerSvc { - type Response = PeerResponse; - type Error = tower::BoxError; - type Future = - Pin> + Send + 'static>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: PeerRequest) -> Self::Future { - async move { Ok(PeerResponse::NA) }.boxed() - } -} diff --git a/p2p/p2p/Cargo.toml b/p2p/p2p/Cargo.toml index 507d362..e6ebccb 100644 --- a/p2p/p2p/Cargo.toml +++ b/p2p/p2p/Cargo.toml @@ -6,34 +6,39 @@ license = "MIT" authors = ["Boog900"] [dependencies] -cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } -cuprate-wire = { path = "../../net/wire" } -cuprate-p2p-core = { path = "../p2p-core", features = ["borsh"] } -cuprate-address-book = { path = "../address-book" } -cuprate-pruning = { path = "../../pruning" } -cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } -cuprate-async-buffer = { path = "../async-buffer" } +cuprate-constants = { workspace = true } +cuprate-fixed-bytes = { workspace = true } +cuprate-wire = { workspace = true } +cuprate-p2p-core = { workspace = true, features = ["borsh"] } +cuprate-address-book = { workspace = true } +cuprate-pruning = { workspace = true } +cuprate-helper = { workspace = true, features = ["asynch"], default-features = false } +cuprate-async-buffer = { workspace = true } +cuprate-types = { workspace = true, default-features = false } monero-serai = { workspace = true, features = ["std"] } tower = { workspace = true, features = ["buffer"] } tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } -rayon = { workspace = true } tokio-util = { workspace = true } +rayon = { workspace = true } tokio-stream = { workspace = true, features = ["sync", "time"] } futures = { workspace = true, features = ["std"] } pin-project = { workspace = true } -dashmap = { workspace = true } +indexmap = { workspace = true, features = ["std"] } thiserror = { workspace = true } bytes = { workspace = true, features = ["std"] } rand = { workspace = true, features = ["std", "std_rng"] } rand_distr = { workspace = true, features = ["std"] } -hex = { workspace = true, features = ["std"] } tracing = { workspace = true, features = ["std", "attributes"] } +borsh = { workspace = true, features = ["derive", "std"] } [dev-dependencies] -cuprate-test-utils = { path = "../../test-utils" } +cuprate-test-utils = { workspace = true } indexmap = { workspace = true } proptest = { workspace = true } tokio-test = { workspace = true } + +[lints] +workspace = true diff --git a/p2p/p2p/src/block_downloader.rs b/p2p/p2p/src/block_downloader.rs index 7d0ab7e..db10300 100644 --- a/p2p/p2p/src/block_downloader.rs +++ b/p2p/p2p/src/block_downloader.rs @@ -1,6 +1,6 @@ //! # Block Downloader //! -//! This module contains the [`BlockDownloader`], which finds a chain to +//! This module contains the block downloader, which finds a chain to //! download from our connected peers and downloads it. See the actual //! `struct` documentation for implementation details. //! @@ -8,7 +8,6 @@ use std::{ cmp::{max, min, Reverse}, collections::{BTreeMap, BinaryHeap}, - sync::Arc, time::Duration, }; @@ -18,23 +17,20 @@ use tokio::{ task::JoinSet, time::{interval, timeout, MissedTickBehavior}, }; -use tower::{Service, ServiceExt}; +use tower::{util::BoxCloneService, Service, ServiceExt}; use tracing::{instrument, Instrument, Span}; use cuprate_async_buffer::{BufferAppender, BufferStream}; -use cuprate_p2p_core::{ - handles::ConnectionHandle, - services::{PeerSyncRequest, PeerSyncResponse}, - NetworkZone, PeerSyncSvc, -}; -use cuprate_pruning::{PruningSeed, CRYPTONOTE_MAX_BLOCK_HEIGHT}; +use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; +use cuprate_p2p_core::{handles::ConnectionHandle, NetworkZone}; +use cuprate_pruning::PruningSeed; use crate::{ - client_pool::{ClientPool, ClientPoolDropGuard}, constants::{ BLOCK_DOWNLOADER_REQUEST_TIMEOUT, EMPTY_CHAIN_ENTRIES_BEFORE_TOP_ASSUMED, LONG_BAN, MAX_BLOCK_BATCH_LEN, MAX_DOWNLOAD_FAILURES, }, + peer_set::ClientDropGuard, }; mod block_queue; @@ -44,6 +40,7 @@ mod request_chain; #[cfg(test)] mod tests; +use crate::peer_set::{PeerSetRequest, PeerSetResponse}; use block_queue::{BlockQueue, ReadyQueueBatch}; use chain_tracker::{BlocksToRetrieve, ChainEntry, ChainTracker}; use download_batch::download_batch_task; @@ -65,20 +62,20 @@ pub struct BlockBatch { pub struct BlockDownloaderConfig { /// The size in bytes of the buffer between the block downloader and the place which /// is consuming the downloaded blocks. - pub buffer_size: usize, + pub buffer_bytes: usize, /// The size of the in progress queue (in bytes) at which we stop requesting more blocks. - pub in_progress_queue_size: usize, + pub in_progress_queue_bytes: usize, /// The [`Duration`] between checking the client pool for free peers. pub check_client_pool_interval: Duration, /// The target size of a single batch of blocks (in bytes). - pub target_batch_size: usize, + pub target_batch_bytes: usize, /// The initial amount of blocks to request (in number of blocks) - pub initial_batch_size: usize, + pub initial_batch_len: usize, } /// An error that occurred in the [`BlockDownloader`]. #[derive(Debug, thiserror::Error)] -pub enum BlockDownloadError { +pub(crate) enum BlockDownloadError { #[error("A request to a peer timed out.")] TimedOut, #[error("The block buffer was closed.")] @@ -121,7 +118,7 @@ pub enum ChainSvcResponse { /// The response for [`ChainSvcRequest::FindFirstUnknown`]. /// /// Contains the index of the first unknown block and its expected height. - FindFirstUnknown(usize, u64), + FindFirstUnknown(Option<(usize, usize)>), /// The response for [`ChainSvcRequest::CumulativeDifficulty`]. /// /// The current cumulative difficulty of our chain. @@ -137,28 +134,20 @@ pub enum ChainSvcResponse { /// The block downloader may fail before the whole chain is downloaded. If this is the case you can /// call this function again, so it can start the search again. #[instrument(level = "error", skip_all, name = "block_downloader")] -pub fn download_blocks( - client_pool: Arc>, - peer_sync_svc: S, +pub fn download_blocks( + peer_set: BoxCloneService, tower::BoxError>, our_chain_svc: C, config: BlockDownloaderConfig, ) -> BufferStream where - S: PeerSyncSvc + Clone, C: Service + Send + 'static, C::Future: Send + 'static, { - let (buffer_appender, buffer_stream) = cuprate_async_buffer::new_buffer(config.buffer_size); + let (buffer_appender, buffer_stream) = cuprate_async_buffer::new_buffer(config.buffer_bytes); - let block_downloader = BlockDownloader::new( - client_pool, - peer_sync_svc, - our_chain_svc, - buffer_appender, - config, - ); + let block_downloader = BlockDownloader::new(peer_set, our_chain_svc, buffer_appender, config); tokio::spawn( block_downloader @@ -194,20 +183,18 @@ where /// - download the next batch of blocks /// - request the next chain entry /// - download an already requested batch of blocks (this might happen due to an error in the previous request -/// or because the queue of ready blocks is too large, so we need the oldest block to clear it). -struct BlockDownloader { - /// The client pool. - client_pool: Arc>, +/// or because the queue of ready blocks is too large, so we need the oldest block to clear it). +struct BlockDownloader { + /// The peer set. + peer_set: BoxCloneService, tower::BoxError>, - /// The service that holds the peer's sync states. - peer_sync_svc: S, /// The service that holds our current chain state. our_chain_svc: C, /// The amount of blocks to request in the next batch. amount_of_blocks_to_request: usize, /// The height at which [`Self::amount_of_blocks_to_request`] was updated. - amount_of_blocks_to_request_updated_at: u64, + amount_of_blocks_to_request_updated_at: usize, /// The amount of consecutive empty chain entries we received. /// @@ -219,18 +206,18 @@ struct BlockDownloader { /// The running chain entry tasks. /// /// Returns a result of the chain entry or an error. - #[allow(clippy::type_complexity)] - chain_entry_task: JoinSet, ChainEntry), BlockDownloadError>>, + #[expect(clippy::type_complexity)] + chain_entry_task: JoinSet, ChainEntry), BlockDownloadError>>, /// The current inflight requests. /// /// This is a map of batch start heights to block IDs and related information of the batch. - inflight_requests: BTreeMap>, + inflight_requests: BTreeMap>, /// A queue of start heights from failed batches that should be retried. /// /// Wrapped in [`Reverse`] so we prioritize early batches. - failed_batches: BinaryHeap>, + failed_batches: BinaryHeap>, block_queue: BlockQueue, @@ -238,9 +225,8 @@ struct BlockDownloader { config: BlockDownloaderConfig, } -impl BlockDownloader +impl BlockDownloader where - S: PeerSyncSvc + Clone, C: Service + Send + 'static, @@ -248,19 +234,15 @@ where { /// Creates a new [`BlockDownloader`] fn new( - client_pool: Arc>, - - peer_sync_svc: S, + peer_set: BoxCloneService, tower::BoxError>, our_chain_svc: C, buffer_appender: BufferAppender, - config: BlockDownloaderConfig, ) -> Self { Self { - client_pool, - peer_sync_svc, + peer_set, our_chain_svc, - amount_of_blocks_to_request: config.initial_batch_size, + amount_of_blocks_to_request: config.initial_batch_len, amount_of_blocks_to_request_updated_at: 0, amount_of_empty_chain_entries: 0, block_download_tasks: JoinSet::new(), @@ -273,10 +255,10 @@ where } /// Checks if we can make use of any peers that are currently pending requests. - async fn check_pending_peers( + fn check_pending_peers( &mut self, chain_tracker: &mut ChainTracker, - pending_peers: &mut BTreeMap>>, + pending_peers: &mut BTreeMap>>, ) { tracing::debug!("Checking if we can give any work to pending peers."); @@ -287,7 +269,8 @@ where continue; } - if let Some(peer) = self.try_handle_free_client(chain_tracker, peer).await { + let client = self.try_handle_free_client(chain_tracker, peer); + if let Some(peer) = client { // This peer is ok however it does not have the data we currently need, this will only happen // because of its pruning seed so just skip over all peers with this pruning seed. peers.push(peer); @@ -302,11 +285,11 @@ where /// This function will find the batch(es) that we are waiting on to clear our ready queue and sends another request /// for them. /// - /// Returns the [`ClientPoolDropGuard`] back if it doesn't have the batch according to its pruning seed. - async fn request_inflight_batch_again( + /// Returns the [`ClientDropGuard`] back if it doesn't have the batch according to its pruning seed. + fn request_inflight_batch_again( &mut self, - client: ClientPoolDropGuard, - ) -> Option> { + client: ClientDropGuard, + ) -> Option> { tracing::debug!( "Requesting an inflight batch, current ready queue size: {}", self.block_queue.size() @@ -352,13 +335,13 @@ where /// /// The batch requested will depend on our current state, failed batches will be prioritised. /// - /// Returns the [`ClientPoolDropGuard`] back if it doesn't have the data we currently need according + /// Returns the [`ClientDropGuard`] back if it doesn't have the data we currently need according /// to its pruning seed. - async fn request_block_batch( + fn request_block_batch( &mut self, chain_tracker: &mut ChainTracker, - client: ClientPoolDropGuard, - ) -> Option> { + client: ClientDropGuard, + ) -> Option> { tracing::trace!("Using peer to request a batch of blocks."); // First look to see if we have any failed requests. while let Some(failed_request) = self.failed_batches.peek() { @@ -398,8 +381,8 @@ where } // If our ready queue is too large send duplicate requests for the blocks we are waiting on. - if self.block_queue.size() >= self.config.in_progress_queue_size { - return self.request_inflight_batch_again(client).await; + if self.block_queue.size() >= self.config.in_progress_queue_bytes { + return self.request_inflight_batch_again(client); } // No failed requests that we can handle, request some new blocks. @@ -432,13 +415,13 @@ where /// This function will use our current state to decide if we should send a request for a chain entry /// or if we should request a batch of blocks. /// - /// Returns the [`ClientPoolDropGuard`] back if it doesn't have the data we currently need according + /// Returns the [`ClientDropGuard`] back if it doesn't have the data we currently need according /// to its pruning seed. - async fn try_handle_free_client( + fn try_handle_free_client( &mut self, chain_tracker: &mut ChainTracker, - client: ClientPoolDropGuard, - ) -> Option> { + client: ClientDropGuard, + ) -> Option> { // We send 2 requests, so if one of them is slow or doesn't have the next chain, we still have a backup. if self.chain_entry_task.len() < 2 // If we have had too many failures then assume the tip has been found so no more chain entries. @@ -472,14 +455,14 @@ where } // Request a batch of blocks instead. - self.request_block_batch(chain_tracker, client).await + self.request_block_batch(chain_tracker, client) } /// Checks the [`ClientPool`] for free peers. async fn check_for_free_clients( &mut self, chain_tracker: &mut ChainTracker, - pending_peers: &mut BTreeMap>>, + pending_peers: &mut BTreeMap>>, ) -> Result<(), BlockDownloadError> { tracing::debug!("Checking for free peers"); @@ -494,29 +477,26 @@ where panic!("Chain service returned wrong response."); }; - let PeerSyncResponse::PeersToSyncFrom(peers) = self - .peer_sync_svc + let PeerSetResponse::PeersWithMorePoW(clients) = self + .peer_set .ready() .await? - .call(PeerSyncRequest::PeersToSyncFrom { + .call(PeerSetRequest::PeersWithMorePoW( current_cumulative_difficulty, - block_needed: None, - }) + )) .await? else { - panic!("Peer sync service returned wrong response."); + unreachable!(); }; - tracing::debug!("Response received from peer sync service"); - - for client in self.client_pool.borrow_clients(&peers) { + for client in clients { pending_peers .entry(client.info.pruning_seed) .or_default() .push(client); } - self.check_pending_peers(chain_tracker, pending_peers).await; + self.check_pending_peers(chain_tracker, pending_peers); Ok(()) } @@ -524,10 +504,10 @@ where /// Handles a response to a request to get blocks from a peer. async fn handle_download_batch_res( &mut self, - start_height: u64, - res: Result<(ClientPoolDropGuard, BlockBatch), BlockDownloadError>, + start_height: usize, + res: Result<(ClientDropGuard, BlockBatch), BlockDownloadError>, chain_tracker: &mut ChainTracker, - pending_peers: &mut BTreeMap>>, + pending_peers: &mut BTreeMap>>, ) -> Result<(), BlockDownloadError> { tracing::debug!("Handling block download response"); @@ -574,7 +554,7 @@ where .or_default() .push(client); - self.check_pending_peers(chain_tracker, pending_peers).await; + self.check_pending_peers(chain_tracker, pending_peers); return Ok(()); }; @@ -585,7 +565,7 @@ where self.amount_of_blocks_to_request = calculate_next_block_batch_size( block_batch.size, block_batch.blocks.len(), - self.config.target_batch_size, + self.config.target_batch_bytes, ); tracing::debug!( @@ -611,7 +591,7 @@ where .or_default() .push(client); - self.check_pending_peers(chain_tracker, pending_peers).await; + self.check_pending_peers(chain_tracker, pending_peers); Ok(()) } @@ -620,12 +600,8 @@ where /// Starts the main loop of the block downloader. async fn run(mut self) -> Result<(), BlockDownloadError> { - let mut chain_tracker = initial_chain_search( - &self.client_pool, - self.peer_sync_svc.clone(), - &mut self.our_chain_svc, - ) - .await?; + let mut chain_tracker = + initial_chain_search(&mut self.peer_set, &mut self.our_chain_svc).await?; let mut pending_peers = BTreeMap::new(); @@ -679,7 +655,7 @@ where .or_default() .push(client); - self.check_pending_peers(&mut chain_tracker, &mut pending_peers).await; + self.check_pending_peers(&mut chain_tracker, &mut pending_peers); } Err(_) => self.amount_of_empty_chain_entries += 1 } @@ -692,18 +668,19 @@ where /// The return value from the block download tasks. struct BlockDownloadTaskResponse { /// The start height of the batch. - start_height: u64, + start_height: usize, /// A result containing the batch or an error. - result: Result<(ClientPoolDropGuard, BlockBatch), BlockDownloadError>, + result: Result<(ClientDropGuard, BlockBatch), BlockDownloadError>, } /// Returns if a peer has all the blocks in a range, according to its [`PruningSeed`]. -fn client_has_block_in_range(pruning_seed: &PruningSeed, start_height: u64, length: usize) -> bool { - pruning_seed.has_full_block(start_height, CRYPTONOTE_MAX_BLOCK_HEIGHT) - && pruning_seed.has_full_block( - start_height + u64::try_from(length).unwrap(), - CRYPTONOTE_MAX_BLOCK_HEIGHT, - ) +const fn client_has_block_in_range( + pruning_seed: &PruningSeed, + start_height: usize, + length: usize, +) -> bool { + pruning_seed.has_full_block(start_height, MAX_BLOCK_HEIGHT_USIZE) + && pruning_seed.has_full_block(start_height + length, MAX_BLOCK_HEIGHT_USIZE) } /// Calculates the next amount of blocks to request in a batch. diff --git a/p2p/p2p/src/block_downloader/block_queue.rs b/p2p/p2p/src/block_downloader/block_queue.rs index b03d847..ba7c02b 100644 --- a/p2p/p2p/src/block_downloader/block_queue.rs +++ b/p2p/p2p/src/block_downloader/block_queue.rs @@ -13,9 +13,9 @@ use super::{BlockBatch, BlockDownloadError}; /// /// Also, the [`Ord`] impl is reversed so older blocks (lower height) come first in a [`BinaryHeap`]. #[derive(Debug, Clone)] -pub struct ReadyQueueBatch { +pub(crate) struct ReadyQueueBatch { /// The start height of the batch. - pub start_height: u64, + pub start_height: usize, /// The batch of blocks. pub block_batch: BlockBatch, } @@ -43,7 +43,7 @@ impl Ord for ReadyQueueBatch { /// The block queue that holds downloaded block batches, adding them to the [`async_buffer`] when the /// oldest batch has been downloaded. -pub struct BlockQueue { +pub(crate) struct BlockQueue { /// A queue of ready batches. ready_batches: BinaryHeap, /// The size, in bytes, of all the batches in [`Self::ready_batches`]. @@ -55,8 +55,8 @@ pub struct BlockQueue { impl BlockQueue { /// Creates a new [`BlockQueue`]. - pub fn new(buffer_appender: BufferAppender) -> BlockQueue { - BlockQueue { + pub(crate) const fn new(buffer_appender: BufferAppender) -> Self { + Self { ready_batches: BinaryHeap::new(), ready_batches_size: 0, buffer_appender, @@ -64,12 +64,12 @@ impl BlockQueue { } /// Returns the oldest batch that has not been put in the [`async_buffer`] yet. - pub fn oldest_ready_batch(&self) -> Option { + pub(crate) fn oldest_ready_batch(&self) -> Option { self.ready_batches.peek().map(|batch| batch.start_height) } /// Returns the size of all the batches that have not been put into the [`async_buffer`] yet. - pub fn size(&self) -> usize { + pub(crate) const fn size(&self) -> usize { self.ready_batches_size } @@ -77,16 +77,16 @@ impl BlockQueue { /// /// `oldest_in_flight_start_height` should be the start height of the oldest batch that is still inflight, if /// there are no batches inflight then this should be [`None`]. - pub async fn add_incoming_batch( + pub(crate) async fn add_incoming_batch( &mut self, new_batch: ReadyQueueBatch, - oldest_in_flight_start_height: Option, + oldest_in_flight_start_height: Option, ) -> Result<(), BlockDownloadError> { self.ready_batches_size += new_batch.block_batch.size; self.ready_batches.push(new_batch); // The height to stop pushing batches into the buffer. - let height_to_stop_at = oldest_in_flight_start_height.unwrap_or(u64::MAX); + let height_to_stop_at = oldest_in_flight_start_height.unwrap_or(usize::MAX); while self .ready_batches @@ -113,27 +113,26 @@ impl BlockQueue { #[cfg(test)] mod tests { - use futures::StreamExt; - use std::{collections::BTreeSet, sync::Arc}; + use std::collections::BTreeSet; + use futures::StreamExt; use proptest::{collection::vec, prelude::*}; - use tokio::sync::Semaphore; use tokio_test::block_on; + use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; use cuprate_p2p_core::handles::HandleBuilder; use super::*; prop_compose! { - fn ready_batch_strategy()(start_height in 0_u64..500_000_000) -> ReadyQueueBatch { - // TODO: The permit will not be needed here when - let (_, peer_handle) = HandleBuilder::new().with_permit(Arc::new(Semaphore::new(1)).try_acquire_owned().unwrap()).build(); + fn ready_batch_strategy()(start_height in 0..MAX_BLOCK_HEIGHT_USIZE) -> ReadyQueueBatch { + let (_, peer_handle) = HandleBuilder::new().build(); ReadyQueueBatch { start_height, block_batch: BlockBatch { blocks: vec![], - size: start_height as usize, + size: start_height, peer_handle, }, } @@ -142,6 +141,7 @@ mod tests { proptest! { #[test] + #[allow(clippy::mutable_key_type)] fn block_queue_returns_items_in_order(batches in vec(ready_batch_strategy(), 0..10_000)) { block_on(async move { let (buffer_tx, mut buffer_rx) = cuprate_async_buffer::new_buffer(usize::MAX); diff --git a/p2p/p2p/src/block_downloader/chain_tracker.rs b/p2p/p2p/src/block_downloader/chain_tracker.rs index 786a0de..df5aebb 100644 --- a/p2p/p2p/src/block_downloader/chain_tracker.rs +++ b/p2p/p2p/src/block_downloader/chain_tracker.rs @@ -2,8 +2,9 @@ use std::{cmp::min, collections::VecDeque}; use cuprate_fixed_bytes::ByteArrayVec; +use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; use cuprate_p2p_core::{client::InternalPeerID, handles::ConnectionHandle, NetworkZone}; -use cuprate_pruning::{PruningSeed, CRYPTONOTE_MAX_BLOCK_HEIGHT}; +use cuprate_pruning::PruningSeed; use crate::constants::MEDIUM_BAN; @@ -20,13 +21,13 @@ pub(crate) struct ChainEntry { /// A batch of blocks to retrieve. #[derive(Clone)] -pub struct BlocksToRetrieve { +pub(crate) struct BlocksToRetrieve { /// The block IDs to get. pub ids: ByteArrayVec<32>, /// The hash of the last block before this batch. pub prev_id: [u8; 32], /// The expected height of the first block in [`BlocksToRetrieve::ids`]. - pub start_height: u64, + pub start_height: usize, /// The peer who told us about this batch. pub peer_who_told_us: InternalPeerID, /// The peer who told us about this batch's handle. @@ -39,7 +40,7 @@ pub struct BlocksToRetrieve { /// An error returned from the [`ChainTracker`]. #[derive(Debug, Clone)] -pub enum ChainTrackerError { +pub(crate) enum ChainTrackerError { /// The new chain entry is invalid. NewEntryIsInvalid, /// The new chain entry does not follow from the top of our chain tracker. @@ -50,11 +51,11 @@ pub enum ChainTrackerError { /// /// This struct allows following a single chain. It takes in [`ChainEntry`]s and /// allows getting [`BlocksToRetrieve`]. -pub struct ChainTracker { +pub(crate) struct ChainTracker { /// A list of [`ChainEntry`]s, in order. entries: VecDeque>, /// The height of the first block, in the first entry in [`Self::entries`]. - first_height: u64, + first_height: usize, /// The hash of the last block in the last entry. top_seen_hash: [u8; 32], /// The hash of the block one below [`Self::first_height`]. @@ -65,9 +66,9 @@ pub struct ChainTracker { impl ChainTracker { /// Creates a new chain tracker. - pub fn new( + pub(crate) fn new( new_entry: ChainEntry, - first_height: u64, + first_height: usize, our_genesis: [u8; 32], previous_hash: [u8; 32], ) -> Self { @@ -76,9 +77,9 @@ impl ChainTracker { entries.push_back(new_entry); Self { - top_seen_hash, entries, first_height, + top_seen_hash, previous_hash, our_genesis, } @@ -86,31 +87,31 @@ impl ChainTracker { /// Returns `true` if the peer is expected to have the next block after our highest seen block /// according to their pruning seed. - pub fn should_ask_for_next_chain_entry(&self, seed: &PruningSeed) -> bool { - seed.has_full_block(self.top_height(), CRYPTONOTE_MAX_BLOCK_HEIGHT) + pub(crate) fn should_ask_for_next_chain_entry(&self, seed: &PruningSeed) -> bool { + seed.has_full_block(self.top_height(), MAX_BLOCK_HEIGHT_USIZE) } /// Returns the simple history, the highest seen block and the genesis block. - pub fn get_simple_history(&self) -> [[u8; 32]; 2] { + pub(crate) const fn get_simple_history(&self) -> [[u8; 32]; 2] { [self.top_seen_hash, self.our_genesis] } /// Returns the height of the highest block we are tracking. - pub fn top_height(&self) -> u64 { + pub(crate) fn top_height(&self) -> usize { let top_block_idx = self .entries .iter() .map(|entry| entry.ids.len()) .sum::(); - self.first_height + u64::try_from(top_block_idx).unwrap() + self.first_height + top_block_idx } /// Returns the total number of queued batches for a certain `batch_size`. /// /// # Panics /// This function panics if `batch_size` is `0`. - pub fn block_requests_queued(&self, batch_size: usize) -> usize { + pub(crate) fn block_requests_queued(&self, batch_size: usize) -> usize { self.entries .iter() .map(|entry| entry.ids.len().div_ceil(batch_size)) @@ -118,7 +119,10 @@ impl ChainTracker { } /// Attempts to add an incoming [`ChainEntry`] to the chain tracker. - pub fn add_entry(&mut self, mut chain_entry: ChainEntry) -> Result<(), ChainTrackerError> { + pub(crate) fn add_entry( + &mut self, + mut chain_entry: ChainEntry, + ) -> Result<(), ChainTrackerError> { if chain_entry.ids.is_empty() { // The peer must send at lest one overlapping block. chain_entry.handle.ban_peer(MEDIUM_BAN); @@ -154,12 +158,12 @@ impl ChainTracker { /// Returns a batch of blocks to request. /// /// The returned batches length will be less than or equal to `max_blocks` - pub fn blocks_to_get( + pub(crate) fn blocks_to_get( &mut self, pruning_seed: &PruningSeed, max_blocks: usize, ) -> Option> { - if !pruning_seed.has_full_block(self.first_height, CRYPTONOTE_MAX_BLOCK_HEIGHT) { + if !pruning_seed.has_full_block(self.first_height, MAX_BLOCK_HEIGHT_USIZE) { return None; } @@ -171,15 +175,12 @@ impl ChainTracker { // - index of the next pruned block for this seed let end_idx = min( min(entry.ids.len(), max_blocks), - usize::try_from( pruning_seed - .get_next_pruned_block(self.first_height, CRYPTONOTE_MAX_BLOCK_HEIGHT) + .get_next_pruned_block(self.first_height, MAX_BLOCK_HEIGHT_USIZE) .expect("We use local values to calculate height which should be below the sanity limit") // Use a big value as a fallback if the seed does no pruning. - .unwrap_or(CRYPTONOTE_MAX_BLOCK_HEIGHT) + .unwrap_or(MAX_BLOCK_HEIGHT_USIZE) - self.first_height, - ) - .unwrap(), ); if end_idx == 0 { @@ -198,7 +199,7 @@ impl ChainTracker { failures: 0, }; - self.first_height += u64::try_from(end_idx).unwrap(); + self.first_height += end_idx; // TODO: improve ByteArrayVec API. self.previous_hash = blocks.ids[blocks.ids.len() - 1]; diff --git a/p2p/p2p/src/block_downloader/download_batch.rs b/p2p/p2p/src/block_downloader/download_batch.rs index e9dfcb4..7b6e4c9 100644 --- a/p2p/p2p/src/block_downloader/download_batch.rs +++ b/p2p/p2p/src/block_downloader/download_batch.rs @@ -8,13 +8,16 @@ use tracing::instrument; use cuprate_fixed_bytes::ByteArrayVec; use cuprate_helper::asynch::rayon_spawn_async; -use cuprate_p2p_core::{handles::ConnectionHandle, NetworkZone, PeerRequest, PeerResponse}; +use cuprate_p2p_core::{ + handles::ConnectionHandle, NetworkZone, PeerRequest, PeerResponse, ProtocolRequest, + ProtocolResponse, +}; use cuprate_wire::protocol::{GetObjectsRequest, GetObjectsResponse}; use crate::{ block_downloader::{BlockBatch, BlockDownloadError, BlockDownloadTaskResponse}, - client_pool::ClientPoolDropGuard, constants::{BLOCK_DOWNLOADER_REQUEST_TIMEOUT, MAX_TRANSACTION_BLOB_SIZE, MEDIUM_BAN}, + peer_set::ClientDropGuard, }; /// Attempts to request a batch of blocks from a peer, returning [`BlockDownloadTaskResponse`]. @@ -27,11 +30,12 @@ use crate::{ attempt = _attempt ) )] +#[expect(clippy::used_underscore_binding)] pub async fn download_batch_task( - client: ClientPoolDropGuard, + client: ClientDropGuard, ids: ByteArrayVec<32>, previous_id: [u8; 32], - expected_start_height: u64, + expected_start_height: usize, _attempt: usize, ) -> BlockDownloadTaskResponse { BlockDownloadTaskResponse { @@ -45,21 +49,20 @@ pub async fn download_batch_task( /// This function will validate the blocks that were downloaded were the ones asked for and that they match /// the expected height. async fn request_batch_from_peer( - mut client: ClientPoolDropGuard, + mut client: ClientDropGuard, ids: ByteArrayVec<32>, previous_id: [u8; 32], - expected_start_height: u64, -) -> Result<(ClientPoolDropGuard, BlockBatch), BlockDownloadError> { - // Request the blocks. + expected_start_height: usize, +) -> Result<(ClientDropGuard, BlockBatch), BlockDownloadError> { + let request = PeerRequest::Protocol(ProtocolRequest::GetObjects(GetObjectsRequest { + blocks: ids.clone(), + pruned: false, + })); + + // Request the blocks and add a timeout to the request let blocks_response = timeout(BLOCK_DOWNLOADER_REQUEST_TIMEOUT, async { - let PeerResponse::GetObjects(blocks_response) = client - .ready() - .await? - .call(PeerRequest::GetObjects(GetObjectsRequest { - blocks: ids.clone(), - pruned: false, - })) - .await? + let PeerResponse::Protocol(ProtocolResponse::GetObjects(blocks_response)) = + client.ready().await?.call(request).await? else { panic!("Connection task returned wrong response."); }; @@ -101,9 +104,10 @@ async fn request_batch_from_peer( Ok((client, batch)) } +#[expect(clippy::needless_pass_by_value)] fn deserialize_batch( blocks_response: GetObjectsResponse, - expected_start_height: u64, + expected_start_height: usize, requested_ids: ByteArrayVec<32>, previous_id: [u8; 32], peer_handle: ConnectionHandle, @@ -113,7 +117,7 @@ fn deserialize_batch( .into_par_iter() .enumerate() .map(|(i, block_entry)| { - let expected_height = u64::try_from(i).unwrap() + expected_start_height; + let expected_height = i + expected_start_height; let mut size = block_entry.block.len(); @@ -123,7 +127,7 @@ fn deserialize_batch( let block_hash = block.hash(); // Check the block matches the one requested and the peer sent enough transactions. - if requested_ids[i] != block_hash || block.txs.len() != block_entry.txs.len() { + if requested_ids[i] != block_hash || block.transactions.len() != block_entry.txs.len() { return Err(BlockDownloadError::PeersResponseWasInvalid); } @@ -142,9 +146,9 @@ fn deserialize_batch( // Check the height lines up as expected. // This must happen after the hash check. - if !block + if block .number() - .is_some_and(|height| height == expected_height) + .is_none_or(|height| height != expected_height) { tracing::warn!( "Invalid chain, expected height: {expected_height}, got height: {:?}", @@ -175,7 +179,7 @@ fn deserialize_batch( .collect::, _>>()?; // Make sure the transactions in the block were the ones the peer sent. - let mut expected_txs = block.txs.iter().collect::>(); + let mut expected_txs = block.transactions.iter().collect::>(); for tx in &txs { if !expected_txs.remove(&tx.hash()) { diff --git a/p2p/p2p/src/block_downloader/request_chain.rs b/p2p/p2p/src/block_downloader/request_chain.rs index f8b5319..4e0f855 100644 --- a/p2p/p2p/src/block_downloader/request_chain.rs +++ b/p2p/p2p/src/block_downloader/request_chain.rs @@ -1,16 +1,12 @@ -use std::{mem, sync::Arc}; +use std::mem; -use rand::prelude::SliceRandom; -use rand::thread_rng; use tokio::{task::JoinSet, time::timeout}; -use tower::{Service, ServiceExt}; +use tower::{util::BoxCloneService, Service, ServiceExt}; use tracing::{instrument, Instrument, Span}; use cuprate_p2p_core::{ - client::InternalPeerID, - handles::ConnectionHandle, - services::{PeerSyncRequest, PeerSyncResponse}, - NetworkZone, PeerRequest, PeerResponse, PeerSyncSvc, + client::InternalPeerID, handles::ConnectionHandle, NetworkZone, PeerRequest, PeerResponse, + ProtocolRequest, ProtocolResponse, }; use cuprate_wire::protocol::{ChainRequest, ChainResponse}; @@ -19,28 +15,30 @@ use crate::{ chain_tracker::{ChainEntry, ChainTracker}, BlockDownloadError, ChainSvcRequest, ChainSvcResponse, }, - client_pool::{ClientPool, ClientPoolDropGuard}, constants::{ BLOCK_DOWNLOADER_REQUEST_TIMEOUT, INITIAL_CHAIN_REQUESTS_TO_SEND, MAX_BLOCKS_IDS_IN_CHAIN_ENTRY, MEDIUM_BAN, }, + peer_set::{ClientDropGuard, PeerSetRequest, PeerSetResponse}, }; /// Request a chain entry from a peer. /// /// Because the block downloader only follows and downloads one chain we only have to send the block hash of /// top block we have found and the genesis block, this is then called `short_history`. -pub async fn request_chain_entry_from_peer( - mut client: ClientPoolDropGuard, +pub(crate) async fn request_chain_entry_from_peer( + mut client: ClientDropGuard, short_history: [[u8; 32]; 2], -) -> Result<(ClientPoolDropGuard, ChainEntry), BlockDownloadError> { - let PeerResponse::GetChain(chain_res) = client +) -> Result<(ClientDropGuard, ChainEntry), BlockDownloadError> { + let PeerResponse::Protocol(ProtocolResponse::GetChain(chain_res)) = client .ready() .await? - .call(PeerRequest::GetChain(ChainRequest { - block_ids: short_history.into(), - prune: true, - })) + .call(PeerRequest::Protocol(ProtocolRequest::GetChain( + ChainRequest { + block_ids: short_history.into(), + prune: true, + }, + ))) .await? else { panic!("Connection task returned wrong response!"); @@ -81,13 +79,11 @@ pub async fn request_chain_entry_from_peer( /// /// We then wait for their response and choose the peer who claims the highest cumulative difficulty. #[instrument(level = "error", skip_all)] -pub async fn initial_chain_search( - client_pool: &Arc>, - mut peer_sync_svc: S, +pub async fn initial_chain_search( + peer_set: &mut BoxCloneService, tower::BoxError>, mut our_chain_svc: C, ) -> Result, BlockDownloadError> where - S: PeerSyncSvc, C: Service, { tracing::debug!("Getting our chain history"); @@ -106,36 +102,22 @@ where let our_genesis = *block_ids.last().expect("Blockchain had no genesis block."); - tracing::debug!("Getting a list of peers with higher cumulative difficulty"); - - let PeerSyncResponse::PeersToSyncFrom(mut peers) = peer_sync_svc + let PeerSetResponse::PeersWithMorePoW(clients) = peer_set .ready() .await? - .call(PeerSyncRequest::PeersToSyncFrom { - block_needed: None, - current_cumulative_difficulty: cumulative_difficulty, - }) + .call(PeerSetRequest::PeersWithMorePoW(cumulative_difficulty)) .await? else { - panic!("peer sync service sent wrong response."); + unreachable!(); }; - - tracing::debug!( - "{} peers claim they have a higher cumulative difficulty", - peers.len() - ); - - // Shuffle the list to remove any possibility of peers being able to prioritize getting picked. - peers.shuffle(&mut thread_rng()); - - let mut peers = client_pool.borrow_clients(&peers); + let mut peers = clients.into_iter(); let mut futs = JoinSet::new(); - let req = PeerRequest::GetChain(ChainRequest { + let req = PeerRequest::Protocol(ProtocolRequest::GetChain(ChainRequest { block_ids: block_ids.into(), prune: false, - }); + })); tracing::debug!("Sending requests for chain entries."); @@ -149,7 +131,7 @@ where futs.spawn(timeout( BLOCK_DOWNLOADER_REQUEST_TIMEOUT, async move { - let PeerResponse::GetChain(chain_res) = + let PeerResponse::Protocol(ProtocolResponse::GetChain(chain_res)) = next_peer.ready().await?.call(cloned_req).await? else { panic!("connection task returned wrong response!"); @@ -177,7 +159,7 @@ where Some(res) => { // res has already been set, replace it if this peer claims higher cumulative difficulty if res.0.cumulative_difficulty() < task_res.0.cumulative_difficulty() { - let _ = mem::replace(res, task_res); + drop(mem::replace(res, task_res)); } } None => { @@ -198,7 +180,7 @@ where tracing::debug!("Highest chin entry contained {} block Ids", hashes.len()); // Find the first unknown block in the batch. - let ChainSvcResponse::FindFirstUnknown(first_unknown, expected_height) = our_chain_svc + let ChainSvcResponse::FindFirstUnknown(first_unknown_ret) = our_chain_svc .ready() .await? .call(ChainSvcRequest::FindFirstUnknown(hashes.clone())) @@ -207,18 +189,18 @@ where panic!("chain service sent wrong response."); }; + // We know all the blocks already + // TODO: The peer could still be on a different chain, however the chain might just be too far split. + let Some((first_unknown, expected_height)) = first_unknown_ret else { + return Err(BlockDownloadError::FailedToFindAChainToFollow); + }; + // The peer must send at least one block we already know. if first_unknown == 0 { peer_handle.ban_peer(MEDIUM_BAN); return Err(BlockDownloadError::PeerSentNoOverlappingBlocks); } - // We know all the blocks already - // TODO: The peer could still be on a different chain, however the chain might just be too far split. - if first_unknown == hashes.len() { - return Err(BlockDownloadError::FailedToFindAChainToFollow); - } - let previous_id = hashes[first_unknown - 1]; let first_entry = ChainEntry { diff --git a/p2p/p2p/src/block_downloader/tests.rs b/p2p/p2p/src/block_downloader/tests.rs index 5d4225c..707b858 100644 --- a/p2p/p2p/src/block_downloader/tests.rs +++ b/p2p/p2p/src/block_downloader/tests.rs @@ -2,7 +2,7 @@ use std::{ fmt::{Debug, Formatter}, future::Future, pin::Pin, - sync::Arc, + sync::{Arc, Mutex}, task::{Context, Poll}, time::Duration, }; @@ -11,29 +11,27 @@ use futures::{FutureExt, StreamExt}; use indexmap::IndexMap; use monero_serai::{ block::{Block, BlockHeader}, - ringct::{RctBase, RctPrunable, RctSignatures}, transaction::{Input, Timelock, Transaction, TransactionPrefix}, }; use proptest::{collection::vec, prelude::*}; -use tokio::{sync::Semaphore, time::timeout}; -use tower::{service_fn, Service}; +use tokio::{sync::mpsc, time::timeout}; +use tower::{buffer::Buffer, service_fn, Service, ServiceExt}; use cuprate_fixed_bytes::ByteArrayVec; use cuprate_p2p_core::{ client::{mock_client, Client, InternalPeerID, PeerInformation}, - network_zones::ClearNet, - services::{PeerSyncRequest, PeerSyncResponse}, - ConnectionDirection, NetworkZone, PeerRequest, PeerResponse, + ClearNet, ConnectionDirection, PeerRequest, PeerResponse, ProtocolRequest, ProtocolResponse, }; use cuprate_pruning::PruningSeed; +use cuprate_types::{BlockCompleteEntry, TransactionBlobs}; use cuprate_wire::{ - common::{BlockCompleteEntry, TransactionBlobs}, protocol::{ChainResponse, GetObjectsResponse}, + CoreSyncData, }; use crate::{ block_downloader::{download_blocks, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse}, - client_pool::ClientPool, + peer_set::PeerSet, }; proptest! { @@ -52,30 +50,27 @@ proptest! { tokio_pool.block_on(async move { timeout(Duration::from_secs(600), async move { - let client_pool = ClientPool::new(); + let (new_connection_tx, new_connection_rx) = mpsc::channel(peers); - let mut peer_ids = Vec::with_capacity(peers); + let peer_set = PeerSet::new(new_connection_rx); for _ in 0..peers { - let client = mock_block_downloader_client(blockchain.clone()); + let client = mock_block_downloader_client(Arc::clone(&blockchain)); - peer_ids.push(client.info.id); - - client_pool.add_new_client(client); + new_connection_tx.try_send(client).unwrap(); } let stream = download_blocks( - client_pool, - SyncStateSvc(peer_ids) , + Buffer::new(peer_set, 10).boxed_clone(), OurChainSvc { genesis: *blockchain.blocks.first().unwrap().0 }, BlockDownloaderConfig { - buffer_size: 1_000, - in_progress_queue_size: 10_000, + buffer_bytes: 1_000, + in_progress_queue_bytes: 10_000, check_client_pool_interval: Duration::from_secs(5), - target_batch_size: 5_000, - initial_batch_size: 1, + target_batch_bytes: 5_000, + initial_batch_len: 1, }); let blocks = stream.map(|blocks| blocks.blocks).concat().await; @@ -92,30 +87,20 @@ proptest! { prop_compose! { /// Returns a strategy to generate a [`Transaction`] that is valid for the block downloader. - fn dummy_transaction_stragtegy(height: u64) + fn dummy_transaction_stragtegy(height: usize) ( extra in vec(any::(), 0..1_000), timelock in 1_usize..50_000_000, ) -> Transaction { - Transaction { + Transaction::V1 { prefix: TransactionPrefix { - version: 1, - timelock: Timelock::Block(timelock), + additional_timelock: Timelock::Block(timelock), inputs: vec![Input::Gen(height)], outputs: vec![], extra, }, signatures: vec![], - rct_signatures: RctSignatures { - base: RctBase { - fee: 0, - pseudo_outs: vec![], - encrypted_amounts: vec![], - commitments: vec![], - }, - prunable: RctPrunable::Null - }, } } } @@ -123,25 +108,25 @@ prop_compose! { prop_compose! { /// Returns a strategy to generate a [`Block`] that is valid for the block downloader. fn dummy_block_stragtegy( - height: u64, + height: usize, previous: [u8; 32], ) ( - miner_tx in dummy_transaction_stragtegy(height), + miner_transaction in dummy_transaction_stragtegy(height), txs in vec(dummy_transaction_stragtegy(height), 0..25) ) -> (Block, Vec) { ( Block { header: BlockHeader { - major_version: 0, - minor_version: 0, + hardfork_version: 0, + hardfork_signal: 0, timestamp: 0, previous, nonce: 0, }, - miner_tx, - txs: txs.iter().map(Transaction::hash).collect(), + miner_transaction, + transactions: txs.iter().map(Transaction::hash).collect(), }, txs ) @@ -169,7 +154,7 @@ prop_compose! { for (height, mut block) in blocks.into_iter().enumerate() { if let Some(last) = blockchain.last() { block.0.header.previous = *last.0; - block.0.miner_tx.prefix.inputs = vec![Input::Gen(height as u64)] + block.0.miner_transaction.prefix_mut().inputs = vec![Input::Gen(height)]; } blockchain.insert(block.0.hash(), block); @@ -182,18 +167,15 @@ prop_compose! { } fn mock_block_downloader_client(blockchain: Arc) -> Client { - let semaphore = Arc::new(Semaphore::new(1)); - - let (connection_guard, connection_handle) = cuprate_p2p_core::handles::HandleBuilder::new() - .with_permit(semaphore.try_acquire_owned().unwrap()) - .build(); + let (connection_guard, connection_handle) = + cuprate_p2p_core::handles::HandleBuilder::new().build(); let request_handler = service_fn(move |req: PeerRequest| { - let bc = blockchain.clone(); + let bc = Arc::clone(&blockchain); async move { match req { - PeerRequest::GetChain(chain_req) => { + PeerRequest::Protocol(ProtocolRequest::GetChain(chain_req)) => { let mut i = 0; while !bc.blocks.contains_key(&chain_req.block_ids[i]) { i += 1; @@ -215,18 +197,20 @@ fn mock_block_downloader_client(blockchain: Arc) -> Client>(); - Ok(PeerResponse::GetChain(ChainResponse { - start_height: 0, - total_height: 0, - cumulative_difficulty_low64: 1, - cumulative_difficulty_top64: 0, - m_block_ids: block_ids.into(), - m_block_weights: vec![], - first_block: Default::default(), - })) + Ok(PeerResponse::Protocol(ProtocolResponse::GetChain( + ChainResponse { + start_height: 0, + total_height: 0, + cumulative_difficulty_low64: 1, + cumulative_difficulty_top64: 0, + m_block_ids: block_ids.into(), + m_block_weights: vec![], + first_block: Default::default(), + }, + ))) } - PeerRequest::GetObjects(obj) => { + PeerRequest::Protocol(ProtocolRequest::GetObjects(obj)) => { let mut res = Vec::with_capacity(obj.blocks.len()); for i in 0..obj.blocks.len() { @@ -249,11 +233,13 @@ fn mock_block_downloader_client(blockchain: Arc) -> Client panic!(), } @@ -264,33 +250,21 @@ fn mock_block_downloader_client(blockchain: Arc) -> Client(Vec>); - -impl Service> for SyncStateSvc { - type Response = PeerSyncResponse; - type Error = tower::BoxError; - type Future = - Pin> + Send + 'static>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: PeerSyncRequest) -> Self::Future { - let peers = self.0.clone(); - - async move { Ok(PeerSyncResponse::PeersToSyncFrom(peers)) }.boxed() - } -} - struct OurChainSvc { genesis: [u8; 32], } @@ -314,7 +288,9 @@ impl Service for OurChainSvc { block_ids: vec![genesis], cumulative_difficulty: 1, }, - ChainSvcRequest::FindFirstUnknown(_) => ChainSvcResponse::FindFirstUnknown(1, 1), + ChainSvcRequest::FindFirstUnknown(_) => { + ChainSvcResponse::FindFirstUnknown(Some((1, 1))) + } ChainSvcRequest::CumulativeDifficulty => ChainSvcResponse::CumulativeDifficulty(1), }) } diff --git a/p2p/p2p/src/broadcast.rs b/p2p/p2p/src/broadcast.rs index db7a41e..38aba32 100644 --- a/p2p/p2p/src/broadcast.rs +++ b/p2p/p2p/src/broadcast.rs @@ -25,10 +25,8 @@ use tower::Service; use cuprate_p2p_core::{ client::InternalPeerID, BroadcastMessage, ConnectionDirection, NetworkZone, }; -use cuprate_wire::{ - common::{BlockCompleteEntry, TransactionBlobs}, - protocol::{NewFluffyBlock, NewTransactions}, -}; +use cuprate_types::{BlockCompleteEntry, TransactionBlobs}; +use cuprate_wire::protocol::{NewFluffyBlock, NewTransactions}; use crate::constants::{ DIFFUSION_FLUSH_AVERAGE_SECONDS_INBOUND, DIFFUSION_FLUSH_AVERAGE_SECONDS_OUTBOUND, @@ -37,7 +35,7 @@ use crate::constants::{ /// The configuration for the [`BroadcastSvc`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct BroadcastConfig { +pub(crate) struct BroadcastConfig { /// The average number of seconds between diffusion flushes for outbound connections. pub diffusion_flush_average_seconds_outbound: Duration, /// The average number of seconds between diffusion flushes for inbound connections. @@ -59,7 +57,8 @@ impl Default for BroadcastConfig { /// - The [`BroadcastSvc`] /// - A function that takes in [`InternalPeerID`]s and produces [`BroadcastMessageStream`]s to give to **outbound** peers. /// - A function that takes in [`InternalPeerID`]s and produces [`BroadcastMessageStream`]s to give to **inbound** peers. -pub fn init_broadcast_channels( +#[expect(clippy::type_complexity)] +pub(crate) fn init_broadcast_channels( config: BroadcastConfig, ) -> ( BroadcastSvc, @@ -195,18 +194,18 @@ impl Service> for BroadcastSvc { }; // An error here means _all_ receivers were dropped which we assume will never happen. - let _ = match direction { - Some(ConnectionDirection::InBound) => { + drop(match direction { + Some(ConnectionDirection::Inbound) => { self.tx_broadcast_channel_inbound.send(nex_tx_info) } - Some(ConnectionDirection::OutBound) => { + Some(ConnectionDirection::Outbound) => { self.tx_broadcast_channel_outbound.send(nex_tx_info) } None => { - let _ = self.tx_broadcast_channel_outbound.send(nex_tx_info.clone()); + drop(self.tx_broadcast_channel_outbound.send(nex_tx_info.clone())); self.tx_broadcast_channel_inbound.send(nex_tx_info) } - }; + }); } } @@ -248,7 +247,7 @@ struct BroadcastTxInfo { /// /// This is given to the connection task to await on for broadcast messages. #[pin_project::pin_project] -pub struct BroadcastMessageStream { +pub(crate) struct BroadcastMessageStream { /// The peer that is holding this stream. addr: InternalPeerID, @@ -338,8 +337,9 @@ impl Stream for BroadcastMessageStream { Poll::Ready(Some(BroadcastMessage::NewTransaction(txs))) } else { tracing::trace!("Diffusion flush timer expired but no txs to diffuse"); - // poll next_flush now to register the waker with it + // poll next_flush now to register the waker with it. // the waker will already be registered with the block broadcast channel. + #[expect(clippy::let_underscore_must_use)] let _ = this.next_flush.poll(cx); Poll::Pending } @@ -415,7 +415,7 @@ mod tests { #[tokio::test] async fn tx_broadcast_direction_correct() { let (mut brcst, outbound_mkr, inbound_mkr) = - init_broadcast_channels::>(TEST_CONFIG); + init_broadcast_channels::>(TEST_CONFIG); let mut outbound_stream = pin!(outbound_mkr(InternalPeerID::Unknown(1))); let mut inbound_stream = pin!(inbound_mkr(InternalPeerID::Unknown(1))); @@ -428,7 +428,7 @@ mod tests { .unwrap() .call(BroadcastRequest::Transaction { tx_bytes: Bytes::from_static(&[1]), - direction: Some(ConnectionDirection::OutBound), + direction: Some(ConnectionDirection::Outbound), received_from: None, }) .await @@ -440,7 +440,7 @@ mod tests { .unwrap() .call(BroadcastRequest::Transaction { tx_bytes: Bytes::from_static(&[2]), - direction: Some(ConnectionDirection::InBound), + direction: Some(ConnectionDirection::Inbound), received_from: None, }) .await @@ -460,7 +460,7 @@ mod tests { let match_tx = |mes, txs| match mes { BroadcastMessage::NewTransaction(tx) => assert_eq!(tx.txs.as_slice(), txs), - _ => panic!("Block broadcast?"), + BroadcastMessage::NewFluffyBlock(_) => panic!("Block broadcast?"), }; let next = outbound_stream.next().await.unwrap(); @@ -474,7 +474,7 @@ mod tests { #[tokio::test] async fn block_broadcast_sent_to_all() { let (mut brcst, outbound_mkr, inbound_mkr) = - init_broadcast_channels::>(TEST_CONFIG); + init_broadcast_channels::>(TEST_CONFIG); let mut outbound_stream = pin!(outbound_mkr(InternalPeerID::Unknown(1))); let mut inbound_stream = pin!(inbound_mkr(InternalPeerID::Unknown(1))); @@ -500,7 +500,7 @@ mod tests { #[tokio::test] async fn tx_broadcast_skipped_for_received_from_peer() { let (mut brcst, outbound_mkr, inbound_mkr) = - init_broadcast_channels::>(TEST_CONFIG); + init_broadcast_channels::>(TEST_CONFIG); let mut outbound_stream = pin!(outbound_mkr(InternalPeerID::Unknown(1))); let mut outbound_stream_from = pin!(outbound_mkr(InternalPeerID::Unknown(0))); @@ -522,7 +522,7 @@ mod tests { let match_tx = |mes, txs| match mes { BroadcastMessage::NewTransaction(tx) => assert_eq!(tx.txs.as_slice(), txs), - _ => panic!("Block broadcast?"), + BroadcastMessage::NewFluffyBlock(_) => panic!("Block broadcast?"), }; let next = outbound_stream.next().await.unwrap(); @@ -538,6 +538,6 @@ mod tests { futures::future::select(inbound_stream_from.next(), outbound_stream_from.next()) ) .await - .is_err()) + .is_err()); } } diff --git a/p2p/p2p/src/client_pool.rs b/p2p/p2p/src/client_pool.rs deleted file mode 100644 index 711491d..0000000 --- a/p2p/p2p/src/client_pool.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! # Client Pool. -//! -//! The [`ClientPool`], is a pool of currently connected peers that can be pulled from. -//! It does _not_ necessarily contain every connected peer as another place could have -//! taken a peer from the pool. -//! -//! When taking peers from the pool they are wrapped in [`ClientPoolDropGuard`], which -//! returns the peer to the pool when it is dropped. -//! -//! Internally the pool is a [`DashMap`] which means care should be taken in `async` code -//! as internally this uses blocking RwLocks. -//! -use std::sync::Arc; - -use dashmap::DashMap; -use tokio::sync::mpsc; -use tracing::{Instrument, Span}; - -use cuprate_p2p_core::{ - client::{Client, InternalPeerID}, - handles::ConnectionHandle, - NetworkZone, -}; - -pub(crate) mod disconnect_monitor; -mod drop_guard_client; - -pub use drop_guard_client::ClientPoolDropGuard; - -/// The client pool, which holds currently connected free peers. -/// -/// See the [module docs](self) for more. -pub struct ClientPool { - /// The connected [`Client`]s. - clients: DashMap, Client>, - /// A channel to send new peer ids down to monitor for disconnect. - new_connection_tx: mpsc::UnboundedSender<(ConnectionHandle, InternalPeerID)>, -} - -impl ClientPool { - /// Returns a new [`ClientPool`] wrapped in an [`Arc`]. - pub fn new() -> Arc> { - let (tx, rx) = mpsc::unbounded_channel(); - - let pool = Arc::new(ClientPool { - clients: DashMap::new(), - new_connection_tx: tx, - }); - - tokio::spawn( - disconnect_monitor::disconnect_monitor(rx, pool.clone()).instrument(Span::current()), - ); - - pool - } - - /// Adds a [`Client`] to the pool, the client must have previously been taken from the - /// pool. - /// - /// See [`ClientPool::add_new_client`] to add a [`Client`] which was not taken from the pool before. - /// - /// # Panics - /// This function panics if `client` already exists in the pool. - fn add_client(&self, client: Client) { - let handle = client.info.handle.clone(); - let id = client.info.id; - - // Fast path: if the client is disconnected don't add it to the peer set. - if handle.is_closed() { - return; - } - - let res = self.clients.insert(id, client); - assert!(res.is_none()); - - // We have to check this again otherwise we could have a race condition where a - // peer is disconnected after the first check, the disconnect monitor tries to remove it, - // and then it is added to the pool. - if handle.is_closed() { - self.remove_client(&id); - } - } - - /// Adds a _new_ [`Client`] to the pool, this client should be a new connection, and not already - /// from the pool. - /// - /// # Panics - /// This function panics if `client` already exists in the pool. - pub fn add_new_client(&self, client: Client) { - self.new_connection_tx - .send((client.info.handle.clone(), client.info.id)) - .unwrap(); - - self.add_client(client); - } - - /// Remove a [`Client`] from the pool. - /// - /// [`None`] is returned if the client did not exist in the pool. - fn remove_client(&self, peer: &InternalPeerID) -> Option> { - self.clients.remove(peer).map(|(_, client)| client) - } - - /// Borrows a [`Client`] from the pool. - /// - /// The [`Client`] is wrapped in [`ClientPoolDropGuard`] which - /// will return the client to the pool when it's dropped. - /// - /// See [`Self::borrow_clients`] for borrowing multiple clients. - pub fn borrow_client( - self: &Arc, - peer: &InternalPeerID, - ) -> Option> { - self.remove_client(peer).map(|client| ClientPoolDropGuard { - pool: Arc::clone(self), - client: Some(client), - }) - } - - /// Borrows multiple [`Client`]s from the pool. - /// - /// Note that the returned iterator is not guaranteed to contain every peer asked for. - /// - /// See [`Self::borrow_client`] for borrowing a single client. - #[allow(private_interfaces)] // TODO: Remove me when 2024 Rust - pub fn borrow_clients<'a, 'b>( - self: &'a Arc, - peers: &'b [InternalPeerID], - ) -> impl Iterator> + sealed::Captures<(&'a (), &'b ())> { - peers.iter().filter_map(|peer| self.borrow_client(peer)) - } -} - -mod sealed { - /// TODO: Remove me when 2024 Rust - /// - /// https://rust-lang.github.io/rfcs/3498-lifetime-capture-rules-2024.html#the-captures-trick - pub trait Captures {} - - impl Captures for T {} -} diff --git a/p2p/p2p/src/client_pool/disconnect_monitor.rs b/p2p/p2p/src/client_pool/disconnect_monitor.rs deleted file mode 100644 index f45d5e3..0000000 --- a/p2p/p2p/src/client_pool/disconnect_monitor.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! # Disconnect Monitor -//! -//! This module contains the [`disconnect_monitor`] task, which monitors connected peers for disconnection -//! and then removes them from the [`ClientPool`] if they do. -use std::{ - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; - -use futures::{stream::FuturesUnordered, StreamExt}; -use tokio::sync::mpsc; -use tokio_util::sync::WaitForCancellationFutureOwned; -use tracing::instrument; - -use cuprate_p2p_core::{client::InternalPeerID, handles::ConnectionHandle, NetworkZone}; - -use super::ClientPool; - -/// The disconnect monitor task. -#[instrument(level = "info", skip_all)] -pub async fn disconnect_monitor( - mut new_connection_rx: mpsc::UnboundedReceiver<(ConnectionHandle, InternalPeerID)>, - client_pool: Arc>, -) { - // We need to hold a weak reference otherwise the client pool and this would hold a reference to - // each other causing the pool to be leaked. - let weak_client_pool = Arc::downgrade(&client_pool); - drop(client_pool); - - tracing::info!("Starting peer disconnect monitor."); - - let mut futs: FuturesUnordered> = FuturesUnordered::new(); - - loop { - tokio::select! { - Some((con_handle, peer_id)) = new_connection_rx.recv() => { - tracing::debug!("Monitoring {peer_id} for disconnect"); - futs.push(PeerDisconnectFut { - closed_fut: con_handle.closed(), - peer_id: Some(peer_id), - }); - } - Some(peer_id) = futs.next() => { - tracing::debug!("{peer_id} has disconnected, removing from client pool."); - let Some(pool) = weak_client_pool.upgrade() else { - tracing::info!("Peer disconnect monitor shutting down."); - return; - }; - - pool.remove_client(&peer_id); - drop(pool); - } - else => { - tracing::info!("Peer disconnect monitor shutting down."); - return; - } - } - } -} - -/// A [`Future`] that resolves when a peer disconnects. -#[pin_project::pin_project] -pub(crate) struct PeerDisconnectFut { - /// The inner [`Future`] that resolves when a peer disconnects. - #[pin] - pub(crate) closed_fut: WaitForCancellationFutureOwned, - /// The peers ID. - pub(crate) peer_id: Option>, -} - -impl Future for PeerDisconnectFut { - type Output = InternalPeerID; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - this.closed_fut - .poll(cx) - .map(|_| this.peer_id.take().unwrap()) - } -} diff --git a/p2p/p2p/src/client_pool/drop_guard_client.rs b/p2p/p2p/src/client_pool/drop_guard_client.rs deleted file mode 100644 index b10c4e9..0000000 --- a/p2p/p2p/src/client_pool/drop_guard_client.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::{ - ops::{Deref, DerefMut}, - sync::Arc, -}; - -use cuprate_p2p_core::{client::Client, NetworkZone}; - -use crate::client_pool::ClientPool; - -/// A wrapper around [`Client`] which returns the client to the [`ClientPool`] when dropped. -pub struct ClientPoolDropGuard { - /// The [`ClientPool`] to return the peer to. - pub(super) pool: Arc>, - /// The [`Client`]. - /// - /// This is set to [`Some`] when this guard is created, then - /// [`take`](Option::take)n and returned to the pool when dropped. - pub(super) client: Option>, -} - -impl Deref for ClientPoolDropGuard { - type Target = Client; - - fn deref(&self) -> &Self::Target { - self.client.as_ref().unwrap() - } -} - -impl DerefMut for ClientPoolDropGuard { - fn deref_mut(&mut self) -> &mut Self::Target { - self.client.as_mut().unwrap() - } -} - -impl Drop for ClientPoolDropGuard { - fn drop(&mut self) { - let client = self.client.take().unwrap(); - - self.pool.add_client(client); - } -} diff --git a/p2p/p2p/src/config.rs b/p2p/p2p/src/config.rs index 90d7f8f..5b4ba4a 100644 --- a/p2p/p2p/src/config.rs +++ b/p2p/p2p/src/config.rs @@ -1,13 +1,16 @@ -use cuprate_address_book::AddressBookConfig; use cuprate_helper::network::Network; use cuprate_p2p_core::NetworkZone; use cuprate_wire::{common::PeerSupportFlags, BasicNodeData}; +pub use cuprate_address_book::AddressBookConfig; + /// P2P config. #[derive(Clone, Debug)] pub struct P2PConfig { /// The [`Network`] we should connect to. pub network: Network, + /// Seed nodes to connect to find peers if our address book is empty. + pub seeds: Vec, /// The number of outbound connections to make and try keep. pub outbound_connections: usize, diff --git a/p2p/p2p/src/connection_maintainer.rs b/p2p/p2p/src/connection_maintainer.rs index 8e5c9bc..245fbf1 100644 --- a/p2p/p2p/src/connection_maintainer.rs +++ b/p2p/p2p/src/connection_maintainer.rs @@ -21,7 +21,6 @@ use cuprate_p2p_core::{ }; use crate::{ - client_pool::ClientPool, config::P2PConfig, constants::{HANDSHAKE_TIMEOUT, MAX_SEED_CONNECTIONS, OUTBOUND_CONNECTION_ATTEMPT_TIMEOUT}, }; @@ -38,7 +37,7 @@ enum OutboundConnectorError { /// set needs specific data that none of the currently connected peers have. pub struct MakeConnectionRequest { /// The block needed that no connected peers have due to pruning. - block_needed: Option, + block_needed: Option, } /// The outbound connection count keeper. @@ -46,7 +45,7 @@ pub struct MakeConnectionRequest { /// This handles maintaining a minimum number of connections and making extra connections when needed, upto a maximum. pub struct OutboundConnectionKeeper { /// The pool of currently connected peers. - pub client_pool: Arc>, + pub new_peers_tx: mpsc::Sender>, /// The channel that tells us to make new _extra_ outbound connections. pub make_connection_rx: mpsc::Receiver, /// The address book service @@ -77,7 +76,7 @@ where { pub fn new( config: P2PConfig, - client_pool: Arc>, + new_peers_tx: mpsc::Sender>, make_connection_rx: mpsc::Receiver, address_book_svc: A, connector_svc: C, @@ -86,7 +85,7 @@ where .expect("Gray peer percent is incorrect should be 0..=1"); Self { - client_pool, + new_peers_tx, make_connection_rx, address_book_svc, connector_svc, @@ -99,16 +98,17 @@ where /// Connects to random seeds to get peers and immediately disconnects #[instrument(level = "info", skip(self))] + #[expect( + clippy::significant_drop_in_scrutinee, + clippy::significant_drop_tightening + )] async fn connect_to_random_seeds(&mut self) -> Result<(), OutboundConnectorError> { - let seeds = N::SEEDS.choose_multiple(&mut thread_rng(), MAX_SEED_CONNECTIONS); + let seeds = self + .config + .seeds + .choose_multiple(&mut thread_rng(), MAX_SEED_CONNECTIONS); - if seeds.len() == 0 { - panic!("No seed nodes available to get peers from"); - } - - // This isn't really needed here to limit connections as the seed nodes will be dropped when we have got - // peers from them. - let semaphore = Arc::new(Semaphore::new(seeds.len())); + assert_ne!(seeds.len(), 0, "No seed nodes available to get peers from"); let mut allowed_errors = seeds.len(); @@ -125,10 +125,7 @@ where .expect("Connector had an error in `poll_ready`") .call(ConnectRequest { addr: *seed, - permit: semaphore - .clone() - .try_acquire_owned() - .expect("This must have enough permits as we just set the amount."), + permit: None, }), ); // Spawn the handshake on a separate task with a timeout, so we don't get stuck connecting to a peer. @@ -136,7 +133,7 @@ where } while let Some(res) = handshake_futs.join_next().await { - if matches!(res, Err(_) | Ok(Err(_)) | Ok(Ok(Err(_)))) { + if matches!(res, Err(_) | Ok(Err(_) | Ok(Err(_)))) { allowed_errors -= 1; } } @@ -151,18 +148,22 @@ where /// Connects to a given outbound peer. #[instrument(level = "info", skip_all)] async fn connect_to_outbound_peer(&mut self, permit: OwnedSemaphorePermit, addr: N::Addr) { - let client_pool = self.client_pool.clone(); + let new_peers_tx = self.new_peers_tx.clone(); let connection_fut = self .connector_svc .ready() .await .expect("Connector had an error in `poll_ready`") - .call(ConnectRequest { addr, permit }); + .call(ConnectRequest { + addr, + permit: Some(permit), + }); tokio::spawn( async move { + #[expect(clippy::significant_drop_in_scrutinee)] if let Ok(Ok(peer)) = timeout(HANDSHAKE_TIMEOUT, connection_fut).await { - client_pool.add_new_client(peer); + drop(new_peers_tx.send(peer).await); } } .instrument(Span::current()), @@ -170,14 +171,16 @@ where } /// Handles a request from the peer set for more peers. + #[expect( + clippy::significant_drop_tightening, + reason = "we need to hold onto a permit" + )] async fn handle_peer_request( &mut self, req: &MakeConnectionRequest, ) -> Result<(), OutboundConnectorError> { // try to get a permit. - let permit = self - .outbound_semaphore - .clone() + let permit = Arc::clone(&self.outbound_semaphore) .try_acquire_owned() .or_else(|_| { // if we can't get a permit add one if we are below the max number of connections. @@ -187,7 +190,9 @@ where } else { self.outbound_semaphore.add_permits(1); self.extra_peers += 1; - Ok(self.outbound_semaphore.clone().try_acquire_owned().unwrap()) + Ok(Arc::clone(&self.outbound_semaphore) + .try_acquire_owned() + .unwrap()) } })?; @@ -276,12 +281,12 @@ where tracing::info!("Shutting down outbound connector, make connection channel closed."); return; }; - // We can't really do much about errors in this function. + #[expect(clippy::let_underscore_must_use, reason = "We can't really do much about errors in this function.")] let _ = self.handle_peer_request(&peer_req).await; }, // This future is not cancellation safe as you will lose your space in the queue but as we are the only place // that actually requires permits that should be ok. - Ok(permit) = self.outbound_semaphore.clone().acquire_owned() => { + Ok(permit) = Arc::clone(&self.outbound_semaphore).acquire_owned() => { if self.handle_free_permit(permit).await.is_err() { // if we got an error then we still have a permit free so to prevent this from just looping // uncontrollably add a timeout. diff --git a/p2p/p2p/src/constants.rs b/p2p/p2p/src/constants.rs index 44dba91..a81557c 100644 --- a/p2p/p2p/src/constants.rs +++ b/p2p/p2p/src/constants.rs @@ -3,6 +3,12 @@ use std::time::Duration; /// The timeout we set on handshakes. pub(crate) const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(20); +/// The timeout we set on receiving ping requests +pub(crate) const PING_REQUEST_TIMEOUT: Duration = Duration::from_secs(5); + +/// The amount of concurrency (maximum number of simultaneous tasks) we allow for handling ping requests +pub(crate) const PING_REQUEST_CONCURRENCY: usize = 2; + /// The maximum amount of connections to make to seed nodes for when we need peers. pub(crate) const MAX_SEED_CONNECTIONS: usize = 3; @@ -10,13 +16,13 @@ pub(crate) const MAX_SEED_CONNECTIONS: usize = 3; pub(crate) const OUTBOUND_CONNECTION_ATTEMPT_TIMEOUT: Duration = Duration::from_secs(5); /// The durations of a short ban. -pub(crate) const SHORT_BAN: Duration = Duration::from_secs(60 * 10); +pub const SHORT_BAN: Duration = Duration::from_secs(60 * 10); /// The durations of a medium ban. -pub(crate) const MEDIUM_BAN: Duration = Duration::from_secs(60 * 60 * 24); +pub const MEDIUM_BAN: Duration = Duration::from_secs(60 * 60 * 24); /// The durations of a long ban. -pub(crate) const LONG_BAN: Duration = Duration::from_secs(60 * 60 * 24 * 7); +pub const LONG_BAN: Duration = Duration::from_secs(60 * 60 * 24 * 7); /// The default amount of time between inbound diffusion flushes. pub(crate) const DIFFUSION_FLUSH_AVERAGE_SECONDS_INBOUND: Duration = Duration::from_secs(5); @@ -46,7 +52,7 @@ pub(crate) const INITIAL_CHAIN_REQUESTS_TO_SEND: usize = 3; /// The enforced maximum amount of blocks to request in a batch. /// /// Requesting more than this will cause the peer to disconnect and potentially lead to bans. -pub(crate) const MAX_BLOCK_BATCH_LEN: usize = 100; +pub const MAX_BLOCK_BATCH_LEN: usize = 100; /// The timeout that the block downloader will use for requests. pub(crate) const BLOCK_DOWNLOADER_REQUEST_TIMEOUT: Duration = Duration::from_secs(30); @@ -55,13 +61,13 @@ pub(crate) const BLOCK_DOWNLOADER_REQUEST_TIMEOUT: Duration = Duration::from_sec /// be less than. /// /// ref: -pub(crate) const MAX_TRANSACTION_BLOB_SIZE: usize = 1_000_000; +pub const MAX_TRANSACTION_BLOB_SIZE: usize = 1_000_000; /// The maximum amount of block IDs allowed in a chain entry response. /// /// ref: // TODO: link to the protocol book when this section is added. -pub(crate) const MAX_BLOCKS_IDS_IN_CHAIN_ENTRY: usize = 25_000; +pub const MAX_BLOCKS_IDS_IN_CHAIN_ENTRY: usize = 25_000; /// The amount of failures downloading a specific batch before we stop attempting to download it. pub(crate) const MAX_DOWNLOAD_FAILURES: usize = 5; diff --git a/p2p/p2p/src/inbound_server.rs b/p2p/p2p/src/inbound_server.rs index 6bc1e6d..0479560 100644 --- a/p2p/p2p/src/inbound_server.rs +++ b/p2p/p2p/src/inbound_server.rs @@ -4,9 +4,10 @@ //! them to the handshaker service and then adds them to the client pool. use std::{pin::pin, sync::Arc}; -use futures::StreamExt; +use futures::{SinkExt, StreamExt}; use tokio::{ - sync::Semaphore, + sync::{mpsc, Semaphore}, + task::JoinSet, time::{sleep, timeout}, }; use tower::{Service, ServiceExt}; @@ -17,17 +18,24 @@ use cuprate_p2p_core::{ services::{AddressBookRequest, AddressBookResponse}, AddressBook, ConnectionDirection, NetworkZone, }; +use cuprate_wire::{ + admin::{PingResponse, PING_OK_RESPONSE_STATUS_TEXT}, + AdminRequestMessage, AdminResponseMessage, Message, +}; use crate::{ - client_pool::ClientPool, - constants::{HANDSHAKE_TIMEOUT, INBOUND_CONNECTION_COOL_DOWN}, + constants::{ + HANDSHAKE_TIMEOUT, INBOUND_CONNECTION_COOL_DOWN, PING_REQUEST_CONCURRENCY, + PING_REQUEST_TIMEOUT, + }, P2PConfig, }; -/// Starts the inbound server. +/// Starts the inbound server. This function will listen to all incoming connections +/// and initiate handshake if needed, after verifying the address isn't banned. #[instrument(level = "warn", skip_all)] pub async fn inbound_server( - client_pool: Arc>, + new_connection_tx: mpsc::Sender>, mut handshaker: HS, mut address_book: A, config: P2PConfig, @@ -40,6 +48,10 @@ where HS::Future: Send + 'static, A: AddressBook, { + // Copying the peer_id before borrowing for ping responses (Make us avoid a `clone()`). + let our_peer_id = config.basic_node_data().peer_id; + + // Mandatory. Extract server config from P2PConfig let Some(server_config) = config.server_config else { tracing::warn!("No inbound server config provided, not listening for inbound connections."); return Ok(()); @@ -53,57 +65,98 @@ where let mut listener = pin!(listener); + // Create semaphore for limiting to maximum inbound connections. let semaphore = Arc::new(Semaphore::new(config.max_inbound_connections)); + // Create ping request handling JoinSet + let mut ping_join_set = JoinSet::new(); + // Listen to incoming connections and extract necessary information. while let Some(connection) = listener.next().await { - let Ok((addr, peer_stream, peer_sink)) = connection else { + let Ok((addr, mut peer_stream, mut peer_sink)) = connection else { continue; }; + // If peer is banned, drop connection if let Some(addr) = &addr { - let AddressBookResponse::IsPeerBanned(banned) = address_book + let AddressBookResponse::GetBan { unban_instant } = address_book .ready() .await? - .call(AddressBookRequest::IsPeerBanned(*addr)) + .call(AddressBookRequest::GetBan(*addr)) .await? else { panic!("Address book returned incorrect response!"); }; - if banned { + if unban_instant.is_some() { continue; } } + // Create a new internal id for new peers let addr = match addr { Some(addr) => InternalPeerID::KnownAddr(addr), None => InternalPeerID::Unknown(rand::random()), }; - if let Ok(permit) = semaphore.clone().try_acquire_owned() { + // If we're still behind our maximum limit, Initiate handshake. + if let Ok(permit) = Arc::clone(&semaphore).try_acquire_owned() { tracing::debug!("Permit free for incoming connection, attempting handshake."); let fut = handshaker.ready().await?.call(DoHandshakeRequest { addr, peer_stream, peer_sink, - direction: ConnectionDirection::InBound, - permit, + direction: ConnectionDirection::Inbound, + permit: Some(permit), }); - let cloned_pool = client_pool.clone(); + let new_connection_tx = new_connection_tx.clone(); tokio::spawn( async move { - if let Ok(Ok(peer)) = timeout(HANDSHAKE_TIMEOUT, fut).await { - cloned_pool.add_new_client(peer); + let client = timeout(HANDSHAKE_TIMEOUT, fut).await; + if let Ok(Ok(peer)) = client { + drop(new_connection_tx.send(peer).await); } } .instrument(Span::current()), ); } else { + // Otherwise check if the node is simply pinging us. tracing::debug!("No permit free for incoming connection."); - // TODO: listen for if the peer is just trying to ping us to see if we are reachable. + + // We only handle 2 ping request conccurently. Otherwise we drop the connection immediately. + if ping_join_set.len() < PING_REQUEST_CONCURRENCY { + ping_join_set.spawn( + async move { + // Await first message from node. If it is a ping request we respond back, otherwise we drop the connection. + let fut = timeout(PING_REQUEST_TIMEOUT, peer_stream.next()); + + // Ok if timeout did not elapsed -> Some if there is a message -> Ok if it has been decoded + if matches!( + fut.await, + Ok(Some(Ok(Message::Request(AdminRequestMessage::Ping)))) + ) { + let response = peer_sink + .send( + Message::Response(AdminResponseMessage::Ping(PingResponse { + status: PING_OK_RESPONSE_STATUS_TEXT, + peer_id: our_peer_id, + })) + .into(), + ) + .await; + + if let Err(err) = response { + tracing::debug!( + "Unable to respond to ping request from peer ({addr}): {err}" + ); + } + } + } + .instrument(Span::current()), + ); + } } sleep(INBOUND_CONNECTION_COOL_DOWN).await; diff --git a/p2p/p2p/src/lib.rs b/p2p/p2p/src/lib.rs index 95154ec..fb50658 100644 --- a/p2p/p2p/src/lib.rs +++ b/p2p/p2p/src/lib.rs @@ -4,37 +4,32 @@ //! a certain [`NetworkZone`] use std::sync::Arc; -use cuprate_async_buffer::BufferStream; use futures::FutureExt; -use tokio::{ - sync::{mpsc, watch}, - task::JoinSet, -}; -use tokio_stream::wrappers::WatchStream; +use tokio::{sync::mpsc, task::JoinSet}; use tower::{buffer::Buffer, util::BoxCloneService, Service, ServiceExt}; use tracing::{instrument, Instrument, Span}; +use cuprate_async_buffer::BufferStream; use cuprate_p2p_core::{ client::Connector, - client::InternalPeerID, - services::{AddressBookRequest, AddressBookResponse, PeerSyncRequest}, - CoreSyncSvc, NetworkZone, PeerRequestHandler, + services::{AddressBookRequest, AddressBookResponse}, + CoreSyncSvc, NetworkZone, ProtocolRequestHandlerMaker, }; -mod block_downloader; +pub mod block_downloader; mod broadcast; -mod client_pool; pub mod config; pub mod connection_maintainer; -mod constants; +pub mod constants; mod inbound_server; -mod sync_states; +mod peer_set; use block_downloader::{BlockBatch, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse}; pub use broadcast::{BroadcastRequest, BroadcastSvc}; -use client_pool::ClientPoolDropGuard; -pub use config::P2PConfig; +pub use config::{AddressBookConfig, P2PConfig}; use connection_maintainer::MakeConnectionRequest; +use peer_set::PeerSet; +pub use peer_set::{ClientDropGuard, PeerSetRequest, PeerSetResponse}; /// Initializes the P2P [`NetworkInterface`] for a specific [`NetworkZone`]. /// @@ -42,30 +37,28 @@ use connection_maintainer::MakeConnectionRequest; /// /// # Usage /// You must provide: -/// - A peer request handler, which is given to each connection +/// - A protocol request handler, which is given to each connection /// - A core sync service, which keeps track of the sync state of our node #[instrument(level = "debug", name = "net", skip_all, fields(zone = N::NAME))] -pub async fn initialize_network( - peer_req_handler: R, +pub async fn initialize_network( + protocol_request_handler_maker: PR, core_sync_svc: CS, config: P2PConfig, ) -> Result, tower::BoxError> where N: NetworkZone, - R: PeerRequestHandler + Clone, + N::Addr: borsh::BorshDeserialize + borsh::BorshSerialize, + PR: ProtocolRequestHandlerMaker + Clone, CS: CoreSyncSvc + Clone, { let address_book = cuprate_address_book::init_address_book(config.address_book_config.clone()).await?; let address_book = Buffer::new( address_book, - config.max_inbound_connections + config.outbound_connections, - ); - - let (sync_states_svc, top_block_watch) = sync_states::PeerSyncSvc::new(); - let sync_states_svc = Buffer::new( - sync_states_svc, - config.max_inbound_connections + config.outbound_connections, + config + .max_inbound_connections + .checked_add(config.outbound_connections) + .unwrap(), ); // Use the default config. Changing the defaults affects tx fluff times, which could affect D++ so for now don't allow changing @@ -79,37 +72,40 @@ where basic_node_data.peer_id = 1; } - let outbound_handshaker = cuprate_p2p_core::client::HandShaker::new( - address_book.clone(), - sync_states_svc.clone(), - core_sync_svc.clone(), - peer_req_handler.clone(), - outbound_mkr, - basic_node_data.clone(), + let outbound_handshaker_builder = + cuprate_p2p_core::client::HandshakerBuilder::new(basic_node_data) + .with_address_book(address_book.clone()) + .with_core_sync_svc(core_sync_svc) + .with_protocol_request_handler_maker(protocol_request_handler_maker) + .with_broadcast_stream_maker(outbound_mkr) + .with_connection_parent_span(Span::current()); + + let inbound_handshaker = outbound_handshaker_builder + .clone() + .with_broadcast_stream_maker(inbound_mkr) + .build(); + + let outbound_handshaker = outbound_handshaker_builder.build(); + + let (new_connection_tx, new_connection_rx) = mpsc::channel( + config + .outbound_connections + .checked_add(config.max_inbound_connections) + .unwrap(), ); - - let inbound_handshaker = cuprate_p2p_core::client::HandShaker::new( - address_book.clone(), - sync_states_svc.clone(), - core_sync_svc.clone(), - peer_req_handler, - inbound_mkr, - basic_node_data, - ); - - let client_pool = client_pool::ClientPool::new(); - let (make_connection_tx, make_connection_rx) = mpsc::channel(3); let outbound_connector = Connector::new(outbound_handshaker); let outbound_connection_maintainer = connection_maintainer::OutboundConnectionKeeper::new( config.clone(), - client_pool.clone(), + new_connection_tx.clone(), make_connection_rx, address_book.clone(), outbound_connector, ); + let peer_set = PeerSet::new(new_connection_rx); + let mut background_tasks = JoinSet::new(); background_tasks.spawn( @@ -119,27 +115,25 @@ where ); background_tasks.spawn( inbound_server::inbound_server( - client_pool.clone(), + new_connection_tx, inbound_handshaker, address_book.clone(), config, ) .map(|res| { if let Err(e) = res { - tracing::error!("Error in inbound connection listener: {e}") + tracing::error!("Error in inbound connection listener: {e}"); } - tracing::info!("Inbound connection listener shutdown") + tracing::info!("Inbound connection listener shutdown"); }) .instrument(Span::current()), ); Ok(NetworkInterface { - pool: client_pool, + peer_set: Buffer::new(peer_set, 10).boxed_clone(), broadcast_svc, - top_block_watch, make_connection_tx, - sync_states_svc, address_book: address_book.boxed_clone(), _background_tasks: Arc::new(background_tasks), }) @@ -149,19 +143,14 @@ where #[derive(Clone)] pub struct NetworkInterface { /// A pool of free connected peers. - pool: Arc>, + peer_set: BoxCloneService, tower::BoxError>, /// A [`Service`] that allows broadcasting to all connected peers. broadcast_svc: BroadcastSvc, - /// A [`watch`] channel that contains the highest seen cumulative difficulty and other info - /// on that claimed chain. - top_block_watch: watch::Receiver, /// A channel to request extra connections. - #[allow(dead_code)] // will be used eventually + #[expect(dead_code, reason = "will be used eventually")] make_connection_tx: mpsc::Sender, /// The address book service. address_book: BoxCloneService, AddressBookResponse, tower::BoxError>, - /// The peer's sync states service. - sync_states_svc: Buffer, PeerSyncRequest>, /// Background tasks that will be aborted when this interface is dropped. _background_tasks: Arc>, } @@ -184,17 +173,7 @@ impl NetworkInterface { + 'static, C::Future: Send + 'static, { - block_downloader::download_blocks( - self.pool.clone(), - self.sync_states_svc.clone(), - our_chain_service, - config, - ) - } - - /// Returns a stream which yields the highest seen sync state from a connected peer. - pub fn top_sync_stream(&self) -> WatchStream { - WatchStream::from_changes(self.top_block_watch.clone()) + block_downloader::download_blocks(self.peer_set.clone(), our_chain_service, config) } /// Returns the address book service. @@ -204,9 +183,10 @@ impl NetworkInterface { self.address_book.clone() } - /// Pulls a client from the client pool, returning it in a guard that will return it there when it's - /// dropped. - pub fn borrow_client(&self, peer: &InternalPeerID) -> Option> { - self.pool.borrow_client(peer) + /// Borrows the `PeerSet`, for access to connected peers. + pub fn peer_set( + &mut self, + ) -> &mut BoxCloneService, tower::BoxError> { + &mut self.peer_set } } diff --git a/p2p/p2p/src/peer_set.rs b/p2p/p2p/src/peer_set.rs new file mode 100644 index 0000000..498eaaf --- /dev/null +++ b/p2p/p2p/src/peer_set.rs @@ -0,0 +1,217 @@ +use std::{ + future::{ready, Future, Ready}, + pin::{pin, Pin}, + task::{Context, Poll}, +}; + +use futures::{stream::FuturesUnordered, StreamExt}; +use indexmap::{IndexMap, IndexSet}; +use rand::{seq::index::sample, thread_rng}; +use tokio::sync::mpsc::Receiver; +use tokio_util::sync::WaitForCancellationFutureOwned; +use tower::Service; + +use cuprate_helper::cast::u64_to_usize; +use cuprate_p2p_core::{ + client::{Client, InternalPeerID}, + ConnectionDirection, NetworkZone, +}; + +mod client_wrappers; + +pub use client_wrappers::ClientDropGuard; +use client_wrappers::StoredClient; + +/// A request to the peer-set. +pub enum PeerSetRequest { + /// The most claimed proof-of-work from a peer in the peer-set. + MostPoWSeen, + /// Peers with more cumulative difficulty than the given cumulative difficulty. + /// + /// Returned peers will be remembered and won't be returned from subsequent calls until the guard is dropped. + PeersWithMorePoW(u128), + /// A random outbound peer. + /// + /// The returned peer will be remembered and won't be returned from subsequent calls until the guard is dropped. + StemPeer, +} + +/// A response from the peer-set. +pub enum PeerSetResponse { + /// [`PeerSetRequest::MostPoWSeen`] + MostPoWSeen { + /// The cumulative difficulty claimed. + cumulative_difficulty: u128, + /// The height claimed. + height: usize, + /// The claimed hash of the top block. + top_hash: [u8; 32], + }, + /// [`PeerSetRequest::PeersWithMorePoW`] + /// + /// Returned peers will be remembered and won't be returned from subsequent calls until the guard is dropped. + PeersWithMorePoW(Vec>), + /// [`PeerSetRequest::StemPeer`] + /// + /// The returned peer will be remembered and won't be returned from subsequent calls until the guard is dropped. + StemPeer(Option>), +} + +/// A [`Future`] that completes when a peer disconnects. +#[pin_project::pin_project] +struct ClosedConnectionFuture { + #[pin] + fut: WaitForCancellationFutureOwned, + id: Option>, +} + +impl Future for ClosedConnectionFuture { + type Output = InternalPeerID; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + this.fut.poll(cx).map(|()| this.id.take().unwrap()) + } +} + +/// A collection of all connected peers on a [`NetworkZone`]. +pub(crate) struct PeerSet { + /// The connected peers. + peers: IndexMap, StoredClient>, + /// A [`FuturesUnordered`] that resolves when a peer disconnects. + closed_connections: FuturesUnordered>, + /// The [`InternalPeerID`]s of all outbound peers. + outbound_peers: IndexSet>, + /// A channel of new peers from the inbound server or outbound connector. + new_peers: Receiver>, +} + +impl PeerSet { + pub(crate) fn new(new_peers: Receiver>) -> Self { + Self { + peers: IndexMap::new(), + closed_connections: FuturesUnordered::new(), + outbound_peers: IndexSet::new(), + new_peers, + } + } + + /// Polls the new peers channel for newly connected peers. + fn poll_new_peers(&mut self, cx: &mut Context<'_>) { + while let Poll::Ready(Some(new_peer)) = self.new_peers.poll_recv(cx) { + if new_peer.info.direction == ConnectionDirection::Outbound { + self.outbound_peers.insert(new_peer.info.id); + } + + self.closed_connections.push(ClosedConnectionFuture { + fut: new_peer.info.handle.closed(), + id: Some(new_peer.info.id), + }); + + self.peers + .insert(new_peer.info.id, StoredClient::new(new_peer)); + } + } + + /// Remove disconnected peers from the peer set. + fn remove_dead_peers(&mut self, cx: &mut Context<'_>) { + while let Poll::Ready(Some(dead_peer)) = self.closed_connections.poll_next_unpin(cx) { + let Some(peer) = self.peers.swap_remove(&dead_peer) else { + continue; + }; + + if peer.client.info.direction == ConnectionDirection::Outbound { + self.outbound_peers.swap_remove(&peer.client.info.id); + } + + self.peers.swap_remove(&dead_peer); + } + } + + /// [`PeerSetRequest::MostPoWSeen`] + fn most_pow_seen(&self) -> PeerSetResponse { + let most_pow_chain = self + .peers + .values() + .map(|peer| { + let core_sync_data = peer.client.info.core_sync_data.lock().unwrap(); + + ( + core_sync_data.cumulative_difficulty(), + u64_to_usize(core_sync_data.current_height), + core_sync_data.top_id, + ) + }) + .max_by_key(|(cumulative_difficulty, ..)| *cumulative_difficulty) + .unwrap_or_default(); + + PeerSetResponse::MostPoWSeen { + cumulative_difficulty: most_pow_chain.0, + height: most_pow_chain.1, + top_hash: most_pow_chain.2, + } + } + + /// [`PeerSetRequest::PeersWithMorePoW`] + fn peers_with_more_pow(&self, cumulative_difficulty: u128) -> PeerSetResponse { + PeerSetResponse::PeersWithMorePoW( + self.peers + .values() + .filter(|&client| { + !client.is_downloading_blocks() + && client + .client + .info + .core_sync_data + .lock() + .unwrap() + .cumulative_difficulty() + > cumulative_difficulty + }) + .map(StoredClient::downloading_blocks_guard) + .collect(), + ) + } + + /// [`PeerSetRequest::StemPeer`] + fn random_peer_for_stem(&self) -> PeerSetResponse { + PeerSetResponse::StemPeer( + sample( + &mut thread_rng(), + self.outbound_peers.len(), + self.outbound_peers.len(), + ) + .into_iter() + .find_map(|i| { + let peer = self.outbound_peers.get_index(i).unwrap(); + let client = self.peers.get(peer).unwrap(); + (!client.is_a_stem_peer()).then(|| client.stem_peer_guard()) + }), + ) + } +} + +impl Service for PeerSet { + type Response = PeerSetResponse; + type Error = tower::BoxError; + type Future = Ready>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_new_peers(cx); + self.remove_dead_peers(cx); + + // TODO: should we return `Pending` if we don't have any peers? + + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: PeerSetRequest) -> Self::Future { + ready(match req { + PeerSetRequest::MostPoWSeen => Ok(self.most_pow_seen()), + PeerSetRequest::PeersWithMorePoW(cumulative_difficulty) => { + Ok(self.peers_with_more_pow(cumulative_difficulty)) + } + PeerSetRequest::StemPeer => Ok(self.random_peer_for_stem()), + }) + } +} diff --git a/p2p/p2p/src/peer_set/client_wrappers.rs b/p2p/p2p/src/peer_set/client_wrappers.rs new file mode 100644 index 0000000..97d7493 --- /dev/null +++ b/p2p/p2p/src/peer_set/client_wrappers.rs @@ -0,0 +1,86 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use cuprate_p2p_core::{ + client::{Client, WeakClient}, + NetworkZone, +}; + +/// A client stored in the peer-set. +pub(super) struct StoredClient { + pub client: Client, + /// An [`AtomicBool`] for if the peer is currently downloading blocks. + downloading_blocks: Arc, + /// An [`AtomicBool`] for if the peer is currently being used to stem txs. + stem_peer: Arc, +} + +impl StoredClient { + pub(super) fn new(client: Client) -> Self { + Self { + client, + downloading_blocks: Arc::new(AtomicBool::new(false)), + stem_peer: Arc::new(AtomicBool::new(false)), + } + } + + /// Returns [`true`] if the [`StoredClient`] is currently downloading blocks. + pub(super) fn is_downloading_blocks(&self) -> bool { + self.downloading_blocks.load(Ordering::Relaxed) + } + + /// Returns [`true`] if the [`StoredClient`] is currently being used to stem txs. + pub(super) fn is_a_stem_peer(&self) -> bool { + self.stem_peer.load(Ordering::Relaxed) + } + + /// Returns a [`ClientDropGuard`] that while it is alive keeps the [`StoredClient`] in the downloading blocks state. + pub(super) fn downloading_blocks_guard(&self) -> ClientDropGuard { + self.downloading_blocks.store(true, Ordering::Relaxed); + + ClientDropGuard { + client: self.client.downgrade(), + bool: Arc::clone(&self.downloading_blocks), + } + } + + /// Returns a [`ClientDropGuard`] that while it is alive keeps the [`StoredClient`] in the stemming peers state. + pub(super) fn stem_peer_guard(&self) -> ClientDropGuard { + self.stem_peer.store(true, Ordering::Relaxed); + + ClientDropGuard { + client: self.client.downgrade(), + bool: Arc::clone(&self.stem_peer), + } + } +} + +/// A [`Drop`] guard for a client returned from the peer-set. +pub struct ClientDropGuard { + client: WeakClient, + bool: Arc, +} + +impl Deref for ClientDropGuard { + type Target = WeakClient; + fn deref(&self) -> &Self::Target { + &self.client + } +} + +impl DerefMut for ClientDropGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.client + } +} + +impl Drop for ClientDropGuard { + fn drop(&mut self) { + self.bool.store(false, Ordering::Relaxed); + } +} diff --git a/p2p/p2p/src/sync_states.rs b/p2p/p2p/src/sync_states.rs deleted file mode 100644 index 1b4e81a..0000000 --- a/p2p/p2p/src/sync_states.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! # Sync States -//! -//! This module contains a [`PeerSyncSvc`], which keeps track of the claimed chain states of connected peers. -//! This allows checking if we are behind and getting a list of peers who claim they are ahead. -use std::{ - cmp::Ordering, - collections::{BTreeMap, HashMap, HashSet}, - future::{ready, Ready}, - task::{Context, Poll}, -}; - -use futures::{stream::FuturesUnordered, StreamExt}; -use tokio::sync::watch; -use tower::Service; - -use cuprate_p2p_core::{ - client::InternalPeerID, - handles::ConnectionHandle, - services::{PeerSyncRequest, PeerSyncResponse}, - NetworkZone, -}; -use cuprate_pruning::{PruningSeed, CRYPTONOTE_MAX_BLOCK_HEIGHT}; -use cuprate_wire::CoreSyncData; - -use crate::{client_pool::disconnect_monitor::PeerDisconnectFut, constants::SHORT_BAN}; - -/// The highest claimed sync info from our connected peers. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct NewSyncInfo { - /// The peers chain height. - pub chain_height: u64, - /// The peers top block's hash. - pub top_hash: [u8; 32], - /// The peers cumulative difficulty. - pub cumulative_difficulty: u128, -} - -/// A service that keeps track of our peers blockchains. -/// -/// This is the service that handles: -/// 1. Finding out if we need to sync -/// 1. Giving the peers that should be synced _from_, to the requester -pub struct PeerSyncSvc { - /// A map of cumulative difficulties to peers. - cumulative_difficulties: BTreeMap>>, - /// A map of peers to cumulative difficulties. - peers: HashMap, (u128, PruningSeed)>, - /// A watch channel for *a* top synced peer info. - new_height_watcher: watch::Sender, - /// The handle to the peer that has data in `new_height_watcher`. - last_peer_in_watcher_handle: Option, - /// A [`FuturesUnordered`] that resolves when a peer disconnects. - closed_connections: FuturesUnordered>, -} - -impl PeerSyncSvc { - /// Creates a new [`PeerSyncSvc`] with a [`Receiver`](watch::Receiver) that will be updated with - /// the highest seen sync data, this makes no guarantees about which peer will be chosen in case of a tie. - pub fn new() -> (Self, watch::Receiver) { - let (watch_tx, mut watch_rx) = watch::channel(NewSyncInfo { - chain_height: 0, - top_hash: [0; 32], - cumulative_difficulty: 0, - }); - - watch_rx.mark_unchanged(); - - ( - Self { - cumulative_difficulties: BTreeMap::new(), - peers: HashMap::new(), - new_height_watcher: watch_tx, - last_peer_in_watcher_handle: None, - closed_connections: FuturesUnordered::new(), - }, - watch_rx, - ) - } - - /// This function checks if any peers have disconnected, removing them if they have. - fn poll_disconnected(&mut self, cx: &mut Context<'_>) { - while let Poll::Ready(Some(peer_id)) = self.closed_connections.poll_next_unpin(cx) { - tracing::trace!("Peer {peer_id} disconnected, removing from peers sync info service."); - let (peer_cum_diff, _) = self.peers.remove(&peer_id).unwrap(); - - let cum_diff_peers = self - .cumulative_difficulties - .get_mut(&peer_cum_diff) - .unwrap(); - cum_diff_peers.remove(&peer_id); - if cum_diff_peers.is_empty() { - // If this was the last peer remove the whole entry for this cumulative difficulty. - self.cumulative_difficulties.remove(&peer_cum_diff); - } - } - } - - /// Returns a list of peers that claim to have a higher cumulative difficulty than `current_cum_diff`. - fn peers_to_sync_from( - &self, - current_cum_diff: u128, - block_needed: Option, - ) -> Vec> { - self.cumulative_difficulties - .range((current_cum_diff + 1)..) - .flat_map(|(_, peers)| peers) - .filter(|peer| { - if let Some(block_needed) = block_needed { - // we just use CRYPTONOTE_MAX_BLOCK_HEIGHT as the blockchain height, this only means - // we don't take into account the tip blocks which are not pruned. - self.peers - .get(peer) - .unwrap() - .1 - .has_full_block(block_needed, CRYPTONOTE_MAX_BLOCK_HEIGHT) - } else { - true - } - }) - .copied() - .collect() - } - - /// Updates a peers sync state. - fn update_peer_sync_info( - &mut self, - peer_id: InternalPeerID, - handle: ConnectionHandle, - core_sync_data: CoreSyncData, - ) -> Result<(), tower::BoxError> { - tracing::trace!( - "Received new core sync data from peer, top hash: {}", - hex::encode(core_sync_data.top_id) - ); - - let new_cumulative_difficulty = core_sync_data.cumulative_difficulty(); - - if let Some((old_cum_diff, _)) = self.peers.get_mut(&peer_id) { - match (*old_cum_diff).cmp(&new_cumulative_difficulty) { - Ordering::Equal => { - // If the cumulative difficulty of the peers chain hasn't changed then no need to update anything. - return Ok(()); - } - Ordering::Greater => { - // This will only happen if a peer lowers its cumulative difficulty during the connection. - // This won't happen if a peer re-syncs their blockchain as then the connection would have closed. - tracing::debug!( - "Peer's claimed cumulative difficulty has dropped, closing connection and banning peer for: {} seconds.", SHORT_BAN.as_secs() - ); - handle.ban_peer(SHORT_BAN); - return Err("Peers cumulative difficulty dropped".into()); - } - Ordering::Less => (), - } - - // Remove the old cumulative difficulty entry for this peer - let old_cum_diff_peers = self.cumulative_difficulties.get_mut(old_cum_diff).unwrap(); - old_cum_diff_peers.remove(&peer_id); - if old_cum_diff_peers.is_empty() { - // If this was the last peer remove the whole entry for this cumulative difficulty. - self.cumulative_difficulties.remove(old_cum_diff); - } - // update the cumulative difficulty - *old_cum_diff = new_cumulative_difficulty; - } else { - // The peer is new so add it the list of peers. - self.peers.insert( - peer_id, - ( - new_cumulative_difficulty, - PruningSeed::decompress_p2p_rules(core_sync_data.pruning_seed)?, - ), - ); - - // add it to the list of peers to watch for disconnection. - self.closed_connections.push(PeerDisconnectFut { - closed_fut: handle.closed(), - peer_id: Some(peer_id), - }) - } - - self.cumulative_difficulties - .entry(new_cumulative_difficulty) - .or_default() - .insert(peer_id); - - // If the claimed cumulative difficulty is higher than the current one in the watcher - // or if the peer in the watch has disconnected, update it. - if self.new_height_watcher.borrow().cumulative_difficulty < new_cumulative_difficulty - || self - .last_peer_in_watcher_handle - .as_ref() - .is_some_and(|handle| handle.is_closed()) - { - tracing::debug!( - "Updating sync watcher channel with new highest seen cumulative difficulty: {new_cumulative_difficulty}" - ); - let _ = self.new_height_watcher.send(NewSyncInfo { - top_hash: core_sync_data.top_id, - chain_height: core_sync_data.current_height, - cumulative_difficulty: new_cumulative_difficulty, - }); - self.last_peer_in_watcher_handle.replace(handle); - } - - Ok(()) - } -} - -impl Service> for PeerSyncSvc { - type Response = PeerSyncResponse; - type Error = tower::BoxError; - type Future = Ready>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.poll_disconnected(cx); - - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: PeerSyncRequest) -> Self::Future { - let res = match req { - PeerSyncRequest::PeersToSyncFrom { - current_cumulative_difficulty, - block_needed, - } => Ok(PeerSyncResponse::PeersToSyncFrom(self.peers_to_sync_from( - current_cumulative_difficulty, - block_needed, - ))), - PeerSyncRequest::IncomingCoreSyncData(peer_id, handle, sync_data) => self - .update_peer_sync_info(peer_id, handle, sync_data) - .map(|_| PeerSyncResponse::Ok), - }; - - ready(res) - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use tokio::sync::Semaphore; - use tower::{Service, ServiceExt}; - - use cuprate_p2p_core::{ - client::InternalPeerID, handles::HandleBuilder, services::PeerSyncRequest, - }; - use cuprate_wire::CoreSyncData; - - use cuprate_p2p_core::services::PeerSyncResponse; - use cuprate_test_utils::test_netzone::TestNetZone; - - use super::PeerSyncSvc; - - #[tokio::test] - async fn top_sync_channel_updates() { - let semaphore = Arc::new(Semaphore::new(1)); - - let (_g, handle) = HandleBuilder::new() - .with_permit(semaphore.try_acquire_owned().unwrap()) - .build(); - - let (mut svc, mut watch) = PeerSyncSvc::>::new(); - - assert!(!watch.has_changed().unwrap()); - - svc.ready() - .await - .unwrap() - .call(PeerSyncRequest::IncomingCoreSyncData( - InternalPeerID::Unknown(0), - handle.clone(), - CoreSyncData { - cumulative_difficulty: 1_000, - cumulative_difficulty_top64: 0, - current_height: 0, - pruning_seed: 0, - top_id: [0; 32], - top_version: 0, - }, - )) - .await - .unwrap(); - - assert!(watch.has_changed().unwrap()); - - assert_eq!(watch.borrow().top_hash, [0; 32]); - assert_eq!(watch.borrow().cumulative_difficulty, 1000); - assert_eq!(watch.borrow_and_update().chain_height, 0); - - svc.ready() - .await - .unwrap() - .call(PeerSyncRequest::IncomingCoreSyncData( - InternalPeerID::Unknown(1), - handle.clone(), - CoreSyncData { - cumulative_difficulty: 1_000, - cumulative_difficulty_top64: 0, - current_height: 0, - pruning_seed: 0, - top_id: [0; 32], - top_version: 0, - }, - )) - .await - .unwrap(); - - assert!(!watch.has_changed().unwrap()); - - svc.ready() - .await - .unwrap() - .call(PeerSyncRequest::IncomingCoreSyncData( - InternalPeerID::Unknown(2), - handle.clone(), - CoreSyncData { - cumulative_difficulty: 1_001, - cumulative_difficulty_top64: 0, - current_height: 0, - pruning_seed: 0, - top_id: [1; 32], - top_version: 0, - }, - )) - .await - .unwrap(); - - assert!(watch.has_changed().unwrap()); - - assert_eq!(watch.borrow().top_hash, [1; 32]); - assert_eq!(watch.borrow().cumulative_difficulty, 1001); - assert_eq!(watch.borrow_and_update().chain_height, 0); - } - - #[tokio::test] - async fn peer_sync_info_updates() { - let semaphore = Arc::new(Semaphore::new(1)); - - let (_g, handle) = HandleBuilder::new() - .with_permit(semaphore.try_acquire_owned().unwrap()) - .build(); - - let (mut svc, _watch) = PeerSyncSvc::>::new(); - - svc.ready() - .await - .unwrap() - .call(PeerSyncRequest::IncomingCoreSyncData( - InternalPeerID::Unknown(0), - handle.clone(), - CoreSyncData { - cumulative_difficulty: 1_000, - cumulative_difficulty_top64: 0, - current_height: 0, - pruning_seed: 0, - top_id: [0; 32], - top_version: 0, - }, - )) - .await - .unwrap(); - - assert_eq!(svc.peers.len(), 1); - assert_eq!(svc.cumulative_difficulties.len(), 1); - - svc.ready() - .await - .unwrap() - .call(PeerSyncRequest::IncomingCoreSyncData( - InternalPeerID::Unknown(0), - handle.clone(), - CoreSyncData { - cumulative_difficulty: 1_001, - cumulative_difficulty_top64: 0, - current_height: 0, - pruning_seed: 0, - top_id: [0; 32], - top_version: 0, - }, - )) - .await - .unwrap(); - - assert_eq!(svc.peers.len(), 1); - assert_eq!(svc.cumulative_difficulties.len(), 1); - - svc.ready() - .await - .unwrap() - .call(PeerSyncRequest::IncomingCoreSyncData( - InternalPeerID::Unknown(1), - handle.clone(), - CoreSyncData { - cumulative_difficulty: 10, - cumulative_difficulty_top64: 0, - current_height: 0, - pruning_seed: 0, - top_id: [0; 32], - top_version: 0, - }, - )) - .await - .unwrap(); - - assert_eq!(svc.peers.len(), 2); - assert_eq!(svc.cumulative_difficulties.len(), 2); - - let PeerSyncResponse::PeersToSyncFrom(peers) = svc - .ready() - .await - .unwrap() - .call(PeerSyncRequest::PeersToSyncFrom { - block_needed: None, - current_cumulative_difficulty: 0, - }) - .await - .unwrap() - else { - panic!("Wrong response for request.") - }; - - assert!( - peers.contains(&InternalPeerID::Unknown(0)) - && peers.contains(&InternalPeerID::Unknown(1)) - ) - } -} diff --git a/pruning/Cargo.toml b/pruning/Cargo.toml index 3f5bd27..4b03551 100644 --- a/pruning/Cargo.toml +++ b/pruning/Cargo.toml @@ -10,6 +10,11 @@ default = [] borsh = ["dep:borsh"] [dependencies] +cuprate-constants = { workspace = true, features = ["block"] } + thiserror = { workspace = true } borsh = { workspace = true, features = ["derive", "std"], optional = true } + +[lints] +workspace = true diff --git a/pruning/src/lib.rs b/pruning/src/lib.rs index 96c3609..e49aedb 100644 --- a/pruning/src/lib.rs +++ b/pruning/src/lib.rs @@ -20,15 +20,16 @@ use std::cmp::Ordering; +use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; + use thiserror::Error; -pub const CRYPTONOTE_MAX_BLOCK_HEIGHT: u64 = 500000000; /// The default log stripes for Monero pruning. pub const CRYPTONOTE_PRUNING_LOG_STRIPES: u32 = 3; /// The amount of blocks that peers keep before another stripe starts storing blocks. -pub const CRYPTONOTE_PRUNING_STRIPE_SIZE: u64 = 4096; +pub const CRYPTONOTE_PRUNING_STRIPE_SIZE: usize = 4096; /// The amount of blocks from the top of the chain that should not be pruned. -pub const CRYPTONOTE_PRUNING_TIP_BLOCKS: u64 = 5500; +pub const CRYPTONOTE_PRUNING_TIP_BLOCKS: usize = 5500; const PRUNING_SEED_LOG_STRIPES_SHIFT: u32 = 7; const PRUNING_SEED_STRIPE_SHIFT: u32 = 0; @@ -41,9 +42,9 @@ pub enum PruningError { LogStripesOutOfRange, #[error("Stripe is out of range")] StripeOutOfRange, - #[error("The block height is greater than `CRYPTONOTE_MAX_BLOCK_HEIGHT`")] + #[error("The block height is greater than `MAX_BLOCK_HEIGHT_USIZE`")] BlockHeightTooLarge, - #[error("The blockchain height is greater than `CRYPTONOTE_MAX_BLOCK_HEIGHT`")] + #[error("The blockchain height is greater than `MAX_BLOCK_HEIGHT_USIZE`")] BlockChainHeightTooLarge, #[error("The calculated height is smaller than the block height entered")] CalculatedHeightSmallerThanEnteredBlock, @@ -71,7 +72,7 @@ impl PruningSeed { /// /// See: [`DecompressedPruningSeed::new`] pub fn new_pruned(stripe: u32, log_stripes: u32) -> Result { - Ok(PruningSeed::Pruned(DecompressedPruningSeed::new( + Ok(Self::Pruned(DecompressedPruningSeed::new( stripe, log_stripes, )?)) @@ -81,9 +82,7 @@ impl PruningSeed { /// /// An error means the pruning seed was invalid. pub fn decompress(seed: u32) -> Result { - Ok(DecompressedPruningSeed::decompress(seed)? - .map(PruningSeed::Pruned) - .unwrap_or(PruningSeed::NotPruned)) + Ok(DecompressedPruningSeed::decompress(seed)?.map_or(Self::NotPruned, Self::Pruned)) } /// Decompresses the seed, performing the same checks as [`PruningSeed::decompress`] and some more according to @@ -103,34 +102,34 @@ impl PruningSeed { } /// Compresses this pruning seed to a u32. - pub fn compress(&self) -> u32 { + pub const fn compress(&self) -> u32 { match self { - PruningSeed::NotPruned => 0, - PruningSeed::Pruned(seed) => seed.compress(), + Self::NotPruned => 0, + Self::Pruned(seed) => seed.compress(), } } /// Returns the `log_stripes` for this seed, if this seed is pruned otherwise [`None`] is returned. - pub fn get_log_stripes(&self) -> Option { + pub const fn get_log_stripes(&self) -> Option { match self { - PruningSeed::NotPruned => None, - PruningSeed::Pruned(seed) => Some(seed.log_stripes), + Self::NotPruned => None, + Self::Pruned(seed) => Some(seed.log_stripes), } } /// Returns the `stripe` for this seed, if this seed is pruned otherwise [`None`] is returned. - pub fn get_stripe(&self) -> Option { + pub const fn get_stripe(&self) -> Option { match self { - PruningSeed::NotPruned => None, - PruningSeed::Pruned(seed) => Some(seed.stripe), + Self::NotPruned => None, + Self::Pruned(seed) => Some(seed.stripe), } } /// Returns `true` if a peer with this pruning seed should have a non-pruned version of a block. - pub fn has_full_block(&self, height: u64, blockchain_height: u64) -> bool { + pub const fn has_full_block(&self, height: usize, blockchain_height: usize) -> bool { match self { - PruningSeed::NotPruned => true, - PruningSeed::Pruned(seed) => seed.has_full_block(height, blockchain_height), + Self::NotPruned => true, + Self::Pruned(seed) => seed.has_full_block(height, blockchain_height), } } @@ -146,19 +145,17 @@ impl PruningSeed { /// ### Errors /// /// This function will return an Error if the inputted `block_height` or - /// `blockchain_height` is greater than [`CRYPTONOTE_MAX_BLOCK_HEIGHT`]. + /// `blockchain_height` is greater than [`MAX_BLOCK_HEIGHT_USIZE`]. /// /// This function will also error if `block_height` > `blockchain_height` pub fn get_next_pruned_block( &self, - block_height: u64, - blockchain_height: u64, - ) -> Result, PruningError> { + block_height: usize, + blockchain_height: usize, + ) -> Result, PruningError> { Ok(match self { - PruningSeed::NotPruned => None, - PruningSeed::Pruned(seed) => { - seed.get_next_pruned_block(block_height, blockchain_height)? - } + Self::NotPruned => None, + Self::Pruned(seed) => seed.get_next_pruned_block(block_height, blockchain_height)?, }) } @@ -171,20 +168,18 @@ impl PruningSeed { /// ### Errors /// /// This function will return an Error if the inputted `block_height` or - /// `blockchain_height` is greater than [`CRYPTONOTE_MAX_BLOCK_HEIGHT`]. + /// `blockchain_height` is greater than [`MAX_BLOCK_HEIGHT_USIZE`]. /// /// This function will also error if `block_height` > `blockchain_height` /// pub fn get_next_unpruned_block( &self, - block_height: u64, - blockchain_height: u64, - ) -> Result { + block_height: usize, + blockchain_height: usize, + ) -> Result { Ok(match self { - PruningSeed::NotPruned => block_height, - PruningSeed::Pruned(seed) => { - seed.get_next_unpruned_block(block_height, blockchain_height)? - } + Self::NotPruned => block_height, + Self::Pruned(seed) => seed.get_next_unpruned_block(block_height, blockchain_height)?, }) } } @@ -199,11 +194,11 @@ impl Ord for PruningSeed { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { // Make sure pruning seeds storing more blocks are greater. - (PruningSeed::NotPruned, PruningSeed::NotPruned) => Ordering::Equal, - (PruningSeed::NotPruned, PruningSeed::Pruned(_)) => Ordering::Greater, - (PruningSeed::Pruned(_), PruningSeed::NotPruned) => Ordering::Less, + (Self::NotPruned, Self::NotPruned) => Ordering::Equal, + (Self::NotPruned, Self::Pruned(_)) => Ordering::Greater, + (Self::Pruned(_), Self::NotPruned) => Ordering::Less, - (PruningSeed::Pruned(seed1), PruningSeed::Pruned(seed2)) => seed1.cmp(seed2), + (Self::Pruned(seed1), Self::Pruned(seed2)) => seed1.cmp(seed2), } } } @@ -222,7 +217,7 @@ pub struct DecompressedPruningSeed { log_stripes: u32, /// The specific portion this peer keeps. /// - /// *MUST* be between 1..=2^log_stripes + /// *MUST* be between `1..=2^log_stripes` stripe: u32, } @@ -268,13 +263,13 @@ impl DecompressedPruningSeed { /// a valid seed you currently MUST pass in a number 1 to 8 for `stripe` /// and 3 for `log_stripes`.* /// - pub fn new(stripe: u32, log_stripes: u32) -> Result { + pub const fn new(stripe: u32, log_stripes: u32) -> Result { if log_stripes > PRUNING_SEED_LOG_STRIPES_MASK { Err(PruningError::LogStripesOutOfRange) } else if !(stripe > 0 && stripe <= (1 << log_stripes)) { Err(PruningError::StripeOutOfRange) } else { - Ok(DecompressedPruningSeed { + Ok(Self { log_stripes, stripe, }) @@ -286,7 +281,7 @@ impl DecompressedPruningSeed { /// Will return Ok(None) if the pruning seed means no pruning. /// /// An error means the pruning seed was invalid. - pub fn decompress(seed: u32) -> Result, PruningError> { + pub const fn decompress(seed: u32) -> Result, PruningError> { if seed == 0 { // No pruning. return Ok(None); @@ -299,20 +294,20 @@ impl DecompressedPruningSeed { return Err(PruningError::StripeOutOfRange); } - Ok(Some(DecompressedPruningSeed { + Ok(Some(Self { log_stripes, stripe, })) } /// Compresses the pruning seed into a u32. - pub fn compress(&self) -> u32 { + pub const fn compress(&self) -> u32 { (self.log_stripes << PRUNING_SEED_LOG_STRIPES_SHIFT) | ((self.stripe - 1) << PRUNING_SEED_STRIPE_SHIFT) } /// Returns `true` if a peer with this pruning seed should have a non-pruned version of a block. - pub fn has_full_block(&self, height: u64, blockchain_height: u64) -> bool { + pub const fn has_full_block(&self, height: usize, blockchain_height: usize) -> bool { match get_block_pruning_stripe(height, blockchain_height, self.log_stripes) { Some(block_stripe) => self.stripe == block_stripe, None => true, @@ -328,20 +323,20 @@ impl DecompressedPruningSeed { /// ### Errors /// /// This function will return an Error if the inputted `block_height` or - /// `blockchain_height` is greater than [`CRYPTONOTE_MAX_BLOCK_HEIGHT`]. + /// `blockchain_height` is greater than [`MAX_BLOCK_HEIGHT_USIZE`]. /// /// This function will also error if `block_height` > `blockchain_height` /// - pub fn get_next_unpruned_block( + pub const fn get_next_unpruned_block( &self, - block_height: u64, - blockchain_height: u64, - ) -> Result { - if block_height > CRYPTONOTE_MAX_BLOCK_HEIGHT || block_height > blockchain_height { + block_height: usize, + blockchain_height: usize, + ) -> Result { + if block_height > MAX_BLOCK_HEIGHT_USIZE || block_height > blockchain_height { return Err(PruningError::BlockHeightTooLarge); } - if blockchain_height > CRYPTONOTE_MAX_BLOCK_HEIGHT { + if blockchain_height > MAX_BLOCK_HEIGHT_USIZE { return Err(PruningError::BlockChainHeightTooLarge); } @@ -373,7 +368,7 @@ impl DecompressedPruningSeed { // amt_of_cycles * blocks in a cycle + how many blocks through a cycles until the seed starts storing blocks let calculated_height = cycles_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << self.log_stripes) - + (self.stripe as u64 - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE; + + (self.stripe as usize - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE; if calculated_height + CRYPTONOTE_PRUNING_TIP_BLOCKS > blockchain_height { // if our calculated height is greater than the amount of tip blocks then the start of the tip blocks will be the next un-pruned @@ -394,15 +389,15 @@ impl DecompressedPruningSeed { /// ### Errors /// /// This function will return an Error if the inputted `block_height` or - /// `blockchain_height` is greater than [`CRYPTONOTE_MAX_BLOCK_HEIGHT`]. + /// `blockchain_height` is greater than [`MAX_BLOCK_HEIGHT_USIZE`]. /// /// This function will also error if `block_height` > `blockchain_height` /// pub fn get_next_pruned_block( &self, - block_height: u64, - blockchain_height: u64, - ) -> Result, PruningError> { + block_height: usize, + blockchain_height: usize, + ) -> Result, PruningError> { if block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height { // If we are within `CRYPTONOTE_PRUNING_TIP_BLOCKS` of the chain we should // not prune blocks. @@ -419,7 +414,7 @@ impl DecompressedPruningSeed { // We can get the end of our "non-pruning" cycle by getting the next stripe's first un-pruned block height. // So we calculate the next un-pruned block for the next stripe and return it as our next pruned block let next_stripe = 1 + (self.stripe & ((1 << self.log_stripes) - 1)); - let seed = DecompressedPruningSeed::new(next_stripe, self.log_stripes) + let seed = Self::new(next_stripe, self.log_stripes) .expect("We just made sure this stripe is in range for this log_stripe"); let calculated_height = seed.get_next_unpruned_block(block_height, blockchain_height)?; @@ -433,17 +428,22 @@ impl DecompressedPruningSeed { } } -fn get_block_pruning_stripe( - block_height: u64, - blockchain_height: u64, +const fn get_block_pruning_stripe( + block_height: usize, + blockchain_height: usize, log_stripe: u32, ) -> Option { if block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height { None } else { + #[expect( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "it's trivial to prove it's ok to us `as` here" + )] Some( - (((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & ((1 << log_stripe) as u64 - 1)) + 1) - as u32, // it's trivial to prove it's ok to us `as` here + (((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & ((1 << log_stripe) as usize - 1)) + + 1) as u32, ) } } @@ -483,16 +483,17 @@ mod tests { #[test] fn get_pruning_log_stripe() { let all_valid_seeds = make_all_pruning_seeds(); - for seed in all_valid_seeds.iter() { - assert_eq!(seed.get_log_stripes().unwrap(), 3) + for seed in &all_valid_seeds { + assert_eq!(seed.get_log_stripes().unwrap(), 3); } } #[test] fn get_pruning_stripe() { let all_valid_seeds = make_all_pruning_seeds(); + #[expect(clippy::cast_possible_truncation)] for (i, seed) in all_valid_seeds.iter().enumerate() { - assert_eq!(seed.get_stripe().unwrap(), i as u32 + 1) + assert_eq!(seed.get_stripe().unwrap(), i as u32 + 1); } } @@ -503,7 +504,7 @@ mod tests { for i in 0_u32..8 { assert_eq!( get_block_pruning_stripe( - (i * 4096) as u64, + (i * 4096) as usize, blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES ) @@ -515,7 +516,7 @@ mod tests { for i in 0_u32..8 { assert_eq!( get_block_pruning_stripe( - 32768 + (i * 4096) as u64, + 32768 + (i * 4096) as usize, blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES ) @@ -527,7 +528,7 @@ mod tests { for i in 1_u32..8 { assert_eq!( get_block_pruning_stripe( - 32767 + (i * 4096) as u64, + 32767 + (i * 4096) as usize, blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES ) @@ -553,32 +554,32 @@ mod tests { for (i, seed) in all_valid_seeds.iter().enumerate() { assert_eq!( seed.get_next_unpruned_block(0, blockchain_height).unwrap(), - i as u64 * 4096 - ) + i * 4096 + ); } for (i, seed) in all_valid_seeds.iter().enumerate() { assert_eq!( - seed.get_next_unpruned_block((i as u64 + 1) * 4096, blockchain_height) + seed.get_next_unpruned_block((i + 1) * 4096, blockchain_height) .unwrap(), - i as u64 * 4096 + 32768 - ) + i * 4096 + 32768 + ); } for (i, seed) in all_valid_seeds.iter().enumerate() { assert_eq!( - seed.get_next_unpruned_block((i as u64 + 8) * 4096, blockchain_height) + seed.get_next_unpruned_block((i + 8) * 4096, blockchain_height) .unwrap(), - i as u64 * 4096 + 32768 - ) + i * 4096 + 32768 + ); } - for seed in all_valid_seeds.iter() { + for seed in &all_valid_seeds { assert_eq!( seed.get_next_unpruned_block(76437863 - 1, blockchain_height) .unwrap(), 76437863 - 1 - ) + ); } let zero_seed = PruningSeed::NotPruned; @@ -591,7 +592,7 @@ mod tests { let seed = PruningSeed::decompress(384).unwrap(); // the next unpruned block is the first tip block - assert_eq!(seed.get_next_unpruned_block(5000, 11000).unwrap(), 5500) + assert_eq!(seed.get_next_unpruned_block(5000, 11000).unwrap(), 5500); } #[test] @@ -605,33 +606,33 @@ mod tests { .unwrap() .unwrap(), 0 - ) + ); } for (i, seed) in all_valid_seeds.iter().enumerate() { assert_eq!( - seed.get_next_pruned_block((i as u64 + 1) * 4096, blockchain_height) + seed.get_next_pruned_block((i + 1) * 4096, blockchain_height) .unwrap() .unwrap(), - (i as u64 + 1) * 4096 - ) + (i + 1) * 4096 + ); } for (i, seed) in all_valid_seeds.iter().enumerate() { assert_eq!( - seed.get_next_pruned_block((i as u64 + 8) * 4096, blockchain_height) + seed.get_next_pruned_block((i + 8) * 4096, blockchain_height) .unwrap() .unwrap(), - (i as u64 + 9) * 4096 - ) + (i + 9) * 4096 + ); } - for seed in all_valid_seeds.iter() { + for seed in &all_valid_seeds { assert_eq!( seed.get_next_pruned_block(76437863 - 1, blockchain_height) .unwrap(), None - ) + ); } let zero_seed = PruningSeed::NotPruned; @@ -644,6 +645,6 @@ mod tests { let seed = PruningSeed::decompress(384).unwrap(); // there is no next pruned block - assert_eq!(seed.get_next_pruned_block(5000, 10000).unwrap(), None) + assert_eq!(seed.get_next_pruned_block(5000, 10000).unwrap(), None); } } diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 47af5cd..c5d4db7 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -9,7 +9,29 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/cuprate-rpc-inte keywords = ["cuprate", "rpc", "interface"] [features] +default = ["dummy", "serde"] +dummy = ["dep:cuprate-helper", "dep:futures"] [dependencies] +cuprate-epee-encoding = { workspace = true, default-features = false } +cuprate-json-rpc = { workspace = true, default-features = false } +cuprate-rpc-types = { workspace = true, features = ["serde", "epee"], default-features = false } +cuprate-helper = { workspace = true, features = ["asynch"], default-features = false, optional = true } + +anyhow = { workspace = true } +axum = { version = "0.7.5", features = ["json"], default-features = false } +serde = { workspace = true, optional = true } +tower = { workspace = true, features = ["util"] } +paste = { workspace = true } +futures = { workspace = true, optional = true } [dev-dependencies] +cuprate-test-utils = { workspace = true } + +axum = { version = "0.7.5", features = ["json", "tokio", "http2"] } +serde_json = { workspace = true, features = ["std"] } +tokio = { workspace = true, features = ["full"] } +ureq = { version = "2.10.1", features = ["json"] } + +[lints] +workspace = true diff --git a/rpc/interface/README.md b/rpc/interface/README.md new file mode 100644 index 0000000..fa5496c --- /dev/null +++ b/rpc/interface/README.md @@ -0,0 +1,164 @@ +# `cuprate-rpc-interface` +This crate provides Cuprate's RPC _interface_. + +This crate is _not_ a standalone RPC server, it is just the interface. + +```text + cuprate-rpc-interface provides these parts + │ │ +┌───────────────────────────┤ ├───────────────────┐ +▼ ▼ ▼ ▼ +CLIENT ─► ROUTE ─► REQUEST ─► HANDLER ─► RESPONSE ─► CLIENT + ▲ ▲ + └───┬───┘ + │ + You provide this part +``` + +Everything coming _in_ from a client is handled by this crate. + +This is where your [`RpcHandler`] turns this `Request` into a `Response`. + +You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client. + +The main handler used by Cuprate is implemented in the `cuprate-rpc-handler` crate; +it implements the standard RPC handlers modeled after `monerod`. + +# Purpose +`cuprate-rpc-interface` is built on-top of [`axum`], +which is the crate _actually_ handling everything. + +This crate simply handles: +- Registering endpoint routes (e.g. `/get_block.bin`) +- Defining handler function signatures +- (De)serialization of requests/responses (JSON-RPC, binary, JSON) + +The actual server details are all handled by the [`axum`] and [`tower`] ecosystem. + +The proper usage of this crate is to: +1. Implement a [`RpcHandler`] +2. Use it with [`RouterBuilder`] to generate an + [`axum::Router`] with all Monero RPC routes set +3. Do whatever with it + +# The [`RpcHandler`] +This is your [`tower::Service`] that converts `Request`s into `Response`s, +i.e. the "inner handler". + +Said concretely, `RpcHandler` is 3 `tower::Service`s where the +request/response types are the 3 endpoint enums from [`cuprate_rpc_types`]: +- [`JsonRpcRequest`](cuprate_rpc_types::json::JsonRpcRequest) & [`JsonRpcResponse`](cuprate_rpc_types::json::JsonRpcResponse) +- [`BinRequest`](cuprate_rpc_types::bin::BinRequest) & [`BinResponse`](cuprate_rpc_types::bin::BinRequest) +- [`OtherRequest`](cuprate_rpc_types::other::OtherRequest) & [`OtherResponse`](cuprate_rpc_types::other::OtherRequest) + +`RpcHandler`'s [`Future`](std::future::Future) is generic, _although_, +it must output `Result<$RESPONSE, anyhow::Error>`. + +The error type must always be [`anyhow::Error`]. + +The `RpcHandler` must also hold some state that is required +for RPC server operation. + +The only state currently needed is [`RpcHandler::restricted`], which determines if an RPC +server is restricted or not, and thus, if some endpoints/methods are allowed or not. + +# Unknown endpoint behavior +TODO: decide what this crate should return (per different endpoint) +when a request is received to an unknown endpoint, including HTTP stuff, e.g. status code. + +# Unknown JSON-RPC method behavior +TODO: decide what this crate returns when a `/json_rpc` +request is received with an unknown method, including HTTP stuff, e.g. status code. + +# Example +Example usage of this crate + starting an RPC server. + +This uses `RpcHandlerDummy` as the handler; it always responds with the +correct response type, but set to a default value regardless of the request. + +```rust +use std::sync::Arc; + +use tokio::{net::TcpListener, sync::Barrier}; + +use cuprate_json_rpc::{Request, Response, Id}; +use cuprate_rpc_types::{ + json::{JsonRpcRequest, JsonRpcResponse, GetBlockCountResponse}, + other::{OtherRequest, OtherResponse}, +}; +use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy}; + +// Send a `/get_height` request. This endpoint has no inputs. +async fn get_height(port: u16) -> OtherResponse { + let url = format!("http://127.0.0.1:{port}/get_height"); + ureq::get(&url) + .set("Content-Type", "application/json") + .call() + .unwrap() + .into_json() + .unwrap() +} + +// Send a JSON-RPC request with the `get_block_count` method. +// +// The returned [`String`] is JSON. +async fn get_block_count(port: u16) -> String { + let url = format!("http://127.0.0.1:{port}/json_rpc"); + let method = JsonRpcRequest::GetBlockCount(Default::default()); + let request = Request::new(method); + ureq::get(&url) + .set("Content-Type", "application/json") + .send_json(request) + .unwrap() + .into_string() + .unwrap() +} + +#[tokio::main] +async fn main() { + // Start a local RPC server. + let port = { + // Create the router. + let state = RpcHandlerDummy { restricted: false }; + let router = RouterBuilder::new().all().build().with_state(state); + + // Start a server. + let listener = TcpListener::bind("127.0.0.1:0") + .await + .unwrap(); + let port = listener.local_addr().unwrap().port(); + + // Run the server with `axum`. + tokio::task::spawn(async move { + axum::serve(listener, router).await.unwrap(); + }); + + port + }; + + // Assert the response is the default. + let response = get_height(port).await; + let expected = OtherResponse::GetHeight(Default::default()); + assert_eq!(response, expected); + + // Assert the response JSON is correct. + let response = get_block_count(port).await; + let expected = r#"{"jsonrpc":"2.0","id":null,"result":{"status":"OK","untrusted":false,"count":0}}"#; + assert_eq!(response, expected); + + // Assert that (de)serialization works. + let expected = Response::ok(Id::Null, Default::default()); + let response: Response = serde_json::from_str(&response).unwrap(); + assert_eq!(response, expected); +} +``` + +# Feature flags +List of feature flags for `cuprate-rpc-interface`. + +All are enabled by default. + +| Feature flag | Does what | +|--------------|-----------| +| `serde` | Enables serde on applicable types +| `dummy` | Enables the `RpcHandlerDummy` type \ No newline at end of file diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index 8b13789..1f84738 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -1 +1,25 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] +mod route; +mod router_builder; +mod rpc_handler; +#[cfg(feature = "dummy")] +mod rpc_handler_dummy; +mod rpc_service; + +pub use router_builder::RouterBuilder; +pub use rpc_handler::RpcHandler; +#[cfg(feature = "dummy")] +pub use rpc_handler_dummy::RpcHandlerDummy; +pub use rpc_service::RpcService; + +// false-positive: used in `README.md`'s doc-test. +#[cfg(test)] +mod test { + extern crate axum; + extern crate cuprate_test_utils; + extern crate serde_json; + extern crate tokio; + extern crate ureq; +} diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs new file mode 100644 index 0000000..2fd9963 --- /dev/null +++ b/rpc/interface/src/route/bin.rs @@ -0,0 +1,116 @@ +//! Binary route functions. + +//---------------------------------------------------------------------------------------------------- Import +use axum::{body::Bytes, extract::State, http::StatusCode}; +use tower::ServiceExt; + +use cuprate_epee_encoding::from_bytes; +use cuprate_rpc_types::{ + bin::{ + BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, + GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, + }, + json::GetOutputDistributionRequest, + RpcCall, +}; + +use crate::rpc_handler::RpcHandler; + +//---------------------------------------------------------------------------------------------------- Routes +/// This macro generates route functions that expect input. +/// +/// See below for usage. +macro_rules! generate_endpoints_with_input { + ($( + // Syntax: + // Function name => Expected input type + $endpoint:ident => $variant:ident + ),*) => { paste::paste! { + $( + /// TODO + pub(crate) async fn $endpoint( + State(handler): State, + mut request: Bytes, + ) -> Result { + // Serialize into the request type. + let request = BinRequest::$variant( + from_bytes(&mut request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + ); + + generate_endpoints_inner!($variant, handler, request) + } + )* + }}; +} + +/// This macro generates route functions that expect _no_ input. +/// +/// See below for usage. +macro_rules! generate_endpoints_with_no_input { + ($( + // Syntax: + // Function name => Expected input type (that is empty) + $endpoint:ident => $variant:ident + ),*) => { paste::paste! { + $( + /// TODO + pub(crate) async fn $endpoint( + State(handler): State, + ) -> Result { + const REQUEST: BinRequest = BinRequest::$variant([<$variant Request>] {}); + generate_endpoints_inner!($variant, handler, REQUEST) + } + )* + }}; +} + +/// De-duplicated inner function body for: +/// - [`generate_endpoints_with_input`] +/// - [`generate_endpoints_with_no_input`] +macro_rules! generate_endpoints_inner { + ($variant:ident, $handler:ident, $request:expr_2021) => { + paste::paste! { + { + // Check if restricted. + if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() { + // TODO: mimic `monerod` behavior. + return Err(StatusCode::FORBIDDEN); + } + + // Send request. + let Ok(response) = $handler.oneshot($request).await else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); + }; + + let BinResponse::$variant(response) = response else { + panic!("RPC handler returned incorrect response"); + }; + + // Serialize to bytes and respond. + match cuprate_epee_encoding::to_bytes(response) { + Ok(bytes) => Ok(bytes.freeze()), + Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } + } + } + }; +} + +generate_endpoints_with_input! { + get_blocks => GetBlocks, + get_blocks_by_height => GetBlocksByHeight, + get_hashes => GetHashes, + get_o_indexes => GetOutputIndexes, + get_outs => GetOuts, + get_output_distribution => GetOutputDistribution +} + +generate_endpoints_with_no_input! { + get_transaction_pool_hashes => GetTransactionPoolHashes +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/fallback.rs b/rpc/interface/src/route/fallback.rs new file mode 100644 index 0000000..9478901 --- /dev/null +++ b/rpc/interface/src/route/fallback.rs @@ -0,0 +1,18 @@ +//! Fallback route functions. + +//---------------------------------------------------------------------------------------------------- Import +use axum::http::StatusCode; + +//---------------------------------------------------------------------------------------------------- Routes +/// Fallback route function. +/// +/// This is used as the fallback endpoint in [`crate::RouterBuilder`]. +pub(crate) async fn fallback() -> StatusCode { + StatusCode::NOT_FOUND +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs new file mode 100644 index 0000000..7efb851 --- /dev/null +++ b/rpc/interface/src/route/json_rpc.rs @@ -0,0 +1,64 @@ +//! JSON-RPC 2.0 endpoint route functions. + +//---------------------------------------------------------------------------------------------------- Import +use std::borrow::Cow; + +use axum::{extract::State, http::StatusCode, Json}; +use tower::ServiceExt; + +use cuprate_json_rpc::{ + error::{ErrorCode, ErrorObject}, + Id, Response, +}; +use cuprate_rpc_types::{ + json::{JsonRpcRequest, JsonRpcResponse}, + RpcCallValue, +}; + +use crate::rpc_handler::RpcHandler; + +//---------------------------------------------------------------------------------------------------- Routes +/// The `/json_rpc` route function used in [`crate::RouterBuilder`]. +pub(crate) async fn json_rpc( + State(handler): State, + Json(request): Json>, +) -> Result>, StatusCode> { + // TODO: + // + // JSON-RPC notifications (requests without `id`) + // must not be responded too, although, the request's side-effects + // must remain. How to do this considering this function will + // always return and cause `axum` to respond? + + // JSON-RPC 2.0 rule: + // If there was an error in detecting the `Request`'s ID, + // the `Response` must contain an `Id::Null` + let id = request.id.unwrap_or(Id::Null); + + // Return early if this RPC server is restricted and + // the requested method is only for non-restricted RPC. + if request.body.is_restricted() && handler.restricted() { + let error_object = ErrorObject { + code: ErrorCode::ServerError(-1 /* TODO */), + message: Cow::Borrowed("Restricted. TODO: mimic monerod message"), + data: None, + }; + + let response = Response::err(id, error_object); + + return Ok(Json(response)); + } + + // Send request. + let Ok(response) = handler.oneshot(request.body).await else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); + }; + + Ok(Json(Response::ok(id, response))) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs new file mode 100644 index 0000000..7ff9ab8 --- /dev/null +++ b/rpc/interface/src/route/mod.rs @@ -0,0 +1,9 @@ +//! Routing functions. +//! +//! These are the function signatures passed to +//! [`crate::RouterBuilder`] when registering routes. + +pub(crate) mod bin; +pub(crate) mod fallback; +pub(crate) mod json_rpc; +pub(crate) mod other; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs new file mode 100644 index 0000000..19a58d9 --- /dev/null +++ b/rpc/interface/src/route/other.rs @@ -0,0 +1,136 @@ +//! Other JSON endpoint route functions. + +//---------------------------------------------------------------------------------------------------- Import +use axum::{extract::State, http::StatusCode, Json}; +use tower::ServiceExt; + +use cuprate_rpc_types::{ + other::{ + GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse, + GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest, + GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest, + GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse, + GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest, + GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse, + InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse, + MiningStatusRequest, MiningStatusResponse, OtherRequest, OtherResponse, OutPeersRequest, + OutPeersResponse, PopBlocksRequest, PopBlocksResponse, SaveBcRequest, SaveBcResponse, + SendRawTransactionRequest, SendRawTransactionResponse, SetBootstrapDaemonRequest, + SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest, + SetLogCategoriesResponse, SetLogHashRateRequest, SetLogHashRateResponse, + SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, StartMiningResponse, + StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse, + UpdateRequest, UpdateResponse, + }, + RpcCall, +}; + +use crate::rpc_handler::RpcHandler; + +//---------------------------------------------------------------------------------------------------- Routes +/// This macro generates route functions that expect input. +/// +/// See below for usage. +macro_rules! generate_endpoints_with_input { + ($( + // Syntax: + // Function name => Expected input type + $endpoint:ident => $variant:ident + ),*) => { paste::paste! { + $( + pub(crate) async fn $endpoint( + State(handler): State, + Json(request): Json<[<$variant Request>]>, + ) -> Result]>, StatusCode> { + generate_endpoints_inner!($variant, handler, request) + } + )* + }}; +} + +/// This macro generates route functions that expect _no_ input. +/// +/// See below for usage. +macro_rules! generate_endpoints_with_no_input { + ($( + // Syntax: + // Function name => Expected input type (that is empty) + $endpoint:ident => $variant:ident + ),*) => { paste::paste! { + $( + pub(crate) async fn $endpoint( + State(handler): State, + ) -> Result]>, StatusCode> { + generate_endpoints_inner!($variant, handler, [<$variant Request>] {}) + } + )* + }}; +} + +/// De-duplicated inner function body for: +/// - [`generate_endpoints_with_input`] +/// - [`generate_endpoints_with_no_input`] +macro_rules! generate_endpoints_inner { + ($variant:ident, $handler:ident, $request:expr_2021) => { + paste::paste! { + { + // Check if restricted. + if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() { + // TODO: mimic `monerod` behavior. + return Err(StatusCode::FORBIDDEN); + } + + // Send request. + let request = OtherRequest::$variant($request); + let Ok(response) = $handler.oneshot(request).await else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); + }; + + let OtherResponse::$variant(response) = response else { + panic!("RPC handler returned incorrect response") + }; + + Ok(Json(response)) + } + } + }; +} + +generate_endpoints_with_input! { + get_transactions => GetTransactions, + is_key_image_spent => IsKeyImageSpent, + send_raw_transaction => SendRawTransaction, + start_mining => StartMining, + get_peer_list => GetPeerList, + set_log_hash_rate => SetLogHashRate, + set_log_level => SetLogLevel, + set_log_categories => SetLogCategories, + set_bootstrap_daemon => SetBootstrapDaemon, + set_limit => SetLimit, + out_peers => OutPeers, + in_peers => InPeers, + get_outs => GetOuts, + update => Update, + pop_blocks => PopBlocks, + get_public_nodes => GetPublicNodes +} + +generate_endpoints_with_no_input! { + get_height => GetHeight, + get_alt_blocks_hashes => GetAltBlocksHashes, + stop_mining => StopMining, + mining_status => MiningStatus, + save_bc => SaveBc, + get_transaction_pool => GetTransactionPool, + get_transaction_pool_stats => GetTransactionPoolStats, + stop_daemon => StopDaemon, + get_limit => GetLimit, + get_net_stats => GetNetStats, + get_transaction_pool_hashes => GetTransactionPoolHashes +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/router_builder.rs b/rpc/interface/src/router_builder.rs new file mode 100644 index 0000000..d18a694 --- /dev/null +++ b/rpc/interface/src/router_builder.rs @@ -0,0 +1,192 @@ +//! Free functions. + +//---------------------------------------------------------------------------------------------------- Use +use axum::Router; + +use crate::{ + route::{bin, fallback, json_rpc, other}, + rpc_handler::RpcHandler, +}; + +//---------------------------------------------------------------------------------------------------- RouterBuilder +/// Generate the `RouterBuilder` struct. +macro_rules! generate_router_builder { + ($( + // Syntax: + // $BUILDER_FUNCTION_NAME => + // $ACTUAL_ENDPOINT_STRING => + // $ENDPOINT_FUNCTION_MODULE::$ENDPOINT_FUNCTION => + // ($HTTP_METHOD(s)) + $endpoint_ident:ident => + $endpoint_string:literal => + $endpoint_module:ident::$endpoint_fn:ident => + ($($http_method:ident),*) + ),* $(,)?) => { + /// Builder for creating the RPC router. + /// + /// This builder allows you to selectively enable endpoints for the router, + /// and a [`fallback`](RouterBuilder::fallback) route. + /// + /// The [`default`](RouterBuilder::default) is to enable [`all`](RouterBuilder::all) routes. + /// + /// # Routes + /// Functions that enable routes are separated into 3 groups: + /// - `json_rpc` (enables all of JSON RPC 2.0) + /// - `other_` (e.g. [`other_get_height`](RouterBuilder::other_get_height)) + /// - `bin_` (e.g. [`bin_get_blocks`](RouterBuilder::bin_get_blocks)) + /// + /// For a list of all `monerod` routes, see + /// [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189), + /// or the source file of this type. + /// + /// # Aliases + /// Some routes have aliases, such as [`/get_height`](RouterBuilder::other_get_height) + /// and [`/getheight`](RouterBuilder::other_getheight). + /// + /// These both route to the same handler function, but they do not enable each other. + /// + /// If desired, you can enable `/get_height` but not `/getheight`. + /// + /// # Example + /// ```rust + /// use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy}; + /// + /// // Create a router with _only_ `/json_rpc` enabled. + /// let only_json_rpc = RouterBuilder::::new() + /// .json_rpc() + /// .build(); + /// + /// // Create a router with: + /// // - `/get_outs.bin` enabled + /// // - A fallback enabled + /// let get_outs_bin_and_fallback = RouterBuilder::::new() + /// .bin_get_outs() + /// .fallback() + /// .build(); + /// + /// // Create a router with all endpoints enabled. + /// let all = RouterBuilder::::new() + /// .all() + /// .build(); + /// ``` + #[derive(Clone)] + pub struct RouterBuilder { + router: Router, + } + + impl RouterBuilder { + /// Create a new [`Self`]. + #[must_use] + pub fn new() -> Self { + Self { + router: Router::new(), + } + } + + /// Build [`Self`] into a [`Router`]. + /// + /// All endpoints enabled in [`RouterBuilder`] + /// will be enabled in this [`Router`]. + pub fn build(self) -> Router { + self.router + } + + /// Enable all endpoints, including [`Self::fallback`]. + #[must_use] + pub fn all(mut self) -> Self { + $( + self = self.$endpoint_ident(); + )* + + self.fallback() + } + + /// Enable the catch-all fallback route. + /// + /// Any unknown or disabled route will route here, e.g.: + /// - `get_info` + /// - `getinfo` + /// - `asdf` + #[must_use] + pub fn fallback(self) -> Self { + Self { + router: self.router.fallback(fallback::fallback), + } + } + + $( + #[doc = concat!( + "Enable the `", + $endpoint_string, + "` endpoint.", + )] + #[must_use] + pub fn $endpoint_ident(self) -> Self { + Self { + router: self.router.route( + $endpoint_string, + ::axum::routing::method_routing::MethodRouter::new() + $(.$http_method($endpoint_module::$endpoint_fn::))* + ), + } + } + )* + } + }; +} + +generate_router_builder! { + // JSON-RPC 2.0 route. + json_rpc => "/json_rpc" => json_rpc::json_rpc => (get, post), + + // Other JSON routes. + other_get_height => "/get_height" => other::get_height => (get, post), + other_getheight => "/getheight" => other::get_height => (get, post), + other_get_transactions => "/get_transactions" => other::get_transactions => (get, post), + other_gettransactions => "/gettransactions" => other::get_transactions => (get, post), + other_get_alt_blocks_hashes => "/get_alt_blocks_hashes" => other::get_alt_blocks_hashes => (get, post), + other_is_key_image_spent => "/is_key_image_spent" => other::is_key_image_spent => (get, post), + other_send_raw_transaction => "/send_raw_transaction" => other::send_raw_transaction => (get, post), + other_sendrawtransaction => "/sendrawtransaction" => other::send_raw_transaction => (get, post), + other_start_mining => "/start_mining" => other::start_mining => (get, post), + other_stop_mining => "/stop_mining" => other::stop_mining => (get, post), + other_mining_status => "/mining_status" => other::mining_status => (get, post), + other_save_bc => "/save_bc" => other::save_bc => (get, post), + other_get_peer_list => "/get_peer_list" => other::get_peer_list => (get, post), + other_get_public_nodes => "/get_public_nodes" => other::get_public_nodes => (get, post), + other_set_log_hash_rate => "/set_log_hash_rate" => other::set_log_hash_rate => (get, post), + other_set_log_level => "/set_log_level" => other::set_log_level => (get, post), + other_set_log_categories => "/set_log_categories" => other::set_log_categories => (get, post), + other_get_transaction_pool => "/get_transaction_pool" => other::get_transaction_pool => (get, post), + other_get_transaction_pool_hashes => "/get_transaction_pool_hashes" => other::get_transaction_pool_hashes => (get, post), + other_get_transaction_pool_stats => "/get_transaction_pool_stats" => other::get_transaction_pool_stats => (get, post), + other_set_bootstrap_daemon => "/set_bootstrap_daemon" => other::set_bootstrap_daemon => (get, post), + other_stop_daemon => "/stop_daemon" => other::stop_daemon => (get, post), + other_get_net_stats => "/get_net_stats" => other::get_net_stats => (get, post), + other_get_limit => "/get_limit" => other::get_limit => (get, post), + other_set_limit => "/set_limit" => other::set_limit => (get, post), + other_out_peers => "/out_peers" => other::out_peers => (get, post), + other_in_peers => "/in_peers" => other::in_peers => (get, post), + other_get_outs => "/get_outs" => other::get_outs => (get, post), + other_update => "/update" => other::update => (get, post), + other_pop_blocks => "/pop_blocks" => other::pop_blocks => (get, post), + + // Binary routes. + bin_get_blocks => "/get_blocks.bin" => bin::get_blocks => (get, post), + bin_getblocks => "/getblocks.bin" => bin::get_blocks => (get, post), + bin_get_blocks_by_height => "/get_blocks_by_height.bin" => bin::get_blocks_by_height => (get, post), + bin_getblocks_by_height => "/getblocks_by_height.bin" => bin::get_blocks_by_height => (get, post), + bin_get_hashes => "/get_hashes.bin" => bin::get_hashes => (get, post), + bin_gethashes => "/gethashes.bin" => bin::get_hashes => (get, post), + bin_get_o_indexes => "/get_o_indexes.bin" => bin::get_o_indexes => (get, post), + bin_get_outs => "/get_outs.bin" => bin::get_outs => (get, post), + bin_get_transaction_pool_hashes => "/get_transaction_pool_hashes.bin" => bin::get_transaction_pool_hashes => (get, post), + bin_get_output_distribution => "/get_output_distribution.bin" => bin::get_output_distribution => (get, post), +} + +impl Default for RouterBuilder { + /// Uses [`Self::all`]. + fn default() -> Self { + Self::new().all() + } +} diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs new file mode 100644 index 0000000..1d2676c --- /dev/null +++ b/rpc/interface/src/rpc_handler.rs @@ -0,0 +1,50 @@ +//! RPC handler trait. + +//---------------------------------------------------------------------------------------------------- Use +use cuprate_rpc_types::{ + bin::{BinRequest, BinResponse}, + json::{JsonRpcRequest, JsonRpcResponse}, + other::{OtherRequest, OtherResponse}, +}; + +use crate::RpcService; + +//---------------------------------------------------------------------------------------------------- RpcHandler +/// An RPC handler. +/// +/// This trait represents a type that can turn `Request`s into `Response`s. +/// +/// Implementors of this trait must be: +/// - A [`tower::Service`] that uses [`JsonRpcRequest`] & [`JsonRpcResponse`] +/// - A [`tower::Service`] that uses [`BinRequest`] & [`BinResponse`] +/// - A [`tower::Service`] that uses [`OtherRequest`] & [`OtherResponse`] +/// +/// In other words, an [`RpcHandler`] is a type that implements [`tower::Service`] 3 times, +/// one for each request/response enum type found in [`cuprate_rpc_types`]. +/// +/// The error type must always be [`anyhow::Error`]. +/// +/// See this crate's `RpcHandlerDummy` for an implementation example of this trait. +/// +/// # Panics +/// Your [`RpcHandler`] must reply to `Request`s with the correct +/// `Response` or else this crate will panic during routing functions. +/// +/// For example, a [`JsonRpcRequest::GetBlockCount`] must be replied with +/// [`JsonRpcResponse::GetBlockCount`]. If anything else is returned, +/// this crate may panic. +pub trait RpcHandler: + RpcService + + RpcService + + RpcService +{ + /// Is this [`RpcHandler`] restricted? + /// + /// If this returns `true`, restricted methods and endpoints such as: + /// - `/json_rpc`'s `relay_tx` method + /// - The `/pop_blocks` endpoint + /// + /// will automatically be denied access when using the + /// [`axum::Router`] provided by [`RouterBuilder`](crate::RouterBuilder). + fn restricted(&self) -> bool; +} diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs new file mode 100644 index 0000000..9d5009e --- /dev/null +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -0,0 +1,180 @@ +//! Dummy implementation of [`RpcHandler`]. + +//---------------------------------------------------------------------------------------------------- Use +use std::task::Poll; + +use anyhow::Error; +use futures::channel::oneshot::channel; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use tower::Service; + +use cuprate_helper::asynch::InfallibleOneshotReceiver; +use cuprate_rpc_types::{ + bin::{BinRequest, BinResponse}, + json::{JsonRpcRequest, JsonRpcResponse}, + other::{OtherRequest, OtherResponse}, +}; + +use crate::rpc_handler::RpcHandler; + +//---------------------------------------------------------------------------------------------------- RpcHandlerDummy +/// An [`RpcHandler`] that always returns [`Default::default`]. +/// +/// This `struct` implements [`RpcHandler`], and always responds +/// with the response `struct` set to [`Default::default`]. +/// +/// See the [`crate`] documentation for example usage. +/// +/// This is mostly used for testing purposes and can +/// be disabled by disable the `dummy` feature flag. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct RpcHandlerDummy { + /// Should this RPC server be [restricted](RpcHandler::restricted)? + /// + /// The dummy will honor this [`bool`] + /// on restricted methods/endpoints. + pub restricted: bool, +} + +impl RpcHandler for RpcHandlerDummy { + fn restricted(&self) -> bool { + self.restricted + } +} + +impl Service for RpcHandlerDummy { + type Response = JsonRpcResponse; + type Error = Error; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: JsonRpcRequest) -> Self::Future { + use cuprate_rpc_types::json::JsonRpcRequest as Req; + use cuprate_rpc_types::json::JsonRpcResponse as Resp; + + #[expect(clippy::default_trait_access)] + let resp = match req { + Req::GetBlockCount(_) => Resp::GetBlockCount(Default::default()), + Req::OnGetBlockHash(_) => Resp::OnGetBlockHash(Default::default()), + Req::SubmitBlock(_) => Resp::SubmitBlock(Default::default()), + Req::GenerateBlocks(_) => Resp::GenerateBlocks(Default::default()), + Req::GetLastBlockHeader(_) => Resp::GetLastBlockHeader(Default::default()), + Req::GetBlockHeaderByHash(_) => Resp::GetBlockHeaderByHash(Default::default()), + Req::GetBlockHeaderByHeight(_) => Resp::GetBlockHeaderByHeight(Default::default()), + Req::GetBlockHeadersRange(_) => Resp::GetBlockHeadersRange(Default::default()), + Req::GetBlock(_) => Resp::GetBlock(Default::default()), + Req::GetConnections(_) => Resp::GetConnections(Default::default()), + Req::GetInfo(_) => Resp::GetInfo(Default::default()), + Req::HardForkInfo(_) => Resp::HardForkInfo(Default::default()), + Req::SetBans(_) => Resp::SetBans(Default::default()), + Req::GetBans(_) => Resp::GetBans(Default::default()), + Req::Banned(_) => Resp::Banned(Default::default()), + Req::FlushTransactionPool(_) => Resp::FlushTransactionPool(Default::default()), + Req::GetOutputHistogram(_) => Resp::GetOutputHistogram(Default::default()), + Req::GetCoinbaseTxSum(_) => Resp::GetCoinbaseTxSum(Default::default()), + Req::GetVersion(_) => Resp::GetVersion(Default::default()), + Req::GetFeeEstimate(_) => Resp::GetFeeEstimate(Default::default()), + Req::GetAlternateChains(_) => Resp::GetAlternateChains(Default::default()), + Req::RelayTx(_) => Resp::RelayTx(Default::default()), + Req::SyncInfo(_) => Resp::SyncInfo(Default::default()), + Req::GetTransactionPoolBacklog(_) => { + Resp::GetTransactionPoolBacklog(Default::default()) + } + Req::GetMinerData(_) => Resp::GetMinerData(Default::default()), + Req::PruneBlockchain(_) => Resp::PruneBlockchain(Default::default()), + Req::CalcPow(_) => Resp::CalcPow(Default::default()), + Req::FlushCache(_) => Resp::FlushCache(Default::default()), + Req::AddAuxPow(_) => Resp::AddAuxPow(Default::default()), + Req::GetTxIdsLoose(_) => Resp::GetTxIdsLoose(Default::default()), + }; + + let (tx, rx) = channel(); + drop(tx.send(Ok(resp))); + InfallibleOneshotReceiver::from(rx) + } +} + +impl Service for RpcHandlerDummy { + type Response = BinResponse; + type Error = Error; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: BinRequest) -> Self::Future { + use cuprate_rpc_types::bin::BinRequest as Req; + use cuprate_rpc_types::bin::BinResponse as Resp; + + #[expect(clippy::default_trait_access)] + let resp = match req { + Req::GetBlocks(_) => Resp::GetBlocks(Default::default()), + Req::GetBlocksByHeight(_) => Resp::GetBlocksByHeight(Default::default()), + Req::GetHashes(_) => Resp::GetHashes(Default::default()), + Req::GetOutputIndexes(_) => Resp::GetOutputIndexes(Default::default()), + Req::GetOuts(_) => Resp::GetOuts(Default::default()), + Req::GetTransactionPoolHashes(_) => Resp::GetTransactionPoolHashes(Default::default()), + Req::GetOutputDistribution(_) => Resp::GetOutputDistribution(Default::default()), + }; + + let (tx, rx) = channel(); + drop(tx.send(Ok(resp))); + InfallibleOneshotReceiver::from(rx) + } +} + +impl Service for RpcHandlerDummy { + type Response = OtherResponse; + type Error = Error; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: OtherRequest) -> Self::Future { + use cuprate_rpc_types::other::OtherRequest as Req; + use cuprate_rpc_types::other::OtherResponse as Resp; + + #[expect(clippy::default_trait_access)] + let resp = match req { + Req::GetHeight(_) => Resp::GetHeight(Default::default()), + Req::GetTransactions(_) => Resp::GetTransactions(Default::default()), + Req::GetAltBlocksHashes(_) => Resp::GetAltBlocksHashes(Default::default()), + Req::IsKeyImageSpent(_) => Resp::IsKeyImageSpent(Default::default()), + Req::SendRawTransaction(_) => Resp::SendRawTransaction(Default::default()), + Req::StartMining(_) => Resp::StartMining(Default::default()), + Req::StopMining(_) => Resp::StopMining(Default::default()), + Req::MiningStatus(_) => Resp::MiningStatus(Default::default()), + Req::SaveBc(_) => Resp::SaveBc(Default::default()), + Req::GetPeerList(_) => Resp::GetPeerList(Default::default()), + Req::SetLogHashRate(_) => Resp::SetLogHashRate(Default::default()), + Req::SetLogLevel(_) => Resp::SetLogLevel(Default::default()), + Req::SetLogCategories(_) => Resp::SetLogCategories(Default::default()), + Req::SetBootstrapDaemon(_) => Resp::SetBootstrapDaemon(Default::default()), + Req::GetTransactionPool(_) => Resp::GetTransactionPool(Default::default()), + Req::GetTransactionPoolStats(_) => Resp::GetTransactionPoolStats(Default::default()), + Req::StopDaemon(_) => Resp::StopDaemon(Default::default()), + Req::GetLimit(_) => Resp::GetLimit(Default::default()), + Req::SetLimit(_) => Resp::SetLimit(Default::default()), + Req::OutPeers(_) => Resp::OutPeers(Default::default()), + Req::InPeers(_) => Resp::InPeers(Default::default()), + Req::GetNetStats(_) => Resp::GetNetStats(Default::default()), + Req::GetOuts(_) => Resp::GetOuts(Default::default()), + Req::Update(_) => Resp::Update(Default::default()), + Req::PopBlocks(_) => Resp::PopBlocks(Default::default()), + Req::GetTransactionPoolHashes(_) => Resp::GetTransactionPoolHashes(Default::default()), + Req::GetPublicNodes(_) => Resp::GetPublicNodes(Default::default()), + }; + + let (tx, rx) = channel(); + drop(tx.send(Ok(resp))); + InfallibleOneshotReceiver::from(rx) + } +} diff --git a/rpc/interface/src/rpc_service.rs b/rpc/interface/src/rpc_service.rs new file mode 100644 index 0000000..285d60b --- /dev/null +++ b/rpc/interface/src/rpc_service.rs @@ -0,0 +1,50 @@ +//! RPC [`tower::Service`] trait. + +//---------------------------------------------------------------------------------------------------- Use +use std::future::Future; + +use tower::Service; + +//---------------------------------------------------------------------------------------------------- RpcService +/// An RPC [`tower::Service`]. +/// +/// This trait solely exists to encapsulate the traits needed +/// to handle RPC requests and respond with responses - **it is +/// not meant to be used directly.** +/// +/// The `Request` and `Response` are generic and +/// are used in the [`tower::Service`] bounds. +/// +/// The error type is always [`anyhow::Error`]. +/// +/// There is a blanket implementation that implements this +/// trait on types that implement `tower::Service` correctly. +/// +/// See [`RpcHandler`](crate::RpcHandler) for more information. +pub trait RpcService: + Clone + + Send + + Sync + + 'static + + Service< + Request, + Response = Response, + Error = anyhow::Error, + Future: Future> + Send + 'static, + > +{ +} + +impl RpcService for T where + Self: Clone + + Send + + Sync + + 'static + + Service< + Request, + Response = Response, + Error = anyhow::Error, + Future: Future> + Send + 'static, + > +{ +} diff --git a/rpc/json-rpc/Cargo.toml b/rpc/json-rpc/Cargo.toml index 777f326..5d2544e 100644 --- a/rpc/json-rpc/Cargo.toml +++ b/rpc/json-rpc/Cargo.toml @@ -17,4 +17,7 @@ serde_json = { workspace = true, features = ["std"] } thiserror = { workspace = true } [dev-dependencies] -pretty_assertions = { workspace = true } \ No newline at end of file +pretty_assertions = { workspace = true } + +[lints] +workspace = true \ No newline at end of file diff --git a/rpc/json-rpc/src/lib.rs b/rpc/json-rpc/src/lib.rs index 45ac2ef..dfc4b18 100644 --- a/rpc/json-rpc/src/lib.rs +++ b/rpc/json-rpc/src/lib.rs @@ -1,96 +1,5 @@ #![doc = include_str!("../README.md")] -//---------------------------------------------------------------------------------------------------- Lints -// Forbid lints. -// Our code, and code generated (e.g macros) cannot overrule these. -#![forbid( - // `unsafe` is allowed but it _must_ be - // commented with `SAFETY: reason`. - clippy::undocumented_unsafe_blocks, - // Never. - unused_unsafe, - redundant_semicolons, - unused_allocation, - coherence_leak_check, - while_true, - - // Maybe can be put into `#[deny]`. - unconditional_recursion, - for_loops_over_fallibles, - unused_braces, - unused_labels, - keyword_idents, - non_ascii_idents, - variant_size_differences, - single_use_lifetimes, - - // Probably can be put into `#[deny]`. - future_incompatible, - let_underscore, - break_with_label_and_loop, - duplicate_macro_attributes, - exported_private_dependencies, - large_assignments, - overlapping_range_endpoints, - semicolon_in_expressions_from_macros, - noop_method_call, - unreachable_pub, -)] -// Deny lints. -// Some of these are `#[allow]`'ed on a per-case basis. -#![deny( - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, - clippy::nursery, - clippy::cargo, - clippy::missing_docs_in_private_items, - unused_mut, - missing_docs, - deprecated, - unused_comparisons, - nonstandard_style -)] -#![allow( - // FIXME: this lint affects crates outside of - // `database/` for some reason, allow for now. - clippy::cargo_common_metadata, - - // FIXME: adding `#[must_use]` onto everything - // might just be more annoying than useful... - // although it is sometimes nice. - clippy::must_use_candidate, - - // FIXME: good lint but too many false positives - // with our `Env` + `RwLock` setup. - clippy::significant_drop_tightening, - - // FIXME: good lint but is less clear in most cases. - clippy::items_after_statements, - - clippy::module_name_repetitions, - clippy::module_inception, - clippy::redundant_pub_crate, - clippy::option_if_let_else, -)] -// Allow some lints when running in debug mode. -#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] -// Allow some lints in tests. -#![cfg_attr( - test, - allow( - clippy::cognitive_complexity, - clippy::needless_pass_by_value, - clippy::cast_possible_truncation, - clippy::too_many_lines - ) -)] - -//---------------------------------------------------------------------------------------------------- Mod/Use pub mod error; mod id; @@ -105,6 +14,5 @@ pub use request::Request; mod response; pub use response::Response; -//---------------------------------------------------------------------------------------------------- TESTS #[cfg(test)] mod tests; diff --git a/rpc/json-rpc/src/response.rs b/rpc/json-rpc/src/response.rs index efd768b..2b84606 100644 --- a/rpc/json-rpc/src/response.rs +++ b/rpc/json-rpc/src/response.rs @@ -304,14 +304,14 @@ where if payload.is_none() { payload = Some(Ok(map.next_value::()?)); } else { - return Err(serde::de::Error::duplicate_field("result/error")); + return Err(Error::duplicate_field("result/error")); } } Key::Error => { if payload.is_none() { payload = Some(Err(map.next_value::()?)); } else { - return Err(serde::de::Error::duplicate_field("result/error")); + return Err(Error::duplicate_field("result/error")); } } Key::Unknown => { diff --git a/rpc/json-rpc/src/tests.rs b/rpc/json-rpc/src/tests.rs index ff8f049..99ce126 100644 --- a/rpc/json-rpc/src/tests.rs +++ b/rpc/json-rpc/src/tests.rs @@ -52,6 +52,7 @@ where } /// Tests an input JSON string matches an expected type `T`. +#[expect(clippy::needless_pass_by_value, reason = "serde signature")] fn assert_de(json: &'static str, expected: T) where T: DeserializeOwned + std::fmt::Debug + Clone + PartialEq, diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index 30e4aa9..6d8797b 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -9,14 +9,23 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/types" keywords = ["cuprate", "rpc", "types", "monero"] [features] -default = [] +default = ["serde", "epee"] +serde = ["dep:serde", "cuprate-fixed-bytes/serde", "cuprate-types/serde"] +epee = ["dep:cuprate-epee-encoding", "cuprate-types/epee"] [dependencies] -cuprate-epee-encoding = { path = "../../net/epee-encoding" } +cuprate-epee-encoding = { workspace = true, optional = true } +cuprate-fixed-bytes = { workspace = true } +cuprate-types = { workspace = true, default-features = false } -monero-serai = { workspace = true } -paste = { workspace = true } -serde = { workspace = true } +paste = { workspace = true } +serde = { workspace = true, optional = true } [dev-dependencies] +cuprate-test-utils = { workspace = true } + +serde = { workspace = true } serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/rpc/types/README.md b/rpc/types/README.md index 65b6d90..b5a4f65 100644 --- a/rpc/types/README.md +++ b/rpc/types/README.md @@ -7,16 +7,19 @@ This crate ports the types used in Monero's RPC interface, including: - Mixed types - Other commonly used RPC types +It also includes some traits for these types. + # Modules This crate's types are split in the following manner: -This crate has 4 modules: -- The root module; `cuprate_rpc_types` -- [`json`] module; JSON types from the `/json_rpc` endpoint -- [`bin`] module; Binary types from the binary endpoints -- [`other`] module; Misc JSON types from other endpoints - -Miscellaneous types are found in the root module, e.g. [`crate::Status`]. +| Module | Purpose | +|--------|---------| +| The root module | Miscellaneous items, e.g. constants. +| [`json`] | Contains JSON request/response (some mixed with binary) that all share the common `/json_rpc` endpoint. | +| [`bin`] | Contains request/response types that are expected to be fully in binary (`cuprate_epee_encoding`) in `monerod` and `cuprated`'s RPC interface. These are called at a custom endpoint instead of `/json_rpc`, e.g. `/get_blocks.bin`. | +| [`other`] | Contains request/response types that are JSON, but aren't called at `/json_rpc` (e.g. [`crate::other::GetHeightRequest`]). | +| [`misc`] | Contains miscellaneous types, e.g. [`crate::misc::Status`]. Many of types here are found and used in request/response types, for example, [`crate::misc::BlockHeader`] is used in [`crate::json::GetLastBlockHeaderResponse`]. | +| [`base`] | Contains base types flattened into many request/response types. Each type in `{json,bin,other}` come in pairs and have identical names, but are suffixed with either `Request` or `Response`. e.g. [`GetBlockCountRequest`](crate::json::GetBlockCountRequest) & [`GetBlockCountResponse`](crate::json::GetBlockCountResponse). @@ -30,23 +33,21 @@ However, each type will document: # Naming The naming for types within `{json,bin,other}` follow the following scheme: -- Convert the endpoint or method name into `UpperCamelCase` -- Remove any suffix extension +1. Convert the endpoint or method name into `UpperCamelCase` +1. Remove any suffix extension +1. Add `Request/Response` suffix For example: | Endpoint/method | Crate location and name | |-----------------|-------------------------| | [`get_block_count`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_count) | [`json::GetBlockCountRequest`] & [`json::GetBlockCountResponse`] -| [`/get_blocks.bin`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blockbin) | `bin::GetBlocksRequest` & `bin::GetBlocksResponse` -| [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) | `other::GetHeightRequest` & `other::GetHeightResponse` - -TODO: fix doc links when types are ready. +| [`/get_blocks.bin`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blockbin) | [`bin::GetBlocksRequest`] & [`bin::GetBlocksResponse`] +| [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) | [`other::GetHeightRequest`] & [`other::GetHeightResponse`] # Mixed types -Note that some types within [`other`] mix JSON & binary together, i.e., -the message overall is JSON, however some fields contain binary -values inside JSON strings, for example: +Note that some types mix JSON & binary together, i.e., the message overall is JSON, +however some fields contain binary values inside JSON strings, for example: ```json { @@ -57,6 +58,56 @@ values inside JSON strings, for example: } ``` -`binary` here is (de)serialized as a normal [`String`]. In order to be clear on which fields contain binary data, the struct fields that have them will use [`crate::BinaryString`] instead of [`String`]. +`binary` here is (de)serialized as a normal [`String`]. In order to be clear on which fields contain binary data, the struct fields that have them will use [`crate::misc::BinaryString`] instead of [`String`]. -TODO: list the specific types. \ No newline at end of file +These mixed types are: +- [`crate::json::GetTransactionPoolBacklogResponse`] +- [`crate::json::GetOutputDistributionResponse`] + +TODO: we need to figure out a type that (de)serializes correctly, `String` errors with `serde_json` + +# Fixed byte containers +TODO + + + +# (De)serialization invariants +Due to how types are defined in this library internally (all through a single macro), +most types implement both `serde` and `epee`. + +However, some of the types will panic with [`unimplemented`] +or will otherwise have undefined implementation in the incorrect context. + +In other words: +- The epee (de)serialization of [`json`] & [`other`] types should **not** be relied upon +- The JSON (de)serialization of [`bin`] types should **not** be relied upon + +The invariants that can be relied upon: +- Types in [`json`] & [`other`] will implement `serde` correctly +- Types in [`bin`] will implement `epee` correctly +- Misc types will implement `serde/epee` correctly as needed + +# Requests and responses +For `enum`s that encapsulate all request/response types, see: +- [`crate::json::JsonRpcRequest`] & [`crate::json::JsonRpcResponse`] +- [`crate::bin::BinRequest`] & [`crate::bin::BinResponse`] +- [`crate::other::OtherRequest`] & [`crate::other::OtherResponse`] + +# Feature flags +List of feature flags for `cuprate-rpc-types`. + +All are enabled by default. + +| Feature flag | Does what | +|--------------|-----------| +| `serde` | Implements `serde` on all types +| `epee` | Implements `cuprate_epee_encoding` on all types \ No newline at end of file diff --git a/rpc/types/src/base.rs b/rpc/types/src/base.rs index 6a29367..89eafc5 100644 --- a/rpc/types/src/base.rs +++ b/rpc/types/src/base.rs @@ -10,76 +10,44 @@ //! - //! - //! - +//! +//! Note that this library doesn't use [`AccessRequestBase`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L114-L122) found in `monerod` +//! as the type is practically deprecated. +//! +//! Although, [`AccessResponseBase`] still exists as to allow +//! outputting the same JSON fields as `monerod` (even if deprecated). //---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "epee")] use cuprate_epee_encoding::epee_object; -use crate::Status; - -//---------------------------------------------------------------------------------------------------- Macro -/// Link the original `monerod` definition for RPC base types. -macro_rules! monero_rpc_base_link { - ($start:literal..=$end:literal) => { - concat!( - "[Definition](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L", - stringify!($start), - "-L", - stringify!($end), - ")." - ) - }; -} +use crate::{macros::monero_definition_link, misc::Status}; //---------------------------------------------------------------------------------------------------- Requests -/// The most common base for responses (nothing). -/// -#[doc = monero_rpc_base_link!(95..=99)] -#[derive( - Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct EmptyRequestBase; - -cuprate_epee_encoding::epee_object! { - EmptyRequestBase, -} - /// A base for RPC request types that support RPC payment. /// -#[doc = monero_rpc_base_link!(114..=122)] -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "rpc/core_rpc_server_commands_defs.h", 114..=122)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct AccessRequestBase { /// The RPC payment client. pub client: String, } -cuprate_epee_encoding::epee_object! { +#[cfg(feature = "epee")] +epee_object! { AccessRequestBase, client: String, } //---------------------------------------------------------------------------------------------------- Responses -/// An empty response base. -/// -/// This is for response types that do not contain -/// any extra fields, e.g. TODO. -// [`CalcPowResponse`](crate::json::CalcPowResponse). -#[derive( - Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct EmptyResponseBase; - -cuprate_epee_encoding::epee_object! { - EmptyResponseBase, -} - +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "rpc/core_rpc_server_commands_defs.h", 101..=112)] /// The most common base for responses. -/// -#[doc = monero_rpc_base_link!(101..=112)] -#[derive( - Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ResponseBase { /// General RPC error code. [`Status::Ok`] means everything looks good. pub status: Status, @@ -89,19 +57,54 @@ pub struct ResponseBase { pub untrusted: bool, } +impl ResponseBase { + /// [`Status::Ok`] and trusted [`Self`]. + /// + /// This is the most common version of [`Self`]. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// assert_eq!(ResponseBase::OK, ResponseBase { + /// status: Status::Ok, + /// untrusted: false, + /// }); + /// ``` + pub const OK: Self = Self { + status: Status::Ok, + untrusted: false, + }; + + /// Same as [`Self::OK`] but with [`Self::untrusted`] set to `true`. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// assert_eq!(ResponseBase::OK_UNTRUSTED, ResponseBase { + /// status: Status::Ok, + /// untrusted: true, + /// }); + /// ``` + pub const OK_UNTRUSTED: Self = Self { + status: Status::Ok, + untrusted: true, + }; +} + +#[cfg(feature = "epee")] epee_object! { ResponseBase, status: Status, untrusted: bool, } +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "rpc/core_rpc_server_commands_defs.h", 124..=136)] /// A base for RPC response types that support RPC payment. -/// -#[doc = monero_rpc_base_link!(124..=136)] -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct AccessResponseBase { /// A flattened [`ResponseBase`]. - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub response_base: ResponseBase, /// If payment for RPC is enabled, the number of credits /// available to the requesting client. Otherwise, `0`. @@ -111,6 +114,69 @@ pub struct AccessResponseBase { pub top_hash: String, } +impl AccessResponseBase { + /// Creates a new [`Self`] with default values. + /// + /// Since RPC payment is semi-deprecated, [`Self::credits`] + /// and [`Self::top_hash`] will always be set to the default + /// values. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let new = AccessResponseBase::new(ResponseBase::OK); + /// assert_eq!(new, AccessResponseBase { + /// response_base: ResponseBase::OK, + /// credits: 0, + /// top_hash: "".into(), + /// }); + /// ``` + pub const fn new(response_base: ResponseBase) -> Self { + Self { + response_base, + credits: 0, + top_hash: String::new(), + } + } + + /// [`Status::Ok`] and trusted [`Self`]. + /// + /// This is the most common version of [`Self`]. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// assert_eq!(AccessResponseBase::OK, AccessResponseBase { + /// response_base: ResponseBase::OK, + /// credits: 0, + /// top_hash: "".into(), + /// }); + /// ``` + pub const OK: Self = Self { + response_base: ResponseBase::OK, + credits: 0, + top_hash: String::new(), + }; + + /// Same as [`Self::OK`] but with `untrusted` set to `true`. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// assert_eq!(AccessResponseBase::OK_UNTRUSTED, AccessResponseBase { + /// response_base: ResponseBase::OK_UNTRUSTED, + /// credits: 0, + /// top_hash: "".into(), + /// }); + /// ``` + pub const OK_UNTRUSTED: Self = Self { + response_base: ResponseBase::OK_UNTRUSTED, + credits: 0, + top_hash: String::new(), + }; +} + +#[cfg(feature = "epee")] epee_object! { AccessResponseBase, credits: u64, diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index f327847..414214c 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -1,8 +1,205 @@ -//! Binary types from [binary](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin) endpoints. +//! Binary types from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). +//! +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +use cuprate_fixed_bytes::ByteArrayVec; -//---------------------------------------------------------------------------------------------------- TODO +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::container_as_blob::ContainerAsBlob; + +use cuprate_types::BlockCompleteEntry; + +use crate::{ + base::AccessResponseBase, + macros::define_request_and_response, + misc::{BlockOutputIndices, GetOutputsOut, OutKeyBin, PoolInfo}, + rpc_call::RpcCallValue, +}; + +#[cfg(any(feature = "epee", feature = "serde"))] +use crate::defaults::{default_false, default_zero}; + +//---------------------------------------------------------------------------------------------------- Definitions +define_request_and_response! { + get_blocks_by_heightbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 264..=286, + GetBlocksByHeight, + Request { + heights: Vec, + }, + AccessResponseBase { + blocks: Vec, + } +} + +define_request_and_response! { + get_hashesbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 309..=338, + GetHashes, + Request { + block_ids: ByteArrayVec<32>, + start_height: u64, + }, + AccessResponseBase { + m_blocks_ids: ByteArrayVec<32>, + start_height: u64, + current_height: u64, + } +} + +#[cfg(not(feature = "epee"))] +define_request_and_response! { + get_o_indexesbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 487..=510, + GetOutputIndexes, + #[derive(Copy)] + Request { + txid: [u8; 32], + }, + AccessResponseBase { + o_indexes: Vec, + } +} + +#[cfg(feature = "epee")] +define_request_and_response! { + get_o_indexesbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 487..=510, + GetOutputIndexes, + #[derive(Copy)] + Request { + txid: [u8; 32], + }, + AccessResponseBase { + o_indexes: Vec as ContainerAsBlob, + } +} + +define_request_and_response! { + get_outsbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 512..=565, + GetOuts, + Request { + outputs: Vec, + get_txid: bool = default_false(), "default_false", + }, + AccessResponseBase { + outs: Vec, + } +} + +define_request_and_response! { + get_transaction_pool_hashesbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1593..=1613, + GetTransactionPoolHashes, + Request {}, + AccessResponseBase { + tx_hashes: ByteArrayVec<32>, + } +} + +define_request_and_response! { + get_blocksbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 162..=262, + + GetBlocks, + + Request { + requested_info: u8 = default_zero::(), "default_zero", + // FIXME: This is a `std::list` in `monerod` because...? + block_ids: ByteArrayVec<32>, + start_height: u64, + prune: bool, + no_miner_tx: bool = default_false(), "default_false", + pool_info_since: u64 = default_zero::(), "default_zero", + }, + + // TODO: add `top_block_hash` field + // + AccessResponseBase { + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, + pool_info: PoolInfo, + } +} + +//---------------------------------------------------------------------------------------------------- Request +/// Binary requests. +/// +/// This enum contains all [`crate::bin`] requests. +/// +/// See also: [`BinResponse`]. +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BinRequest { + GetBlocks(GetBlocksRequest), + GetBlocksByHeight(GetBlocksByHeightRequest), + GetHashes(GetHashesRequest), + GetOutputIndexes(GetOutputIndexesRequest), + GetOuts(GetOutsRequest), + GetTransactionPoolHashes(GetTransactionPoolHashesRequest), + GetOutputDistribution(crate::json::GetOutputDistributionRequest), +} + +impl RpcCallValue for BinRequest { + fn is_restricted(&self) -> bool { + match self { + Self::GetBlocks(x) => x.is_restricted(), + Self::GetBlocksByHeight(x) => x.is_restricted(), + Self::GetHashes(x) => x.is_restricted(), + Self::GetOutputIndexes(x) => x.is_restricted(), + Self::GetOuts(x) => x.is_restricted(), + Self::GetTransactionPoolHashes(x) => x.is_restricted(), + Self::GetOutputDistribution(x) => x.is_restricted(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::GetBlocks(x) => x.is_empty(), + Self::GetBlocksByHeight(x) => x.is_empty(), + Self::GetHashes(x) => x.is_empty(), + Self::GetOutputIndexes(x) => x.is_empty(), + Self::GetOuts(x) => x.is_empty(), + Self::GetTransactionPoolHashes(x) => x.is_empty(), + Self::GetOutputDistribution(x) => x.is_empty(), + } + } +} + +//---------------------------------------------------------------------------------------------------- Response +/// Binary responses. +/// +/// This enum contains all [`crate::bin`] responses. +/// +/// See also: [`BinRequest`]. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum BinResponse { + GetBlocks(GetBlocksResponse), + GetBlocksByHeight(GetBlocksByHeightResponse), + GetHashes(GetHashesResponse), + GetOutputIndexes(GetOutputIndexesResponse), + GetOuts(GetOutsResponse), + GetTransactionPoolHashes(GetTransactionPoolHashesResponse), + GetOutputDistribution(crate::json::GetOutputDistributionResponse), +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/types/src/constants.rs b/rpc/types/src/constants.rs index 2d5266f..8c6120b 100644 --- a/rpc/types/src/constants.rs +++ b/rpc/types/src/constants.rs @@ -15,6 +15,7 @@ // What this means for Cuprate: just follow `monerod`. //---------------------------------------------------------------------------------------------------- Import +use crate::macros::monero_definition_link; //---------------------------------------------------------------------------------------------------- Status // Common RPC status strings: @@ -23,39 +24,29 @@ // Note that these are _distinct_ from the ones in ZMQ: // . -/// +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 78)] pub const CORE_RPC_STATUS_OK: &str = "OK"; -/// +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 79)] pub const CORE_RPC_STATUS_BUSY: &str = "BUSY"; -/// +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 80)] pub const CORE_RPC_STATUS_NOT_MINING: &str = "NOT MINING"; -/// +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 81)] pub const CORE_RPC_STATUS_PAYMENT_REQUIRED: &str = "PAYMENT REQUIRED"; -/// Custom `CORE_RPC_STATUS` for usage in Cuprate. -pub const CORE_RPC_STATUS_UNKNOWN: &str = "UNKNOWN"; - //---------------------------------------------------------------------------------------------------- Versions +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 90)] /// RPC major version. -/// -/// See: . pub const CORE_RPC_VERSION_MAJOR: u32 = 3; +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 91)] /// RPC miror version. -/// -/// See: . pub const CORE_RPC_VERSION_MINOR: u32 = 14; +#[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 92..=93)] /// RPC version. -/// -/// See: . -/// -/// ```rust -/// assert_eq!(cuprate_rpc_types::CORE_RPC_VERSION, 196_622); -/// ``` pub const CORE_RPC_VERSION: u32 = (CORE_RPC_VERSION_MAJOR << 16) | CORE_RPC_VERSION_MINOR; //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/types/src/defaults.rs b/rpc/types/src/defaults.rs new file mode 100644 index 0000000..def5df4 --- /dev/null +++ b/rpc/types/src/defaults.rs @@ -0,0 +1,69 @@ +//! These functions define the default values +//! of optional fields in request/response types. +//! +//! For example, [`crate::json::GetBlockRequest`] +//! has a [`crate::json::GetBlockRequest::height`] +//! field and a [`crate::json::GetBlockRequest::hash`] +//! field, when the RPC interface reads JSON without +//! `height`, it will use [`default_height`] to fill that in. + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- TODO +/// Default [`bool`] type used in request/response types, `false`. +#[inline] +pub(crate) const fn default_false() -> bool { + false +} + +/// Default [`bool`] type used in _some_ request/response types, `true`. +#[inline] +pub(crate) const fn default_true() -> bool { + true +} + +/// Default [`String`] type used in request/response types. +#[inline] +pub(crate) const fn default_string() -> String { + String::new() +} + +/// Default block height used in request/response types. +#[inline] +pub(crate) const fn default_height() -> u64 { + 0 +} + +/// Default [`Vec`] used in request/response types. +#[inline] +pub(crate) const fn default_vec() -> Vec { + Vec::new() +} + +/// Default `0` value used in request/response types. +#[inline] +pub(crate) fn default_zero>() -> T { + T::from(0) +} + +/// Default `1` value used in request/response types. +#[inline] +pub(crate) fn default_one>() -> T { + T::from(1) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use super::*; + + /// Tests that [`default_zero`] returns `0` on all unsigned numbers. + #[test] + fn zero() { + assert_eq!(default_zero::(), 0); + assert_eq!(default_zero::(), 0); + assert_eq!(default_zero::(), 0); + assert_eq!(default_zero::(), 0); + assert_eq!(default_zero::(), 0); + } +} diff --git a/rpc/types/src/free.rs b/rpc/types/src/free.rs new file mode 100644 index 0000000..a41c853 --- /dev/null +++ b/rpc/types/src/free.rs @@ -0,0 +1,20 @@ +//! Free functions. + +//---------------------------------------------------------------------------------------------------- Serde +// These are functions used for conditionally (de)serialization. + +/// Returns `true` if the input `u` is equal to `0`. +#[inline] +#[expect(clippy::trivially_copy_pass_by_ref, reason = "serde signature")] +#[expect(dead_code, reason = "TODO: see if needed after handlers.")] +pub(crate) const fn is_zero(u: &u64) -> bool { + *u == 0 +} + +/// Returns `true` the input `u` is equal to `1`. +#[inline] +#[expect(clippy::trivially_copy_pass_by_ref, reason = "serde signature")] +#[expect(dead_code, reason = "TODO: see if needed after handlers.")] +pub(crate) const fn is_one(u: &u64) -> bool { + *u == 1 +} diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 5f5f8ff..a454cab 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -1,14 +1,88 @@ //! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. //! -//! . +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use crate::{ - base::{EmptyRequestBase, EmptyResponseBase, ResponseBase}, + base::{AccessResponseBase, ResponseBase}, macros::define_request_and_response, + misc::{ + AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, + GetMinerDataTxBacklogEntry, HardforkEntry, HistogramEntry, SetBan, Span, Status, + SyncInfoPeer, TxBacklogEntry, + }, + rpc_call::RpcCallValue, }; -//---------------------------------------------------------------------------------------------------- Struct definitions +#[cfg(any(feature = "epee", feature = "serde"))] +use crate::defaults::{ + default_false, default_height, default_one, default_string, default_true, default_vec, + default_zero, +}; + +//---------------------------------------------------------------------------------------------------- Macro +/// Adds a (de)serialization doc-test to a type in `json.rs`. +/// +/// It expects a const string from `cuprate_test_utils::rpc::data` +/// and the expected value it should (de)serialize into/from. +/// +/// It tests that the provided const JSON string can properly +/// (de)serialize into the expected value. +/// +/// See below for example usage. This macro is only used in this file. +macro_rules! serde_doc_test { + ( + // `const` string from `cuprate_test_utils::rpc::data` + // v + $cuprate_test_utils_rpc_const:ident => $expected:expr_2021 + // ^ + // Expected value as an expression + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::json::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, json::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "// The expected data.\n", + "let expected = ", + stringify!($expected), + ";\n", + "\n", + "// Assert it can be turned into a JSON value.\n", + "let value = from_str::(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "let Value::Object(map) = value else {\n", + " panic!();\n", + "};\n", + "\n", + "// If a request...\n", + "if let Some(params) = map.get(\"params\") {\n", + " let response = from_value::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(params.clone()).unwrap();\n", + " assert_eq!(response, expected);\n", + " return;\n", + "}\n", + "\n", + "// Else, if a response...\n", + "let result = map.get(\"result\").unwrap().clone();\n", + "let response = from_value::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(result.clone()).unwrap();\n", + "assert_eq!(response, expected);\n", + "```\n", + ) + } + }; +} + +//---------------------------------------------------------------------------------------------------- Definitions // This generates 2 structs: // // - `GetBlockTemplateRequest` @@ -24,40 +98,111 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994, // The base type name. - GetBlockTemplate, - - // The base request type. // - // This must be a type found in [`crate::base`]. + // After the type name, 2 optional idents are allowed: + // - `restricted` + // - `empty` + // + // These have to be within `()` and will affect the + // [`crate::RpcCall`] implementation on the request type. + // + // This type is not either restricted or empty so nothing is + // here, but the correct syntax is shown in a comment below: + GetBlockTemplate /* (restricted, empty) */, + + // The request type. + // + // If `Request {/* fields */}` is provided, a struct is generate as-is. + // + // If `Request {}` is specified here, it will create a `pub type YOUR_REQUEST_TYPE = ()` + // instead of a `struct`, see below in other macro definitions for an example. + // + // If there are any additional attributes (`/// docs` or `#[derive]`s) + // for the struct, they go here, e.g.: + // + #[doc = serde_doc_test!( + // ^ This is a macro that adds a doc-test to this type. + // It is optional but it is added to nearly all types. + // The syntax is: + // `$const` => `$expected` + // where `$const` is a `const` string from + // `cuprate_test_utils::rpc::data` and `$expected` is an + // actual expression that the string _should_ (de)serialize into/from. + GET_BLOCK_TEMPLATE_REQUEST => GetBlockTemplateRequest { + extra_nonce: String::default(), + prev_block: String::default(), + reserve_size: 60, + wallet_address: "44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns".into(), + } + )] + Request { + // Within the `{}` is an infinite matching pattern of: + // ``` + // $ATTRIBUTES + // $FIELD_NAME: $FIELD_TYPE, + // ``` + // The struct generated and all fields are `pub`. + + // This optional expression can be placed after + // a `field: field_type`. this indicates to the + // macro to (de)serialize this field using this + // default expression if it doesn't exist in epee. + // + // See `cuprate_epee_encoding::epee_object` for info. + // + // The default function must be specified twice: + // + // 1. As an expression + // 2. As a string literal + // + // For example: `extra_nonce: String /* = default_string(), "default_string" */,` + // + // This is a HACK since `serde`'s default attribute only takes in + // string literals and macros (stringify) within attributes do not work. + extra_nonce: String = default_string(), "default_string", + prev_block: String = default_string(), "default_string", + + // Another optional expression: + // This indicates to the macro to (de)serialize + // this field as another type in epee. + // + // See `cuprate_epee_encoding::epee_object` for info. + reserve_size: u64 /* as Type */, + + wallet_address: String, + }, + + // The response type. + // + // If `Response {/* fields */}` is used, + // this will generate a struct as-is. + // + // If a type found in [`crate::base`] is used, // It acts as a "base" that gets flattened into - // the actually request type. + // the actual request type. // // "Flatten" means the field(s) of a struct gets inlined // directly into the struct during (de)serialization, see: // . - // - // For example here, we're using [`crate::base::EmptyRequestBase`], - // which means that there is no extra fields flattened. - // - // If a request is not specified here, it will create a `type alias YOUR_REQUEST_TYPE = ()` - // instead of a `struct`, see below in other macro definitions for an example. - EmptyRequestBase { - reserve_size: u64, - wallet_address: String, - prev_block: String, - extra_nonce: String, - }, - - // The base response type. - // - // This is the same as the request base type, - // it must be a type found in [`crate::base`]. - // - // If there are any additional attributes (`/// docs` or `#[derive]`s) - // for the struct, they go here, e.g.: - // #[derive(Copy)] + #[doc = serde_doc_test!( + GET_BLOCK_TEMPLATE_RESPONSE => GetBlockTemplateResponse { + base: ResponseBase::OK, + blockhashing_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401".into(), + blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), + difficulty_top64: 0, + difficulty: 283305047039, + expected_reward: 600000000000, + height: 3195018, + next_seed_hash: "".into(), + prev_hash: "9d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a".into(), + reserved_offset: 131, + seed_hash: "e2aa0b7b55042cd48b02e395d78fa66a29815ccc1584e38db2d1f0e8485cd44f".into(), + seed_height: 3194880, + wide_difficulty: "0x41f64bf3ff".into(), + } + )] ResponseBase { - // This is using `crate::base::ResponseBase`, + // This is using [`crate::base::ResponseBase`], // so the type we generate will contain this field: // ``` // base: crate::base::ResponseBase, @@ -69,25 +214,18 @@ define_request_and_response! { // status: crate::Status, // untrusted: bool, // ``` - - // Within the `{}` is an infinite matching pattern of: - // ``` - // $ATTRIBUTES - // $FIELD_NAME: $FIELD_TYPE, - // ``` - // The struct generated and all fields are `pub`. - difficulty: u64, - wide_difficulty: String, - difficulty_top64: u64, - height: u64, - reserved_offset: u64, - expected_reward: u64, - prev_hash: String, - seed_height: u64, - seed_hash: String, - next_seed_hash: String, - blocktemplate_blob: String, blockhashing_blob: String, + blocktemplate_blob: String, + difficulty_top64: u64, + difficulty: u64, + expected_reward: u64, + height: u64, + next_seed_hash: String, + prev_hash: String, + reserved_offset: u64, + seed_hash: String, + seed_height: u64, + wide_difficulty: String, } } @@ -95,12 +233,19 @@ define_request_and_response! { get_block_count, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 919..=933, - GetBlockCount, + GetBlockCount (empty), - // There is no request type specified, + // There are no request fields specified, // this will cause the macro to generate a // type alias to `()` instead of a `struct`. + Request {}, + #[doc = serde_doc_test!( + GET_BLOCK_COUNT_RESPONSE => GetBlockCountResponse { + base: ResponseBase::OK, + count: 3195019, + } + )] ResponseBase { count: u64, } @@ -110,18 +255,1514 @@ define_request_and_response! { on_get_block_hash, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 935..=939, + OnGetBlockHash, + + #[doc = serde_doc_test!( + ON_GET_BLOCK_HASH_REQUEST => OnGetBlockHashRequest { + block_height: [912345], + } + )] + #[cfg_attr(feature = "serde", serde(transparent))] + #[repr(transparent)] #[derive(Copy)] - EmptyRequestBase { - #[serde(flatten)] - block_height: u64, + Request { + // This is `std::vector` in `monerod` but + // it must be a 1 length array or else it will error. + block_height: [u64; 1], }, - EmptyResponseBase { - #[serde(flatten)] + + #[doc = serde_doc_test!( + ON_GET_BLOCK_HASH_RESPONSE => OnGetBlockHashResponse { + block_hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + } + )] + #[cfg_attr(feature = "serde", serde(transparent))] + #[repr(transparent)] + Response { block_hash: String, } } +define_request_and_response! { + submit_block, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1114..=1128, + + SubmitBlock, + + #[doc = serde_doc_test!( + SUBMIT_BLOCK_REQUEST => SubmitBlockRequest { + block_blob: ["0707e6bdfedc053771512f1bc27c62731ae9e8f2443db64ce742f4e57f5cf8d393de28551e441a0000000002fb830a01ffbf830a018cfe88bee283060274c0aae2ef5730e680308d9c00b6da59187ad0352efe3c71d36eeeb28782f29f2501bd56b952c3ddc3e350c2631d3a5086cac172c56893831228b17de296ff4669de020200000000".into()], + } + )] + #[cfg_attr(feature = "serde", serde(transparent))] + #[repr(transparent)] + Request { + // This is `std::vector` in `monerod` but + // it must be a 1 length array or else it will error. + block_blob: [String; 1], + }, + + // FIXME: `cuprate_test_utils` only has an `error` response for this. + ResponseBase { + block_id: String, + } +} + +define_request_and_response! { + generateblocks, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1130..=1161, + + GenerateBlocks (restricted), + + #[doc = serde_doc_test!( + GENERATE_BLOCKS_REQUEST => GenerateBlocksRequest { + amount_of_blocks: 1, + prev_block: String::default(), + wallet_address: "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A".into(), + starting_nonce: 0 + } + )] + Request { + amount_of_blocks: u64, + prev_block: String = default_string(), "default_string", + starting_nonce: u32, + wallet_address: String, + }, + + #[doc = serde_doc_test!( + GENERATE_BLOCKS_RESPONSE => GenerateBlocksResponse { + base: ResponseBase::OK, + blocks: vec!["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4".into()], + height: 9783, + } + )] + ResponseBase { + blocks: Vec, + height: u64, + } +} + +define_request_and_response! { + get_last_block_header, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1214..=1238, + + GetLastBlockHeader, + + #[derive(Copy)] + Request { + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_LAST_BLOCK_HEADER_RESPONSE => GetLastBlockHeaderResponse { + base: AccessResponseBase::OK, + block_header: BlockHeader { + block_size: 200419, + block_weight: 200419, + cumulative_difficulty: 366125734645190820, + cumulative_difficulty_top64: 0, + depth: 0, + difficulty: 282052561854, + difficulty_top64: 0, + hash: "57238217820195ac4c08637a144a885491da167899cf1d20e8e7ce0ae0a3434e".into(), + height: 3195020, + long_term_weight: 200419, + major_version: 16, + miner_tx_hash: "7a42667237d4f79891bb407c49c712a9299fb87fce799833a7b633a3a9377dbd".into(), + minor_version: 16, + nonce: 1885649739, + num_txes: 37, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "22c72248ae9c5a2863c94735d710a3525c499f70707d1c2f395169bc5c8a0da3".into(), + reward: 615702960000, + timestamp: 1721245548, + wide_cumulative_difficulty: "0x514bd6a74a7d0a4".into(), + wide_difficulty: "0x41aba48bbe".into() + } + } + )] + AccessResponseBase { + block_header: BlockHeader, + } +} + +define_request_and_response! { + get_block_header_by_hash, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1240..=1269, + GetBlockHeaderByHash, + #[doc = serde_doc_test!( + GET_BLOCK_HEADER_BY_HASH_REQUEST => GetBlockHeaderByHashRequest { + hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + hashes: vec![], + fill_pow_hash: false, + } + )] + Request { + hash: String, + hashes: Vec = default_vec::(), "default_vec", + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_BLOCK_HEADER_BY_HASH_RESPONSE => GetBlockHeaderByHashResponse { + base: AccessResponseBase::OK, + block_headers: vec![], + block_header: BlockHeader { + block_size: 210, + block_weight: 210, + cumulative_difficulty: 754734824984346, + cumulative_difficulty_top64: 0, + depth: 2282676, + difficulty: 815625611, + difficulty_top64: 0, + hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + height: 912345, + long_term_weight: 210, + major_version: 1, + miner_tx_hash: "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30".into(), + minor_version: 2, + nonce: 1646, + num_txes: 0, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78".into(), + reward: 7388968946286, + timestamp: 1452793716, + wide_cumulative_difficulty: "0x2ae6d65248f1a".into(), + wide_difficulty: "0x309d758b".into() + }, + } + )] + AccessResponseBase { + block_header: BlockHeader, + block_headers: Vec = default_vec::(), "default_vec", + } +} + +define_request_and_response! { + get_block_header_by_height, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1271..=1296, + + GetBlockHeaderByHeight, + + #[derive(Copy)] + #[doc = serde_doc_test!( + GET_BLOCK_HEADER_BY_HEIGHT_REQUEST => GetBlockHeaderByHeightRequest { + height: 912345, + fill_pow_hash: false, + } + )] + Request { + height: u64, + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_BLOCK_HEADER_BY_HEIGHT_RESPONSE => GetBlockHeaderByHeightResponse { + base: AccessResponseBase::OK, + block_header: BlockHeader { + block_size: 210, + block_weight: 210, + cumulative_difficulty: 754734824984346, + cumulative_difficulty_top64: 0, + depth: 2282677, + difficulty: 815625611, + difficulty_top64: 0, + hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + height: 912345, + long_term_weight: 210, + major_version: 1, + miner_tx_hash: "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30".into(), + minor_version: 2, + nonce: 1646, + num_txes: 0, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78".into(), + reward: 7388968946286, + timestamp: 1452793716, + wide_cumulative_difficulty: "0x2ae6d65248f1a".into(), + wide_difficulty: "0x309d758b".into() + }, + } + )] + AccessResponseBase { + block_header: BlockHeader, + } +} + +define_request_and_response! { + get_block_headers_range, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1756..=1783, + + GetBlockHeadersRange, + + #[derive(Copy)] + #[doc = serde_doc_test!( + GET_BLOCK_HEADERS_RANGE_REQUEST => GetBlockHeadersRangeRequest { + start_height: 1545999, + end_height: 1546000, + fill_pow_hash: false, + } + )] + Request { + start_height: u64, + end_height: u64, + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_BLOCK_HEADERS_RANGE_RESPONSE => GetBlockHeadersRangeResponse { + base: AccessResponseBase::OK, + headers: vec![ + BlockHeader { + block_size: 301413, + block_weight: 301413, + cumulative_difficulty: 13185267971483472, + cumulative_difficulty_top64: 0, + depth: 1649024, + difficulty: 134636057921, + difficulty_top64: 0, + hash: "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a".into(), + height: 1545999, + long_term_weight: 301413, + major_version: 6, + miner_tx_hash: "9909c6f8a5267f043c3b2b079fb4eacc49ef9c1dee1c028eeb1a259b95e6e1d9".into(), + minor_version: 6, + nonce: 3246403956, + num_txes: 20, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "0ef6e948f77b8f8806621003f5de24b1bcbea150bc0e376835aea099674a5db5".into(), + reward: 5025593029981, + timestamp: 1523002893, + wide_cumulative_difficulty: "0x2ed7ee6db56750".into(), + wide_difficulty: "0x1f58ef3541".into() + }, + BlockHeader { + block_size: 13322, + block_weight: 13322, + cumulative_difficulty: 13185402687569710, + cumulative_difficulty_top64: 0, + depth: 1649023, + difficulty: 134716086238, + difficulty_top64: 0, + hash: "b408bf4cfcd7de13e7e370c84b8314c85b24f0ba4093ca1d6eeb30b35e34e91a".into(), + height: 1546000, + long_term_weight: 13322, + major_version: 7, + miner_tx_hash: "7f749c7c64acb35ef427c7454c45e6688781fbead9bbf222cb12ad1a96a4e8f6".into(), + minor_version: 7, + nonce: 3737164176, + num_txes: 1, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a".into(), + reward: 4851952181070, + timestamp: 1523002931, + wide_cumulative_difficulty: "0x2ed80dcb69bf2e".into(), + wide_difficulty: "0x1f5db457de".into() + } + ], + } + )] + AccessResponseBase { + headers: Vec, + } +} + +define_request_and_response! { + get_block, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1298..=1313, + GetBlock, + + #[doc = serde_doc_test!( + GET_BLOCK_REQUEST => GetBlockRequest { + height: 2751506, + hash: String::default(), + fill_pow_hash: false, + } + )] + Request { + // `monerod` has both `hash` and `height` fields. + // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. + // + hash: String = default_string(), "default_string", + height: u64 = default_height(), "default_height", + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_BLOCK_RESPONSE => GetBlockResponse { + base: AccessResponseBase::OK, + blob: "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000".into(), + block_header: BlockHeader { + block_size: 106, + block_weight: 106, + cumulative_difficulty: 236046001376524168, + cumulative_difficulty_top64: 0, + depth: 443517, + difficulty: 313732272488, + difficulty_top64: 0, + hash: "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428".into(), + height: 2751506, + long_term_weight: 176470, + major_version: 16, + miner_tx_hash: "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd".into(), + minor_version: 16, + nonce: 4110909056, + num_txes: 0, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7".into(), + reward: 600000000000, + timestamp: 1667941829, + wide_cumulative_difficulty: "0x3469a966eb2f788".into(), + wide_difficulty: "0x490be69168".into() + }, + json: "{\n \"major_version\": 16, \n \"minor_version\": 16, \n \"timestamp\": 1667941829, \n \"prev_id\": \"b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7\", \n \"nonce\": 4110909056, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2751566, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2751506\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 600000000000, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85\", \n \"view_tag\": \"d0\"\n }\n }\n }\n ], \n \"extra\": [ 1, 159, 98, 157, 139, 54, 189, 22, 162, 191, 206, 62, 168, 12, 49, 220, 77, 135, 98, 198, 113, 101, 174, 194, 24, 69, 73, 78, 50, 183, 88, 47, 224, 2, 17, 0, 0, 0, 41, 122, 120, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ ]\n}".into(), + miner_tx_hash: "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd".into(), + tx_hashes: vec![], + } + )] + AccessResponseBase { + blob: String, + block_header: BlockHeader, + /// `cuprate_rpc_types::json::block::Block` should be used + /// to create this JSON string in a type-safe manner. + json: String, + miner_tx_hash: String, + tx_hashes: Vec = default_vec::(), "default_vec", + } +} + +define_request_and_response! { + get_connections, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1734..=1754, + + GetConnections (restricted, empty), + + Request {}, + + #[doc = serde_doc_test!( + GET_CONNECTIONS_RESPONSE => GetConnectionsResponse { + base: ResponseBase::OK, + connections: vec![ + ConnectionInfo { + address: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083".into(), + address_type: cuprate_types::AddressType::Tor, + avg_download: 0, + avg_upload: 0, + connection_id: "22ef856d0f1d44cc95e84fecfd065fe2".into(), + current_download: 0, + current_upload: 0, + height: 3195026, + host: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion".into(), + incoming: false, + ip: "".into(), + live_time: 76651, + local_ip: false, + localhost: false, + peer_id: "0000000000000001".into(), + port: "".into(), + pruning_seed: 0, + recv_count: 240328, + recv_idle_time: 34, + rpc_credits_per_hash: 0, + rpc_port: 0, + send_count: 3406572, + send_idle_time: 30, + state: cuprate_types::ConnectionState::Normal, + support_flags: 0 + }, + ConnectionInfo { + address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(), + address_type: cuprate_types::AddressType::Tor, + avg_download: 0, + avg_upload: 0, + connection_id: "c7734e15936f485a86d2b0534f87e499".into(), + current_download: 0, + current_upload: 0, + height: 3195024, + host: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion".into(), + incoming: false, + ip: "".into(), + live_time: 76755, + local_ip: false, + localhost: false, + peer_id: "0000000000000001".into(), + port: "".into(), + pruning_seed: 389, + recv_count: 237657, + recv_idle_time: 120, + rpc_credits_per_hash: 0, + rpc_port: 0, + send_count: 3370566, + send_idle_time: 120, + state: cuprate_types::ConnectionState::Normal, + support_flags: 0 + } + ], + } + )] + ResponseBase { + // FIXME: This is a `std::list` in `monerod` because...? + connections: Vec, + } +} + +define_request_and_response! { + get_info, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 693..=789, + GetInfo (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_INFO_RESPONSE => GetInfoResponse { + base: AccessResponseBase::OK, + adjusted_time: 1721245289, + alt_blocks_count: 16, + block_size_limit: 600000, + block_size_median: 300000, + block_weight_limit: 600000, + block_weight_median: 300000, + bootstrap_daemon_address: "".into(), + busy_syncing: false, + cumulative_difficulty: 366127702242611947, + cumulative_difficulty_top64: 0, + database_size: 235169075200, + difficulty: 280716748706, + difficulty_top64: 0, + free_space: 30521749504, + grey_peerlist_size: 4996, + height: 3195028, + height_without_bootstrap: 3195028, + incoming_connections_count: 62, + mainnet: true, + nettype: "mainnet".into(), + offline: false, + outgoing_connections_count: 1143, + restricted: false, + rpc_connections_count: 1, + stagenet: false, + start_time: 1720462427, + synchronized: true, + target: 120, + target_height: 0, + testnet: false, + top_block_hash: "bdf06d18ed1931a8ee62654e9b6478cc459bc7072628b8e36f4524d339552946".into(), + tx_count: 43205750, + tx_pool_size: 12, + update_available: false, + version: "0.18.3.3-release".into(), + was_bootstrap_ever_used: false, + white_peerlist_size: 1000, + wide_cumulative_difficulty: "0x514bf349299d2eb".into(), + wide_difficulty: "0x415c05a7a2".into() + } + )] + AccessResponseBase { + adjusted_time: u64, + alt_blocks_count: u64, + block_size_limit: u64, + block_size_median: u64, + block_weight_limit: u64, + block_weight_median: u64, + bootstrap_daemon_address: String, + busy_syncing: bool, + cumulative_difficulty_top64: u64, + cumulative_difficulty: u64, + database_size: u64, + difficulty_top64: u64, + difficulty: u64, + free_space: u64, + grey_peerlist_size: u64, + height: u64, + height_without_bootstrap: u64, + incoming_connections_count: u64, + mainnet: bool, + nettype: String, + offline: bool, + outgoing_connections_count: u64, + restricted: bool, + rpc_connections_count: u64, + stagenet: bool, + start_time: u64, + synchronized: bool, + target_height: u64, + target: u64, + testnet: bool, + top_block_hash: String, + tx_count: u64, + tx_pool_size: u64, + update_available: bool, + version: String, + was_bootstrap_ever_used: bool, + white_peerlist_size: u64, + wide_cumulative_difficulty: String, + wide_difficulty: String, + } +} + +define_request_and_response! { + hard_fork_info, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1958..=1995, + HardForkInfo, + + #[doc = serde_doc_test!( + HARD_FORK_INFO_REQUEST => HardForkInfoRequest { + version: 16, + } + )] + #[derive(Copy)] + Request { + version: u8, + }, + + #[doc = serde_doc_test!( + HARD_FORK_INFO_RESPONSE => HardForkInfoResponse { + base: AccessResponseBase::OK, + earliest_height: 2689608, + enabled: true, + state: 0, + threshold: 0, + version: 16, + votes: 10080, + voting: 16, + window: 10080 + } + )] + AccessResponseBase { + earliest_height: u64, + enabled: bool, + state: u32, + threshold: u32, + version: u8, + votes: u32, + voting: u8, + window: u32, + } +} + +define_request_and_response! { + set_bans, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2032..=2067, + + SetBans (restricted), + + #[doc = serde_doc_test!( + SET_BANS_REQUEST => SetBansRequest { + bans: vec![ SetBan { + host: "192.168.1.51".into(), + ip: 0, + ban: true, + seconds: 30 + }] + } + )] + Request { + bans: Vec, + }, + + #[doc = serde_doc_test!( + SET_BANS_RESPONSE => SetBansResponse { + base: ResponseBase::OK, + } + )] + ResponseBase {} +} + +define_request_and_response! { + get_bans, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1997..=2030, + GetBans (restricted, empty), + Request {}, + + #[doc = serde_doc_test!( + GET_BANS_RESPONSE => GetBansResponse { + base: ResponseBase::OK, + bans: vec![ + GetBan { + host: "104.248.206.131".into(), + ip: 2211379304, + seconds: 689754 + }, + GetBan { + host: "209.222.252.0/24".into(), + ip: 0, + seconds: 689754 + } + ] + } + )] + ResponseBase { + bans: Vec, + } +} + +define_request_and_response! { + banned, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2069..=2094, + + Banned (restricted), + + #[doc = serde_doc_test!( + BANNED_REQUEST => BannedRequest { + address: "95.216.203.255".into(), + } + )] + Request { + address: String, + }, + + #[doc = serde_doc_test!( + BANNED_RESPONSE => BannedResponse { + banned: true, + seconds: 689655, + status: Status::Ok, + } + )] + Response { + banned: bool, + seconds: u32, + status: Status, + } +} + +define_request_and_response! { + flush_txpool, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2096..=2116, + + FlushTransactionPool (restricted), + + #[doc = serde_doc_test!( + FLUSH_TRANSACTION_POOL_REQUEST => FlushTransactionPoolRequest { + txids: vec!["dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308".into()], + } + )] + Request { + txids: Vec = default_vec::(), "default_vec", + }, + + #[doc = serde_doc_test!( + FLUSH_TRANSACTION_POOL_RESPONSE => FlushTransactionPoolResponse { + status: Status::Ok, + } + )] + #[repr(transparent)] + Response { + status: Status, + } +} + +define_request_and_response! { + get_output_histogram, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2118..=2168, + GetOutputHistogram, + + #[doc = serde_doc_test!( + GET_OUTPUT_HISTOGRAM_REQUEST => GetOutputHistogramRequest { + amounts: vec![20000000000], + min_count: 0, + max_count: 0, + unlocked: false, + recent_cutoff: 0, + } + )] + Request { + amounts: Vec, + min_count: u64 = default_zero::(), "default_zero", + max_count: u64 = default_zero::(), "default_zero", + unlocked: bool = default_false(), "default_false", + recent_cutoff: u64 = default_zero::(), "default_zero", + }, + + #[doc = serde_doc_test!( + GET_OUTPUT_HISTOGRAM_RESPONSE => GetOutputHistogramResponse { + base: AccessResponseBase::OK, + histogram: vec![HistogramEntry { + amount: 20000000000, + recent_instances: 0, + total_instances: 381490, + unlocked_instances: 0 + }] + } + )] + AccessResponseBase { + histogram: Vec, + } +} + +define_request_and_response! { + get_coinbase_tx_sum, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2213..=2248, + + GetCoinbaseTxSum (restricted), + + #[doc = serde_doc_test!( + GET_COINBASE_TX_SUM_REQUEST => GetCoinbaseTxSumRequest { + height: 1563078, + count: 2 + } + )] + Request { + height: u64, + count: u64, + }, + + #[doc = serde_doc_test!( + GET_COINBASE_TX_SUM_RESPONSE => GetCoinbaseTxSumResponse { + base: AccessResponseBase::OK, + emission_amount: 9387854817320, + emission_amount_top64: 0, + fee_amount: 83981380000, + fee_amount_top64: 0, + wide_emission_amount: "0x889c7c06828".into(), + wide_fee_amount: "0x138dae29a0".into() + } + )] + AccessResponseBase { + emission_amount: u64, + emission_amount_top64: u64, + fee_amount: u64, + fee_amount_top64: u64, + wide_emission_amount: String, + wide_fee_amount: String, + } +} + +define_request_and_response! { + get_version, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2170..=2211, + + GetVersion (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_VERSION_RESPONSE => GetVersionResponse { + base: ResponseBase::OK, + current_height: 3195051, + hard_forks: vec![ + HardforkEntry { + height: 1, + hf_version: 1 + }, + HardforkEntry { + height: 1009827, + hf_version: 2 + }, + HardforkEntry { + height: 1141317, + hf_version: 3 + }, + HardforkEntry { + height: 1220516, + hf_version: 4 + }, + HardforkEntry { + height: 1288616, + hf_version: 5 + }, + HardforkEntry { + height: 1400000, + hf_version: 6 + }, + HardforkEntry { + height: 1546000, + hf_version: 7 + }, + HardforkEntry { + height: 1685555, + hf_version: 8 + }, + HardforkEntry { + height: 1686275, + hf_version: 9 + }, + HardforkEntry { + height: 1788000, + hf_version: 10 + }, + HardforkEntry { + height: 1788720, + hf_version: 11 + }, + HardforkEntry { + height: 1978433, + hf_version: 12 + }, + HardforkEntry { + height: 2210000, + hf_version: 13 + }, + HardforkEntry { + height: 2210720, + hf_version: 14 + }, + HardforkEntry { + height: 2688888, + hf_version: 15 + }, + HardforkEntry { + height: 2689608, + hf_version: 16 + } + ], + release: true, + version: 196621, + target_height: 0, + } + )] + ResponseBase { + version: u32, + release: bool, + current_height: u64 = default_zero::(), "default_zero", + target_height: u64 = default_zero::(), "default_zero", + hard_forks: Vec = default_vec(), "default_vec", + } +} + +define_request_and_response! { + get_fee_estimate, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2250..=2277, + + GetFeeEstimate, + + Request { + grace_blocks: u64 = default_zero::(), "default_zero", + }, + + #[doc = serde_doc_test!( + GET_FEE_ESTIMATE_RESPONSE => GetFeeEstimateResponse { + base: AccessResponseBase::OK, + fee: 20000, + fees: vec![20000,80000,320000,4000000], + quantization_mask: 10000, + } + )] + AccessResponseBase { + fee: u64, + fees: Vec, + quantization_mask: u64 = default_one::(), "default_one", + } +} + +define_request_and_response! { + get_alternate_chains, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2279..=2310, + GetAlternateChains (restricted, empty), + Request {}, + + #[doc = serde_doc_test!( + GET_ALTERNATE_CHAINS_RESPONSE => GetAlternateChainsResponse { + base: ResponseBase::OK, + chains: vec![ + ChainInfo { + block_hash: "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into(), + block_hashes: vec!["4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into()], + difficulty: 357404825113208373, + difficulty_top64: 0, + height: 3167471, + length: 1, + main_chain_parent_block: "69b5075ea627d6ba06b1c30b7e023884eeaef5282cf58ec847dab838ddbcdd86".into(), + wide_difficulty: "0x4f5c1cb79e22635".into(), + }, + ChainInfo { + block_hash: "33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401".into(), + block_hashes: vec!["33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401".into()], + difficulty: 354736121711617293, + difficulty_top64: 0, + height: 3157465, + length: 1, + main_chain_parent_block: "fd522fcc4cefe5c8c0e5c5600981b3151772c285df3a4e38e5c4011cf466d2cb".into(), + wide_difficulty: "0x4ec469f8b9ee50d".into(), + } + ], + } + )] + ResponseBase { + chains: Vec, + } +} + +define_request_and_response! { + relay_tx, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2361..=2381, + + RelayTx (restricted), + + #[doc = serde_doc_test!( + RELAY_TX_REQUEST => RelayTxRequest { + txids: vec!["9fd75c429cbe52da9a52f2ffc5fbd107fe7fd2099c0d8de274dc8a67e0c98613".into()] + } + )] + Request { + txids: Vec, + }, + + #[doc = serde_doc_test!( + RELAY_TX_RESPONSE => RelayTxResponse { + status: Status::Ok, + } + )] + #[repr(transparent)] + Response { + status: Status, + } +} + +define_request_and_response! { + sync_info, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2383..=2443, + + SyncInfo (restricted, empty), + + Request {}, + + #[doc = serde_doc_test!( + SYNC_INFO_RESPONSE => SyncInfoResponse { + base: AccessResponseBase::OK, + height: 3195157, + next_needed_pruning_seed: 0, + overview: "[]".into(), + spans: vec![], + peers: vec![ + SyncInfoPeer { + info: ConnectionInfo { + address: "142.93.128.65:44986".into(), + address_type: cuprate_types::AddressType::Ipv4, + avg_download: 1, + avg_upload: 1, + connection_id: "a5803c4c2dac49e7b201dccdef54c862".into(), + current_download: 2, + current_upload: 1, + height: 3195157, + host: "142.93.128.65".into(), + incoming: true, + ip: "142.93.128.65".into(), + live_time: 18, + local_ip: false, + localhost: false, + peer_id: "6830e9764d3e5687".into(), + port: "44986".into(), + pruning_seed: 0, + recv_count: 20340, + recv_idle_time: 0, + rpc_credits_per_hash: 0, + rpc_port: 18089, + send_count: 32235, + send_idle_time: 6, + state: cuprate_types::ConnectionState::Normal, + support_flags: 1 + } + }, + SyncInfoPeer { + info: ConnectionInfo { + address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(), + address_type: cuprate_types::AddressType::Tor, + avg_download: 0, + avg_upload: 0, + connection_id: "277f7c821bc546878c8bd29977e780f5".into(), + current_download: 0, + current_upload: 0, + height: 3195157, + host: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion".into(), + incoming: false, + ip: "".into(), + live_time: 2246, + local_ip: false, + localhost: false, + peer_id: "0000000000000001".into(), + port: "".into(), + pruning_seed: 389, + recv_count: 65164, + recv_idle_time: 15, + rpc_credits_per_hash: 0, + rpc_port: 0, + send_count: 99120, + send_idle_time: 15, + state: cuprate_types::ConnectionState::Normal, + support_flags: 0 + } + } + ], + target_height: 0, + } + )] + AccessResponseBase { + height: u64, + next_needed_pruning_seed: u32, + overview: String, + // FIXME: This is a `std::list` in `monerod` because...? + peers: Vec = default_vec::(), "default_vec", + // FIXME: This is a `std::list` in `monerod` because...? + spans: Vec = default_vec::(), "default_vec", + target_height: u64, + } +} + +define_request_and_response! { + get_txpool_backlog, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1637..=1664, + GetTransactionPoolBacklog (empty), + Request {}, + + // TODO: enable test after binary string impl. + // #[doc = serde_doc_test!( + // GET_TRANSACTION_POOL_BACKLOG_RESPONSE => GetTransactionPoolBacklogResponse { + // base: ResponseBase::OK, + // backlog: "...Binary...".into(), + // } + // )] + ResponseBase { + // TODO: this is a [`BinaryString`]. + backlog: Vec, + } +} + +define_request_and_response! { + get_output_distribution, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2445..=2520, + + /// This type is also used in the (undocumented) + /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) + /// binary endpoint. + GetOutputDistribution, + + #[doc = serde_doc_test!( + GET_OUTPUT_DISTRIBUTION_REQUEST => GetOutputDistributionRequest { + amounts: vec![628780000], + from_height: 1462078, + binary: true, + compress: false, + cumulative: false, + to_height: 0, + } + )] + Request { + amounts: Vec, + binary: bool = default_true(), "default_true", + compress: bool = default_false(), "default_false", + cumulative: bool = default_false(), "default_false", + from_height: u64 = default_zero::(), "default_zero", + to_height: u64 = default_zero::(), "default_zero", + }, + + // TODO: enable test after binary string impl. + // #[doc = serde_doc_test!( + // GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse { + // base: AccessResponseBase::OK, + // distributions: vec![Distribution::Uncompressed(DistributionUncompressed { + // start_height: 1462078, + // base: 0, + // distribution: vec![], + // amount: 2628780000, + // binary: true, + // })], + // } + // )] + AccessResponseBase { + distributions: Vec, + } +} + +define_request_and_response! { + get_miner_data, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 996..=1044, + GetMinerData (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_MINER_DATA_RESPONSE => GetMinerDataResponse { + base: ResponseBase::OK, + already_generated_coins: 18186022843595960691, + difficulty: "0x48afae42de".into(), + height: 2731375, + major_version: 16, + median_weight: 300000, + prev_id: "78d50c5894d187c4946d54410990ca59a75017628174a9e8c7055fa4ca5c7c6d".into(), + seed_hash: "a6b869d50eca3a43ec26fe4c369859cf36ae37ce6ecb76457d31ffeb8a6ca8a6".into(), + tx_backlog: vec![ + GetMinerDataTxBacklogEntry { + fee: 30700000, + id: "9868490d6bb9207fdd9cf17ca1f6c791b92ca97de0365855ea5c089f67c22208".into(), + weight: 1535 + }, + GetMinerDataTxBacklogEntry { + fee: 44280000, + id: "b6000b02bbec71e18ad704bcae09fb6e5ae86d897ced14a718753e76e86c0a0a".into(), + weight: 2214 + }, + ], + } + )] + ResponseBase { + major_version: u8, + height: u64, + prev_id: String, + seed_hash: String, + difficulty: String, + median_weight: u64, + already_generated_coins: u64, + tx_backlog: Vec, + } +} + +define_request_and_response! { + prune_blockchain, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2747..=2772, + + PruneBlockchain (restricted), + + #[derive(Copy)] + #[doc = serde_doc_test!( + PRUNE_BLOCKCHAIN_REQUEST => PruneBlockchainRequest { + check: true + } + )] + Request { + check: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + PRUNE_BLOCKCHAIN_RESPONSE => PruneBlockchainResponse { + base: ResponseBase::OK, + pruned: true, + pruning_seed: 387, + } + )] + ResponseBase { + pruned: bool, + pruning_seed: u32, + } +} + +define_request_and_response! { + calc_pow, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1046..=1066, + + CalcPow (restricted), + + #[doc = serde_doc_test!( + CALC_POW_REQUEST => CalcPowRequest { + major_version: 14, + height: 2286447, + block_blob: "0e0ed286da8006ecdc1aab3033cf1716c52f13f9d8ae0051615a2453643de94643b550d543becd0000000002abc78b0101ffefc68b0101fcfcf0d4b422025014bb4a1eade6622fd781cb1063381cad396efa69719b41aa28b4fce8c7ad4b5f019ce1dc670456b24a5e03c2d9058a2df10fec779e2579753b1847b74ee644f16b023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051399a1bc46a846474f5b33db24eae173a26393b976054ee14f9feefe99925233802867097564c9db7a36af5bb5ed33ab46e63092bd8d32cef121608c3258edd55562812e21cc7e3ac73045745a72f7d74581d9a0849d6f30e8b2923171253e864f4e9ddea3acb5bc755f1c4a878130a70c26297540bc0b7a57affb6b35c1f03d8dbd54ece8457531f8cba15bb74516779c01193e212050423020e45aa2c15dcb".into(), + seed_hash: "d432f499205150873b2572b5f033c9c6e4b7c6f3394bd2dd93822cd7085e7307".into(), + } + )] + Request { + major_version: u8, + height: u64, + block_blob: String, + seed_hash: String, + }, + + #[doc = serde_doc_test!( + CALC_POW_RESPONSE => CalcPowResponse { + pow_hash: "d0402d6834e26fb94a9ce38c6424d27d2069896a9b8b1ce685d79936bca6e0a8".into(), + } + )] + #[cfg_attr(feature = "serde", serde(transparent))] + #[repr(transparent)] + Response { + pow_hash: String, + } +} + +define_request_and_response! { + flush_cache, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2774..=2796, + + FlushCache (restricted), + + #[derive(Copy)] + #[doc = serde_doc_test!( + FLUSH_CACHE_REQUEST => FlushCacheRequest { + bad_txs: true, + bad_blocks: true + } + )] + Request { + bad_txs: bool = default_false(), "default_false", + bad_blocks: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + FLUSH_CACHE_RESPONSE => FlushCacheResponse { + base: ResponseBase::OK, + } + )] + ResponseBase {} +} + +define_request_and_response! { + add_aux_pow, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1068..=1112, + + AddAuxPow, + + #[doc = serde_doc_test!( + ADD_AUX_POW_REQUEST => AddAuxPowRequest { + blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), + aux_pow: vec![AuxPow { + id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(), + hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into() + }] + } + )] + Request { + blocktemplate_blob: String, + aux_pow: Vec, + }, + + #[doc = serde_doc_test!( + ADD_AUX_POW_RESPONSE => AddAuxPowResponse { + base: ResponseBase::OK, + aux_pow: vec![AuxPow { + hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(), + id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(), + }], + blockhashing_blob: "1010ee97e2a106e9f8ebe8887e5b609949ac8ea6143e560ed13552b110cb009b21f0cfca1eaccf00000000b2685c1283a646bc9020c758daa443be145b7370ce5a6efacb3e614117032e2c22".into(), + blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), + merkle_root: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(), + merkle_tree_depth: 0, + } + )] + ResponseBase { + blocktemplate_blob: String, + blockhashing_blob: String, + merkle_root: String, + merkle_tree_depth: u64, + aux_pow: Vec, + } +} + +define_request_and_response! { + UNDOCUMENTED_METHOD, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2798..=2823, + + GetTxIdsLoose, + + Request { + txid_template: String, + num_matching_bits: u32, + }, + ResponseBase { + txids: Vec, + } +} + +//---------------------------------------------------------------------------------------------------- Request +/// JSON-RPC requests. +/// +/// This enum contains all [`crate::json`] requests. +/// +/// See also: [`JsonRpcResponse`]. +/// +/// TODO: document and test (de)serialization behavior after figuring out `method/params`. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr( + feature = "serde", + serde(rename_all = "snake_case", tag = "method", content = "params") +)] +pub enum JsonRpcRequest { + GetBlockCount(GetBlockCountRequest), + OnGetBlockHash(OnGetBlockHashRequest), + SubmitBlock(SubmitBlockRequest), + GenerateBlocks(GenerateBlocksRequest), + GetLastBlockHeader(GetLastBlockHeaderRequest), + GetBlockHeaderByHash(GetBlockHeaderByHashRequest), + GetBlockHeaderByHeight(GetBlockHeaderByHeightRequest), + GetBlockHeadersRange(GetBlockHeadersRangeRequest), + GetBlock(GetBlockRequest), + GetConnections(GetConnectionsRequest), + GetInfo(GetInfoRequest), + HardForkInfo(HardForkInfoRequest), + SetBans(SetBansRequest), + GetBans(GetBansRequest), + Banned(BannedRequest), + FlushTransactionPool(FlushTransactionPoolRequest), + GetOutputHistogram(GetOutputHistogramRequest), + GetCoinbaseTxSum(GetCoinbaseTxSumRequest), + GetVersion(GetVersionRequest), + GetFeeEstimate(GetFeeEstimateRequest), + GetAlternateChains(GetAlternateChainsRequest), + RelayTx(RelayTxRequest), + SyncInfo(SyncInfoRequest), + GetTransactionPoolBacklog(GetTransactionPoolBacklogRequest), + GetMinerData(GetMinerDataRequest), + PruneBlockchain(PruneBlockchainRequest), + CalcPow(CalcPowRequest), + FlushCache(FlushCacheRequest), + AddAuxPow(AddAuxPowRequest), + GetTxIdsLoose(GetTxIdsLooseRequest), +} + +impl RpcCallValue for JsonRpcRequest { + fn is_restricted(&self) -> bool { + match self { + Self::GetBlockCount(x) => x.is_restricted(), + Self::OnGetBlockHash(x) => x.is_restricted(), + Self::SubmitBlock(x) => x.is_restricted(), + Self::GetLastBlockHeader(x) => x.is_restricted(), + Self::GetBlockHeaderByHash(x) => x.is_restricted(), + Self::GetBlockHeaderByHeight(x) => x.is_restricted(), + Self::GetBlockHeadersRange(x) => x.is_restricted(), + Self::GetBlock(x) => x.is_restricted(), + Self::GetInfo(x) => x.is_restricted(), + Self::HardForkInfo(x) => x.is_restricted(), + Self::GetOutputHistogram(x) => x.is_restricted(), + Self::GetVersion(x) => x.is_restricted(), + Self::GetFeeEstimate(x) => x.is_restricted(), + Self::GetTransactionPoolBacklog(x) => x.is_restricted(), + Self::GetMinerData(x) => x.is_restricted(), + Self::AddAuxPow(x) => x.is_restricted(), + Self::GetTxIdsLoose(x) => x.is_restricted(), + Self::GenerateBlocks(x) => x.is_restricted(), + Self::GetConnections(x) => x.is_restricted(), + Self::SetBans(x) => x.is_restricted(), + Self::GetBans(x) => x.is_restricted(), + Self::Banned(x) => x.is_restricted(), + Self::FlushTransactionPool(x) => x.is_restricted(), + Self::GetCoinbaseTxSum(x) => x.is_restricted(), + Self::GetAlternateChains(x) => x.is_restricted(), + Self::RelayTx(x) => x.is_restricted(), + Self::SyncInfo(x) => x.is_restricted(), + Self::PruneBlockchain(x) => x.is_restricted(), + Self::CalcPow(x) => x.is_restricted(), + Self::FlushCache(x) => x.is_restricted(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::GetBlockCount(x) => x.is_empty(), + Self::OnGetBlockHash(x) => x.is_empty(), + Self::SubmitBlock(x) => x.is_empty(), + Self::GetLastBlockHeader(x) => x.is_empty(), + Self::GetBlockHeaderByHash(x) => x.is_empty(), + Self::GetBlockHeaderByHeight(x) => x.is_empty(), + Self::GetBlockHeadersRange(x) => x.is_empty(), + Self::GetBlock(x) => x.is_empty(), + Self::GetInfo(x) => x.is_empty(), + Self::HardForkInfo(x) => x.is_empty(), + Self::GetOutputHistogram(x) => x.is_empty(), + Self::GetVersion(x) => x.is_empty(), + Self::GetFeeEstimate(x) => x.is_empty(), + Self::GetTransactionPoolBacklog(x) => x.is_empty(), + Self::GetMinerData(x) => x.is_empty(), + Self::AddAuxPow(x) => x.is_empty(), + Self::GetTxIdsLoose(x) => x.is_empty(), + Self::GenerateBlocks(x) => x.is_empty(), + Self::GetConnections(x) => x.is_empty(), + Self::SetBans(x) => x.is_empty(), + Self::GetBans(x) => x.is_empty(), + Self::Banned(x) => x.is_empty(), + Self::FlushTransactionPool(x) => x.is_empty(), + Self::GetCoinbaseTxSum(x) => x.is_empty(), + Self::GetAlternateChains(x) => x.is_empty(), + Self::RelayTx(x) => x.is_empty(), + Self::SyncInfo(x) => x.is_empty(), + Self::PruneBlockchain(x) => x.is_empty(), + Self::CalcPow(x) => x.is_empty(), + Self::FlushCache(x) => x.is_empty(), + } + } +} + +//---------------------------------------------------------------------------------------------------- Response +/// JSON-RPC responses. +/// +/// This enum contains all [`crate::json`] responses. +/// +/// See also: [`JsonRpcRequest`]. +/// +/// # (De)serialization +/// The `serde` implementation will (de)serialize from +/// the inner variant itself, e.g. [`JsonRpcRequest::Banned`] +/// has the same (de)serialization as [`BannedResponse`]. +/// +/// ```rust +/// use cuprate_rpc_types::{misc::*, json::*}; +/// +/// let response = JsonRpcResponse::Banned(BannedResponse { +/// banned: true, +/// seconds: 123, +/// status: Status::Ok, +/// }); +/// let json = serde_json::to_string(&response).unwrap(); +/// assert_eq!(json, r#"{"banned":true,"seconds":123,"status":"OK"}"#); +/// let response: JsonRpcResponse = serde_json::from_str(&json).unwrap(); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged, rename_all = "snake_case"))] +pub enum JsonRpcResponse { + GetBlockCount(GetBlockCountResponse), + OnGetBlockHash(OnGetBlockHashResponse), + SubmitBlock(SubmitBlockResponse), + GenerateBlocks(GenerateBlocksResponse), + GetLastBlockHeader(GetLastBlockHeaderResponse), + GetBlockHeaderByHash(GetBlockHeaderByHashResponse), + GetBlockHeaderByHeight(GetBlockHeaderByHeightResponse), + GetBlockHeadersRange(GetBlockHeadersRangeResponse), + GetBlock(GetBlockResponse), + GetConnections(GetConnectionsResponse), + GetInfo(GetInfoResponse), + HardForkInfo(HardForkInfoResponse), + SetBans(SetBansResponse), + GetBans(GetBansResponse), + Banned(BannedResponse), + FlushTransactionPool(FlushTransactionPoolResponse), + GetOutputHistogram(GetOutputHistogramResponse), + GetCoinbaseTxSum(GetCoinbaseTxSumResponse), + GetVersion(GetVersionResponse), + GetFeeEstimate(GetFeeEstimateResponse), + GetAlternateChains(GetAlternateChainsResponse), + RelayTx(RelayTxResponse), + SyncInfo(SyncInfoResponse), + GetTransactionPoolBacklog(GetTransactionPoolBacklogResponse), + GetMinerData(GetMinerDataResponse), + PruneBlockchain(PruneBlockchainResponse), + CalcPow(CalcPowResponse), + FlushCache(FlushCacheResponse), + AddAuxPow(AddAuxPowResponse), + GetTxIdsLoose(GetTxIdsLooseResponse), +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 780208b..403a3ea 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -1,116 +1,36 @@ #![doc = include_str!("../README.md")] -//---------------------------------------------------------------------------------------------------- Lints -// Forbid lints. -// Our code, and code generated (e.g macros) cannot overrule these. -#![forbid( - // `unsafe` is allowed but it _must_ be - // commented with `SAFETY: reason`. - clippy::undocumented_unsafe_blocks, - - // Never. - unused_unsafe, - redundant_semicolons, - unused_allocation, - coherence_leak_check, - while_true, - clippy::missing_docs_in_private_items, - - // Maybe can be put into `#[deny]`. - unconditional_recursion, - for_loops_over_fallibles, - unused_braces, - unused_labels, - keyword_idents, - non_ascii_idents, - variant_size_differences, - single_use_lifetimes, - - // Probably can be put into `#[deny]`. - future_incompatible, - let_underscore, - break_with_label_and_loop, - duplicate_macro_attributes, - exported_private_dependencies, - large_assignments, - overlapping_range_endpoints, - semicolon_in_expressions_from_macros, - noop_method_call, -)] -// Deny lints. -// Some of these are `#[allow]`'ed on a per-case basis. -#![deny( - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, - clippy::nursery, - clippy::cargo, - unused_doc_comments, - unused_mut, - missing_docs, - deprecated, - unused_comparisons, - nonstandard_style, - unreachable_pub -)] +#![cfg_attr(docsrs, feature(doc_cfg))] #![allow( - // FIXME: this lint affects crates outside of - // `database/` for some reason, allow for now. - clippy::cargo_common_metadata, - - // FIXME: adding `#[must_use]` onto everything - // might just be more annoying than useful... - // although it is sometimes nice. - clippy::must_use_candidate, - - // FIXME: good lint but too many false positives - // with our `Env` + `RwLock` setup. - clippy::significant_drop_tightening, - - // FIXME: good lint but is less clear in most cases. - clippy::items_after_statements, - - // TODO - rustdoc::bare_urls, - - clippy::module_name_repetitions, - clippy::module_inception, - clippy::redundant_pub_crate, - clippy::option_if_let_else, + clippy::allow_attributes, + reason = "macros (internal + serde) make this lint hard to satisfy" )] -// Allow some lints when running in debug mode. -#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] -// Allow some lints in tests. -#![cfg_attr( - test, - allow( - clippy::cognitive_complexity, - clippy::needless_pass_by_value, - clippy::cast_possible_truncation, - clippy::too_many_lines - ) -)] -// TODO: remove me after finishing impl -#![allow(dead_code)] -//---------------------------------------------------------------------------------------------------- Use -mod binary_string; mod constants; +#[cfg(any(feature = "serde", feature = "epee"))] +mod defaults; +mod free; mod macros; -mod status; +mod rpc_call; -pub use binary_string::BinaryString; -pub use constants::{ - CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK, - CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN, CORE_RPC_VERSION, - CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR, -}; -pub use status::Status; +#[cfg(feature = "serde")] +mod serde; pub mod base; pub mod bin; pub mod json; +pub mod misc; pub mod other; + +pub use constants::{ + CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK, + CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR, + CORE_RPC_VERSION_MINOR, +}; +pub use rpc_call::{RpcCall, RpcCallValue}; + +// false-positive: used in tests +#[cfg(test)] +mod test { + extern crate cuprate_test_utils; + extern crate serde_json; +} diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index 2728800..db1b5d8 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -1,14 +1,12 @@ //! Macros. -//---------------------------------------------------------------------------------------------------- Struct definition -/// A template for generating 2 `struct`s with a bunch of information filled out. -/// -/// These are the RPC request and response `struct`s. +//---------------------------------------------------------------------------------------------------- define_request_and_response +/// A template for generating the RPC request and response `struct`s. /// /// These `struct`s automatically implement: /// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash` /// - `serde::{Serialize, Deserialize}` -/// - `epee_encoding::EpeeObject` +/// - `cuprate_epee_encoding::EpeeObject` /// /// It's best to see the output of this macro via the documentation /// of the generated structs via `cargo doc`s to see which parts @@ -17,37 +15,37 @@ /// See the [`crate::json`] module for example usage. /// /// # Macro internals -/// This macro has 2 branches with almost the same output: -/// 1. An empty `Request` type -/// 2. An `Request` type with fields +/// This macro uses: +/// - [`define_request`] +/// - [`define_response`] +/// - [`define_request_and_response_doc`] /// -/// The first branch is the same as the second with the exception -/// that if the caller of this macro provides no fields, it will -/// generate: +/// # `define_request` +/// This macro has 2 branches. If the caller provides +/// `Request {}`, i.e. no fields, it will generate: /// ``` /// pub type Request = (); /// ``` -/// instead of: +/// If they _did_ specify fields, it will generate: /// ``` /// pub struct Request {/* fields */} /// ``` -/// /// This is because having a bunch of types that are all empty structs /// means they are not compatible and it makes it cumbersome for end-users. /// Really, they semantically are empty types, so `()` is used. /// -/// Again, other than this, the 2 branches do (should) not differ. +/// # `define_response` +/// This macro has 2 branches. If the caller provides `Response` +/// it will generate a normal struct with no additional fields. /// -/// FIXME: there's probably a less painful way to branch here on input -/// without having to duplicate 80% of the macro. Sub-macros were attempted -/// but they ended up unreadable. So for now, make sure to fix the other -/// branch as well when making changes. The only de-duplicated part is -/// the doc generation with [`define_request_and_response_doc`]. +/// If the caller provides a base type from [`crate::base`], it will +/// flatten that into the request type automatically. +/// +/// E.g. `Response {/*...*/}` and `ResponseBase {/*...*/}` +/// would trigger the different branches. macro_rules! define_request_and_response { - //------------------------------------------------------------------------------ - // This version of the macro expects a `Request` type with no fields, i.e. `Request {}`. ( - // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. + // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. $monero_daemon_rpc_doc_link:ident, // The commit hash and `$file.$extension` in which this type is defined in @@ -59,90 +57,27 @@ macro_rules! define_request_and_response { $monero_code_line_end:literal, // The base `struct` name. - $type_name:ident, - - // The response type (and any doc comments, derives, etc). - $( #[$response_type_attr:meta] )* - $response_base_type:ty { - // And any fields. - $( - $( #[$response_field_attr:meta] )* - $response_field:ident: $response_field_type:ty, - )* - } - ) => { paste::paste! { - #[doc = $crate::macros::define_request_and_response_doc!( - "response", - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - [<$type_name Request>], - )] - /// - /// This request has no inputs. - pub type [<$type_name Request>] = (); - - #[allow(dead_code)] - #[allow(missing_docs)] - #[derive(serde::Serialize, serde::Deserialize)] - #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] - $( #[$response_type_attr] )* - #[doc = $crate::macros::define_request_and_response_doc!( - "request", - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - [<$type_name Response>], - )] - pub struct [<$type_name Response>] { - #[serde(flatten)] - pub base: $response_base_type, - - $( - $( #[$response_field_attr] )* - pub $response_field: $response_field_type, - )* - } - - ::cuprate_epee_encoding::epee_object! { - [<$type_name Response>], - $( - $response_field: $response_field_type, - )* - !flatten: base: $response_base_type, - } - }}; - - //------------------------------------------------------------------------------ - // This version of the macro expects a `Request` type with fields. - ( - // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. - $monero_daemon_rpc_doc_link:ident, - - // The commit hash and `$file.$extension` in which this type is defined in - // the Monero codebase in the `rpc/` directory, followed by the specific lines. - $monero_code_commit:ident => - $monero_code_filename:ident. - $monero_code_filename_extension:ident => - $monero_code_line_start:literal..= - $monero_code_line_end:literal, - - // The base `struct` name. - $type_name:ident, + // Attributes added here will apply to _both_ + // request and response types. + $( #[$type_attr:meta] )* + // After the type name, 2 optional idents are allowed: + // + // - `restricted` + // - `empty` + // + // These have to be within `()` and will affect the + // [`crate::RpcCall`] implementation on the request type. + $type_name:ident $(($restricted:ident $(, $empty:ident)?))?, // The request type (and any doc comments, derives, etc). $( #[$request_type_attr:meta] )* - $request_base_type:ty { + Request { // And any fields. $( - $( #[$request_field_attr:meta] )* - $request_field:ident: $request_field_type:ty, + $( #[$request_field_attr:meta] )* // Field attribute. + $request_field:ident: $request_field_type:ty // field_name: field type + $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization + $(= $request_field_type_default:expr_2021, $request_field_type_default_string:literal)?, // (optional) default value )* }, @@ -152,79 +87,273 @@ macro_rules! define_request_and_response { // And any fields. $( $( #[$response_field_attr:meta] )* - $response_field:ident: $response_field_type:ty, + $response_field:ident: $response_field_type:ty + $(as $response_field_type_as:ty)? + $(= $response_field_type_default:expr_2021, $response_field_type_default_string:literal)?, )* } ) => { paste::paste! { - #[allow(dead_code)] - #[allow(missing_docs)] - #[derive(serde::Serialize, serde::Deserialize)] - #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] - $( #[$request_type_attr] )* - #[doc = $crate::macros::define_request_and_response_doc!( - "response", - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - [<$type_name Request>], - )] - pub struct [<$type_name Request>] { - #[serde(flatten)] - pub base: $request_base_type, - - $( - $( #[$request_field_attr] )* - pub $request_field: $request_field_type, - )* + $crate::macros::define_request! { + #[allow(dead_code, missing_docs, reason = "inside a macro")] + #[doc = $crate::macros::define_request_and_response_doc!( + "response" => [<$type_name Response>], + $monero_daemon_rpc_doc_link, + $monero_code_commit, + $monero_code_filename, + $monero_code_filename_extension, + $monero_code_line_start, + $monero_code_line_end, + )] + /// + $( #[$type_attr] )* + /// + $( #[$request_type_attr] )* + [<$type_name Request>] $(($restricted $(, $empty)?))? { + $( + $( #[$request_field_attr] )* + $request_field: $request_field_type + $(as $request_field_type_as)? + $(= $request_field_type_default, $request_field_type_default_string)?, + )* + } } - ::cuprate_epee_encoding::epee_object! { - [<$type_name Request>], - $( - $request_field: $request_field_type, - )* - !flatten: base: $request_base_type, - } - - #[allow(dead_code)] - #[allow(missing_docs)] - #[derive(serde::Serialize, serde::Deserialize)] - #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] - $( #[$response_type_attr] )* - #[doc = $crate::macros::define_request_and_response_doc!( - "request", - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - [<$type_name Response>], - )] - pub struct [<$type_name Response>] { - #[serde(flatten)] - pub base: $response_base_type, - - $( - $( #[$response_field_attr] )* - pub $response_field: $response_field_type, - )* - } - - ::cuprate_epee_encoding::epee_object! { - [<$type_name Response>], - $( - $response_field: $response_field_type, - )* - !flatten: base: $response_base_type, + $crate::macros::define_response! { + #[allow(dead_code, missing_docs, reason = "inside a macro")] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[doc = $crate::macros::define_request_and_response_doc!( + "request" => [<$type_name Request>], + $monero_daemon_rpc_doc_link, + $monero_code_commit, + $monero_code_filename, + $monero_code_filename_extension, + $monero_code_line_start, + $monero_code_line_end, + )] + /// + $( #[$type_attr] )* + /// + $( #[$response_type_attr] )* + $response_base_type => [<$type_name Response>] { + $( + $( #[$response_field_attr] )* + $response_field: $response_field_type + $(as $response_field_type_as)? + $(= $response_field_type_default, $response_field_type_default_string)?, + )* + } } }}; } pub(crate) use define_request_and_response; +//---------------------------------------------------------------------------------------------------- impl_rpc_call +/// Implement [`crate::RpcCall`] and [`crate::RpcCallValue`] on request types. +/// +/// Input for this is: +/// `$REQUEST_TYPE restricted empty` +/// where `restricted` and `empty` are the idents themselves. +/// The implementation for [`crate::RpcCall`] will change +/// depending if they exist or not. +macro_rules! impl_rpc_call { + // Restricted and empty RPC calls. + ($t:ident, restricted, empty) => { + impl $crate::RpcCall for $t { + const IS_RESTRICTED: bool = true; + const IS_EMPTY: bool = true; + } + + impl From<()> for $t { + fn from(_: ()) -> Self { + Self {} + } + } + + impl From<$t> for () { + fn from(_: $t) -> Self {} + } + }; + + // Empty RPC calls. + ($t:ident, empty) => { + impl $crate::RpcCall for $t { + const IS_RESTRICTED: bool = false; + const IS_EMPTY: bool = true; + } + + impl From<()> for $t { + fn from(_: ()) -> Self { + Self {} + } + } + + impl From<$t> for () { + fn from(_: $t) -> Self {} + } + }; + + // Restricted RPC calls. + ($t:ident, restricted) => { + impl $crate::RpcCall for $t { + const IS_RESTRICTED: bool = true; + const IS_EMPTY: bool = false; + } + }; + + // Not restrict or empty RPC calls. + ($t:ident) => { + impl $crate::RpcCall for $t { + const IS_RESTRICTED: bool = false; + const IS_EMPTY: bool = false; + } + }; +} +pub(crate) use impl_rpc_call; + +//---------------------------------------------------------------------------------------------------- define_request +/// Define a request type. +/// +/// This is only used in [`define_request_and_response`], see it for docs. +macro_rules! define_request { + //------------------------------------------------------------------------------ + // This branch will generate a type alias to `()` if only given `{}` as input. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + $t:ident $(($restricted:ident $(, $empty:ident)?))? { + // And any fields. + $( + $( #[$field_attr:meta] )* // field attributes + // field_name: FieldType + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr_2021, $field_default_string:literal)?, + // The $field_default is an optional extra token that represents + // a default value to pass to [`cuprate_epee_encoding::epee_object`], + // see it for usage. + )* + } + ) => { + #[allow(dead_code, missing_docs, reason = "inside a macro")] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + $( #[$attr] )* + pub struct $t { + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + $crate::macros::impl_rpc_call!($t $(, $restricted $(, $empty)?)?); + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + } + }; +} +pub(crate) use define_request; + +//---------------------------------------------------------------------------------------------------- define_response +/// Define a response type. +/// +/// This is used in [`define_request_and_response`], see it for docs. +macro_rules! define_response { + //------------------------------------------------------------------------------ + // This version of the macro expects the literal ident + // `Response` => $response_type_name. + // + // It will create a `struct` that _doesn't_ use a base from [`crate::base`], + // for example, [`crate::json::BannedResponse`] doesn't use a base, so it + // uses this branch. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + Response => $t:ident { + // And any fields. + // See [`define_request`] for docs, this does the same thing. + $( + $( #[$field_attr:meta] )* + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr_2021, $field_default_string:literal)?, + )* + } + ) => { + $( #[$attr] )* + pub struct $t { + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + } + }; + + //------------------------------------------------------------------------------ + // This version of the macro expects a `Request` base type from [`crate::bases`]. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response base type => actual name of the struct + $base:ty => $t:ident { + // And any fields. + // See [`define_request`] for docs, this does the same thing. + $( + $( #[$field_attr:meta] )* + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr_2021, $field_default_string:literal)?, + )* + } + ) => { + $( #[$attr] )* + pub struct $t { + #[cfg_attr(feature = "serde", serde(flatten))] + pub base: $base, + + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + !flatten: base: $base, + } + }; +} +pub(crate) use define_response; + +//---------------------------------------------------------------------------------------------------- define_request_and_response_doc /// Generate documentation for the types generated /// by the [`define_request_and_response`] macro. /// @@ -239,7 +368,7 @@ macro_rules! define_request_and_response_doc { // Remember this is linking to the _other_ type, // so if defining a `Request` type, input should // be "response". - $request_or_response:literal, + $request_or_response:literal => $request_or_response_type:ident, $monero_daemon_rpc_doc_link:ident, $monero_code_commit:ident, @@ -247,7 +376,6 @@ macro_rules! define_request_and_response_doc { $monero_code_filename_extension:ident, $monero_code_line_start:literal, $monero_code_line_end:literal, - $type_name:ident, ) => { concat!( "", @@ -269,9 +397,34 @@ macro_rules! define_request_and_response_doc { "), [", $request_or_response, "](", - stringify!($type_name), + stringify!($request_or_response_type), ")." ) }; } pub(crate) use define_request_and_response_doc; + +//---------------------------------------------------------------------------------------------------- Macro +/// Output a string link to `monerod` source code. +macro_rules! monero_definition_link { + ( + $commit:ident, // Git commit hash + $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h` + $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0` + ) => { + concat!( + "[Definition](https://github.com/monero-project/monero/blob/", + stringify!($commit), + "/src/", + $file_path, + "#L", + stringify!($start), + $( + "-L", + stringify!($end), + )? + ")." + ) + }; +} +pub(crate) use monero_definition_link; diff --git a/rpc/types/src/binary_string.rs b/rpc/types/src/misc/binary_string.rs similarity index 80% rename from rpc/types/src/binary_string.rs rename to rpc/types/src/misc/binary_string.rs index b644ad3..5c3908d 100644 --- a/rpc/types/src/binary_string.rs +++ b/rpc/types/src/misc/binary_string.rs @@ -1,14 +1,14 @@ -//! TODO +//! JSON string containing binary data. //---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- BinaryString -/// TODO +/// TODO: we need to figure out a type that (de)serializes correctly, `String` errors with `serde_json` /// /// ```rust /// use serde::Deserialize; /// use serde_json::from_str; -/// use cuprate_rpc_types::BinaryString; +/// use cuprate_rpc_types::misc::BinaryString; /// /// #[derive(Deserialize)] /// struct Key { diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs new file mode 100644 index 0000000..e920d12 --- /dev/null +++ b/rpc/types/src/misc/distribution.rs @@ -0,0 +1,299 @@ +//! Output distributions for [`crate::json::GetOutputDistributionResponse`]. + +//---------------------------------------------------------------------------------------------------- Use +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + epee_object, error, + macros::bytes::{Buf, BufMut}, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, +}; + +//---------------------------------------------------------------------------------------------------- Free +/// TODO: . +/// +/// Used for [`Distribution::CompressedBinary::distribution`]. +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 45..=55 +)] +#[cfg(any(feature = "epee", feature = "serde"))] +fn compress_integer_array(_: &[u64]) -> Vec { + todo!() +} + +/// TODO: . +/// +/// Used for [`Distribution::CompressedBinary::distribution`]. +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 57..=72 +)] +#[cfg(any(feature = "epee", feature = "serde"))] +fn decompress_integer_array(_: &[u8]) -> Vec { + todo!() +} + +//---------------------------------------------------------------------------------------------------- Distribution +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2468..=2508 +)] +/// Used in [`crate::json::GetOutputDistributionResponse`]. +/// +/// # Internals +/// This type's (de)serialization depends on `monerod`'s (de)serialization. +/// +/// During serialization: +/// [`Self::Uncompressed`] will emit: +/// - `compress: false` +/// +/// [`Self::CompressedBinary`] will emit: +/// - `binary: true` +/// - `compress: true` +/// +/// Upon deserialization, the presence of a `compressed_data` +/// field signifies that the [`Self::CompressedBinary`] should +/// be selected. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum Distribution { + /// Distribution data will be (de)serialized as either JSON or binary (uncompressed). + Uncompressed(DistributionUncompressed), + /// Distribution data will be (de)serialized as compressed binary. + CompressedBinary(DistributionCompressedBinary), +} + +impl Default for Distribution { + fn default() -> Self { + Self::Uncompressed(DistributionUncompressed::default()) + } +} + +/// Data within [`Distribution::Uncompressed`]. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DistributionUncompressed { + pub start_height: u64, + pub base: u64, + /// TODO: this is a binary JSON string if `binary == true`. + pub distribution: Vec, + pub amount: u64, + pub binary: bool, +} + +#[cfg(feature = "epee")] +epee_object! { + DistributionUncompressed, + start_height: u64, + base: u64, + distribution: Vec, + amount: u64, + binary: bool, +} + +/// Data within [`Distribution::CompressedBinary`]. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DistributionCompressedBinary { + pub start_height: u64, + pub base: u64, + #[cfg_attr( + feature = "serde", + serde(serialize_with = "serialize_distribution_as_compressed_data") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "deserialize_compressed_data_as_distribution") + )] + #[cfg_attr(feature = "serde", serde(rename = "compressed_data"))] + pub distribution: Vec, + pub amount: u64, +} + +#[cfg(feature = "epee")] +epee_object! { + DistributionCompressedBinary, + start_height: u64, + base: u64, + distribution: Vec, + amount: u64, +} + +/// Serializer function for [`DistributionCompressedBinary::distribution`]. +/// +/// 1. Compresses the distribution array +/// 2. Serializes the compressed data +#[cfg(feature = "serde")] +#[expect(clippy::ptr_arg)] +fn serialize_distribution_as_compressed_data(v: &Vec, s: S) -> Result +where + S: serde::Serializer, +{ + compress_integer_array(v).serialize(s) +} + +/// Deserializer function for [`DistributionCompressedBinary::distribution`]. +/// +/// 1. Deserializes as `compressed_data` field. +/// 2. Decompresses and returns the data +#[cfg(feature = "serde")] +fn deserialize_compressed_data_as_distribution<'de, D>(d: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + Vec::::deserialize(d).map(|v| decompress_integer_array(&v)) +} + +//---------------------------------------------------------------------------------------------------- Epee +#[cfg(feature = "epee")] +/// [`EpeeObjectBuilder`] for [`Distribution`]. +/// +/// Not for public usage. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct __DistributionEpeeBuilder { + pub start_height: Option, + pub base: Option, + pub distribution: Option>, + pub amount: Option, + pub compressed_data: Option>, + pub binary: Option, + pub compress: Option, +} + +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for __DistributionEpeeBuilder { + fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { + macro_rules! read_epee_field { + ($($field:ident),*) => { + match name { + $( + stringify!($field) => { self.$field = Some(read_epee_value(r)?); }, + )* + _ => return Ok(false), + } + }; + } + + read_epee_field! { + start_height, + base, + amount, + binary, + compress, + compressed_data, + distribution + } + + Ok(true) + } + + fn finish(self) -> error::Result { + const ELSE: error::Error = error::Error::Format("Required field was not found!"); + + let start_height = self.start_height.ok_or(ELSE)?; + let base = self.base.ok_or(ELSE)?; + let amount = self.amount.ok_or(ELSE)?; + + let distribution = if let Some(compressed_data) = self.compressed_data { + let distribution = decompress_integer_array(&compressed_data); + Distribution::CompressedBinary(DistributionCompressedBinary { + start_height, + base, + distribution, + amount, + }) + } else if let Some(distribution) = self.distribution { + Distribution::Uncompressed(DistributionUncompressed { + binary: self.binary.ok_or(ELSE)?, + distribution, + start_height, + base, + amount, + }) + } else { + return Err(ELSE); + }; + + Ok(distribution) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for Distribution { + type Builder = __DistributionEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + match self { + // Inner struct fields + `compress`. + Self::Uncompressed(s) => s.number_of_fields() + 1, + // Inner struct fields + `compress` + `binary`. + Self::CompressedBinary(s) => s.number_of_fields() + 2, + } + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + match self { + Self::Uncompressed(s) => { + s.write_fields(w)?; + write_field(false, "compress", w)?; + } + + Self::CompressedBinary(DistributionCompressedBinary { + start_height, + base, + distribution, + amount, + }) => { + let compressed_data = compress_integer_array(&distribution); + + start_height.write(w)?; + base.write(w)?; + compressed_data.write(w)?; + amount.write(w)?; + + write_field(true, "binary", w)?; + write_field(true, "compress", w)?; + } + } + + Ok(()) + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod tests { + // use pretty_assertions::assert_eq; + + // use super::*; + + // TODO: re-enable tests after (de)compression functions are implemented. + + // /// Tests that [`compress_integer_array`] outputs as expected. + // #[test] + // fn compress() { + // let varints = &[16_384, 16_383, 16_382, 16_381]; + // let bytes = compress_integer_array(varints).unwrap(); + + // let expected = [2, 0, 1, 0, 253, 255, 249, 255, 245, 255]; + // assert_eq!(expected, *bytes); + // } + + // /// Tests that [`decompress_integer_array`] outputs as expected. + // #[test] + // fn decompress() { + // let bytes = &[2, 0, 1, 0, 253, 255, 249, 255, 245, 255]; + // let varints = decompress_integer_array(bytes); + + // let expected = vec![16_384, 16_383, 16_382, 16_381]; + // assert_eq!(expected, varints); + // } +} diff --git a/rpc/types/src/misc/key_image_spent_status.rs b/rpc/types/src/misc/key_image_spent_status.rs new file mode 100644 index 0000000..4b2eb53 --- /dev/null +++ b/rpc/types/src/misc/key_image_spent_status.rs @@ -0,0 +1,85 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + error, + macros::bytes::{Buf, BufMut}, + EpeeValue, Marker, +}; + +//---------------------------------------------------------------------------------------------------- KeyImageSpentStatus +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 456..=460 +)] +/// Used in [`crate::other::IsKeyImageSpentResponse`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum KeyImageSpentStatus { + Unspent = 0, + SpentInBlockchain = 1, + SpentInPool = 2, +} + +impl KeyImageSpentStatus { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_rpc_types::misc::KeyImageSpentStatus as K; + /// + /// assert_eq!(K::Unspent.to_u8(), 0); + /// assert_eq!(K::SpentInBlockchain.to_u8(), 1); + /// assert_eq!(K::SpentInPool.to_u8(), 2); + /// ``` + pub const fn to_u8(self) -> u8 { + match self { + Self::Unspent => 0, + Self::SpentInBlockchain => 1, + Self::SpentInPool => 2, + } + } + + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 2`. + /// + /// ```rust + /// use cuprate_rpc_types::misc::KeyImageSpentStatus as K; + /// + /// assert_eq!(K::from_u8(0), Some(K::Unspent)); + /// assert_eq!(K::from_u8(1), Some(K::SpentInBlockchain)); + /// assert_eq!(K::from_u8(2), Some(K::SpentInPool)); + /// assert_eq!(K::from_u8(3), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::Unspent, + 1 => Self::SpentInBlockchain, + 2 => Self::SpentInPool, + _ => return None, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for KeyImageSpentStatus { + const MARKER: Marker = u8::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 2")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) + } +} diff --git a/rpc/types/src/misc/misc.rs b/rpc/types/src/misc/misc.rs new file mode 100644 index 0000000..2d88f2a --- /dev/null +++ b/rpc/types/src/misc/misc.rs @@ -0,0 +1,520 @@ +//! Miscellaneous types. +//! +//! These are `struct`s that appear in request/response types. +//! For example, [`crate::json::GetConnectionsResponse`] contains +//! the [`crate::misc::ConnectionInfo`] struct defined here. + +//---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::epee_object; + +#[cfg(any(feature = "epee", feature = "serde"))] +use crate::defaults::default_zero; + +use crate::macros::monero_definition_link; + +//---------------------------------------------------------------------------------------------------- Macros +/// This macro (local to this file) defines all the misc types. +/// +/// This macro: +/// 1. Defines a `pub struct` with all `pub` fields +/// 2. Implements `serde` on the struct +/// 3. Implements `epee` on the struct +/// +/// When using, consider documenting: +/// - The original Monero definition site with [`monero_definition_link`] +/// - The request/responses where the `struct` is used +macro_rules! define_struct_and_impl_epee { + ( + // Optional `struct` attributes. + $( #[$struct_attr:meta] )* + // The `struct`'s name. + $struct_name:ident { + // And any fields. + $( + $( #[$field_attr:meta] )* // Field attributes + // Field name => the type => optional `epee_object` default value. + $field_name:ident: $field_type:ty $(= $field_default:expr_2021)?, + )* + } + ) => { + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + $( #[$struct_attr] )* + pub struct $struct_name { + $( + $( #[$field_attr] )* + pub $field_name: $field_type, + )* + } + + #[cfg(feature = "epee")] + epee_object! { + $struct_name, + $( + $field_name: $field_type $(= $field_default)?, + )* + } + }; +} + +//---------------------------------------------------------------------------------------------------- Type Definitions +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1163..=1212 + )] + /// + /// Used in: + /// - [`crate::json::GetLastBlockHeaderResponse`] + /// - [`crate::json::GetBlockHeaderByHashResponse`] + /// - [`crate::json::GetBlockHeaderByHeightResponse`] + /// - [`crate::json::GetBlockHeadersRangeResponse`] + /// - [`crate::json::GetBlockResponse`] + BlockHeader { + block_size: u64, + block_weight: u64, + cumulative_difficulty_top64: u64, + cumulative_difficulty: u64, + depth: u64, + difficulty_top64: u64, + difficulty: u64, + hash: String, + height: u64, + long_term_weight: u64, + major_version: u8, + miner_tx_hash: String, + minor_version: u8, + nonce: u32, + num_txes: u64, + orphan_status: bool, + pow_hash: String, + prev_hash: String, + reward: u64, + timestamp: u64, + wide_cumulative_difficulty: String, + wide_difficulty: String, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "cryptonote_protocol/cryptonote_protocol_defs.h", + 47..=116 + )] + /// Used in [`crate::json::GetConnectionsResponse`]. + ConnectionInfo { + address: String, + address_type: cuprate_types::AddressType, + avg_download: u64, + avg_upload: u64, + connection_id: String, + current_download: u64, + current_upload: u64, + height: u64, + host: String, + incoming: bool, + ip: String, + live_time: u64, + localhost: bool, + local_ip: bool, + peer_id: String, + port: String, + pruning_seed: u32, + recv_count: u64, + recv_idle_time: u64, + rpc_credits_per_hash: u32, + rpc_port: u16, + send_count: u64, + send_idle_time: u64, + // Exists in the original definition, but isn't + // used or (de)serialized for RPC purposes. + // ssl: bool, + state: cuprate_types::ConnectionState, + support_flags: u32, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2034..=2047 + )] + /// Used in [`crate::json::SetBansRequest`]. + SetBan { + #[cfg_attr(feature = "serde", serde(default = "crate::defaults::default_string"))] + host: String, + #[cfg_attr(feature = "serde", serde(default = "default_zero"))] + ip: u32, + ban: bool, + seconds: u32, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1999..=2010 + )] + /// Used in [`crate::json::GetBansResponse`]. + GetBan { + host: String, + ip: u32, + seconds: u32, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2139..=2156 + )] + #[derive(Copy)] + /// Used in [`crate::json::GetOutputHistogramResponse`]. + HistogramEntry { + amount: u64, + total_instances: u64, + unlocked_instances: u64, + recent_instances: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2180..=2191 + )] + #[derive(Copy)] + /// Used in [`crate::json::GetVersionResponse`]. + HardforkEntry { + height: u64, + hf_version: u8, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2289..=2310 + )] + /// Used in [`crate::json::GetAlternateChainsResponse`]. + ChainInfo { + block_hash: String, + block_hashes: Vec, + difficulty: u64, + difficulty_top64: u64, + height: u64, + length: u64, + main_chain_parent_block: String, + wide_difficulty: String, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2393..=2400 + )] + /// Used in [`crate::json::SyncInfoResponse`]. + SyncInfoPeer { + info: ConnectionInfo, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2402..=2421 + )] + /// Used in [`crate::json::SyncInfoResponse`]. + Span { + connection_id: String, + nblocks: u64, + rate: u32, + remote_address: String, + size: u64, + speed: u32, + start_block_height: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1637..=1642 + )] + #[derive(Copy)] + /// Used in [`crate::json::GetTransactionPoolBacklogResponse`]. + TxBacklogEntry { + weight: u64, + fee: u64, + time_in_pool: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/rpc_handler.h", + 45..=50 + )] + /// Used in [`crate::json::GetOutputDistributionResponse`]. + OutputDistributionData { + distribution: Vec, + start_height: u64, + base: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1016..=1027 + )] + /// Used in [`crate::json::GetMinerDataResponse`]. + /// + /// Note that this is different than [`crate::misc::TxBacklogEntry`]. + GetMinerDataTxBacklogEntry { + id: String, + weight: u64, + fee: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1070..=1079 + )] + /// Used in [`crate::json::AddAuxPowRequest`]. + AuxPow { + id: String, + hash: String, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 192..=199 + )] + /// Used in [`crate::bin::GetBlocksResponse`]. + TxOutputIndices { + indices: Vec, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 201..=208 + )] + /// Used in [`crate::bin::GetBlocksResponse`]. + BlockOutputIndices { + indices: Vec, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 210..=221 + )] + /// Used in [`crate::bin::GetBlocksResponse`]. + PoolTxInfo { + tx_hash: [u8; 32], + tx_blob: String, + double_spend_seen: bool, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 512..=521 + )] + #[derive(Copy)] + /// + /// Used in: + /// - [`crate::bin::GetOutsRequest`] + /// - [`crate::other::GetOutsRequest`] + GetOutputsOut { + amount: u64, + index: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 538..=553 + )] + #[derive(Copy)] + /// Used in [`crate::bin::GetOutsRequest`]. + OutKeyBin { + key: [u8; 32], + mask: [u8; 32], + unlocked: bool, + height: u64, + txid: [u8; 32], + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1335..=1367 + )] + /// Used in [`crate::other::GetPeerListResponse`]. + Peer { + id: u64, + host: String, + ip: u32, + port: u16, + #[cfg_attr(feature = "serde", serde(default = "default_zero"))] + rpc_port: u16 = default_zero::(), + #[cfg_attr(feature = "serde", serde(default = "default_zero"))] + rpc_credits_per_hash: u32 = default_zero::(), + last_seen: u64, + #[cfg_attr(feature = "serde", serde(default = "default_zero"))] + pruning_seed: u32 = default_zero::(), + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1398..=1417 + )] + /// + /// Used in: + /// - [`crate::other::GetPeerListResponse`] + /// - [`crate::other::GetPublicNodesResponse`] + PublicNode { + host: String, + last_seen: u64, + rpc_port: u16, + rpc_credits_per_hash: u32, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1519..=1556 + )] + /// Used in [`crate::other::GetTransactionPoolResponse`]. + TxInfo { + blob_size: u64, + do_not_relay: bool, + double_spend_seen: bool, + fee: u64, + id_hash: String, + kept_by_block: bool, + last_failed_height: u64, + last_failed_id_hash: String, + last_relayed_time: u64, + max_used_block_height: u64, + max_used_block_id_hash: String, + receive_time: u64, + relayed: bool, + tx_blob: String, + tx_json: String, // TODO: this should be another struct + #[cfg_attr(feature = "serde", serde(default = "default_zero"))] + weight: u64 = default_zero::(), + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1558..=1567 + )] + /// Used in [`crate::other::GetTransactionPoolResponse`]. + SpentKeyImageInfo { + id_hash: String, + txs_hashes: Vec, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1666..=1675 + )] + #[derive(Copy)] + /// Used in [`crate::other::GetTransactionPoolStatsResponse`]. + TxpoolHisto { + txs: u32, + bytes: u64, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 1677..=1710 + )] + /// Used in [`crate::other::GetTransactionPoolStatsResponse`]. + TxpoolStats { + bytes_max: u32, + bytes_med: u32, + bytes_min: u32, + bytes_total: u64, + fee_total: u64, + histo_98pc: u64, + histo: Vec, + num_10m: u32, + num_double_spends: u32, + num_failing: u32, + num_not_relayed: u32, + oldest: u64, + txs_total: u32, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 582..=597 + )] + /// Used in [`crate::other::GetOutsResponse`]. + OutKey { + key: String, + mask: String, + unlocked: bool, + height: u64, + txid: String, + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test {} diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs new file mode 100644 index 0000000..4976756 --- /dev/null +++ b/rpc/types/src/misc/mod.rs @@ -0,0 +1,37 @@ +//! Miscellaneous types. +//! +//! These are data types that appear in request/response types. +//! +//! For example, [`crate::json::GetConnectionsResponse`] contains +//! the [`crate::misc::ConnectionInfo`] struct defined here. + +//---------------------------------------------------------------------------------------------------- Lints +#![allow( + missing_docs, // Docs are at: + clippy::struct_excessive_bools, // hey man, tell that to the people who wrote `monerod` +)] + +//---------------------------------------------------------------------------------------------------- Mod +mod binary_string; +mod distribution; +mod key_image_spent_status; +#[expect(clippy::module_inception)] +mod misc; +mod pool_info; +mod pool_info_extent; +mod status; +mod tx_entry; + +pub use binary_string::BinaryString; +pub use distribution::{Distribution, DistributionCompressedBinary, DistributionUncompressed}; +pub use key_image_spent_status::KeyImageSpentStatus; +pub use misc::{ + AuxPow, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, GetBan, + GetMinerDataTxBacklogEntry, GetOutputsOut, HardforkEntry, HistogramEntry, OutKey, OutKeyBin, + OutputDistributionData, Peer, PoolTxInfo, PublicNode, SetBan, Span, SpentKeyImageInfo, + SyncInfoPeer, TxBacklogEntry, TxInfo, TxOutputIndices, TxpoolHisto, TxpoolStats, +}; +pub use pool_info::PoolInfo; +pub use pool_info_extent::PoolInfoExtent; +pub use status::Status; +pub use tx_entry::TxEntry; diff --git a/rpc/types/src/misc/pool_info.rs b/rpc/types/src/misc/pool_info.rs new file mode 100644 index 0000000..e9ba875 --- /dev/null +++ b/rpc/types/src/misc/pool_info.rs @@ -0,0 +1,171 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use crate::misc::PoolInfoExtent; +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + epee_object, error, + macros::bytes::{Buf, BufMut}, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, +}; + +use cuprate_fixed_bytes::ByteArrayVec; + +use crate::misc::PoolTxInfo; + +//---------------------------------------------------------------------------------------------------- PoolInfo +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 223..=228 +)] +/// Used in [`crate::bin::GetBlocksResponse`]. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum PoolInfo { + #[default] + None, + Incremental(PoolInfoIncremental), + Full(PoolInfoFull), +} + +//---------------------------------------------------------------------------------------------------- Internal data +/// Data within [`PoolInfo::Incremental`]. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PoolInfoIncremental { + pub added_pool_txs: Vec, + pub remaining_added_pool_txids: ByteArrayVec<32>, + pub removed_pool_txids: ByteArrayVec<32>, +} + +#[cfg(feature = "epee")] +epee_object! { + PoolInfoIncremental, + added_pool_txs: Vec, + remaining_added_pool_txids: ByteArrayVec<32>, + removed_pool_txids: ByteArrayVec<32>, +} + +/// Data within [`PoolInfo::Full`]. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PoolInfoFull { + pub added_pool_txs: Vec, + pub remaining_added_pool_txids: ByteArrayVec<32>, +} + +#[cfg(feature = "epee")] +epee_object! { + PoolInfoFull, + added_pool_txs: Vec, + remaining_added_pool_txids: ByteArrayVec<32>, +} + +//---------------------------------------------------------------------------------------------------- PoolInfo epee impl +#[cfg(feature = "epee")] +/// [`EpeeObjectBuilder`] for [`GetBlocksResponse`]. +/// +/// Not for public usage. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct __PoolInfoEpeeBuilder { + /// This is a distinct field in `monerod`, + /// which as represented in this library with [`PoolInfo`]'s `u8` tag. + pub pool_info_extent: Option, + + pub added_pool_txs: Option>, + pub remaining_added_pool_txids: Option>, + pub removed_pool_txids: Option>, +} + +// Custom epee implementation. +// +// HACK/INVARIANT: +// If any data within [`PoolInfo`] changes, the below code should be changed as well. +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for __PoolInfoEpeeBuilder { + fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { + macro_rules! read_epee_field { + ($($field:ident),*) => { + match name { + $( + stringify!($field) => { self.$field = Some(read_epee_value(r)?); }, + )* + _ => return Ok(false), + } + }; + } + + read_epee_field! { + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids, + removed_pool_txids + } + + Ok(true) + } + + fn finish(self) -> error::Result { + // INVARIANT: + // `monerod` omits serializing the field itself when a container is empty, + // `unwrap_or_default()` is used over `error()` in these cases. + // Some of the uses are when values have default fallbacks: `pool_info_extent`. + + let pool_info_extent = self.pool_info_extent.unwrap_or_default(); + let this = match pool_info_extent { + PoolInfoExtent::None => PoolInfo::None, + PoolInfoExtent::Incremental => PoolInfo::Incremental(PoolInfoIncremental { + added_pool_txs: self.added_pool_txs.unwrap_or_default(), + remaining_added_pool_txids: self.remaining_added_pool_txids.unwrap_or_default(), + removed_pool_txids: self.removed_pool_txids.unwrap_or_default(), + }), + PoolInfoExtent::Full => PoolInfo::Full(PoolInfoFull { + added_pool_txs: self.added_pool_txs.unwrap_or_default(), + remaining_added_pool_txids: self.remaining_added_pool_txids.unwrap_or_default(), + }), + }; + + Ok(this) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for PoolInfo { + type Builder = __PoolInfoEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + // Inner struct fields. + let inner_fields = match self { + Self::None => 0, + Self::Incremental(s) => s.number_of_fields(), + Self::Full(s) => s.number_of_fields(), + }; + + // [`PoolInfoExtent`] + inner struct fields + 1 + inner_fields + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + const FIELD: &str = "pool_info_extent"; + + match self { + Self::None => { + write_field(PoolInfoExtent::None.to_u8(), FIELD, w)?; + } + Self::Incremental(s) => { + s.write_fields(w)?; + write_field(PoolInfoExtent::Incremental.to_u8(), FIELD, w)?; + } + Self::Full(s) => { + s.write_fields(w)?; + write_field(PoolInfoExtent::Full.to_u8(), FIELD, w)?; + } + } + + Ok(()) + } +} diff --git a/rpc/types/src/misc/pool_info_extent.rs b/rpc/types/src/misc/pool_info_extent.rs new file mode 100644 index 0000000..6372cd6 --- /dev/null +++ b/rpc/types/src/misc/pool_info_extent.rs @@ -0,0 +1,86 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + error, + macros::bytes::{Buf, BufMut}, + EpeeValue, Marker, +}; + +//---------------------------------------------------------------------------------------------------- PoolInfoExtent +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 223..=228 +)] +/// Used in [`crate::bin::GetBlocksResponse`]. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum PoolInfoExtent { + #[default] + None = 0, + Incremental = 1, + Full = 2, +} + +impl PoolInfoExtent { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_rpc_types::misc::PoolInfoExtent as P; + /// + /// assert_eq!(P::None.to_u8(), 0); + /// assert_eq!(P::Incremental.to_u8(), 1); + /// assert_eq!(P::Full.to_u8(), 2); + /// ``` + pub const fn to_u8(self) -> u8 { + match self { + Self::None => 0, + Self::Incremental => 1, + Self::Full => 2, + } + } + + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 2`. + /// + /// ```rust + /// use cuprate_rpc_types::misc::PoolInfoExtent as P; + /// + /// assert_eq!(P::from_u8(0), Some(P::None)); + /// assert_eq!(P::from_u8(1), Some(P::Incremental)); + /// assert_eq!(P::from_u8(2), Some(P::Full)); + /// assert_eq!(P::from_u8(3), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::None, + 1 => Self::Incremental, + 2 => Self::Full, + _ => return None, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for PoolInfoExtent { + const MARKER: Marker = ::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 2")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) + } +} diff --git a/rpc/types/src/status.rs b/rpc/types/src/misc/status.rs similarity index 76% rename from rpc/types/src/status.rs rename to rpc/types/src/misc/status.rs index e8ac6ce..79725cf 100644 --- a/rpc/types/src/status.rs +++ b/rpc/types/src/misc/status.rs @@ -3,8 +3,10 @@ //---------------------------------------------------------------------------------------------------- Import use std::fmt::Display; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "epee")] use cuprate_epee_encoding::{ macros::bytes::{Buf, BufMut}, EpeeValue, Marker, @@ -12,88 +14,84 @@ use cuprate_epee_encoding::{ use crate::constants::{ CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK, - CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN, + CORE_RPC_STATUS_PAYMENT_REQUIRED, }; //---------------------------------------------------------------------------------------------------- Status +// TODO: this type needs to expand more. +// There are a lot of RPC calls that will return a random +// string inside, which isn't compatible with [`Status`]. + /// RPC response status. /// /// This type represents `monerod`'s frequently appearing string field, `status`. /// -/// This field appears within RPC [JSON response](crate::json) types. -/// /// Reference: . /// /// ## Serialization and string formatting /// ```rust /// use cuprate_rpc_types::{ -/// Status, +/// misc::Status, /// CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK, -/// CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN +/// CORE_RPC_STATUS_PAYMENT_REQUIRED /// }; /// use serde_json::to_string; /// -/// let unknown = Status::Unknown; +/// let other = Status::Other("OTHER".into()); /// /// assert_eq!(to_string(&Status::Ok).unwrap(), r#""OK""#); /// assert_eq!(to_string(&Status::Busy).unwrap(), r#""BUSY""#); /// assert_eq!(to_string(&Status::NotMining).unwrap(), r#""NOT MINING""#); /// assert_eq!(to_string(&Status::PaymentRequired).unwrap(), r#""PAYMENT REQUIRED""#); -/// assert_eq!(to_string(&unknown).unwrap(), r#""UNKNOWN""#); +/// assert_eq!(to_string(&other).unwrap(), r#""OTHER""#); /// /// assert_eq!(Status::Ok.as_ref(), CORE_RPC_STATUS_OK); /// assert_eq!(Status::Busy.as_ref(), CORE_RPC_STATUS_BUSY); /// assert_eq!(Status::NotMining.as_ref(), CORE_RPC_STATUS_NOT_MINING); /// assert_eq!(Status::PaymentRequired.as_ref(), CORE_RPC_STATUS_PAYMENT_REQUIRED); -/// assert_eq!(unknown.as_ref(), CORE_RPC_STATUS_UNKNOWN); +/// assert_eq!(other.as_ref(), "OTHER"); /// /// assert_eq!(format!("{}", Status::Ok), CORE_RPC_STATUS_OK); /// assert_eq!(format!("{}", Status::Busy), CORE_RPC_STATUS_BUSY); /// assert_eq!(format!("{}", Status::NotMining), CORE_RPC_STATUS_NOT_MINING); /// assert_eq!(format!("{}", Status::PaymentRequired), CORE_RPC_STATUS_PAYMENT_REQUIRED); -/// assert_eq!(format!("{}", unknown), CORE_RPC_STATUS_UNKNOWN); +/// assert_eq!(format!("{}", other), "OTHER"); /// /// assert_eq!(format!("{:?}", Status::Ok), "Ok"); /// assert_eq!(format!("{:?}", Status::Busy), "Busy"); /// assert_eq!(format!("{:?}", Status::NotMining), "NotMining"); /// assert_eq!(format!("{:?}", Status::PaymentRequired), "PaymentRequired"); -/// assert_eq!(format!("{:?}", unknown), "Unknown"); +/// assert_eq!(format!("{:?}", other), r#"Other("OTHER")"#); /// ``` -#[derive( - Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Status { // FIXME: // `#[serde(rename = "")]` only takes raw string literals? // We have to re-type the constants here... /// Successful RPC response, everything is OK; [`CORE_RPC_STATUS_OK`]. - #[serde(rename = "OK")] + #[cfg_attr(feature = "serde", serde(rename = "OK"))] #[default] Ok, /// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`]. - #[serde(rename = "BUSY")] + #[cfg_attr(feature = "serde", serde(rename = "BUSY"))] Busy, /// The daemon is not mining; [`CORE_RPC_STATUS_NOT_MINING`]. - #[serde(rename = "NOT MINING")] + #[cfg_attr(feature = "serde", serde(rename = "NOT MINING"))] NotMining, /// Payment is required for RPC; [`CORE_RPC_STATUS_PAYMENT_REQUIRED`]. - #[serde(rename = "PAYMENT REQUIRED")] + #[cfg_attr(feature = "serde", serde(rename = "PAYMENT REQUIRED"))] PaymentRequired, - /// Some unknown other string; [`CORE_RPC_STATUS_UNKNOWN`]. + /// Some unknown other string. /// - /// This exists to act as a catch-all if `monerod` adds - /// a string and a Cuprate node hasn't updated yet. - /// - /// The reason this isn't `Unknown(String)` is because that - /// disallows [`Status`] to be [`Copy`], and thus other types - /// that contain it. - #[serde(other)] - #[serde(rename = "UNKNOWN")] - Unknown, + /// This exists to act as a catch-all for all of + /// `monerod`'s other strings it puts in the `status` field. + #[cfg_attr(feature = "serde", serde(rename = "OTHER"), serde(untagged))] + Other(String), } impl From for Status { @@ -103,7 +101,7 @@ impl From for Status { CORE_RPC_STATUS_BUSY => Self::Busy, CORE_RPC_STATUS_NOT_MINING => Self::NotMining, CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired, - _ => Self::Unknown, + _ => Self::Other(s), } } } @@ -115,7 +113,7 @@ impl AsRef for Status { Self::Busy => CORE_RPC_STATUS_BUSY, Self::NotMining => CORE_RPC_STATUS_NOT_MINING, Self::PaymentRequired => CORE_RPC_STATUS_PAYMENT_REQUIRED, - Self::Unknown => CORE_RPC_STATUS_UNKNOWN, + Self::Other(s) => s, } } } @@ -132,6 +130,7 @@ impl Display for Status { // // See below for more impl info: // . +#[cfg(feature = "epee")] impl EpeeValue for Status { const MARKER: Marker = ::MARKER; @@ -146,7 +145,7 @@ impl EpeeValue for Status { fn epee_default_value() -> Option { // - Some(Self::Unknown) + Some(Self::Other(String::new())) } fn write(self, w: &mut B) -> cuprate_epee_encoding::Result<()> { @@ -161,17 +160,18 @@ mod test { // Test epee (de)serialization works. #[test] + #[cfg(feature = "epee")] fn epee() { for status in [ Status::Ok, Status::Busy, Status::NotMining, Status::PaymentRequired, - Status::Unknown, + Status::Other(String::new()), ] { let mut buf = vec![]; - ::write(status, &mut buf).unwrap(); + ::write(status.clone(), &mut buf).unwrap(); let status2 = ::read(&mut buf.as_slice(), &::MARKER) .unwrap(); diff --git a/rpc/types/src/misc/tx_entry.rs b/rpc/types/src/misc/tx_entry.rs new file mode 100644 index 0000000..59dd460 --- /dev/null +++ b/rpc/types/src/misc/tx_entry.rs @@ -0,0 +1,148 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + error, + macros::bytes::{Buf, BufMut}, + EpeeObject, EpeeObjectBuilder, +}; + +#[cfg(feature = "serde")] +use crate::serde::{serde_false, serde_true}; + +//---------------------------------------------------------------------------------------------------- TxEntry +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 389..=428 +)] +/// Used in [`crate::other::GetTransactionsResponse`]. +/// +/// # Epee +/// This type is only used in a JSON endpoint, so the +/// epee implementation on this type only panics. +/// +/// It is only implemented to satisfy the RPC type generator +/// macro, which requires all objects to be serde + epee. +/// +/// # Example +/// ```rust +/// use cuprate_rpc_types::misc::*; +/// use serde_json::{json, from_value}; +/// +/// let json = json!({ +/// "as_hex": String::default(), +/// "as_json": String::default(), +/// "block_height": u64::default(), +/// "block_timestamp": u64::default(), +/// "confirmations": u64::default(), +/// "double_spend_seen": bool::default(), +/// "output_indices": Vec::::default(), +/// "prunable_as_hex": String::default(), +/// "prunable_hash": String::default(), +/// "pruned_as_hex": String::default(), +/// "tx_hash": String::default(), +/// "in_pool": bool::default(), +/// }); +/// let tx_entry = from_value::(json).unwrap(); +/// assert!(matches!(tx_entry, TxEntry::InPool { .. })); +/// +/// let json = json!({ +/// "as_hex": String::default(), +/// "as_json": String::default(), +/// "double_spend_seen": bool::default(), +/// "prunable_as_hex": String::default(), +/// "prunable_hash": String::default(), +/// "pruned_as_hex": String::default(), +/// "received_timestamp": u64::default(), +/// "relayed": bool::default(), +/// "tx_hash": String::default(), +/// "in_pool": bool::default(), +/// }); +/// let tx_entry = from_value::(json).unwrap(); +/// assert!(matches!(tx_entry, TxEntry::NotInPool { .. })); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum TxEntry { + /// This entry exists in the transaction pool. + InPool { + /// This field is [flattened](https://serde.rs/field-attrs.html#flatten). + #[cfg_attr(feature = "serde", serde(flatten))] + prefix: TxEntryPrefix, + block_height: u64, + block_timestamp: u64, + confirmations: u64, + output_indices: Vec, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] + /// Will always be serialized as `true`. + in_pool: bool, + }, + /// This entry _does not_ exist in the transaction pool. + NotInPool { + /// This field is [flattened](https://serde.rs/field-attrs.html#flatten). + #[cfg_attr(feature = "serde", serde(flatten))] + prefix: TxEntryPrefix, + received_timestamp: u64, + relayed: bool, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] + /// Will always be serialized as `false`. + in_pool: bool, + }, +} + +impl Default for TxEntry { + fn default() -> Self { + Self::NotInPool { + prefix: Default::default(), + received_timestamp: u64::default(), + relayed: bool::default(), + in_pool: false, + } + } +} + +/// Common fields in all [`TxEntry`] variants. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TxEntryPrefix { + as_hex: String, + /// `cuprate_rpc_types::json::tx::Transaction` should be used + /// to create this JSON string in a type-safe manner. + as_json: String, + double_spend_seen: bool, + tx_hash: String, + prunable_as_hex: String, + prunable_hash: String, + pruned_as_hex: String, +} + +//---------------------------------------------------------------------------------------------------- Epee +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for () { + fn add_field(&mut self, _: &str, _: &mut B) -> error::Result { + unreachable!() + } + + fn finish(self) -> error::Result { + unreachable!() + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for TxEntry { + type Builder = (); + + fn number_of_fields(&self) -> u64 { + unreachable!() + } + + fn write_fields(self, _: &mut B) -> error::Result<()> { + unreachable!() + } +} diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 22547ed..d5cbe82 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -1,19 +1,1130 @@ //! JSON types from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. //! -//! . +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import -use crate::{base::ResponseBase, macros::define_request_and_response}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::{ + base::{AccessResponseBase, ResponseBase}, + macros::define_request_and_response, + misc::{ + GetOutputsOut, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, + TxpoolStats, + }, + RpcCallValue, +}; + +#[cfg(any(feature = "serde", feature = "epee"))] +use crate::defaults::{default_false, default_string, default_true, default_vec, default_zero}; + +//---------------------------------------------------------------------------------------------------- Macro +/// Adds a (de)serialization doc-test to a type in `other.rs`. +/// +/// It expects a const string from `cuprate_test_utils::rpc::data` +/// and the expected value it should (de)serialize into/from. +/// +/// It tests that the provided const JSON string can properly +/// (de)serialize into the expected value. +/// +/// See below for example usage. This macro is only used in this file. +macro_rules! serde_doc_test { + // This branch _only_ tests that the type can be deserialize + // from the string, not that any value is correct. + // + // Practically, this is used for structs that have + // many values that are complicated to test, e.g. `GET_TRANSACTIONS_RESPONSE`. + // + // HACK: + // The type itself doesn't need to be specified because it happens + // to just be the `CamelCase` version of the provided const. + ( + // `const` string from `cuprate_test_utils::rpc::data`. + $cuprate_test_utils_rpc_const:ident + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "let string = from_str::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "```\n", + ) + } + }; + + // This branch tests that the type can be deserialize + // from the string AND that values are correct. + ( + // `const` string from `cuprate_test_utils::rpc::data` + // v + $cuprate_test_utils_rpc_const:ident => $expected:expr_2021 + // ^ + // Expected value as an expression + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "// The expected data.\n", + "let expected = ", + stringify!($expected), + ";\n", + "\n", + "let string = from_str::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "\n", + "assert_eq!(string, expected);\n", + "```\n", + ) + } + }; +} + +//---------------------------------------------------------------------------------------------------- Definitions +define_request_and_response! { + get_height, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 138..=160, + GetHeight (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_HEIGHT_RESPONSE => GetHeightResponse { + base: ResponseBase::OK, + hash: "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f".into(), + height: 3195160, + } + )] + ResponseBase { + hash: String, + height: u64, + } +} + +define_request_and_response! { + get_transactions, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 370..=451, + GetTransactions, + + #[doc = serde_doc_test!( + GET_TRANSACTIONS_REQUEST => GetTransactionsRequest { + txs_hashes: vec!["d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408".into()], + decode_as_json: false, + prune: false, + split: false, + } + )] + Request { + txs_hashes: Vec, + // FIXME: this is documented as optional but it isn't serialized as an optional + // but it is set _somewhere_ to false in `monerod` + // + decode_as_json: bool = default_false(), "default_false", + prune: bool = default_false(), "default_false", + split: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!(GET_TRANSACTIONS_RESPONSE)] + AccessResponseBase { + txs_as_hex: Vec = default_vec::(), "default_vec", + /// `cuprate_rpc_types::json::tx::Transaction` should be used + /// to create this JSON string in a type-safe manner. + txs_as_json: Vec = default_vec::(), "default_vec", + missed_tx: Vec = default_vec::(), "default_vec", + txs: Vec = default_vec::(), "default_vec", + } +} + +define_request_and_response! { + get_alt_blocks_hashes, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 288..=308, + GetAltBlocksHashes (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_ALT_BLOCKS_HASHES_RESPONSE => GetAltBlocksHashesResponse { + base: AccessResponseBase::OK, + blks_hashes: vec!["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109".into()], + } + )] + AccessResponseBase { + blks_hashes: Vec, + } +} + +define_request_and_response! { + is_key_image_spent, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 454..=484, + + IsKeyImageSpent, + + #[doc = serde_doc_test!( + IS_KEY_IMAGE_SPENT_REQUEST => IsKeyImageSpentRequest { + key_images: vec![ + "8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3".into(), + "7319134bfc50668251f5b899c66b005805ee255c136f0e1cecbb0f3a912e09d4".into() + ] + } + )] + Request { + key_images: Vec, + }, + + #[doc = serde_doc_test!( + IS_KEY_IMAGE_SPENT_RESPONSE => IsKeyImageSpentResponse { + base: AccessResponseBase::OK, + spent_status: vec![1, 1], + } + )] + AccessResponseBase { + /// FIXME: These are [`KeyImageSpentStatus`](crate::misc::KeyImageSpentStatus) in [`u8`] form. + spent_status: Vec, + } +} + +define_request_and_response! { + send_raw_transaction, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 370..=451, + + SendRawTransaction, + + #[doc = serde_doc_test!( + SEND_RAW_TRANSACTION_REQUEST => SendRawTransactionRequest { + tx_as_hex: "dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308".into(), + do_not_relay: false, + do_sanity_checks: true, + } + )] + Request { + tx_as_hex: String, + do_not_relay: bool = default_false(), "default_false", + do_sanity_checks: bool = default_true(), "default_true", + }, + + #[doc = serde_doc_test!( + SEND_RAW_TRANSACTION_RESPONSE => SendRawTransactionResponse { + base: AccessResponseBase { + response_base: ResponseBase { + status: Status::Other("Failed".into()), + untrusted: false, + }, + credits: 0, + top_hash: "".into(), + }, + double_spend: false, + fee_too_low: false, + invalid_input: false, + invalid_output: false, + low_mixin: false, + not_relayed: false, + overspend: false, + reason: "".into(), + sanity_check_failed: false, + too_big: false, + too_few_outputs: false, + tx_extra_too_big: false, + nonzero_unlock_time: false, + } + )] + AccessResponseBase { + double_spend: bool, + fee_too_low: bool, + invalid_input: bool, + invalid_output: bool, + low_mixin: bool, + nonzero_unlock_time: bool = default_false(), "default_false", + not_relayed: bool, + overspend: bool, + reason: String, + sanity_check_failed: bool, + too_big: bool, + too_few_outputs: bool, + tx_extra_too_big: bool, + } +} + +define_request_and_response! { + start_mining, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 665..=691, + + StartMining (restricted), + + #[doc = serde_doc_test!( + START_MINING_REQUEST => StartMiningRequest { + do_background_mining: false, + ignore_battery: true, + miner_address: "47xu3gQpF569au9C2ajo5SSMrWji6xnoE5vhr94EzFRaKAGw6hEGFXYAwVADKuRpzsjiU1PtmaVgcjUJF89ghGPhUXkndHc".into(), + threads_count: 1 + } + )] + Request { + miner_address: String, + threads_count: u64, + do_background_mining: bool, + ignore_battery: bool, + }, + + #[doc = serde_doc_test!( + START_MINING_RESPONSE => StartMiningResponse { + base: ResponseBase::OK, + } + )] + ResponseBase {} +} + +define_request_and_response! { + stop_mining, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 825..=843, + StopMining (restricted, empty), + Request {}, + + #[doc = serde_doc_test!( + STOP_MINING_RESPONSE => StopMiningResponse { + base: ResponseBase::OK, + } + )] + ResponseBase {} +} + +define_request_and_response! { + mining_status, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 846..=895, + MiningStatus (restricted), + Request {}, + + #[doc = serde_doc_test!( + MINING_STATUS_RESPONSE => MiningStatusResponse { + base: ResponseBase::OK, + active: false, + address: "".into(), + bg_idle_threshold: 0, + bg_ignore_battery: false, + bg_min_idle_seconds: 0, + bg_target: 0, + block_reward: 0, + block_target: 120, + difficulty: 292022797663, + difficulty_top64: 0, + is_background_mining_enabled: false, + pow_algorithm: "RandomX".into(), + speed: 0, + threads_count: 0, + wide_difficulty: "0x43fdea455f".into(), + } + )] + ResponseBase { + active: bool, + address: String, + bg_idle_threshold: u8, + bg_ignore_battery: bool, + bg_min_idle_seconds: u8, + bg_target: u8, + block_reward: u64, + block_target: u32, + difficulty: u64, + difficulty_top64: u64, + is_background_mining_enabled: bool, + pow_algorithm: String, + speed: u64, + threads_count: u32, + wide_difficulty: String, + } +} -//---------------------------------------------------------------------------------------------------- TODO define_request_and_response! { save_bc, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 898..=916, - SaveBc, + SaveBc (restricted), + Request {}, + + #[doc = serde_doc_test!( + SAVE_BC_RESPONSE => SaveBcResponse { + base: ResponseBase::OK, + } + )] ResponseBase {} } +define_request_and_response! { + get_peer_list, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1369..=1417, + + GetPeerList (restricted), + + #[doc = serde_doc_test!( + GET_PEER_LIST_REQUEST => GetPeerListRequest { + public_only: true, + include_blocked: false, + } + )] + Request { + public_only: bool = default_true(), "default_true", + include_blocked: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_PEER_LIST_RESPONSE => GetPeerListResponse { + base: ResponseBase::OK, + gray_list: vec![ + Peer { + host: "161.97.193.0".into(), + id: 18269586253849566614, + ip: 12673441, + last_seen: 0, + port: 18080, + rpc_port: 0, + rpc_credits_per_hash: 0, + pruning_seed: 0, + }, + Peer { + host: "193.142.4.2".into(), + id: 10865563782170056467, + ip: 33853121, + last_seen: 0, + port: 18085, + pruning_seed: 387, + rpc_port: 19085, + rpc_credits_per_hash: 0, + } + ], + white_list: vec![ + Peer { + host: "78.27.98.0".into(), + id: 11368279936682035606, + ip: 6429518, + last_seen: 1721246387, + port: 18080, + pruning_seed: 384, + rpc_port: 0, + rpc_credits_per_hash: 0, + }, + Peer { + host: "67.4.163.2".into(), + id: 16545113262826842499, + ip: 44237891, + last_seen: 1721246387, + port: 18080, + rpc_port: 0, + rpc_credits_per_hash: 0, + pruning_seed: 0, + }, + Peer { + host: "70.52.75.3".into(), + id: 3863337548778177169, + ip: 55260230, + last_seen: 1721246387, + port: 18080, + rpc_port: 18081, + rpc_credits_per_hash: 0, + pruning_seed: 0, + } + ] + } + )] + ResponseBase { + white_list: Vec, + gray_list: Vec, + } +} + +define_request_and_response! { + set_log_hash_rate, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1450..=1470, + + SetLogHashRate (restricted), + + #[derive(Copy)] + #[doc = serde_doc_test!( + SET_LOG_HASH_RATE_REQUEST => SetLogHashRateRequest { + visible: true, + } + )] + Request { + visible: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + SET_LOG_HASH_RATE_RESPONSE => SetLogHashRateResponse { + base: ResponseBase::OK, + } + )] + ResponseBase {} +} + +define_request_and_response! { + set_log_level, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1450..=1470, + + SetLogLevel (restricted), + + #[derive(Copy)] + #[doc = serde_doc_test!( + SET_LOG_LEVEL_REQUEST => SetLogLevelRequest { + level: 1 + } + )] + Request { + level: u8, + }, + + #[doc = serde_doc_test!( + SET_LOG_LEVEL_RESPONSE => SetLogLevelResponse { + base: ResponseBase::OK, + } + )] + ResponseBase {} +} + +define_request_and_response! { + set_log_categories, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1494..=1517, + + SetLogCategories (restricted), + + #[doc = serde_doc_test!( + SET_LOG_CATEGORIES_REQUEST => SetLogCategoriesRequest { + categories: "*:INFO".into(), + } + )] + Request { + categories: String = default_string(), "default_string", + }, + + #[doc = serde_doc_test!( + SET_LOG_CATEGORIES_RESPONSE => SetLogCategoriesResponse { + base: ResponseBase::OK, + categories: "*:INFO".into(), + } + )] + ResponseBase { + categories: String, + } +} + +define_request_and_response! { + set_bootstrap_daemon, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1785..=1812, + + SetBootstrapDaemon (restricted), + + #[doc = serde_doc_test!( + SET_BOOTSTRAP_DAEMON_REQUEST => SetBootstrapDaemonRequest { + address: "http://getmonero.org:18081".into(), + username: String::new(), + password: String::new(), + proxy: String::new(), + } + )] + Request { + address: String, + username: String = default_string(), "default_string", + password: String = default_string(), "default_string", + proxy: String = default_string(), "default_string", + }, + + #[doc = serde_doc_test!( + SET_BOOTSTRAP_DAEMON_RESPONSE => SetBootstrapDaemonResponse { + status: Status::Ok, + } + )] + Response { + status: Status, + } +} + +define_request_and_response! { + get_transaction_pool, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1569..=1591, + + GetTransactionPool (empty), + Request {}, + + #[doc = serde_doc_test!(GET_TRANSACTION_POOL_RESPONSE)] + AccessResponseBase { + transactions: Vec, + spent_key_images: Vec, + } +} + +define_request_and_response! { + get_transaction_pool_stats, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1712..=1732, + + GetTransactionPoolStats (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_TRANSACTION_POOL_STATS_RESPONSE => GetTransactionPoolStatsResponse { + base: AccessResponseBase::OK, + pool_stats: TxpoolStats { + bytes_max: 11843, + bytes_med: 2219, + bytes_min: 1528, + bytes_total: 144192, + fee_total: 7018100000, + histo: vec![ + TxpoolHisto { bytes: 11219, txs: 4 }, + TxpoolHisto { bytes: 9737, txs: 5 }, + TxpoolHisto { bytes: 8757, txs: 4 }, + TxpoolHisto { bytes: 14763, txs: 4 }, + TxpoolHisto { bytes: 15007, txs: 6 }, + TxpoolHisto { bytes: 15924, txs: 6 }, + TxpoolHisto { bytes: 17869, txs: 8 }, + TxpoolHisto { bytes: 10894, txs: 5 }, + TxpoolHisto { bytes: 38485, txs: 10 }, + TxpoolHisto { bytes: 1537, txs: 1 }, + ], + histo_98pc: 186, + num_10m: 0, + num_double_spends: 0, + num_failing: 0, + num_not_relayed: 0, + oldest: 1721261651, + txs_total: 53 + } + } + )] + AccessResponseBase { + pool_stats: TxpoolStats, + } +} + +define_request_and_response! { + stop_daemon, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1814..=1831, + + StopDaemon (restricted, empty), + Request {}, + + #[doc = serde_doc_test!( + STOP_DAEMON_RESPONSE => StopDaemonResponse { + status: Status::Ok, + } + )] + Response { + status: Status, + } +} + +define_request_and_response! { + get_limit, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1852..=1874, + + GetLimit (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_LIMIT_RESPONSE => GetLimitResponse { + base: ResponseBase::OK, + limit_down: 1280000, + limit_up: 1280000, + } + )] + ResponseBase { + limit_down: u64, + limit_up: u64, + } +} + +define_request_and_response! { + set_limit, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1876..=1903, + + SetLimit (restricted), + + #[doc = serde_doc_test!( + SET_LIMIT_REQUEST => SetLimitRequest { + limit_down: 1024, + limit_up: 0, + } + )] + Request { + // FIXME: These may need to be `Option`. + limit_down: i64 = default_zero::(), "default_zero", + limit_up: i64 = default_zero::(), "default_zero", + }, + + #[doc = serde_doc_test!( + SET_LIMIT_RESPONSE => SetLimitResponse { + base: ResponseBase::OK, + limit_down: 1024, + limit_up: 128, + } + )] + ResponseBase { + limit_down: i64, + limit_up: i64, + } +} + +define_request_and_response! { + out_peers, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1876..=1903, + + OutPeers (restricted), + + #[doc = serde_doc_test!( + OUT_PEERS_REQUEST => OutPeersRequest { + out_peers: 3232235535, + set: true, + } + )] + Request { + set: bool = default_true(), "default_true", + out_peers: u32, + }, + + #[doc = serde_doc_test!( + OUT_PEERS_RESPONSE => OutPeersResponse { + base: ResponseBase::OK, + out_peers: 3232235535, + } + )] + ResponseBase { + out_peers: u32, + } +} + +define_request_and_response! { + in_peers, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1932..=1956, + InPeers (restricted), + Request { + set: bool = default_true(), "default_true", + in_peers: u32, + }, + ResponseBase { + in_peers: u32, + } +} + +define_request_and_response! { + get_net_stats, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 793..=822, + + GetNetStats (restricted, empty), + Request {}, + + #[doc = serde_doc_test!( + GET_NET_STATS_RESPONSE => GetNetStatsResponse { + base: ResponseBase::OK, + start_time: 1721251858, + total_bytes_in: 16283817214, + total_bytes_out: 34225244079, + total_packets_in: 5981922, + total_packets_out: 3627107, + } + )] + ResponseBase { + start_time: u64, + total_packets_in: u64, + total_bytes_in: u64, + total_packets_out: u64, + total_bytes_out: u64, + } +} + +define_request_and_response! { + get_outs, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 567..=609, + + GetOuts, + #[doc = serde_doc_test!( + GET_OUTS_REQUEST => GetOutsRequest { + outputs: vec![ + GetOutputsOut { amount: 1, index: 0 }, + GetOutputsOut { amount: 1, index: 1 }, + ], + get_txid: true + } + )] + Request { + outputs: Vec, + get_txid: bool, + }, + + #[doc = serde_doc_test!( + GET_OUTS_RESPONSE => GetOutsResponse { + base: ResponseBase::OK, + outs: vec![ + OutKey { + height: 51941, + key: "08980d939ec297dd597119f498ad69fed9ca55e3a68f29f2782aae887ef0cf8e".into(), + mask: "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522".into(), + txid: "9d651903b80fb70b9935b72081cd967f543662149aed3839222511acd9100601".into(), + unlocked: true + }, + OutKey { + height: 51945, + key: "454fe46c405be77625fa7e3389a04d3be392346983f27603561ac3a3a74f4a75".into(), + mask: "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522".into(), + txid: "230bff732dc5f225df14fff82aadd1bf11b3fb7ad3a03413c396a617e843f7d0".into(), + unlocked: true + }, + ] + } + )] + ResponseBase { + outs: Vec, + } +} + +define_request_and_response! { + update, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2324..=2359, + + Update (restricted), + + #[doc = serde_doc_test!( + UPDATE_REQUEST => UpdateRequest { + command: "check".into(), + path: "".into(), + } + )] + Request { + command: String, + path: String = default_string(), "default_string", + }, + + #[doc = serde_doc_test!( + UPDATE_RESPONSE => UpdateResponse { + base: ResponseBase::OK, + auto_uri: "".into(), + hash: "".into(), + path: "".into(), + update: false, + user_uri: "".into(), + version: "".into(), + } + )] + ResponseBase { + auto_uri: String, + hash: String, + path: String, + update: bool, + user_uri: String, + version: String, + } +} + +define_request_and_response! { + pop_blocks, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2722..=2745, + + PopBlocks (restricted), + + #[doc = serde_doc_test!( + POP_BLOCKS_REQUEST => PopBlocksRequest { + nblocks: 6 + } + )] + Request { + nblocks: u64, + }, + + #[doc = serde_doc_test!( + POP_BLOCKS_RESPONSE => PopBlocksResponse { + base: ResponseBase::OK, + height: 76482, + } + )] + ResponseBase { + height: u64, + } +} + +define_request_and_response! { + UNDOCUMENTED_ENDPOINT, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1615..=1635, + + GetTransactionPoolHashes (empty), + Request {}, + + #[doc = serde_doc_test!( + GET_TRANSACTION_POOL_HASHES_RESPONSE => GetTransactionPoolHashesResponse { + base: ResponseBase::OK, + tx_hashes: vec![ + "aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03".into(), + "794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11".into(), + "1e9d2ae11f2168a228942077483e70940d34e8658c972bbc3e7f7693b90edf17".into(), + "7375c928f261d00f07197775eb0bfa756e5f23319819152faa0b3c670fe54c1b".into(), + "2e4d5f8c5a45498f37fb8b6ca4ebc1efa0c371c38c901c77e66b08c072287329".into(), + "eee6d596cf855adfb10e1597d2018e3a61897ac467ef1d4a5406b8d20bfbd52f".into(), + "59c574d7ba9bb4558470f74503c7518946a85ea22c60fccfbdec108ce7d8f236".into(), + "0d57bec1e1075a9e1ac45cf3b3ced1ad95ccdf2a50ce360190111282a0178655".into(), + "60d627b2369714a40009c07d6185ebe7fa4af324fdfa8d95a37a936eb878d062".into(), + "661d7e728a901a8cb4cf851447d9cd5752462687ed0b776b605ba706f06bdc7d".into(), + "b80e1f09442b00b3fffe6db5d263be6267c7586620afff8112d5a8775a6fc58e".into(), + "974063906d1ddfa914baf85176b0f689d616d23f3d71ed4798458c8b4f9b9d8f".into(), + "d2575ae152a180be4981a9d2fc009afcd073adaa5c6d8b022c540a62d6c905bb".into(), + "3d78aa80ee50f506683bab9f02855eb10257a08adceda7cbfbdfc26b10f6b1bb".into(), + "8b5bc125bdb73b708500f734501d55088c5ac381a0879e1141634eaa72b6a4da".into(), + "11c06f4d2f00c912ca07313ed2ea5366f3cae914a762bed258731d3d9e3706df".into(), + "b3644dc7c9a3a53465fe80ad3769e516edaaeb7835e16fdd493aac110d472ae1".into(), + "ed2478ad793b923dbf652c8612c40799d764e5468897021234a14a37346bc6ee".into() + ], + } + )] + ResponseBase { + tx_hashes: Vec, + } +} + +define_request_and_response! { + UNDOCUMENTED_ENDPOINT, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1419..=1448, + + GetPublicNodes (restricted), + + #[doc = serde_doc_test!( + GET_PUBLIC_NODES_REQUEST => GetPublicNodesRequest { + gray: false, + white: true, + include_blocked: false, + } + )] + Request { + gray: bool = default_false(), "default_false", + white: bool = default_true(), "default_true", + include_blocked: bool = default_false(), "default_false", + }, + + #[doc = serde_doc_test!( + GET_PUBLIC_NODES_RESPONSE => GetPublicNodesResponse { + base: ResponseBase::OK, + gray: vec![], + white: vec![ + PublicNode { + host: "70.52.75.3".into(), + last_seen: 1721246387, + rpc_credits_per_hash: 0, + rpc_port: 18081, + }, + PublicNode { + host: "zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083".into(), + last_seen: 1720186288, + rpc_credits_per_hash: 0, + rpc_port: 18089, + } + ] + } + )] + ResponseBase { + gray: Vec = default_vec::(), "default_vec", + white: Vec = default_vec::(), "default_vec", + } +} + +//---------------------------------------------------------------------------------------------------- Request +/// Other JSON requests. +/// +/// This enum contains all [`crate::other`] requests. +/// +/// See also: [`OtherResponse`]. +/// +/// # (De)serialization +/// The `serde` implementation will (de)serialize from +/// the inner variant itself, e.g. [`OtherRequest::SetLogLevel`] +/// has the same (de)serialization as [`SetLogLevelRequest`]. +/// +/// ```rust +/// use cuprate_rpc_types::other::*; +/// +/// let request = OtherRequest::SetLogLevel(Default::default()); +/// let json = serde_json::to_string(&request).unwrap(); +/// assert_eq!(json, r#"{"level":0}"#); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum OtherRequest { + GetHeight(GetHeightRequest), + GetTransactions(GetTransactionsRequest), + GetAltBlocksHashes(GetAltBlocksHashesRequest), + IsKeyImageSpent(IsKeyImageSpentRequest), + SendRawTransaction(SendRawTransactionRequest), + StartMining(StartMiningRequest), + StopMining(StopMiningRequest), + MiningStatus(MiningStatusRequest), + SaveBc(SaveBcRequest), + GetPeerList(GetPeerListRequest), + SetLogHashRate(SetLogHashRateRequest), + SetLogLevel(SetLogLevelRequest), + SetLogCategories(SetLogCategoriesRequest), + SetBootstrapDaemon(SetBootstrapDaemonRequest), + GetTransactionPool(GetTransactionPoolRequest), + GetTransactionPoolStats(GetTransactionPoolStatsRequest), + StopDaemon(StopDaemonRequest), + GetLimit(GetLimitRequest), + SetLimit(SetLimitRequest), + OutPeers(OutPeersRequest), + InPeers(InPeersRequest), + GetNetStats(GetNetStatsRequest), + GetOuts(GetOutsRequest), + Update(UpdateRequest), + PopBlocks(PopBlocksRequest), + GetTransactionPoolHashes(GetTransactionPoolHashesRequest), + GetPublicNodes(GetPublicNodesRequest), +} + +impl RpcCallValue for OtherRequest { + fn is_restricted(&self) -> bool { + match self { + Self::GetHeight(x) => x.is_restricted(), + Self::GetTransactions(x) => x.is_restricted(), + Self::GetAltBlocksHashes(x) => x.is_restricted(), + Self::IsKeyImageSpent(x) => x.is_restricted(), + Self::SendRawTransaction(x) => x.is_restricted(), + Self::StartMining(x) => x.is_restricted(), + Self::StopMining(x) => x.is_restricted(), + Self::MiningStatus(x) => x.is_restricted(), + Self::SaveBc(x) => x.is_restricted(), + Self::GetPeerList(x) => x.is_restricted(), + Self::SetLogHashRate(x) => x.is_restricted(), + Self::SetLogLevel(x) => x.is_restricted(), + Self::SetLogCategories(x) => x.is_restricted(), + Self::SetBootstrapDaemon(x) => x.is_restricted(), + Self::GetTransactionPool(x) => x.is_restricted(), + Self::GetTransactionPoolStats(x) => x.is_restricted(), + Self::StopDaemon(x) => x.is_restricted(), + Self::GetLimit(x) => x.is_restricted(), + Self::SetLimit(x) => x.is_restricted(), + Self::OutPeers(x) => x.is_restricted(), + Self::InPeers(x) => x.is_restricted(), + Self::GetNetStats(x) => x.is_restricted(), + Self::GetOuts(x) => x.is_restricted(), + Self::Update(x) => x.is_restricted(), + Self::PopBlocks(x) => x.is_restricted(), + Self::GetTransactionPoolHashes(x) => x.is_restricted(), + Self::GetPublicNodes(x) => x.is_restricted(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::GetHeight(x) => x.is_empty(), + Self::GetTransactions(x) => x.is_empty(), + Self::GetAltBlocksHashes(x) => x.is_empty(), + Self::IsKeyImageSpent(x) => x.is_empty(), + Self::SendRawTransaction(x) => x.is_empty(), + Self::StartMining(x) => x.is_empty(), + Self::StopMining(x) => x.is_empty(), + Self::MiningStatus(x) => x.is_empty(), + Self::SaveBc(x) => x.is_empty(), + Self::GetPeerList(x) => x.is_empty(), + Self::SetLogHashRate(x) => x.is_empty(), + Self::SetLogLevel(x) => x.is_empty(), + Self::SetLogCategories(x) => x.is_empty(), + Self::SetBootstrapDaemon(x) => x.is_empty(), + Self::GetTransactionPool(x) => x.is_empty(), + Self::GetTransactionPoolStats(x) => x.is_empty(), + Self::StopDaemon(x) => x.is_empty(), + Self::GetLimit(x) => x.is_empty(), + Self::SetLimit(x) => x.is_empty(), + Self::OutPeers(x) => x.is_empty(), + Self::InPeers(x) => x.is_empty(), + Self::GetNetStats(x) => x.is_empty(), + Self::GetOuts(x) => x.is_empty(), + Self::Update(x) => x.is_empty(), + Self::PopBlocks(x) => x.is_empty(), + Self::GetTransactionPoolHashes(x) => x.is_empty(), + Self::GetPublicNodes(x) => x.is_empty(), + } + } +} + +//---------------------------------------------------------------------------------------------------- Response +/// Other JSON responses. +/// +/// This enum contains all [`crate::other`] responses. +/// +/// See also: [`OtherRequest`]. +/// +/// # (De)serialization +/// The `serde` implementation will (de)serialize from +/// the inner variant itself, e.g. [`OtherRequest::SetBootstrapDaemon`] +/// has the same (de)serialization as [`SetBootstrapDaemonResponse`]. +/// +/// ```rust +/// use cuprate_rpc_types::other::*; +/// +/// let response = OtherResponse::SetBootstrapDaemon(Default::default()); +/// let json = serde_json::to_string(&response).unwrap(); +/// assert_eq!(json, r#"{"status":"OK"}"#); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum OtherResponse { + GetHeight(GetHeightResponse), + GetTransactions(GetTransactionsResponse), + GetAltBlocksHashes(GetAltBlocksHashesResponse), + IsKeyImageSpent(IsKeyImageSpentResponse), + SendRawTransaction(SendRawTransactionResponse), + StartMining(StartMiningResponse), + StopMining(StopMiningResponse), + MiningStatus(MiningStatusResponse), + SaveBc(SaveBcResponse), + GetPeerList(GetPeerListResponse), + SetLogHashRate(SetLogHashRateResponse), + SetLogLevel(SetLogLevelResponse), + SetLogCategories(SetLogCategoriesResponse), + SetBootstrapDaemon(SetBootstrapDaemonResponse), + GetTransactionPool(GetTransactionPoolResponse), + GetTransactionPoolStats(GetTransactionPoolStatsResponse), + StopDaemon(StopDaemonResponse), + GetLimit(GetLimitResponse), + SetLimit(SetLimitResponse), + OutPeers(OutPeersResponse), + InPeers(InPeersResponse), + GetNetStats(GetNetStatsResponse), + GetOuts(GetOutsResponse), + Update(UpdateResponse), + PopBlocks(PopBlocksResponse), + GetTransactionPoolHashes(GetTransactionPoolHashesResponse), + GetPublicNodes(GetPublicNodesResponse), +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/rpc_call.rs b/rpc/types/src/rpc_call.rs new file mode 100644 index 0000000..5fb742e --- /dev/null +++ b/rpc/types/src/rpc_call.rs @@ -0,0 +1,96 @@ +//! RPC call metadata. + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- RpcCall +/// Metadata about an RPC call. +/// +/// This trait describes some metadata about RPC requests. +/// +/// It is implemented on all request types within: +/// - [`crate::json`] +/// - [`crate::other`] +/// - [`crate::bin`] +/// +/// See also [`RpcCallValue`] for a dynamic by-value version of this trait. +pub trait RpcCall { + /// Is `true` if this RPC method should + /// only be allowed on local servers. + /// + /// If this is `false`, it should be + /// okay to execute the method even on restricted + /// RPC servers. + /// + /// ```rust + /// use cuprate_rpc_types::{RpcCall, json::*}; + /// + /// // Allowed method, even on restricted RPC servers (18089). + /// assert!(!GetBlockCountRequest::IS_RESTRICTED); + /// + /// // Restricted methods, only allowed + /// // for unrestricted RPC servers (18081). + /// assert!(GetConnectionsRequest::IS_RESTRICTED); + /// ``` + const IS_RESTRICTED: bool; + + /// Is `true` if this RPC method has no inputs, i.e. it is a `struct` with no fields. + /// + /// ```rust + /// use cuprate_rpc_types::{RpcCall, json::*}; + /// + /// assert!(GetBlockCountRequest::IS_EMPTY); + /// assert!(!OnGetBlockHashRequest::IS_EMPTY); + /// ``` + const IS_EMPTY: bool; +} + +//---------------------------------------------------------------------------------------------------- RpcCallValue +/// By-value version of [`RpcCall`]. +/// +/// This trait is a mirror of [`RpcCall`], +/// except it takes `self` by value instead +/// of being a `const` property. +/// +/// This exists for `enum`s where requests must be dynamically +/// `match`ed like [`JsonRpcRequest`](crate::json::JsonRpcRequest). +/// +/// All types that implement [`RpcCall`] automatically implement [`RpcCallValue`]. +pub trait RpcCallValue { + /// Same as [`RpcCall::IS_RESTRICTED`]. + /// + /// ```rust + /// use cuprate_rpc_types::{RpcCallValue, json::*}; + /// + /// assert!(!GetBlockCountRequest::default().is_restricted()); + /// assert!(GetConnectionsRequest::default().is_restricted()); + /// ``` + fn is_restricted(&self) -> bool; + + /// Same as [`RpcCall::IS_EMPTY`]. + /// + /// ```rust + /// use cuprate_rpc_types::{RpcCallValue, json::*}; + /// + /// assert!(GetBlockCountRequest::default().is_empty()); + /// assert!(!OnGetBlockHashRequest::default().is_empty()); + /// ``` + fn is_empty(&self) -> bool; +} + +impl RpcCallValue for T { + #[inline] + fn is_restricted(&self) -> bool { + Self::IS_RESTRICTED + } + + #[inline] + fn is_empty(&self) -> bool { + Self::IS_EMPTY + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/types/src/serde.rs b/rpc/types/src/serde.rs new file mode 100644 index 0000000..e624a66 --- /dev/null +++ b/rpc/types/src/serde.rs @@ -0,0 +1,32 @@ +//! Custom (de)serialization functions for serde. + +//---------------------------------------------------------------------------------------------------- Lints +#![allow(clippy::trivially_copy_pass_by_ref)] // serde fn signature + +//---------------------------------------------------------------------------------------------------- Import +use serde::Serializer; + +//---------------------------------------------------------------------------------------------------- Free functions +/// Always serializes `true`. +#[inline] +pub(crate) fn serde_true(_: &bool, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(true) +} + +/// Always serializes `false`. +#[inline] +pub(crate) fn serde_false(_: &bool, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(false) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/storage/README.md b/storage/README.md index b04d8e7..77a8bcb 100644 --- a/storage/README.md +++ b/storage/README.md @@ -1,5 +1,10 @@ -# storage +# Storage +This subdirectory contains all things related to the on-disk storage of data within Cuprate. -TODO: This subdirectory used to be `database/` and is in the middle of being shifted around. +See for design documentation +and the following links for user documentation: -The old `database/` design document is in `cuprate-blockchain/` which will eventually be ported Cuprate's architecture book. +- +- +- +- \ No newline at end of file diff --git a/storage/blockchain/Cargo.toml b/storage/blockchain/Cargo.toml index bab582d..c935924 100644 --- a/storage/blockchain/Cargo.toml +++ b/storage/blockchain/Cargo.toml @@ -9,44 +9,44 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/storage/cuprate-bloc keywords = ["cuprate", "blockchain", "database"] [features] -default = ["heed", "service"] +default = ["heed"] # default = ["redb", "service"] # default = ["redb-memory", "service"] heed = ["cuprate-database/heed"] redb = ["cuprate-database/redb"] redb-memory = ["cuprate-database/redb-memory"] -service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"] +serde = ["dep:serde", "cuprate-database/serde", "cuprate-database-service/serde", "cuprate-helper/serde"] [dependencies] -# FIXME: -# We only need the `thread` feature if `service` is enabled. -# Figure out how to enable features of an already pulled in dependency conditionally. -cuprate-database = { path = "../database" } -cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] } -cuprate-types = { path = "../../types", features = ["blockchain"] } +cuprate-database = { workspace = true } +cuprate-database-service = { workspace = true } +cuprate-helper = { workspace = true, features = ["fs", "map", "crypto", "tx", "thread"] } +cuprate-types = { workspace = true, features = ["blockchain"] } +cuprate-pruning = { workspace = true } -bitflags = { workspace = true, features = ["serde", "bytemuck"] } -bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } +bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] } +bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } curve25519-dalek = { workspace = true } -cuprate-pruning = { path = "../../pruning" } +rand = { workspace = true, features = ["std", "std_rng"] } monero-serai = { workspace = true, features = ["std"] } -paste = { workspace = true } +serde = { workspace = true, optional = true } -# `service` feature. -crossbeam = { workspace = true, features = ["std"], optional = true } -futures = { workspace = true, optional = true } -tokio = { workspace = true, features = ["full"], optional = true } -tokio-util = { workspace = true, features = ["full"], optional = true } -tower = { workspace = true, features = ["full"], optional = true } +tower = { workspace = true } thread_local = { workspace = true } -rayon = { workspace = true, optional = true } +rayon = { workspace = true } +bytes = { workspace = true } [dev-dependencies] -cuprate-helper = { path = "../../helper", features = ["thread"] } -cuprate-test-utils = { path = "../../test-utils" } +cuprate-constants = { workspace = true } +cuprate-helper = { workspace = true, features = ["thread", "cast"] } +cuprate-test-utils = { workspace = true } -bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } -tempfile = { version = "3.10.0" } +tokio = { workspace = true, features = ["full"] } +tempfile = { workspace = true } pretty_assertions = { workspace = true } +proptest = { workspace = true } hex = { workspace = true } hex-literal = { workspace = true } + +[lints] +workspace = true diff --git a/storage/blockchain/DESIGN.md b/storage/blockchain/DESIGN.md deleted file mode 100644 index 22f729f..0000000 --- a/storage/blockchain/DESIGN.md +++ /dev/null @@ -1,600 +0,0 @@ -# Database -FIXME: This documentation must be updated and moved to the architecture book. - -Cuprate's blockchain implementation. - -- [1. Documentation](#1-documentation) -- [2. File structure](#2-file-structure) - - [2.1 `src/`](#21-src) - - [2.2 `src/backend/`](#22-srcbackend) - - [2.3 `src/config/`](#23-srcconfig) - - [2.4 `src/ops/`](#24-srcops) - - [2.5 `src/service/`](#25-srcservice) -- [3. Backends](#3-backends) - - [3.1 heed](#31-heed) - - [3.2 redb](#32-redb) - - [3.3 redb-memory](#33-redb-memory) - - [3.4 sanakirja](#34-sanakirja) - - [3.5 MDBX](#35-mdbx) -- [4. Layers](#4-layers) - - [4.1 Backend](#41-backend) - - [4.2 Trait](#42-trait) - - [4.3 ConcreteEnv](#43-concreteenv) - - [4.4 ops](#44-ops) - - [4.5 service](#45-service) -- [5. The service](#5-the-service) - - [5.1 Initialization](#51-initialization) - - [5.2 Requests](#53-requests) - - [5.3 Responses](#54-responses) - - [5.4 Thread model](#52-thread-model) - - [5.5 Shutdown](#55-shutdown) -- [6. Syncing](#6-Syncing) -- [7. Resizing](#7-resizing) -- [8. (De)serialization](#8-deserialization) -- [9. Schema](#9-schema) - - [9.1 Tables](#91-tables) - - [9.2 Multimap tables](#92-multimap-tables) -- [10. Known issues and tradeoffs](#10-known-issues-and-tradeoffs) - - [10.1 Traits abstracting backends](#101-traits-abstracting-backends) - - [10.2 Hot-swappable backends](#102-hot-swappable-backends) - - [10.3 Copying unaligned bytes](#103-copying-unaligned-bytes) - - [10.4 Endianness](#104-endianness) - - [10.5 Extra table data](#105-extra-table-data) - ---- - -## 1. Documentation -Documentation for `database/` is split into 3 locations: - -| Documentation location | Purpose | -|---------------------------|---------| -| `database/README.md` | High level design of `cuprate-database` -| `cuprate-database` | Practical usage documentation/warnings/notes/etc -| Source file `// comments` | Implementation-specific details (e.g, how many reader threads to spawn?) - -This README serves as the implementation design document. - -For actual practical usage, `cuprate-database`'s types and general usage are documented via standard Rust tooling. - -Run: -```bash -cargo doc --package cuprate-database --open -``` -at the root of the repo to open/read the documentation. - -If this documentation is too abstract, refer to any of the source files, they are heavily commented. There are many `// Regular comments` that explain more implementation specific details that aren't present here or in the docs. Use the file reference below to find what you're looking for. - -The code within `src/` is also littered with some `grep`-able comments containing some keywords: - -| Word | Meaning | -|-------------|---------| -| `INVARIANT` | This code makes an _assumption_ that must be upheld for correctness -| `SAFETY` | This `unsafe` code is okay, for `x,y,z` reasons -| `FIXME` | This code works but isn't ideal -| `HACK` | This code is a brittle workaround -| `PERF` | This code is weird for performance reasons -| `TODO` | This must be implemented; There should be 0 of these in production code -| `SOMEDAY` | This should be implemented... someday - -## 2. File structure -A quick reference of the structure of the folders & files in `cuprate-database`. - -Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`. - -### 2.1 `src/` -The top-level `src/` files. - -| File | Purpose | -|------------------------|---------| -| `constants.rs` | General constants used throughout `cuprate-database` -| `database.rs` | Abstracted database; `trait DatabaseR{o,w}` -| `env.rs` | Abstracted database environment; `trait Env` -| `error.rs` | Database error types -| `free.rs` | General free functions (related to the database) -| `key.rs` | Abstracted database keys; `trait Key` -| `resize.rs` | Database resizing algorithms -| `storable.rs` | Data (de)serialization; `trait Storable` -| `table.rs` | Database table abstraction; `trait Table` -| `tables.rs` | All the table definitions used by `cuprate-database` -| `tests.rs` | Utilities for `cuprate_database` testing -| `transaction.rs` | Database transaction abstraction; `trait TxR{o,w}` -| `types.rs` | Database-specific types -| `unsafe_unsendable.rs` | Marker type to impl `Send` for objects not `Send` - -### 2.2 `src/backend/` -This folder contains the implementation for actual databases used as the backend for `cuprate-database`. - -Each backend has its own folder. - -| Folder/File | Purpose | -|-------------|---------| -| `heed/` | Backend using using [`heed`](https://github.com/meilisearch/heed) (LMDB) -| `redb/` | Backend using [`redb`](https://github.com/cberner/redb) -| `tests.rs` | Backend-agnostic tests - -All backends follow the same file structure: - -| File | Purpose | -|------------------|---------| -| `database.rs` | Implementation of `trait DatabaseR{o,w}` -| `env.rs` | Implementation of `trait Env` -| `error.rs` | Implementation of backend's errors to `cuprate_database`'s error types -| `storable.rs` | Compatibility layer between `cuprate_database::Storable` and backend-specific (de)serialization -| `transaction.rs` | Implementation of `trait TxR{o,w}` -| `types.rs` | Type aliases for long backend-specific types - -### 2.3 `src/config/` -This folder contains the `cupate_database::config` module; configuration options for the database. - -| File | Purpose | -|---------------------|---------| -| `config.rs` | Main database `Config` struct -| `reader_threads.rs` | Reader thread configuration for `service` thread-pool -| `sync_mode.rs` | Disk sync configuration for backends - -### 2.4 `src/ops/` -This folder contains the `cupate_database::ops` module. - -These are higher-level functions abstracted over the database, that are Monero-related. - -| File | Purpose | -|-----------------|---------| -| `block.rs` | Block related (main functions) -| `blockchain.rs` | Blockchain related (height, cumulative values, etc) -| `key_image.rs` | Key image related -| `macros.rs` | Macros specific to `ops/` -| `output.rs` | Output related -| `property.rs` | Database properties (pruned, version, etc) -| `tx.rs` | Transaction related - -### 2.5 `src/service/` -This folder contains the `cupate_database::service` module. - -The `async`hronous request/response API other Cuprate crates use instead of managing the database directly themselves. - -| File | Purpose | -|----------------|---------| -| `free.rs` | General free functions used (related to `cuprate_database::service`) -| `read.rs` | Read thread-pool definitions and logic -| `tests.rs` | Thread-pool tests and test helper functions -| `types.rs` | `cuprate_database::service`-related type aliases -| `write.rs` | Writer thread definitions and logic - -## 3. Backends -`cuprate-database`'s `trait`s allow abstracting over the actual database, such that any backend in particular could be used. - -Each database's implementation for those `trait`'s are located in its respective folder in `src/backend/${DATABASE_NAME}/`. - -### 3.1 heed -The default database used is [`heed`](https://github.com/meilisearch/heed) (LMDB). The upstream versions from [`crates.io`](https://crates.io/crates/heed) are used. `LMDB` should not need to be installed as `heed` has a build script that pulls it in automatically. - -`heed`'s filenames inside Cuprate's database folder (`~/.local/share/cuprate/database/`) are: - -| Filename | Purpose | -|------------|---------| -| `data.mdb` | Main data file -| `lock.mdb` | Database lock file - -`heed`-specific notes: -- [There is a maximum reader limit](https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L1372). Other potential processes (e.g. `xmrblocks`) that are also reading the `data.mdb` file need to be accounted for -- [LMDB does not work on remote filesystem](https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/lmdb.h#L129) - -### 3.2 redb -The 2nd database backend is the 100% Rust [`redb`](https://github.com/cberner/redb). - -The upstream versions from [`crates.io`](https://crates.io/crates/redb) are used. - -`redb`'s filenames inside Cuprate's database folder (`~/.local/share/cuprate/database/`) are: - -| Filename | Purpose | -|-------------|---------| -| `data.redb` | Main data file - - - -### 3.3 redb-memory -This backend is 100% the same as `redb`, although, it uses `redb::backend::InMemoryBackend` which is a database that completely resides in memory instead of a file. - -All other details about this should be the same as the normal `redb` backend. - -### 3.4 sanakirja -[`sanakirja`](https://docs.rs/sanakirja) was a candidate as a backend, however there were problems with maximum value sizes. - -The default maximum value size is [1012 bytes](https://docs.rs/sanakirja/1.4.1/sanakirja/trait.Storable.html) which was too small for our requirements. Using [`sanakirja::Slice`](https://docs.rs/sanakirja/1.4.1/sanakirja/union.Slice.html) and [sanakirja::UnsizedStorage](https://docs.rs/sanakirja/1.4.1/sanakirja/trait.UnsizedStorable.html) was attempted, but there were bugs found when inserting a value in-between `512..=4096` bytes. - -As such, it is not implemented. - -### 3.5 MDBX -[`MDBX`](https://erthink.github.io/libmdbx) was a candidate as a backend, however MDBX deprecated the custom key/value comparison functions, this makes it a bit trickier to implement [`9.2 Multimap tables`](#92-multimap-tables). It is also quite similar to the main backend LMDB (of which it was originally a fork of). - -As such, it is not implemented (yet). - -## 4. Layers -`cuprate_database` is logically abstracted into 5 layers, with each layer being built upon the last. - -Starting from the lowest: -1. Backend -2. Trait -3. ConcreteEnv -4. `ops` -5. `service` - - - -### 4.1 Backend -This is the actual database backend implementation (or a Rust shim over one). - -Examples: -- `heed` (LMDB) -- `redb` - -`cuprate_database` itself just uses a backend, it does not implement one. - -All backends have the following attributes: -- [Embedded](https://en.wikipedia.org/wiki/Embedded_database) -- [Multiversion concurrency control](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) -- [ACID](https://en.wikipedia.org/wiki/ACID) -- Are `(key, value)` oriented and have the expected API (`get()`, `insert()`, `delete()`) -- Are table oriented (`"table_name" -> (key, value)`) -- Allows concurrent readers - -### 4.2 Trait -`cuprate_database` provides a set of `trait`s that abstract over the various database backends. - -This allows the function signatures and behavior to stay the same but allows for swapping out databases in an easier fashion. - -All common behavior of the backend's are encapsulated here and used instead of using the backend directly. - -Examples: -- [`trait Env`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/env.rs) -- [`trait {TxRo, TxRw}`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/transaction.rs) -- [`trait {DatabaseRo, DatabaseRw}`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/database.rs) - -For example, instead of calling `LMDB` or `redb`'s `get()` function directly, `DatabaseRo::get()` is called. - -### 4.3 ConcreteEnv -This is the non-generic, concrete `struct` provided by `cuprate_database` that contains all the data necessary to operate the database. The actual database backend `ConcreteEnv` will use internally depends on which backend feature is used. - -`ConcreteEnv` implements `trait Env`, which opens the door to all the other traits. - -The equivalent objects in the backends themselves are: -- [`heed::Env`](https://docs.rs/heed/0.20.0/heed/struct.Env.html) -- [`redb::Database`](https://docs.rs/redb/2.1.0/redb/struct.Database.html) - -This is the main object used when handling the database directly, although that is not strictly necessary as a user if the [`4.5 service`](#45-service) layer is used. - -### 4.4 ops -These are Monero-specific functions that use the abstracted `trait` forms of the database. - -Instead of dealing with the database directly: -- `get()` -- `delete()` - -the `ops` layer provides more abstract functions that deal with commonly used Monero operations: -- `add_block()` -- `pop_block()` - -### 4.5 service -The final layer abstracts the database completely into a [Monero-specific `async` request/response API](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/types/src/service.rs#L18-L78) using [`tower::Service`](https://docs.rs/tower/latest/tower/trait.Service.html). - -For more information on this layer, see the next section: [`5. The service`](#5-the-service). - -## 5. The service -The main API `cuprate_database` exposes for other crates to use is the `cuprate_database::service` module. - -This module exposes an `async` request/response API with `tower::Service`, backed by a threadpool, that allows reading/writing Monero-related data from/to the database. - -`cuprate_database::service` itself manages the database using a separate writer thread & reader thread-pool, and uses the previously mentioned [`4.4 ops`](#44-ops) functions when responding to requests. - -### 5.1 Initialization -The service is started simply by calling: [`cuprate_database::service::init()`](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/service/free.rs#L23). - -This function initializes the database, spawns threads, and returns a: -- Read handle to the database (cloneable) -- Write handle to the database (not cloneable) - -These "handles" implement the `tower::Service` trait, which allows sending requests and receiving responses `async`hronously. - -### 5.2 Requests -Along with the 2 handles, there are 2 types of requests: -- [`ReadRequest`](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/types/src/service.rs#L23-L90) -- [`WriteRequest`](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/types/src/service.rs#L93-L105) - -`ReadRequest` is for retrieving various types of information from the database. - -`WriteRequest` currently only has 1 variant: to write a block to the database. - -### 5.3 Responses -After sending one of the above requests using the read/write handle, the value returned is _not_ the response, yet an `async`hronous channel that will eventually return the response: -```rust,ignore -// Send a request. -// tower::Service::call() -// V -let response_channel: Channel = read_handle.call(ReadResponse::ChainHeight)?; - -// Await the response. -let response: ReadResponse = response_channel.await?; - -// Assert the response is what we expected. -assert_eq!(matches!(response), Response::ChainHeight(_)); -``` - -After `await`ing the returned channel, a `Response` will eventually be returned when the `service` threadpool has fetched the value from the database and sent it off. - -Both read/write requests variants match in name with `Response` variants, i.e. -- `ReadRequest::ChainHeight` leads to `Response::ChainHeight` -- `WriteRequest::WriteBlock` leads to `Response::WriteBlockOk` - -### 5.4 Thread model -As mentioned in the [`4. Layers`](#4-layers) section, the base database abstractions themselves are not concerned with parallelism, they are mostly functions to be called from a single-thread. - -However, the `cuprate_database::service` API, _does_ have a thread model backing it. - -When [`cuprate_database::service`'s initialization function](https://github.com/Cuprate/cuprate/blob/9c27ba5791377d639cb5d30d0f692c228568c122/database/src/service/free.rs#L33-L44) is called, threads will be spawned and maintained until the user drops (disconnects) the returned handles. - -The current behavior for thread count is: -- [1 writer thread](https://github.com/Cuprate/cuprate/blob/9c27ba5791377d639cb5d30d0f692c228568c122/database/src/service/write.rs#L52-L66) -- [As many reader threads as there are system threads](https://github.com/Cuprate/cuprate/blob/9c27ba5791377d639cb5d30d0f692c228568c122/database/src/service/read.rs#L104-L126) - -For example, on a system with 32-threads, `cuprate_database` will spawn: -- 1 writer thread -- 32 reader threads - -whose sole responsibility is to listen for database requests, access the database (potentially in parallel), and return a response. - -Note that the `1 system thread = 1 reader thread` model is only the default setting, the reader thread count can be configured by the user to be any number between `1 .. amount_of_system_threads`. - -The reader threads are managed by [`rayon`](https://docs.rs/rayon). - -For an example of where multiple reader threads are used: given a request that asks if any key-image within a set already exists, `cuprate_database` will [split that work between the threads with `rayon`](https://github.com/Cuprate/cuprate/blob/9c27ba5791377d639cb5d30d0f692c228568c122/database/src/service/read.rs#L490-L503). - -### 5.5 Shutdown -Once the read/write handles are `Drop`ed, the backing thread(pool) will gracefully exit, automatically. - -Note the writer thread and reader threadpool aren't connected whatsoever; dropping the write handle will make the writer thread exit, however, the reader handle is free to be held onto and can be continued to be read from - and vice-versa for the write handle. - -## 6. Syncing -`cuprate_database`'s database has 5 disk syncing modes. - -1. FastThenSafe -1. Safe -1. Async -1. Threshold -1. Fast - -The default mode is `Safe`. - -This means that upon each transaction commit, all the data that was written will be fully synced to disk. This is the slowest, but safest mode of operation. - -Note that upon any database `Drop`, whether via `service` or dropping the database directly, the current implementation will sync to disk regardless of any configuration. - -For more information on the other modes, read the documentation [here](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/config/sync_mode.rs#L63-L144). - -## 7. Resizing -Database backends that require manually resizing will, by default, use a similar algorithm as `monerod`'s. - -Note that this only relates to the `service` module, where the database is handled by `cuprate_database` itself, not the user. In the case of a user directly using `cuprate_database`, it is up to them on how to resize. - -Within `service`, the resizing logic defined [here](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/service/write.rs#L139-L201) does the following: - -- If there's not enough space to fit a write request's data, start a resize -- Each resize adds around [`1_073_745_920`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/resize.rs#L104-L160) bytes to the current map size -- A resize will be attempted `3` times before failing - -There are other [resizing algorithms](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/resize.rs#L38-L47) that define how the database's memory map grows, although currently the behavior of [`monerod`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/resize.rs#L104-L160) is closely followed. - -## 8. (De)serialization -All types stored inside the database are either bytes already, or are perfectly bitcast-able. - -As such, they do not incur heavy (de)serialization costs when storing/fetching them from the database. The main (de)serialization used is [`bytemuck`](https://docs.rs/bytemuck)'s traits and casting functions. - -The size & layout of types is stable across compiler versions, as they are set and determined with [`#[repr(C)]`](https://doc.rust-lang.org/nomicon/other-reprs.html#reprc) and `bytemuck`'s derive macros such as [`bytemuck::Pod`](https://docs.rs/bytemuck/latest/bytemuck/derive.Pod.html). - -Note that the data stored in the tables are still type-safe; we still refer to the key and values within our tables by the type. - -The main deserialization `trait` for database storage is: [`cuprate_database::Storable`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L16-L115). - -- Before storage, the type is [simply cast into bytes](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L125) -- When fetching, the bytes are [simply cast into the type](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L130) - -When a type is casted into bytes, [the reference is casted](https://docs.rs/bytemuck/latest/bytemuck/fn.bytes_of.html), i.e. this is zero-cost serialization. - -However, it is worth noting that when bytes are casted into the type, [it is copied](https://docs.rs/bytemuck/latest/bytemuck/fn.pod_read_unaligned.html). This is due to byte alignment guarantee issues with both backends, see: -- https://github.com/AltSysrq/lmdb-zero/issues/8 -- https://github.com/cberner/redb/issues/360 - -Without this, `bytemuck` will panic with [`TargetAlignmentGreaterAndInputNotAligned`](https://docs.rs/bytemuck/latest/bytemuck/enum.PodCastError.html#variant.TargetAlignmentGreaterAndInputNotAligned) when casting. - -Copying the bytes fixes this problem, although it is more costly than necessary. However, in the main use-case for `cuprate_database` (the `service` module) the bytes would need to be owned regardless as the `Request/Response` API uses owned data types (`T`, `Vec`, `HashMap`, etc). - -Practically speaking, this means lower-level database functions that normally look like such: -```rust -fn get(key: &Key) -> &Value; -``` -end up looking like this in `cuprate_database`: -```rust -fn get(key: &Key) -> Value; -``` - -Since each backend has its own (de)serialization methods, our types are wrapped in compatibility types that map our `Storable` functions into whatever is required for the backend, e.g: -- [`StorableHeed`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/backend/heed/storable.rs#L11-L45) -- [`StorableRedb`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/backend/redb/storable.rs#L11-L30) - -Compatibility structs also exist for any `Storable` containers: -- [`StorableVec`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L135-L191) -- [`StorableBytes`](https://github.com/Cuprate/cuprate/blob/2ac90420c658663564a71b7ecb52d74f3c2c9d0f/database/src/storable.rs#L208-L241) - -Again, it's unfortunate that these must be owned, although in `service`'s use-case, they would have to be owned anyway. - -## 9. Schema -This following section contains Cuprate's database schema, it may change throughout the development of Cuprate, as such, nothing here is final. - -### 9.1 Tables -The `CamelCase` names of the table headers documented here (e.g. `TxIds`) are the actual type name of the table within `cuprate_database`. - -Note that words written within `code blocks` mean that it is a real type defined and usable within `cuprate_database`. Other standard types like u64 and type aliases (TxId) are written normally. - -Within `cuprate_database::tables`, the below table is essentially defined as-is with [a macro](https://github.com/Cuprate/cuprate/blob/31ce89412aa174fc33754f22c9a6d9ef5ddeda28/database/src/tables.rs#L369-L470). - -Many of the data types stored are the same data types, although are different semantically, as such, a map of aliases used and their real data types is also provided below. - -| Alias | Real Type | -|----------------------------------------------------|-----------| -| BlockHeight, Amount, AmountIndex, TxId, UnlockTime | u64 -| BlockHash, KeyImage, TxHash, PrunableHash | [u8; 32] - -| Table | Key | Value | Description | -|-------------------|----------------------|--------------------|-------------| -| `BlockBlobs` | BlockHeight | `StorableVec` | Maps a block's height to a serialized byte form of a block -| `BlockHeights` | BlockHash | BlockHeight | Maps a block's hash to its height -| `BlockInfos` | BlockHeight | `BlockInfo` | Contains metadata of all blocks -| `KeyImages` | KeyImage | () | This table is a set with no value, it stores transaction key images -| `NumOutputs` | Amount | u64 | Maps an output's amount to the number of outputs with that amount -| `Outputs` | `PreRctOutputId` | `Output` | This table contains legacy CryptoNote outputs which have clear amounts. This table will not contain an output with 0 amount. -| `PrunedTxBlobs` | TxId | `StorableVec` | Contains pruned transaction blobs (even if the database is not pruned) -| `PrunableTxBlobs` | TxId | `StorableVec` | Contains the prunable part of a transaction -| `PrunableHashes` | TxId | PrunableHash | Contains the hash of the prunable part of a transaction -| `RctOutputs` | AmountIndex | `RctOutput` | Contains RingCT outputs mapped from their global RCT index -| `TxBlobs` | TxId | `StorableVec` | Serialized transaction blobs (bytes) -| `TxIds` | TxHash | TxId | Maps a transaction's hash to its index/ID -| `TxHeights` | TxId | BlockHeight | Maps a transaction's ID to the height of the block it comes from -| `TxOutputs` | TxId | `StorableVec` | Gives the amount indices of a transaction's outputs -| `TxUnlockTime` | TxId | UnlockTime | Stores the unlock time of a transaction (only if it has a non-zero lock time) - -The definitions for aliases and types (e.g. `RctOutput`) are within the [`cuprate_database::types`](https://github.com/Cuprate/cuprate/blob/31ce89412aa174fc33754f22c9a6d9ef5ddeda28/database/src/types.rs#L51) module. - - - -### 9.2 Multimap tables -When referencing outputs, Monero will [use the amount and the amount index](https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/blockchain_db/lmdb/db_lmdb.cpp#L3447-L3449). This means 2 keys are needed to reach an output. - -With LMDB you can set the `DUP_SORT` flag on a table and then set the key/value to: -```rust -Key = KEY_PART_1 -``` -```rust -Value = { - KEY_PART_2, - VALUE // The actual value we are storing. -} -``` - -Then you can set a custom value sorting function that only takes `KEY_PART_2` into account; this is how `monerod` does it. - -This requires that the underlying database supports: -- multimap tables -- custom sort functions on values -- setting a cursor on a specific key/value - ---- - -Another way to implement this is as follows: -```rust -Key = { KEY_PART_1, KEY_PART_2 } -``` -```rust -Value = VALUE -``` - -Then the key type is simply used to look up the value; this is how `cuprate_database` does it. - -For example, the key/value pair for outputs is: -```rust -PreRctOutputId => Output -``` -where `PreRctOutputId` looks like this: -```rust -struct PreRctOutputId { - amount: u64, - amount_index: u64, -} -``` - -## 10. Known issues and tradeoffs -`cuprate_database` takes many tradeoffs, whether due to: -- Prioritizing certain values over others -- Not having a better solution -- Being "good enough" - -This is a list of the larger ones, along with issues that don't have answers yet. - -### 10.1 Traits abstracting backends -Although all database backends used are very similar, they have some crucial differences in small implementation details that must be worked around when conforming them to `cuprate_database`'s traits. - -Put simply: using `cuprate_database`'s traits is less efficient and more awkward than using the backend directly. - -For example: -- [Data types must be wrapped in compatibility layers when they otherwise wouldn't be](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/backend/heed/env.rs#L101-L116) -- [There are types that only apply to a specific backend, but are visible to all](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/error.rs#L86-L89) -- [There are extra layers of abstraction to smoothen the differences between all backends](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/env.rs#L62-L68) -- [Existing functionality of backends must be taken away, as it isn't supported in the others](https://github.com/Cuprate/cuprate/blob/d0ac94a813e4cd8e0ed8da5e85a53b1d1ace2463/database/src/database.rs#L27-L34) - -This is a _tradeoff_ that `cuprate_database` takes, as: -- The backend itself is usually not the source of bottlenecks in the greater system, as such, small inefficiencies are OK -- None of the lost functionality is crucial for operation -- The ability to use, test, and swap between multiple database backends is [worth it](https://github.com/Cuprate/cuprate/pull/35#issuecomment-1952804393) - -### 10.2 Hot-swappable backends -Using a different backend is really as simple as re-building `cuprate_database` with a different feature flag: -```bash -# Use LMDB. -cargo build --package cuprate-database --features heed - -# Use redb. -cargo build --package cuprate-database --features redb -``` - -This is "good enough" for now, however ideally, this hot-swapping of backends would be able to be done at _runtime_. - -As it is now, `cuprate_database` cannot compile both backends and swap based on user input at runtime; it must be compiled with a certain backend, which will produce a binary with only that backend. - -This also means things like [CI testing multiple backends is awkward](https://github.com/Cuprate/cuprate/blob/main/.github/workflows/ci.yml#L132-L136), as we must re-compile with different feature flags instead. - -### 10.3 Copying unaligned bytes -As mentioned in [`8. (De)serialization`](#8-deserialization), bytes are _copied_ when they are turned into a type `T` due to unaligned bytes being returned from database backends. - -Using a regular reference cast results in an improperly aligned type `T`; [such a type even existing causes undefined behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html). In our case, `bytemuck` saves us by panicking before this occurs. - -Thus, when using `cuprate_database`'s database traits, an _owned_ `T` is returned. - -This is doubly unfortunately for `&[u8]` as this does not even need deserialization. - -For example, `StorableVec` could have been this: -```rust -enum StorableBytes<'a, T: Storable> { - Owned(T), - Ref(&'a T), -} -``` -but this would require supporting types that must be copied regardless with the occasional `&[u8]` that can be returned without casting. This was hard to do so in a generic way, thus all `[u8]`'s are copied and returned as owned `StorableVec`s. - -This is a _tradeoff_ `cuprate_database` takes as: -- `bytemuck::pod_read_unaligned` is cheap enough -- The main API, `service`, needs to return owned value anyway -- Having no references removes a lot of lifetime complexity - -The alternative is either: -- Using proper (de)serialization instead of casting (which comes with its own costs) -- Somehow fixing the alignment issues in the backends mentioned previously - -### 10.4 Endianness -`cuprate_database`'s (de)serialization and storage of bytes are native-endian, as in, byte storage order will depend on the machine it is running on. - -As Cuprate's build-targets are all little-endian ([big-endian by default machines barely exist](https://en.wikipedia.org/wiki/Endianness#Hardware)), this doesn't matter much and the byte ordering can be seen as a constant. - -Practically, this means `cuprated`'s database files can be transferred across computers, as can `monerod`'s. - -### 10.5 Extra table data -Some of `cuprate_database`'s tables differ from `monerod`'s tables, for example, the way [`9.2 Multimap tables`](#92-multimap-tables) tables are done requires that the primary key is stored _for all_ entries, compared to `monerod` only needing to store it once. - -For example: -```rust -// `monerod` only stores `amount: 1` once, -// `cuprated` stores it each time it appears. -struct PreRctOutputId { amount: 1, amount_index: 0 } -struct PreRctOutputId { amount: 1, amount_index: 1 } -``` - -This means `cuprated`'s database will be slightly larger than `monerod`'s. - -The current method `cuprate_database` uses will be "good enough" until usage shows that it must be optimized as multimap tables are tricky to implement across all backends. diff --git a/storage/blockchain/README.md b/storage/blockchain/README.md index 8a2162c..8ab8b43 100644 --- a/storage/blockchain/README.md +++ b/storage/blockchain/README.md @@ -5,6 +5,10 @@ This documentation is mostly for practical usage of `cuprate_blockchain`. For a high-level overview, see the database section in [Cuprate's architecture book](https://architecture.cuprate.org). +If you're looking for a database crate, consider using the lower-level +[`cuprate-database`](https://doc.cuprate.org/cuprate_database) +crate that this crate is built on-top of. + # Purpose This crate does 3 things: 1. Uses [`cuprate_database`] as a base database layer @@ -28,9 +32,6 @@ use cuprate_blockchain::{ This ensures the types/traits used from `cuprate_database` are the same ones used by `cuprate_blockchain` internally. # Feature flags -The `service` module requires the `service` feature to be enabled. -See the module for more documentation. - Different database backends are enabled by the feature flags: - `heed` (LMDB) - `redb` @@ -41,17 +42,17 @@ The default is `heed`. # Invariants when not using `service` -`cuprate_blockchain` can be used without the `service` feature enabled but +`cuprate_blockchain` can be used without the `service` module but there are some things that must be kept in mind when doing so. Failing to uphold these invariants may cause panics. 1. `LMDB` requires the user to resize the memory map resizing (see [`cuprate_database::RuntimeError::ResizeNeeded`] -1. `LMDB` has a maximum reader transaction count, currently it is set to `128` +1. `LMDB` has a maximum reader transaction count, currently, [it is set to `126`](https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/mdb.c#L794-L799) 1. `LMDB` has [maximum key/value byte size](http://www.lmdb.tech/doc/group__internal.html#gac929399f5d93cef85f874b9e9b1d09e0) which must not be exceeded # Examples -The below is an example of using `cuprate_blockchain` +The below is an example of using `cuprate_blockchain`'s lowest API, i.e. using a mix of this crate and `cuprate_database`'s traits directly - **this is NOT recommended.** @@ -67,8 +68,7 @@ use cuprate_blockchain::{ DatabaseRo, DatabaseRw, TxRo, TxRw, }, config::ConfigBuilder, - tables::{Tables, TablesMut}, - OpenTables, + tables::{Tables, TablesMut, OpenTables}, }; # fn main() -> Result<(), Box> { @@ -76,7 +76,7 @@ use cuprate_blockchain::{ let tmp_dir = tempfile::tempdir()?; let db_dir = tmp_dir.path().to_owned(); let config = ConfigBuilder::new() - .db_directory(db_dir.into()) + .data_directory(db_dir.into()) .build(); // Initialize the database environment. diff --git a/storage/blockchain/src/config/config.rs b/storage/blockchain/src/config.rs similarity index 61% rename from storage/blockchain/src/config/config.rs rename to storage/blockchain/src/config.rs index c58e292..4bef2cb 100644 --- a/storage/blockchain/src/config/config.rs +++ b/storage/blockchain/src/config.rs @@ -1,15 +1,59 @@ -//! The main [`Config`] struct, holding all configurable values. +//! Database configuration. +//! +//! This module contains the main [`Config`]uration struct +//! for the database [`Env`](cuprate_database::Env)ironment, +//! and blockchain-specific configuration. +//! +//! It also contains types related to configuration settings. +//! +//! The main constructor is the [`ConfigBuilder`]. +//! +//! These configurations are processed at runtime, meaning +//! the `Env` can/will dynamically adjust its behavior based +//! on these values. +//! +//! # Example +//! ```rust +//! use cuprate_blockchain::{ +//! cuprate_database::{Env, config::SyncMode}, +//! config::{ConfigBuilder, ReaderThreads}, +//! }; +//! +//! # fn main() -> Result<(), Box> { +//! let tmp_dir = tempfile::tempdir()?; +//! let db_dir = tmp_dir.path().to_owned(); +//! +//! let config = ConfigBuilder::new() +//! // Use a custom database directory. +//! .data_directory(db_dir.into()) +//! // Use as many reader threads as possible (when using `service`). +//! .reader_threads(ReaderThreads::OnePerThread) +//! // Use the fastest sync mode. +//! .sync_mode(SyncMode::Fast) +//! // Build into `Config` +//! .build(); +//! +//! // Start a database `service` using this configuration. +//! let (_, _, env) = cuprate_blockchain::service::init(config.clone())?; +//! // It's using the config we provided. +//! assert_eq!(env.config(), &config.db_config); +//! # Ok(()) } +//! ``` //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, path::Path}; +use std::{borrow::Cow, path::PathBuf}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use cuprate_database::{config::SyncMode, resize::ResizeAlgorithm}; -use cuprate_helper::fs::cuprate_blockchain_dir; +use cuprate_helper::{ + fs::{blockchain_path, CUPRATE_DATA_DIR}, + network::Network, +}; -use crate::config::ReaderThreads; +// re-exports +pub use cuprate_database_service::ReaderThreads; //---------------------------------------------------------------------------------------------------- ConfigBuilder /// Builder for [`Config`]. @@ -18,8 +62,9 @@ use crate::config::ReaderThreads; #[derive(Debug, Clone, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ConfigBuilder { - /// [`Config::db_directory`]. - db_directory: Option>, + network: Network, + + data_dir: Option, /// [`Config::cuprate_database_config`]. db_config: cuprate_database::config::ConfigBuilder, @@ -35,10 +80,12 @@ impl ConfigBuilder { /// after this function to use default values. pub fn new() -> Self { Self { - db_directory: None, - db_config: cuprate_database::config::ConfigBuilder::new(Cow::Borrowed( - cuprate_blockchain_dir(), - )), + network: Network::default(), + data_dir: None, + db_config: cuprate_database::config::ConfigBuilder::new(Cow::Owned(blockchain_path( + &CUPRATE_DATA_DIR, + Network::Mainnet, + ))), reader_threads: None, } } @@ -46,21 +93,21 @@ impl ConfigBuilder { /// Build into a [`Config`]. /// /// # Default values - /// If [`ConfigBuilder::db_directory`] was not called, - /// the default [`cuprate_blockchain_dir`] will be used. + /// If [`ConfigBuilder::data_directory`] was not called, + /// [`blockchain_path`] with [`CUPRATE_DATA_DIR`] [`Network::Mainnet`] will be used. /// /// For all other values, [`Default::default`] is used. pub fn build(self) -> Config { // INVARIANT: all PATH safety checks are done // in `helper::fs`. No need to do them here. - let db_directory = self - .db_directory - .unwrap_or_else(|| Cow::Borrowed(cuprate_blockchain_dir())); + let data_dir = self + .data_dir + .unwrap_or_else(|| CUPRATE_DATA_DIR.to_path_buf()); let reader_threads = self.reader_threads.unwrap_or_default(); let db_config = self .db_config - .db_directory(db_directory) + .db_directory(Cow::Owned(blockchain_path(&data_dir, self.network))) .reader_threads(reader_threads.as_threads()) .build(); @@ -70,10 +117,17 @@ impl ConfigBuilder { } } - /// Set a custom database directory (and file) [`Path`]. + /// Change the network this blockchain database is for. #[must_use] - pub fn db_directory(mut self, db_directory: Cow<'static, Path>) -> Self { - self.db_directory = Some(db_directory); + pub const fn network(mut self, network: Network) -> Self { + self.network = network; + self + } + + /// Set a custom database directory (and file) [`PathBuf`]. + #[must_use] + pub fn data_directory(mut self, db_directory: PathBuf) -> Self { + self.data_dir = Some(db_directory); self } @@ -104,9 +158,7 @@ impl ConfigBuilder { /// Good default for testing, and resource-available machines. #[must_use] pub fn fast(mut self) -> Self { - self.db_config = - cuprate_database::config::ConfigBuilder::new(Cow::Borrowed(cuprate_blockchain_dir())) - .fast(); + self.db_config = self.db_config.fast(); self.reader_threads = Some(ReaderThreads::OnePerThread); self @@ -118,9 +170,7 @@ impl ConfigBuilder { /// Good default for resource-limited machines, e.g. a cheap VPS. #[must_use] pub fn low_power(mut self) -> Self { - self.db_config = - cuprate_database::config::ConfigBuilder::new(Cow::Borrowed(cuprate_blockchain_dir())) - .low_power(); + self.db_config = self.db_config.low_power(); self.reader_threads = Some(ReaderThreads::One); self @@ -129,10 +179,13 @@ impl ConfigBuilder { impl Default for ConfigBuilder { fn default() -> Self { - let db_directory = Cow::Borrowed(cuprate_blockchain_dir()); Self { - db_directory: Some(db_directory.clone()), - db_config: cuprate_database::config::ConfigBuilder::new(db_directory), + network: Network::default(), + data_dir: Some(CUPRATE_DATA_DIR.to_path_buf()), + db_config: cuprate_database::config::ConfigBuilder::new(Cow::Owned(blockchain_path( + &CUPRATE_DATA_DIR, + Network::default(), + ))), reader_threads: Some(ReaderThreads::default()), } } @@ -160,7 +213,7 @@ impl Config { /// Create a new [`Config`] with sane default settings. /// /// The [`cuprate_database::config::Config::db_directory`] - /// will be set to [`cuprate_blockchain_dir`]. + /// will be set to [`blockchain_path`] with [`CUPRATE_DATA_DIR`] [`Network::Mainnet`]. /// /// All other values will be [`Default::default`]. /// @@ -172,14 +225,14 @@ impl Config { /// resize::ResizeAlgorithm, /// DATABASE_DATA_FILENAME, /// }; - /// use cuprate_helper::fs::*; + /// use cuprate_helper::{fs::*, network::Network}; /// /// use cuprate_blockchain::config::*; /// /// let config = Config::new(); /// - /// assert_eq!(config.db_config.db_directory(), cuprate_blockchain_dir()); - /// assert!(config.db_config.db_file().starts_with(cuprate_blockchain_dir())); + /// assert_eq!(config.db_config.db_directory().as_ref(), blockchain_path(&CUPRATE_DATA_DIR, Network::Mainnet).as_path()); + /// assert!(config.db_config.db_file().starts_with(&*CUPRATE_DATA_DIR)); /// assert!(config.db_config.db_file().ends_with(DATABASE_DATA_FILENAME)); /// assert_eq!(config.db_config.sync_mode, SyncMode::default()); /// assert_eq!(config.db_config.resize_algorithm, ResizeAlgorithm::default()); diff --git a/storage/blockchain/src/config/backend.rs b/storage/blockchain/src/config/backend.rs deleted file mode 100644 index ee72b3d..0000000 --- a/storage/blockchain/src/config/backend.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! SOMEDAY - -//---------------------------------------------------------------------------------------------------- Import -use std::{ - borrow::Cow, - num::NonZeroUsize, - path::{Path, PathBuf}, -}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use cuprate_helper::fs::cuprate_blockchain_dir; - -use crate::{ - config::{ReaderThreads, SyncMode}, - constants::DATABASE_DATA_FILENAME, - resize::ResizeAlgorithm, -}; - -//---------------------------------------------------------------------------------------------------- Backend -/// SOMEDAY: allow runtime hot-swappable backends. -#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum Backend { - #[default] - /// SOMEDAY - Heed, - /// SOMEDAY - Redb, -} diff --git a/storage/blockchain/src/config/mod.rs b/storage/blockchain/src/config/mod.rs deleted file mode 100644 index 7ecc14c..0000000 --- a/storage/blockchain/src/config/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Database configuration. -//! -//! This module contains the main [`Config`]uration struct -//! for the database [`Env`](cuprate_database::Env)ironment, -//! and blockchain-specific configuration. -//! -//! It also contains types related to configuration settings. -//! -//! The main constructor is the [`ConfigBuilder`]. -//! -//! These configurations are processed at runtime, meaning -//! the `Env` can/will dynamically adjust its behavior based -//! on these values. -//! -//! # Example -//! ```rust -//! use cuprate_blockchain::{ -//! cuprate_database::{Env, config::SyncMode}, -//! config::{ConfigBuilder, ReaderThreads}, -//! }; -//! -//! # fn main() -> Result<(), Box> { -//! let tmp_dir = tempfile::tempdir()?; -//! let db_dir = tmp_dir.path().to_owned(); -//! -//! let config = ConfigBuilder::new() -//! // Use a custom database directory. -//! .db_directory(db_dir.into()) -//! // Use as many reader threads as possible (when using `service`). -//! .reader_threads(ReaderThreads::OnePerThread) -//! // Use the fastest sync mode. -//! .sync_mode(SyncMode::Fast) -//! // Build into `Config` -//! .build(); -//! -//! // Start a database `service` using this configuration. -//! let (reader_handle, _) = cuprate_blockchain::service::init(config.clone())?; -//! // It's using the config we provided. -//! assert_eq!(reader_handle.env().config(), &config.db_config); -//! # Ok(()) } -//! ``` - -mod config; -pub use config::{Config, ConfigBuilder}; - -mod reader_threads; -pub use reader_threads::ReaderThreads; diff --git a/storage/blockchain/src/config/sync_mode.rs b/storage/blockchain/src/config/sync_mode.rs deleted file mode 100644 index 1d20339..0000000 --- a/storage/blockchain/src/config/sync_mode.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Database [`Env`](crate::Env) configuration. -//! -//! This module contains the main [`Config`]uration struct -//! for the database [`Env`](crate::Env)ironment, and data -//! structures related to any configuration setting. -//! -//! These configurations are processed at runtime, meaning -//! the `Env` can/will dynamically adjust its behavior -//! based on these values. - -//---------------------------------------------------------------------------------------------------- Import - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -//---------------------------------------------------------------------------------------------------- SyncMode -/// Disk synchronization mode. -/// -/// This controls how/when the database syncs its data to disk. -/// -/// Regardless of the variant chosen, dropping [`Env`](crate::Env) -/// will always cause it to fully sync to disk. -/// -/// # Sync vs Async -/// All invariants except [`SyncMode::Async`] & [`SyncMode::Fast`] -/// are `synchronous`, as in the database will wait until the OS has -/// finished syncing all the data to disk before continuing. -/// -/// `SyncMode::Async` & `SyncMode::Fast` are `asynchronous`, meaning -/// the database will _NOT_ wait until the data is fully synced to disk -/// before continuing. Note that this doesn't mean the database itself -/// won't be synchronized between readers/writers, but rather that the -/// data _on disk_ may not be immediately synchronized after a write. -/// -/// Something like: -/// ```rust,ignore -/// db.put("key", value); -/// db.get("key"); -/// ``` -/// will be fine, most likely pulling from memory instead of disk. -/// -/// # SOMEDAY -/// Dynamic sync's are not yet supported. -/// -/// Only: -/// -/// - [`SyncMode::Safe`] -/// - [`SyncMode::Async`] -/// - [`SyncMode::Fast`] -/// -/// are supported, all other variants will panic on [`crate::Env::open`]. -#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum SyncMode { - /// Use [`SyncMode::Fast`] until fully synced, - /// then use [`SyncMode::Safe`]. - /// - // # SOMEDAY: how to implement this? - // ref: - // monerod-solution: - // cuprate-issue: - // - // We could: - // ```rust,ignore - // if current_db_block <= top_block.saturating_sub(N) { - // // don't sync() - // } else { - // // sync() - // } - // ``` - // where N is some threshold we pick that is _close_ enough - // to being synced where we want to start being safer. - // - // Essentially, when we are in a certain % range of being finished, - // switch to safe mode, until then, go fast. - FastThenSafe, - - #[default] - /// Fully sync to disk per transaction. - /// - /// Every database transaction commit will - /// fully sync all data to disk, _synchronously_, - /// so the database (writer) halts until synced. - /// - /// This is expected to be very slow. - /// - /// This matches: - /// - LMDB without any special sync flags - /// - [`redb::Durability::Immediate`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Immediate) - Safe, - - /// Asynchrously sync to disk per transaction. - /// - /// This is the same as [`SyncMode::Safe`], - /// but the syncs will be asynchronous, i.e. - /// each transaction commit will sync to disk, - /// but only eventually, not necessarily immediately. - /// - /// This matches: - /// - [`MDB_MAPASYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#gab034ed0d8e5938090aef5ee0997f7e94) - /// - [`redb::Durability::Eventual`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Eventual) - Async, - - /// Fully sync to disk after we cross this transaction threshold. - /// - /// After committing [`usize`] amount of database - /// transactions, it will be sync to disk. - /// - /// `0` behaves the same as [`SyncMode::Safe`], and a ridiculously large - /// number like `usize::MAX` is practically the same as [`SyncMode::Fast`]. - Threshold(usize), - - /// Only flush at database shutdown. - /// - /// This is the fastest, yet unsafest option. - /// - /// It will cause the database to never _actively_ sync, - /// letting the OS decide when to flush data to disk. - /// - /// This matches: - /// - [`MDB_NOSYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#ga5791dd1adb09123f82dd1f331209e12e) + [`MDB_MAPASYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#gab034ed0d8e5938090aef5ee0997f7e94) - /// - [`redb::Durability::None`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.None) - /// - /// `monerod` reference: - /// - /// # Corruption - /// In the case of a system crash, the database - /// may become corrupted when using this option. - // - // FIXME: we could call this `unsafe` - // and use that terminology in the config file - // so users know exactly what they are getting - // themselves into. - Fast, -} diff --git a/storage/blockchain/src/constants.rs b/storage/blockchain/src/constants.rs index 7f00d4c..8726885 100644 --- a/storage/blockchain/src/constants.rs +++ b/storage/blockchain/src/constants.rs @@ -14,21 +14,6 @@ /// pub const DATABASE_VERSION: u64 = 0; -//---------------------------------------------------------------------------------------------------- Error Messages -/// Corrupt database error message. -/// -/// The error message shown to end-users in panic -/// messages if we think the database is corrupted. -/// -/// This is meant to be user-friendly. -pub const DATABASE_CORRUPT_MSG: &str = r"Cuprate has encountered a fatal error. The database may be corrupted. - -TODO: instructions on: -1. What to do -2. How to fix (re-sync, recover, etc) -3. General advice for preventing corruption -4. etc"; - //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test {} diff --git a/storage/blockchain/src/free.rs b/storage/blockchain/src/free.rs index 255860a..8288e65 100644 --- a/storage/blockchain/src/free.rs +++ b/storage/blockchain/src/free.rs @@ -3,10 +3,10 @@ //---------------------------------------------------------------------------------------------------- Import use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw}; -use crate::{config::Config, open_tables::OpenTables}; +use crate::{config::Config, tables::OpenTables}; //---------------------------------------------------------------------------------------------------- Free functions -/// Open the blockchain database, using the passed [`Config`]. +/// Open the blockchain database using the passed [`Config`]. /// /// This calls [`cuprate_database::Env::open`] and prepares the /// database to be ready for blockchain-related usage, e.g. @@ -50,20 +50,12 @@ pub fn open(config: Config) -> Result { // we want since it is agnostic, so we are responsible for this. { let env_inner = env.env_inner(); - let tx_rw = env_inner.tx_rw(); - let tx_rw = match tx_rw { - Ok(tx_rw) => tx_rw, - Err(e) => return Err(runtime_to_init_error(e)), - }; + let tx_rw = env_inner.tx_rw().map_err(runtime_to_init_error)?; // Create all tables. - if let Err(e) = OpenTables::create_tables(&env_inner, &tx_rw) { - return Err(runtime_to_init_error(e)); - }; + OpenTables::create_tables(&env_inner, &tx_rw).map_err(runtime_to_init_error)?; - if let Err(e) = tx_rw.commit() { - return Err(runtime_to_init_error(e)); - } + TxRw::commit(tx_rw).map_err(runtime_to_init_error)?; } Ok(env) diff --git a/storage/blockchain/src/lib.rs b/storage/blockchain/src/lib.rs index ad33e2a..7db8cc6 100644 --- a/storage/blockchain/src/lib.rs +++ b/storage/blockchain/src/lib.rs @@ -1,103 +1,9 @@ #![doc = include_str!("../README.md")] -//---------------------------------------------------------------------------------------------------- Lints -// Forbid lints. -// Our code, and code generated (e.g macros) cannot overrule these. -#![forbid( - // `unsafe` is allowed but it _must_ be - // commented with `SAFETY: reason`. - clippy::undocumented_unsafe_blocks, - - // Never. - unused_unsafe, - redundant_semicolons, - unused_allocation, - coherence_leak_check, - while_true, - clippy::missing_docs_in_private_items, - - // Maybe can be put into `#[deny]`. - unconditional_recursion, - for_loops_over_fallibles, - unused_braces, - unused_labels, - keyword_idents, - non_ascii_idents, - variant_size_differences, - single_use_lifetimes, - - // Probably can be put into `#[deny]`. - future_incompatible, - let_underscore, - break_with_label_and_loop, - duplicate_macro_attributes, - exported_private_dependencies, - large_assignments, - overlapping_range_endpoints, - semicolon_in_expressions_from_macros, - noop_method_call, - unreachable_pub, -)] -// Deny lints. -// Some of these are `#[allow]`'ed on a per-case basis. -#![deny( - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, - clippy::nursery, - clippy::cargo, - unused_crate_dependencies, - unused_doc_comments, - unused_mut, - missing_docs, - deprecated, - unused_comparisons, - nonstandard_style -)] #![allow( - // FIXME: this lint affects crates outside of - // `database/` for some reason, allow for now. - clippy::cargo_common_metadata, - - // FIXME: adding `#[must_use]` onto everything - // might just be more annoying than useful... - // although it is sometimes nice. - clippy::must_use_candidate, - - // FIXME: good lint but too many false positives - // with our `Env` + `RwLock` setup. - clippy::significant_drop_tightening, - - // FIXME: good lint but is less clear in most cases. - clippy::items_after_statements, - - clippy::module_name_repetitions, - clippy::module_inception, - clippy::redundant_pub_crate, - clippy::option_if_let_else, -)] -// Allow some lints when running in debug mode. -#![cfg_attr( - debug_assertions, - allow( - clippy::todo, - clippy::multiple_crate_versions, - // unused_crate_dependencies, - ) -)] -// Allow some lints in tests. -#![cfg_attr( - test, - allow( - clippy::cognitive_complexity, - clippy::needless_pass_by_value, - clippy::cast_possible_truncation, - clippy::too_many_lines - ) + // See `cuprate-database` for reasoning. + clippy::significant_drop_tightening )] + // Only allow building 64-bit targets. // // This allows us to assume 64-bit @@ -114,30 +20,21 @@ compile_error!("Cuprate is only compatible with 64-bit CPUs"); // // Documentation for each module is located in the respective file. -pub mod config; - mod constants; -pub use constants::{DATABASE_CORRUPT_MSG, DATABASE_VERSION}; - -mod open_tables; -pub use open_tables::OpenTables; - mod free; + +pub use constants::DATABASE_VERSION; +pub use cuprate_database; pub use free::open; +pub mod config; pub mod ops; +pub mod service; pub mod tables; pub mod types; -pub use cuprate_database; - -//---------------------------------------------------------------------------------------------------- Feature-gated -#[cfg(feature = "service")] -pub mod service; - //---------------------------------------------------------------------------------------------------- Private #[cfg(test)] pub(crate) mod tests; -#[cfg(feature = "service")] // only needed in `service` for now pub(crate) mod unsafe_sendable; diff --git a/storage/blockchain/src/open_tables.rs b/storage/blockchain/src/open_tables.rs deleted file mode 100644 index 4b265e8..0000000 --- a/storage/blockchain/src/open_tables.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Import -use cuprate_database::{EnvInner, RuntimeError, TxRo, TxRw}; - -use crate::tables::{TablesIter, TablesMut}; - -//---------------------------------------------------------------------------------------------------- Table function macro -/// `crate`-private macro for callings functions on all tables. -/// -/// This calls the function `$fn` with the optional -/// arguments `$args` on all tables - returning early -/// (within whatever scope this is called) if any -/// of the function calls error. -/// -/// Else, it evaluates to an `Ok((tuple, of, all, table, types, ...))`, -/// i.e., an `impl Table[Mut]` wrapped in `Ok`. -macro_rules! call_fn_on_all_tables_or_early_return { - ( - $($fn:ident $(::)?)* - ( - $($arg:ident),* $(,)? - ) - ) => {{ - Ok(( - $($fn ::)*<$crate::tables::BlockInfos>($($arg),*)?, - $($fn ::)*<$crate::tables::BlockBlobs>($($arg),*)?, - $($fn ::)*<$crate::tables::BlockHeights>($($arg),*)?, - $($fn ::)*<$crate::tables::KeyImages>($($arg),*)?, - $($fn ::)*<$crate::tables::NumOutputs>($($arg),*)?, - $($fn ::)*<$crate::tables::PrunedTxBlobs>($($arg),*)?, - $($fn ::)*<$crate::tables::PrunableHashes>($($arg),*)?, - $($fn ::)*<$crate::tables::Outputs>($($arg),*)?, - $($fn ::)*<$crate::tables::PrunableTxBlobs>($($arg),*)?, - $($fn ::)*<$crate::tables::RctOutputs>($($arg),*)?, - $($fn ::)*<$crate::tables::TxBlobs>($($arg),*)?, - $($fn ::)*<$crate::tables::TxIds>($($arg),*)?, - $($fn ::)*<$crate::tables::TxHeights>($($arg),*)?, - $($fn ::)*<$crate::tables::TxOutputs>($($arg),*)?, - $($fn ::)*<$crate::tables::TxUnlockTime>($($arg),*)?, - )) - }}; -} -pub(crate) use call_fn_on_all_tables_or_early_return; - -//---------------------------------------------------------------------------------------------------- OpenTables -/// Open all tables at once. -/// -/// This trait encapsulates the functionality of opening all tables at once. -/// It can be seen as the "constructor" for the [`Tables`](crate::tables::Tables) object. -/// -/// Note that this is already implemented on [`cuprate_database::EnvInner`], thus: -/// - You don't need to implement this -/// - It can be called using `env_inner.open_tables()` notation -/// -/// # Example -/// ```rust -/// use cuprate_blockchain::{ -/// cuprate_database::{Env, EnvInner}, -/// config::ConfigBuilder, -/// tables::{Tables, TablesMut}, -/// OpenTables, -/// }; -/// -/// # fn main() -> Result<(), Box> { -/// // Create a configuration for the database environment. -/// let tmp_dir = tempfile::tempdir()?; -/// let db_dir = tmp_dir.path().to_owned(); -/// let config = ConfigBuilder::new() -/// .db_directory(db_dir.into()) -/// .build(); -/// -/// // Initialize the database environment. -/// let env = cuprate_blockchain::open(config)?; -/// -/// // Open up a transaction. -/// let env_inner = env.env_inner(); -/// let tx_rw = env_inner.tx_rw()?; -/// -/// // Open _all_ tables in write mode using [`OpenTables::open_tables_mut`]. -/// // Note how this is being called on `env_inner`. -/// // | -/// // v -/// let mut tables = env_inner.open_tables_mut(&tx_rw)?; -/// # Ok(()) } -/// ``` -pub trait OpenTables<'env, Ro, Rw> -where - Self: 'env, - Ro: TxRo<'env>, - Rw: TxRw<'env>, -{ - /// Open all tables in read/iter mode. - /// - /// This calls [`EnvInner::open_db_ro`] on all database tables - /// and returns a structure that allows access to all tables. - /// - /// # Errors - /// This will only return [`RuntimeError::Io`] if it errors. - /// - /// As all tables are created upon [`crate::open`], - /// this function will never error because a table doesn't exist. - fn open_tables(&'env self, tx_ro: &Ro) -> Result; - - /// Open all tables in read-write mode. - /// - /// This calls [`EnvInner::open_db_rw`] on all database tables - /// and returns a structure that allows access to all tables. - /// - /// # Errors - /// This will only return [`RuntimeError::Io`] on errors. - fn open_tables_mut(&'env self, tx_rw: &Rw) -> Result; - - /// Create all database tables. - /// - /// This will create all the [`Table`](cuprate_database::Table)s - /// found in [`tables`](crate::tables). - /// - /// # Errors - /// This will only return [`RuntimeError::Io`] on errors. - fn create_tables(&'env self, tx_rw: &Rw) -> Result<(), RuntimeError>; -} - -impl<'env, Ei, Ro, Rw> OpenTables<'env, Ro, Rw> for Ei -where - Ei: EnvInner<'env, Ro, Rw>, - Ro: TxRo<'env>, - Rw: TxRw<'env>, -{ - fn open_tables(&'env self, tx_ro: &Ro) -> Result { - call_fn_on_all_tables_or_early_return! { - Self::open_db_ro(self, tx_ro) - } - } - - fn open_tables_mut(&'env self, tx_rw: &Rw) -> Result { - call_fn_on_all_tables_or_early_return! { - Self::open_db_rw(self, tx_rw) - } - } - - fn create_tables(&'env self, tx_rw: &Rw) -> Result<(), RuntimeError> { - match call_fn_on_all_tables_or_early_return! { - Self::create_db(self, tx_rw) - } { - Ok(_) => Ok(()), - Err(e) => Err(e), - } - } -} - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - use std::borrow::Cow; - - use cuprate_database::{Env, EnvInner}; - - use crate::{config::ConfigBuilder, tests::tmp_concrete_env}; - - use super::*; - - /// Tests that [`crate::open`] creates all tables. - #[test] - fn test_all_tables_are_created() { - let (env, _tmp) = tmp_concrete_env(); - let env_inner = env.env_inner(); - let tx_ro = env_inner.tx_ro().unwrap(); - env_inner.open_tables(&tx_ro).unwrap(); - } - - /// Tests that direct usage of - /// [`cuprate_database::ConcreteEnv`] - /// does NOT create all tables. - #[test] - #[should_panic(expected = "`Result::unwrap()` on an `Err` value: TableNotFound")] - fn test_no_tables_are_created() { - let tempdir = tempfile::tempdir().unwrap(); - let config = ConfigBuilder::new() - .db_directory(Cow::Owned(tempdir.path().into())) - .low_power() - .build(); - let env = cuprate_database::ConcreteEnv::open(config.db_config).unwrap(); - - let env_inner = env.env_inner(); - let tx_ro = env_inner.tx_ro().unwrap(); - env_inner.open_tables(&tx_ro).unwrap(); - } -} diff --git a/storage/blockchain/src/ops/alt_block/block.rs b/storage/blockchain/src/ops/alt_block/block.rs new file mode 100644 index 0000000..480bd7d --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/block.rs @@ -0,0 +1,334 @@ +use bytemuck::TransparentWrapper; +use monero_serai::block::{Block, BlockHeader}; + +use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, StorableVec}; +use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits}; +use cuprate_types::{AltBlockInformation, Chain, ChainId, ExtendedBlockHeader, HardFork}; + +use crate::{ + ops::{ + alt_block::{add_alt_transaction_blob, get_alt_transaction, update_alt_chain_info}, + block::get_block_info, + macros::doc_error, + }, + tables::{Tables, TablesMut}, + types::{AltBlockHeight, BlockHash, BlockHeight, CompactAltBlockInfo}, +}; + +/// Flush all alt-block data from all the alt-block tables. +/// +/// This function completely empties the alt block tables. +pub fn flush_alt_blocks<'a, E: cuprate_database::EnvInner<'a>>( + env_inner: &E, + tx_rw: &mut E::Rw<'_>, +) -> DbResult<()> { + use crate::tables::{ + AltBlockBlobs, AltBlockHeights, AltBlocksInfo, AltChainInfos, AltTransactionBlobs, + AltTransactionInfos, + }; + + env_inner.clear_db::(tx_rw)?; + env_inner.clear_db::(tx_rw)?; + env_inner.clear_db::(tx_rw)?; + env_inner.clear_db::(tx_rw)?; + env_inner.clear_db::(tx_rw)?; + env_inner.clear_db::(tx_rw) +} + +/// Add a [`AltBlockInformation`] to the database. +/// +/// This extracts all the data from the input block and +/// maps/adds them to the appropriate database tables. +/// +#[doc = doc_error!()] +/// +/// # Panics +/// This function will panic if: +/// - `alt_block.height` is == `0` +/// - `alt_block.txs.len()` != `alt_block.block.transactions.len()` +/// +pub fn add_alt_block(alt_block: &AltBlockInformation, tables: &mut impl TablesMut) -> DbResult<()> { + let alt_block_height = AltBlockHeight { + chain_id: alt_block.chain_id.into(), + height: alt_block.height, + }; + + tables + .alt_block_heights_mut() + .put(&alt_block.block_hash, &alt_block_height)?; + + update_alt_chain_info(&alt_block_height, &alt_block.block.header.previous, tables)?; + + let (cumulative_difficulty_low, cumulative_difficulty_high) = + split_u128_into_low_high_bits(alt_block.cumulative_difficulty); + + let alt_block_info = CompactAltBlockInfo { + block_hash: alt_block.block_hash, + pow_hash: alt_block.pow_hash, + height: alt_block.height, + weight: alt_block.weight, + long_term_weight: alt_block.long_term_weight, + cumulative_difficulty_low, + cumulative_difficulty_high, + }; + + tables + .alt_blocks_info_mut() + .put(&alt_block_height, &alt_block_info)?; + + tables.alt_block_blobs_mut().put( + &alt_block_height, + StorableVec::wrap_ref(&alt_block.block_blob), + )?; + + assert_eq!(alt_block.txs.len(), alt_block.block.transactions.len()); + for tx in &alt_block.txs { + add_alt_transaction_blob(tx, tables)?; + } + + Ok(()) +} + +/// Retrieves an [`AltBlockInformation`] from the database. +/// +/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others +/// even if they are technically part of this chain. +#[doc = doc_error!()] +pub fn get_alt_block( + alt_block_height: &AltBlockHeight, + tables: &impl Tables, +) -> DbResult { + let block_info = tables.alt_blocks_info().get(alt_block_height)?; + + let block_blob = tables.alt_block_blobs().get(alt_block_height)?.0; + + let block = Block::read(&mut block_blob.as_slice())?; + + let txs = block + .transactions + .iter() + .map(|tx_hash| get_alt_transaction(tx_hash, tables)) + .collect::>()?; + + Ok(AltBlockInformation { + block, + block_blob, + txs, + block_hash: block_info.block_hash, + pow_hash: block_info.pow_hash, + height: block_info.height, + weight: block_info.weight, + long_term_weight: block_info.long_term_weight, + cumulative_difficulty: combine_low_high_bits_to_u128( + block_info.cumulative_difficulty_low, + block_info.cumulative_difficulty_high, + ), + chain_id: alt_block_height.chain_id.into(), + }) +} + +/// Retrieves the hash of the block at the given `block_height` on the alt chain with +/// the given [`ChainId`]. +/// +/// This function will get blocks from the whole chain, for example if you were to ask for height +/// `0` with any [`ChainId`] (as long that chain actually exists) you will get the main chain genesis. +/// +#[doc = doc_error!()] +pub fn get_alt_block_hash( + block_height: &BlockHeight, + alt_chain: ChainId, + tables: &impl Tables, +) -> DbResult { + let alt_chains = tables.alt_chain_infos(); + + // First find what [`ChainId`] this block would be stored under. + let original_chain = { + let mut chain = alt_chain.into(); + loop { + let chain_info = alt_chains.get(&chain)?; + + if chain_info.common_ancestor_height < *block_height { + break Chain::Alt(chain.into()); + } + + match chain_info.parent_chain.into() { + Chain::Main => break Chain::Main, + Chain::Alt(alt_chain_id) => { + chain = alt_chain_id.into(); + continue; + } + } + } + }; + + // Get the block hash. + match original_chain { + Chain::Main => { + get_block_info(block_height, tables.block_infos()).map(|info| info.block_hash) + } + Chain::Alt(chain_id) => tables + .alt_blocks_info() + .get(&AltBlockHeight { + chain_id: chain_id.into(), + height: *block_height, + }) + .map(|info| info.block_hash), + } +} + +/// Retrieves the [`ExtendedBlockHeader`] of the alt-block with an exact [`AltBlockHeight`]. +/// +/// This function will look at only the blocks with the given [`AltBlockHeight::chain_id`], no others +/// even if they are technically part of this chain. +/// +#[doc = doc_error!()] +pub fn get_alt_block_extended_header_from_height( + height: &AltBlockHeight, + table: &impl Tables, +) -> DbResult { + let block_info = table.alt_blocks_info().get(height)?; + + let block_blob = table.alt_block_blobs().get(height)?.0; + + let block_header = BlockHeader::read(&mut block_blob.as_slice())?; + + Ok(ExtendedBlockHeader { + version: HardFork::from_version(block_header.hardfork_version) + .expect("Block in DB must have correct version"), + vote: block_header.hardfork_version, + timestamp: block_header.timestamp, + cumulative_difficulty: combine_low_high_bits_to_u128( + block_info.cumulative_difficulty_low, + block_info.cumulative_difficulty_high, + ), + block_weight: block_info.weight, + long_term_weight: block_info.long_term_weight, + }) +} + +#[cfg(test)] +mod tests { + use std::num::NonZero; + + use cuprate_database::{Env, EnvInner, TxRw}; + use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3}; + use cuprate_types::{Chain, ChainId}; + + use crate::{ + ops::{ + alt_block::{ + add_alt_block, flush_alt_blocks, get_alt_block, + get_alt_block_extended_header_from_height, get_alt_block_hash, + get_alt_chain_history_ranges, + }, + block::{add_block, pop_block}, + }, + tables::{OpenTables, Tables}, + tests::{assert_all_tables_are_empty, map_verified_block_to_alt, tmp_concrete_env}, + types::AltBlockHeight, + }; + + #[expect(clippy::range_plus_one)] + #[test] + fn all_alt_blocks() { + let (env, _tmp) = tmp_concrete_env(); + let env_inner = env.env_inner(); + assert_all_tables_are_empty(&env); + + let chain_id = ChainId(NonZero::new(1).unwrap()); + + // Add initial block. + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + let mut initial_block = BLOCK_V1_TX2.clone(); + initial_block.height = 0; + + add_block(&initial_block, &mut tables).unwrap(); + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + let alt_blocks = [ + map_verified_block_to_alt(BLOCK_V9_TX3.clone(), chain_id), + map_verified_block_to_alt(BLOCK_V16_TX0.clone(), chain_id), + ]; + + // Add alt-blocks + { + let tx_rw = env_inner.tx_rw().unwrap(); + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + + let mut prev_hash = BLOCK_V1_TX2.block_hash; + for (i, mut alt_block) in alt_blocks.into_iter().enumerate() { + let height = i + 1; + + alt_block.height = height; + alt_block.block.header.previous = prev_hash; + alt_block.block_blob = alt_block.block.serialize(); + + add_alt_block(&alt_block, &mut tables).unwrap(); + + let alt_height = AltBlockHeight { + chain_id: chain_id.into(), + height, + }; + + let alt_block_2 = get_alt_block(&alt_height, &tables).unwrap(); + assert_eq!(alt_block.block, alt_block_2.block); + + let headers = get_alt_chain_history_ranges( + 0..(height + 1), + chain_id, + tables.alt_chain_infos(), + ) + .unwrap(); + + assert_eq!(headers.len(), 2); + assert_eq!(headers[1], (Chain::Main, 0..1)); + assert_eq!(headers[0], (Chain::Alt(chain_id), 1..(height + 1))); + + prev_hash = alt_block.block_hash; + + let header = + get_alt_block_extended_header_from_height(&alt_height, &tables).unwrap(); + + assert_eq!(header.timestamp, alt_block.block.header.timestamp); + assert_eq!(header.block_weight, alt_block.weight); + assert_eq!(header.long_term_weight, alt_block.long_term_weight); + assert_eq!( + header.cumulative_difficulty, + alt_block.cumulative_difficulty + ); + assert_eq!( + header.version.as_u8(), + alt_block.block.header.hardfork_version + ); + assert_eq!(header.vote, alt_block.block.header.hardfork_signal); + + let block_hash = get_alt_block_hash(&height, chain_id, &tables).unwrap(); + + assert_eq!(block_hash, alt_block.block_hash); + } + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + { + let mut tx_rw = env_inner.tx_rw().unwrap(); + + flush_alt_blocks(&env_inner, &mut tx_rw).unwrap(); + + let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); + pop_block(None, &mut tables).unwrap(); + + drop(tables); + TxRw::commit(tx_rw).unwrap(); + } + + assert_all_tables_are_empty(&env); + } +} diff --git a/storage/blockchain/src/ops/alt_block/chain.rs b/storage/blockchain/src/ops/alt_block/chain.rs new file mode 100644 index 0000000..676fd7f --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/chain.rs @@ -0,0 +1,117 @@ +use std::cmp::{max, min}; + +use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, RuntimeError}; +use cuprate_types::{Chain, ChainId}; + +use crate::{ + ops::macros::{doc_add_alt_block_inner_invariant, doc_error}, + tables::{AltChainInfos, TablesMut}, + types::{AltBlockHeight, AltChainInfo, BlockHash, BlockHeight}, +}; + +/// Updates the [`AltChainInfo`] with information on a new alt-block. +/// +#[doc = doc_add_alt_block_inner_invariant!()] +#[doc = doc_error!()] +/// +/// # Panics +/// +/// This will panic if [`AltBlockHeight::height`] == `0`. +pub fn update_alt_chain_info( + alt_block_height: &AltBlockHeight, + prev_hash: &BlockHash, + tables: &mut impl TablesMut, +) -> DbResult<()> { + let parent_chain = match tables.alt_block_heights().get(prev_hash) { + Ok(alt_parent_height) => Chain::Alt(alt_parent_height.chain_id.into()), + Err(RuntimeError::KeyNotFound) => Chain::Main, + Err(e) => return Err(e), + }; + + // try update the info if one exists for this chain. + let update = tables + .alt_chain_infos_mut() + .update(&alt_block_height.chain_id, |mut info| { + if info.chain_height < alt_block_height.height + 1 { + // If the chain height is increasing we only need to update the chain height. + info.chain_height = alt_block_height.height + 1; + } else { + // If the chain height is not increasing we are popping blocks and need to update the + // split point. + info.common_ancestor_height = alt_block_height.height.checked_sub(1).unwrap(); + info.parent_chain = parent_chain.into(); + } + + info.chain_height = alt_block_height.height + 1; + Some(info) + }); + + match update { + Ok(()) => return Ok(()), + Err(RuntimeError::KeyNotFound) => (), + Err(e) => return Err(e), + } + + // If one doesn't already exist add it. + + tables.alt_chain_infos_mut().put( + &alt_block_height.chain_id, + &AltChainInfo { + parent_chain: parent_chain.into(), + common_ancestor_height: alt_block_height.height.checked_sub(1).unwrap(), + chain_height: alt_block_height.height + 1, + }, + ) +} + +/// Get the height history of an alt-chain in reverse chronological order. +/// +/// Height history is a list of height ranges with the corresponding [`Chain`] they are stored under. +/// For example if your range goes from height `0` the last entry in the list will be [`Chain::Main`] +/// upto the height where the first split occurs. +#[doc = doc_error!()] +pub fn get_alt_chain_history_ranges( + range: std::ops::Range, + alt_chain: ChainId, + alt_chain_infos: &impl DatabaseRo, +) -> DbResult)>> { + let mut ranges = Vec::with_capacity(5); + + let mut i = range.end; + let mut current_chain_id = alt_chain.into(); + while i > range.start { + let chain_info = alt_chain_infos.get(¤t_chain_id)?; + + let start_height = max(range.start, chain_info.common_ancestor_height + 1); + let end_height = min(i, chain_info.chain_height); + + ranges.push(( + Chain::Alt(current_chain_id.into()), + start_height..end_height, + )); + i = chain_info.common_ancestor_height + 1; + + match chain_info.parent_chain.into() { + Chain::Main => { + ranges.push((Chain::Main, range.start..i)); + break; + } + Chain::Alt(alt_chain_id) => { + let alt_chain_id = alt_chain_id.into(); + + // This shouldn't be possible to hit, however in a test with custom (invalid) block data + // this caused an infinite loop. + if alt_chain_id == current_chain_id { + return Err(RuntimeError::Io(std::io::Error::other( + "Loop detected in ChainIDs, invalid alt chain.", + ))); + } + + current_chain_id = alt_chain_id; + continue; + } + } + } + + Ok(ranges) +} diff --git a/storage/blockchain/src/ops/alt_block/mod.rs b/storage/blockchain/src/ops/alt_block/mod.rs new file mode 100644 index 0000000..1654d27 --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/mod.rs @@ -0,0 +1,58 @@ +//! Alternative Block/Chain Ops +//! +//! Alternative chains are chains that potentially have more proof-of-work than the main-chain +//! which we are tracking to potentially re-org to. +//! +//! Cuprate uses an ID system for alt-chains. When a split is made from the main-chain we generate +//! a random [`ChainID`](cuprate_types::ChainId) and assign it to the chain: +//! +//! ```text +//! | +//! | +//! | split +//! |------------- +//! | | +//! | | +//! \|/ \|/ +//! main-chain ChainID(X) +//! ``` +//! +//! In that example if we were to receive an alt-block which immediately follows the top block of `ChainID(X)` +//! then that block will also be stored under `ChainID(X)`. However, if it follows from another block from `ChainID(X)` +//! we will split into a chain with a different ID: +//! +//! ```text +//! | +//! | +//! | split +//! |------------- +//! | | split +//! | |-------------| +//! | | | +//! | | | +//! | | | +//! \|/ \|/ \|/ +//! main-chain ChainID(X) ChainID(Z) +//! ``` +//! +//! As you can see if we wanted to get all the alt-blocks in `ChainID(Z)` that now includes some blocks from `ChainID(X)` as well. +//! [`get_alt_chain_history_ranges`] covers this and is the method to get the ranges of heights needed from each [`ChainID`](cuprate_types::ChainId) +//! to get all the alt-blocks in a given [`ChainID`](cuprate_types::ChainId). +//! +//! Although this should be kept in mind as a possibility, because Cuprate's block downloader will only track a single chain it is +//! unlikely that we will be tracking [`ChainID`](cuprate_types::ChainId)s that don't immediately connect to the main-chain. +//! +//! ## Why not use the block's `previous` field? +//! +//! Although that would be easier, it makes getting a range of block extremely slow, as we have to build the weight cache to verify +//! blocks, roughly 100,000 block headers needed, this cost is too high. +mod block; +mod chain; +mod tx; + +pub use block::{ + add_alt_block, flush_alt_blocks, get_alt_block, get_alt_block_extended_header_from_height, + get_alt_block_hash, +}; +pub use chain::{get_alt_chain_history_ranges, update_alt_chain_info}; +pub use tx::{add_alt_transaction_blob, get_alt_transaction}; diff --git a/storage/blockchain/src/ops/alt_block/tx.rs b/storage/blockchain/src/ops/alt_block/tx.rs new file mode 100644 index 0000000..b410fed --- /dev/null +++ b/storage/blockchain/src/ops/alt_block/tx.rs @@ -0,0 +1,76 @@ +use bytemuck::TransparentWrapper; +use monero_serai::transaction::Transaction; + +use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, RuntimeError, StorableVec}; +use cuprate_types::VerifiedTransactionInformation; + +use crate::{ + ops::macros::{doc_add_alt_block_inner_invariant, doc_error}, + tables::{Tables, TablesMut}, + types::{AltTransactionInfo, TxHash}, +}; + +/// Adds a [`VerifiedTransactionInformation`] from an alt-block +/// if it is not already in the DB. +/// +/// If the transaction is in the main-chain this function will still fill in the +/// [`AltTransactionInfos`](crate::tables::AltTransactionInfos) table, as that +/// table holds data which we don't keep around for main-chain txs. +/// +#[doc = doc_add_alt_block_inner_invariant!()] +#[doc = doc_error!()] +pub fn add_alt_transaction_blob( + tx: &VerifiedTransactionInformation, + tables: &mut impl TablesMut, +) -> DbResult<()> { + tables.alt_transaction_infos_mut().put( + &tx.tx_hash, + &AltTransactionInfo { + tx_weight: tx.tx_weight, + fee: tx.fee, + tx_hash: tx.tx_hash, + }, + )?; + + if tables.tx_ids().get(&tx.tx_hash).is_ok() + || tables.alt_transaction_blobs().get(&tx.tx_hash).is_ok() + { + return Ok(()); + } + + tables + .alt_transaction_blobs_mut() + .put(&tx.tx_hash, StorableVec::wrap_ref(&tx.tx_blob))?; + + Ok(()) +} + +/// Retrieve a [`VerifiedTransactionInformation`] from the database. +/// +#[doc = doc_error!()] +pub fn get_alt_transaction( + tx_hash: &TxHash, + tables: &impl Tables, +) -> DbResult { + let tx_info = tables.alt_transaction_infos().get(tx_hash)?; + + let tx_blob = match tables.alt_transaction_blobs().get(tx_hash) { + Ok(blob) => blob.0, + Err(RuntimeError::KeyNotFound) => { + let tx_id = tables.tx_ids().get(tx_hash)?; + + let blob = tables.tx_blobs().get(&tx_id)?; + + blob.0 + } + Err(e) => return Err(e), + }; + + Ok(VerifiedTransactionInformation { + tx: Transaction::read(&mut tx_blob.as_slice()).unwrap(), + tx_blob, + tx_weight: tx_info.tx_weight, + fee: tx_info.fee, + tx_hash: tx_info.tx_hash, + }) +} diff --git a/storage/blockchain/src/ops/block.rs b/storage/blockchain/src/ops/block.rs index 9097f0e..2dc88aa 100644 --- a/storage/blockchain/src/ops/block.rs +++ b/storage/blockchain/src/ops/block.rs @@ -1,23 +1,35 @@ -//! Blocks functions. +//! Block functions. //---------------------------------------------------------------------------------------------------- Import use bytemuck::TransparentWrapper; -use monero_serai::block::Block; +use bytes::Bytes; +use monero_serai::{ + block::{Block, BlockHeader}, + transaction::Transaction, +}; use cuprate_database::{ - RuntimeError, StorableVec, {DatabaseRo, DatabaseRw}, + DbResult, RuntimeError, StorableVec, {DatabaseIter, DatabaseRo, DatabaseRw}, +}; +use cuprate_helper::cast::usize_to_u64; +use cuprate_helper::{ + map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits}, + tx::tx_fee, +}; +use cuprate_types::{ + AltBlockInformation, BlockCompleteEntry, ChainId, ExtendedBlockHeader, HardFork, + TransactionBlobs, VerifiedBlockInformation, VerifiedTransactionInformation, }; -use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits}; -use cuprate_types::{ExtendedBlockHeader, VerifiedBlockInformation}; use crate::{ ops::{ + alt_block, blockchain::{chain_height, cumulative_generated_coins}, macros::doc_error, output::get_rct_num_outputs, tx::{add_tx, remove_tx}, }, - tables::{BlockHeights, BlockInfos, Tables, TablesMut}, + tables::{BlockHeights, BlockInfos, Tables, TablesIter, TablesMut}, types::{BlockHash, BlockHeight, BlockInfo}, }; @@ -32,17 +44,9 @@ use crate::{ /// # Panics /// This function will panic if: /// - `block.height > u32::MAX` (not normally possible) -/// - `block.height` is not != [`chain_height`] -/// -/// # Already exists -/// This function will operate normally even if `block` already -/// exists, i.e., this function will not return `Err` even if you -/// call this function infinitely with the same block. +/// - `block.height` is != [`chain_height`] // no inline, too big. -pub fn add_block( - block: &VerifiedBlockInformation, - tables: &mut impl TablesMut, -) -> Result<(), RuntimeError> { +pub fn add_block(block: &VerifiedBlockInformation, tables: &mut impl TablesMut) -> DbResult<()> { //------------------------------------------------------ Check preconditions first // Cast height to `u32` for storage (handled at top of function). @@ -65,19 +69,19 @@ pub fn add_block( #[cfg(debug_assertions)] { assert_eq!(block.block.serialize(), block.block_blob); - assert_eq!(block.block.txs.len(), block.txs.len()); + assert_eq!(block.block.transactions.len(), block.txs.len()); for (i, tx) in block.txs.iter().enumerate() { assert_eq!(tx.tx_blob, tx.tx.serialize()); - assert_eq!(tx.tx_hash, block.block.txs[i]); + assert_eq!(tx.tx_hash, block.block.transactions[i]); } } //------------------------------------------------------ Transaction / Outputs / Key Images // Add the miner transaction first. - { - let tx = &block.block.miner_tx; - add_tx(tx, &tx.serialize(), &tx.hash(), &chain_height, tables)?; - } + let mining_tx_index = { + let tx = &block.block.miner_transaction; + add_tx(tx, &tx.serialize(), &tx.hash(), &chain_height, tables)? + }; for tx in &block.txs { add_tx(&tx.tx, &tx.tx_blob, &tx.tx_hash, &chain_height, tables)?; @@ -89,9 +93,10 @@ pub fn add_block( // RCT output count needs account for _this_ block's outputs. let cumulative_rct_outs = get_rct_num_outputs(tables.rct_outputs())?; + // `saturating_add` is used here as cumulative generated coins overflows due to tail emission. let cumulative_generated_coins = cumulative_generated_coins(&block.height.saturating_sub(1), tables.block_infos())? - + block.generated_coins; + .saturating_add(block.generated_coins); let (cumulative_difficulty_low, cumulative_difficulty_high) = split_u128_into_low_high_bits(block.cumulative_difficulty); @@ -106,16 +111,23 @@ pub fn add_block( cumulative_rct_outs, timestamp: block.block.header.timestamp, block_hash: block.block_hash, - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - weight: block.weight as u64, - long_term_weight: block.long_term_weight as u64, + weight: block.weight, + long_term_weight: block.long_term_weight, + mining_tx_index, }, )?; - // Block blobs. - tables - .block_blobs_mut() - .put(&block.height, StorableVec::wrap_ref(&block.block_blob))?; + // Block header blob. + tables.block_header_blobs_mut().put( + &block.height, + StorableVec::wrap_ref(&block.block.header.serialize()), + )?; + + // Block transaction hashes + tables.block_txs_hashes_mut().put( + &block.height, + StorableVec::wrap_ref(&block.block.transactions), + )?; // Block heights. tables @@ -129,37 +141,145 @@ pub fn add_block( /// Remove the top/latest block from the database. /// /// The removed block's data is returned. +/// +/// If a [`ChainId`] is specified the popped block will be added to the alt block tables under +/// that [`ChainId`]. Otherwise, the block will be completely removed from the DB. #[doc = doc_error!()] /// /// In `pop_block()`'s case, [`RuntimeError::KeyNotFound`] /// will be returned if there are no blocks left. // no inline, too big pub fn pop_block( + move_to_alt_chain: Option, tables: &mut impl TablesMut, -) -> Result<(BlockHeight, BlockHash, Block), RuntimeError> { +) -> DbResult<(BlockHeight, BlockHash, Block)> { //------------------------------------------------------ Block Info // Remove block data from tables. - let (block_height, block_hash) = { - let (block_height, block_info) = tables.block_infos_mut().pop_last()?; - (block_height, block_info.block_hash) - }; + let (block_height, block_info) = tables.block_infos_mut().pop_last()?; // Block heights. - tables.block_heights_mut().delete(&block_hash)?; + tables.block_heights_mut().delete(&block_info.block_hash)?; // Block blobs. - // We deserialize the block blob into a `Block`, such - // that we can remove the associated transactions later. - let block_blob = tables.block_blobs_mut().take(&block_height)?.0; - let block = Block::read(&mut block_blob.as_slice())?; + // + // We deserialize the block header blob and mining transaction blob + // to form a `Block`, such that we can remove the associated transactions + // later. + let block_header = tables.block_header_blobs_mut().take(&block_height)?.0; + let block_txs_hashes = tables.block_txs_hashes_mut().take(&block_height)?.0; + let miner_transaction = tables.tx_blobs().get(&block_info.mining_tx_index)?.0; + let block = Block { + header: BlockHeader::read(&mut block_header.as_slice())?, + miner_transaction: Transaction::read(&mut miner_transaction.as_slice())?, + transactions: block_txs_hashes, + }; //------------------------------------------------------ Transaction / Outputs / Key Images - remove_tx(&block.miner_tx.hash(), tables)?; - for tx_hash in &block.txs { - remove_tx(tx_hash, tables)?; + remove_tx(&block.miner_transaction.hash(), tables)?; + + let remove_tx_iter = block.transactions.iter().map(|tx_hash| { + let (_, tx) = remove_tx(tx_hash, tables)?; + Ok::<_, RuntimeError>(tx) + }); + + if let Some(chain_id) = move_to_alt_chain { + let txs = remove_tx_iter + .map(|result| { + let tx = result?; + Ok(VerifiedTransactionInformation { + tx_weight: tx.weight(), + tx_blob: tx.serialize(), + tx_hash: tx.hash(), + fee: tx_fee(&tx), + tx, + }) + }) + .collect::>>()?; + + alt_block::add_alt_block( + &AltBlockInformation { + block: block.clone(), + block_blob: block.serialize(), + txs, + block_hash: block_info.block_hash, + // We know the PoW is valid for this block so just set it so it will always verify as valid. + pow_hash: [0; 32], + height: block_height, + weight: block_info.weight, + long_term_weight: block_info.long_term_weight, + cumulative_difficulty: combine_low_high_bits_to_u128( + block_info.cumulative_difficulty_low, + block_info.cumulative_difficulty_high, + ), + chain_id, + }, + tables, + )?; + } else { + for result in remove_tx_iter { + drop(result?); + } } - Ok((block_height, block_hash, block)) + Ok((block_height, block_info.block_hash, block)) +} + +//---------------------------------------------------------------------------------------------------- `get_block_blob_with_tx_indexes` +/// Retrieve a block's raw bytes, the index of the miner transaction and the number of non miner-txs in the block. +/// +#[doc = doc_error!()] +pub fn get_block_blob_with_tx_indexes( + block_height: &BlockHeight, + tables: &impl Tables, +) -> Result<(Vec, u64, usize), RuntimeError> { + let miner_tx_idx = tables.block_infos().get(block_height)?.mining_tx_index; + + let block_txs = tables.block_txs_hashes().get(block_height)?.0; + let numb_txs = block_txs.len(); + + // Get the block header + let mut block = tables.block_header_blobs().get(block_height)?.0; + + // Add the miner tx to the blob. + let mut miner_tx_blob = tables.tx_blobs().get(&miner_tx_idx)?.0; + block.append(&mut miner_tx_blob); + + // Add the blocks tx hashes. + monero_serai::io::write_varint(&block_txs.len(), &mut block) + .expect("The number of txs per block will not exceed u64::MAX"); + + let block_txs_bytes = bytemuck::must_cast_slice(&block_txs); + block.extend_from_slice(block_txs_bytes); + + Ok((block, miner_tx_idx, numb_txs)) +} + +//---------------------------------------------------------------------------------------------------- `get_block_extended_header_*` +/// Retrieve a [`BlockCompleteEntry`] from the database. +/// +#[doc = doc_error!()] +pub fn get_block_complete_entry( + block_hash: &BlockHash, + tables: &impl TablesIter, +) -> Result { + let block_height = tables.block_heights().get(block_hash)?; + let (block_blob, miner_tx_idx, numb_non_miner_txs) = + get_block_blob_with_tx_indexes(&block_height, tables)?; + + let first_tx_idx = miner_tx_idx + 1; + + let tx_blobs = tables + .tx_blobs_iter() + .get_range(first_tx_idx..(usize_to_u64(numb_non_miner_txs) + first_tx_idx))? + .map(|tx_blob| Ok(Bytes::from(tx_blob?.0))) + .collect::>()?; + + Ok(BlockCompleteEntry { + block: Bytes::from(block_blob), + txs: TransactionBlobs::Normal(tx_blobs), + pruned: false, + block_weight: 0, + }) } //---------------------------------------------------------------------------------------------------- `get_block_extended_header_*` @@ -176,35 +296,38 @@ pub fn pop_block( pub fn get_block_extended_header( block_hash: &BlockHash, tables: &impl Tables, -) -> Result { +) -> DbResult { get_block_extended_header_from_height(&tables.block_heights().get(block_hash)?, tables) } /// Same as [`get_block_extended_header`] but with a [`BlockHeight`]. #[doc = doc_error!()] +#[expect( + clippy::missing_panics_doc, + reason = "The panic is only possible with a corrupt DB" +)] #[inline] pub fn get_block_extended_header_from_height( block_height: &BlockHeight, tables: &impl Tables, -) -> Result { +) -> DbResult { let block_info = tables.block_infos().get(block_height)?; - let block_blob = tables.block_blobs().get(block_height)?.0; - let block = Block::read(&mut block_blob.as_slice())?; + let block_header_blob = tables.block_header_blobs().get(block_height)?.0; + let block_header = BlockHeader::read(&mut block_header_blob.as_slice())?; let cumulative_difficulty = combine_low_high_bits_to_u128( block_info.cumulative_difficulty_low, block_info.cumulative_difficulty_high, ); - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - #[allow(clippy::cast_possible_truncation)] Ok(ExtendedBlockHeader { cumulative_difficulty, - version: block.header.major_version, - vote: block.header.minor_version, - timestamp: block.header.timestamp, - block_weight: block_info.weight as usize, - long_term_weight: block_info.long_term_weight as usize, + version: HardFork::from_version(block_header.hardfork_version) + .expect("Stored block must have a valid hard-fork"), + vote: block_header.hardfork_signal, + timestamp: block_header.timestamp, + block_weight: block_info.weight, + long_term_weight: block_info.long_term_weight, }) } @@ -213,7 +336,7 @@ pub fn get_block_extended_header_from_height( #[inline] pub fn get_block_extended_header_top( tables: &impl Tables, -) -> Result<(ExtendedBlockHeader, BlockHeight), RuntimeError> { +) -> DbResult<(ExtendedBlockHeader, BlockHeight)> { let height = chain_height(tables.block_heights())?.saturating_sub(1); let header = get_block_extended_header_from_height(&height, tables)?; Ok((header, height)) @@ -226,7 +349,7 @@ pub fn get_block_extended_header_top( pub fn get_block_info( block_height: &BlockHeight, table_block_infos: &impl DatabaseRo, -) -> Result { +) -> DbResult { table_block_infos.get(block_height) } @@ -236,7 +359,7 @@ pub fn get_block_info( pub fn get_block_height( block_hash: &BlockHash, table_block_heights: &impl DatabaseRo, -) -> Result { +) -> DbResult { table_block_heights.get(block_hash) } @@ -251,31 +374,27 @@ pub fn get_block_height( pub fn block_exists( block_hash: &BlockHash, table_block_heights: &impl DatabaseRo, -) -> Result { +) -> DbResult { table_block_heights.contains(block_hash) } //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] -#[allow( - clippy::significant_drop_tightening, - clippy::cognitive_complexity, - clippy::too_many_lines -)] +#[expect(clippy::too_many_lines)] mod test { use pretty_assertions::assert_eq; use cuprate_database::{Env, EnvInner, TxRw}; - use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3}; - - use super::*; + use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3}; use crate::{ - open_tables::OpenTables, ops::tx::{get_tx, tx_exists}, + tables::OpenTables, tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen}, }; + use super::*; + /// Tests all above block functions. /// /// Note that this doesn't test the correctness of values added, as the @@ -290,14 +409,14 @@ mod test { assert_all_tables_are_empty(&env); let mut blocks = [ - block_v1_tx2().clone(), - block_v9_tx3().clone(), - block_v16_tx0().clone(), + BLOCK_V1_TX2.clone(), + BLOCK_V9_TX3.clone(), + BLOCK_V16_TX0.clone(), ]; // HACK: `add_block()` asserts blocks with non-sequential heights // cannot be added, to get around this, manually edit the block height. for (height, block) in blocks.iter_mut().enumerate() { - block.height = height as u64; + block.height = height; assert_eq!(block.block.serialize(), block.block_blob); } let generated_coins_sum = blocks @@ -327,7 +446,8 @@ mod test { // Assert only the proper tables were added to. AssertTableLen { block_infos: 3, - block_blobs: 3, + block_header_blobs: 3, + block_txs_hashes: 3, block_heights: 3, key_images: 69, num_outputs: 41, @@ -369,8 +489,8 @@ mod test { let b1 = block_header_from_hash; let b2 = block; assert_eq!(b1, block_header_from_height); - assert_eq!(b1.version, b2.block.header.major_version); - assert_eq!(b1.vote, b2.block.header.minor_version); + assert_eq!(b1.version.as_u8(), b2.block.header.hardfork_version); + assert_eq!(b1.vote, b2.block.header.hardfork_signal); assert_eq!(b1.timestamp, b2.block.header.timestamp); assert_eq!(b1.cumulative_difficulty, b2.cumulative_difficulty); assert_eq!(b1.block_weight, b2.weight); @@ -388,7 +508,7 @@ mod test { assert_eq!(tx.tx_blob, tx2.serialize()); assert_eq!(tx.tx_weight, tx2.weight()); - assert_eq!(tx.tx_hash, block.block.txs[i]); + assert_eq!(tx.tx_hash, block.block.transactions[i]); assert_eq!(tx.tx_hash, tx2.hash()); } } @@ -410,7 +530,8 @@ mod test { for block_hash in block_hashes.into_iter().rev() { println!("pop_block(): block_hash: {}", hex::encode(block_hash)); - let (_popped_height, popped_hash, _popped_block) = pop_block(&mut tables).unwrap(); + let (_popped_height, popped_hash, _popped_block) = + pop_block(None, &mut tables).unwrap(); assert_eq!(block_hash, popped_hash); @@ -438,9 +559,9 @@ mod test { let tx_rw = env_inner.tx_rw().unwrap(); let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); - let mut block = block_v9_tx3().clone(); + let mut block = BLOCK_V9_TX3.clone(); - block.height = u64::from(u32::MAX) + 1; + block.height = cuprate_helper::cast::u32_to_usize(u32::MAX) + 1; add_block(&block, &mut tables).unwrap(); } @@ -457,7 +578,7 @@ mod test { let tx_rw = env_inner.tx_rw().unwrap(); let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap(); - let mut block = block_v9_tx3().clone(); + let mut block = BLOCK_V9_TX3.clone(); // HACK: `add_block()` asserts blocks with non-sequential heights // cannot be added, to get around this, manually edit the block height. block.height = 0; diff --git a/storage/blockchain/src/ops/blockchain.rs b/storage/blockchain/src/ops/blockchain.rs index 16e0a3c..54dd752 100644 --- a/storage/blockchain/src/ops/blockchain.rs +++ b/storage/blockchain/src/ops/blockchain.rs @@ -1,12 +1,12 @@ //! Blockchain functions - chain height, generated coins, etc. //---------------------------------------------------------------------------------------------------- Import -use cuprate_database::{DatabaseRo, RuntimeError}; +use cuprate_database::{DatabaseRo, DbResult, RuntimeError}; use crate::{ - ops::macros::doc_error, + ops::{block::block_exists, macros::doc_error}, tables::{BlockHeights, BlockInfos}, - types::BlockHeight, + types::{BlockHash, BlockHeight}, }; //---------------------------------------------------------------------------------------------------- Free Functions @@ -22,10 +22,9 @@ use crate::{ /// So the height of a new block would be `chain_height()`. #[doc = doc_error!()] #[inline] -pub fn chain_height( - table_block_heights: &impl DatabaseRo, -) -> Result { - table_block_heights.len() +pub fn chain_height(table_block_heights: &impl DatabaseRo) -> DbResult { + #[expect(clippy::cast_possible_truncation, reason = "we enforce 64-bit")] + table_block_heights.len().map(|height| height as usize) } /// Retrieve the height of the top block. @@ -44,10 +43,11 @@ pub fn chain_height( #[inline] pub fn top_block_height( table_block_heights: &impl DatabaseRo, -) -> Result { +) -> DbResult { match table_block_heights.len()? { 0 => Err(RuntimeError::KeyNotFound), - height => Ok(height - 1), + #[expect(clippy::cast_possible_truncation, reason = "we enforce 64-bit")] + height => Ok(height as usize - 1), } } @@ -68,7 +68,7 @@ pub fn top_block_height( pub fn cumulative_generated_coins( block_height: &BlockHeight, table_block_infos: &impl DatabaseRo, -) -> Result { +) -> DbResult { match table_block_infos.get(block_height) { Ok(block_info) => Ok(block_info.cumulative_generated_coins), Err(RuntimeError::KeyNotFound) if block_height == &0 => Ok(0), @@ -76,20 +76,57 @@ pub fn cumulative_generated_coins( } } +/// Find the split point between our chain and a list of [`BlockHash`]s from another chain. +/// +/// This function accepts chains in chronological and reverse chronological order, however +/// if the wrong order is specified the return value is meaningless. +/// +/// For chronologically ordered chains this will return the index of the first unknown, for reverse +/// chronologically ordered chains this will return the index of the first known. +/// +/// If all blocks are known for chronologically ordered chains or unknown for reverse chronologically +/// ordered chains then the length of the chain will be returned. +#[doc = doc_error!()] +#[inline] +pub fn find_split_point( + block_ids: &[BlockHash], + chronological_order: bool, + table_block_heights: &impl DatabaseRo, +) -> Result { + let mut err = None; + + // Do a binary search to find the first unknown/known block in the batch. + let idx = block_ids.partition_point(|block_id| { + match block_exists(block_id, table_block_heights) { + Ok(exists) => exists == chronological_order, + Err(e) => { + err.get_or_insert(e); + // if this happens the search is scrapped, just return `false` back. + false + } + } + }); + + if let Some(e) = err { + return Err(e); + } + + Ok(idx) +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { use pretty_assertions::assert_eq; use cuprate_database::{Env, EnvInner, TxRw}; - use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3}; + use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3}; use super::*; use crate::{ - open_tables::OpenTables, ops::block::add_block, - tables::Tables, + tables::{OpenTables, Tables}, tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen}, }; @@ -107,11 +144,11 @@ mod test { assert_all_tables_are_empty(&env); let mut blocks = [ - block_v1_tx2().clone(), - block_v9_tx3().clone(), - block_v16_tx0().clone(), + BLOCK_V1_TX2.clone(), + BLOCK_V9_TX3.clone(), + BLOCK_V16_TX0.clone(), ]; - let blocks_len = u64::try_from(blocks.len()).unwrap(); + let blocks_len = blocks.len(); // Add blocks. { @@ -128,7 +165,6 @@ mod test { ); for (i, block) in blocks.iter_mut().enumerate() { - let i = u64::try_from(i).unwrap(); // HACK: `add_block()` asserts blocks with non-sequential heights // cannot be added, to get around this, manually edit the block height. block.height = i; @@ -138,7 +174,8 @@ mod test { // Assert reads are correct. AssertTableLen { block_infos: 3, - block_blobs: 3, + block_header_blobs: 3, + block_txs_hashes: 3, block_heights: 3, key_images: 69, num_outputs: 41, diff --git a/storage/blockchain/src/ops/key_image.rs b/storage/blockchain/src/ops/key_image.rs index a518490..5f17912 100644 --- a/storage/blockchain/src/ops/key_image.rs +++ b/storage/blockchain/src/ops/key_image.rs @@ -1,7 +1,7 @@ //! Key image functions. //---------------------------------------------------------------------------------------------------- Import -use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError}; +use cuprate_database::{DatabaseRo, DatabaseRw, DbResult}; use crate::{ ops::macros::{doc_add_block_inner_invariant, doc_error}, @@ -17,7 +17,7 @@ use crate::{ pub fn add_key_image( key_image: &KeyImage, table_key_images: &mut impl DatabaseRw, -) -> Result<(), RuntimeError> { +) -> DbResult<()> { table_key_images.put(key_image, &()) } @@ -28,7 +28,7 @@ pub fn add_key_image( pub fn remove_key_image( key_image: &KeyImage, table_key_images: &mut impl DatabaseRw, -) -> Result<(), RuntimeError> { +) -> DbResult<()> { table_key_images.delete(key_image) } @@ -38,7 +38,7 @@ pub fn remove_key_image( pub fn key_image_exists( key_image: &KeyImage, table_key_images: &impl DatabaseRo, -) -> Result { +) -> DbResult { table_key_images.contains(key_image) } @@ -52,8 +52,7 @@ mod test { use super::*; use crate::{ - open_tables::OpenTables, - tables::{Tables, TablesMut}, + tables::{OpenTables, Tables, TablesMut}, tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen}, }; diff --git a/storage/blockchain/src/ops/macros.rs b/storage/blockchain/src/ops/macros.rs index b7cdba4..9c6ef7d 100644 --- a/storage/blockchain/src/ops/macros.rs +++ b/storage/blockchain/src/ops/macros.rs @@ -8,7 +8,7 @@ macro_rules! doc_error { () => { r#"# Errors -This function returns [`RuntimeError::KeyNotFound`] if the input (if applicable) doesn't exist or other `RuntimeError`'s on database errors."# +This function returns [`cuprate_database::RuntimeError::KeyNotFound`] if the input (if applicable) doesn't exist or other `RuntimeError`'s on database errors."# }; } pub(super) use doc_error; @@ -31,3 +31,25 @@ When calling this function, ensure that either: }; } pub(super) use doc_add_block_inner_invariant; + +/// Generate `# Invariant` documentation for internal alt block `fn`'s +/// that should be called directly with caution. +/// +/// This is pretty much the same as [`doc_add_block_inner_invariant`], +/// it's not worth the effort to reduce the duplication. +macro_rules! doc_add_alt_block_inner_invariant { + () => { + r#"# ⚠️ Invariant ⚠️ +This function mainly exists to be used internally by the parent function [`crate::ops::alt_block::add_alt_block`]. + +`add_alt_block()` makes sure all data related to the input is mutated, while +this function _does not_, it specifically mutates _particular_ tables. + +This is usually undesired - although this function is still available to call directly. + +When calling this function, ensure that either: +1. This effect (incomplete database mutation) is what is desired, or that... +2. ...the other tables will also be mutated to a correct state"# + }; +} +pub(super) use doc_add_alt_block_inner_invariant; diff --git a/storage/blockchain/src/ops/mod.rs b/storage/blockchain/src/ops/mod.rs index 5821120..96b25af 100644 --- a/storage/blockchain/src/ops/mod.rs +++ b/storage/blockchain/src/ops/mod.rs @@ -5,14 +5,14 @@ //! database operations. //! //! # `impl Table` -//! `ops/` functions take [`Tables`](crate::tables::Tables) and +//! Functions in this module take [`Tables`](crate::tables::Tables) and //! [`TablesMut`](crate::tables::TablesMut) directly - these are //! _already opened_ database tables. //! -//! As such, the function puts the responsibility -//! of transactions, tables, etc on the caller. +//! As such, the responsibility of +//! transactions, tables, etc, are on the caller. //! -//! This does mean these functions are mostly as lean +//! Notably, this means that these functions are as lean //! as possible, so calling them in a loop should be okay. //! //! # Atomicity @@ -54,16 +54,15 @@ //! ```rust //! use hex_literal::hex; //! -//! use cuprate_test_utils::data::block_v16_tx0; +//! use cuprate_test_utils::data::BLOCK_V16_TX0; //! use cuprate_blockchain::{ //! cuprate_database::{ //! ConcreteEnv, //! Env, EnvInner, //! DatabaseRo, DatabaseRw, TxRo, TxRw, //! }, -//! OpenTables, //! config::ConfigBuilder, -//! tables::{Tables, TablesMut}, +//! tables::{Tables, TablesMut, OpenTables}, //! ops::block::{add_block, pop_block}, //! }; //! @@ -72,7 +71,7 @@ //! let tmp_dir = tempfile::tempdir()?; //! let db_dir = tmp_dir.path().to_owned(); //! let config = ConfigBuilder::new() -//! .db_directory(db_dir.into()) +//! .data_directory(db_dir.into()) //! .build(); //! //! // Initialize the database environment. @@ -84,7 +83,7 @@ //! let mut tables = env_inner.open_tables_mut(&tx_rw)?; //! //! // Write a block to the database. -//! let mut block = block_v16_tx0().clone(); +//! let mut block = BLOCK_V16_TX0.clone(); //! # block.height = 0; //! add_block(&block, &mut tables)?; //! @@ -95,7 +94,7 @@ //! // Read the data, assert it is correct. //! let tx_rw = env_inner.tx_rw()?; //! let mut tables = env_inner.open_tables_mut(&tx_rw)?; -//! let (height, hash, serai_block) = pop_block(&mut tables)?; +//! let (height, hash, serai_block) = pop_block(None, &mut tables)?; //! //! assert_eq!(height, 0); //! assert_eq!(serai_block, block.block); @@ -103,6 +102,7 @@ //! # Ok(()) } //! ``` +pub mod alt_block; pub mod block; pub mod blockchain; pub mod key_image; diff --git a/storage/blockchain/src/ops/output.rs b/storage/blockchain/src/ops/output.rs index f08f7b3..96d94bb 100644 --- a/storage/blockchain/src/ops/output.rs +++ b/storage/blockchain/src/ops/output.rs @@ -1,12 +1,13 @@ //! Output functions. //---------------------------------------------------------------------------------------------------- Import -use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, Scalar}; -use monero_serai::{transaction::Timelock, H}; +use curve25519_dalek::edwards::CompressedEdwardsY; +use monero_serai::transaction::Timelock; use cuprate_database::{ - RuntimeError, {DatabaseRo, DatabaseRw}, + DbResult, RuntimeError, {DatabaseRo, DatabaseRw}, }; +use cuprate_helper::crypto::compute_zero_commitment; use cuprate_helper::map::u64_to_timelock; use cuprate_types::OutputOnChain; @@ -29,7 +30,7 @@ pub fn add_output( amount: Amount, output: &Output, tables: &mut impl TablesMut, -) -> Result { +) -> DbResult { // FIXME: this would be much better expressed with a // `btree_map::Entry`-like API, fix `trait DatabaseRw`. let num_outputs = match tables.num_outputs().get(&amount) { @@ -60,7 +61,7 @@ pub fn add_output( pub fn remove_output( pre_rct_output_id: &PreRctOutputId, tables: &mut impl TablesMut, -) -> Result<(), RuntimeError> { +) -> DbResult<()> { // Decrement the amount index by 1, or delete the entry out-right. // FIXME: this would be much better expressed with a // `btree_map::Entry`-like API, fix `trait DatabaseRw`. @@ -85,7 +86,7 @@ pub fn remove_output( pub fn get_output( pre_rct_output_id: &PreRctOutputId, table_outputs: &impl DatabaseRo, -) -> Result { +) -> DbResult { table_outputs.get(pre_rct_output_id) } @@ -94,7 +95,7 @@ pub fn get_output( /// This returns the amount of pre-RCT outputs currently stored. #[doc = doc_error!()] #[inline] -pub fn get_num_outputs(table_outputs: &impl DatabaseRo) -> Result { +pub fn get_num_outputs(table_outputs: &impl DatabaseRo) -> DbResult { table_outputs.len() } @@ -109,7 +110,7 @@ pub fn get_num_outputs(table_outputs: &impl DatabaseRo) -> Result, -) -> Result { +) -> DbResult { let amount_index = get_rct_num_outputs(table_rct_outputs)?; table_rct_outputs.put(&amount_index, rct_output)?; Ok(amount_index) @@ -122,7 +123,7 @@ pub fn add_rct_output( pub fn remove_rct_output( amount_index: &AmountIndex, table_rct_outputs: &mut impl DatabaseRw, -) -> Result<(), RuntimeError> { +) -> DbResult<()> { table_rct_outputs.delete(amount_index) } @@ -132,7 +133,7 @@ pub fn remove_rct_output( pub fn get_rct_output( amount_index: &AmountIndex, table_rct_outputs: &impl DatabaseRo, -) -> Result { +) -> DbResult { table_rct_outputs.get(amount_index) } @@ -141,9 +142,7 @@ pub fn get_rct_output( /// This returns the amount of RCT outputs currently stored. #[doc = doc_error!()] #[inline] -pub fn get_rct_num_outputs( - table_rct_outputs: &impl DatabaseRo, -) -> Result { +pub fn get_rct_num_outputs(table_rct_outputs: &impl DatabaseRo) -> DbResult { table_rct_outputs.len() } @@ -154,10 +153,8 @@ pub fn output_to_output_on_chain( output: &Output, amount: Amount, table_tx_unlock_time: &impl DatabaseRo, -) -> Result { - // FIXME: implement lookup table for common values: - // - let commitment = ED25519_BASEPOINT_POINT + H() * Scalar::from(amount); +) -> DbResult { + let commitment = compute_zero_commitment(amount); let time_lock = if output .output_flags @@ -173,7 +170,7 @@ pub fn output_to_output_on_chain( .unwrap_or(None); Ok(OutputOnChain { - height: u64::from(output.height), + height: output.height as usize, time_lock, key, commitment, @@ -192,7 +189,7 @@ pub fn output_to_output_on_chain( pub fn rct_output_to_output_on_chain( rct_output: &RctOutput, table_tx_unlock_time: &impl DatabaseRo, -) -> Result { +) -> DbResult { // INVARIANT: Commitments stored are valid when stored by the database. let commitment = CompressedEdwardsY::from_slice(&rct_output.commitment) .unwrap() @@ -213,7 +210,7 @@ pub fn rct_output_to_output_on_chain( .unwrap_or(None); Ok(OutputOnChain { - height: u64::from(rct_output.height), + height: rct_output.height as usize, time_lock, key, commitment, @@ -224,10 +221,7 @@ pub fn rct_output_to_output_on_chain( /// /// Note that this still support RCT outputs, in that case, [`PreRctOutputId::amount`] should be `0`. #[doc = doc_error!()] -pub fn id_to_output_on_chain( - id: &PreRctOutputId, - tables: &impl Tables, -) -> Result { +pub fn id_to_output_on_chain(id: &PreRctOutputId, tables: &impl Tables) -> DbResult { // v2 transactions. if id.amount == 0 { let rct_output = get_rct_output(&id.amount_index, tables.rct_outputs())?; @@ -254,8 +248,7 @@ mod test { use cuprate_database::{Env, EnvInner}; use crate::{ - open_tables::OpenTables, - tables::{Tables, TablesMut}, + tables::{OpenTables, Tables, TablesMut}, tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen}, types::OutputFlags, }; @@ -317,7 +310,8 @@ mod test { // Assert proper tables were added to. AssertTableLen { block_infos: 0, - block_blobs: 0, + block_header_blobs: 0, + block_txs_hashes: 0, block_heights: 0, key_images: 0, num_outputs: 1, diff --git a/storage/blockchain/src/ops/property.rs b/storage/blockchain/src/ops/property.rs index 7810000..3dbb950 100644 --- a/storage/blockchain/src/ops/property.rs +++ b/storage/blockchain/src/ops/property.rs @@ -3,10 +3,9 @@ //! SOMEDAY: the database `properties` table is not yet implemented. //---------------------------------------------------------------------------------------------------- Import +use cuprate_database::DbResult; use cuprate_pruning::PruningSeed; -use cuprate_database::RuntimeError; - use crate::ops::macros::doc_error; //---------------------------------------------------------------------------------------------------- Free Functions @@ -20,7 +19,7 @@ use crate::ops::macros::doc_error; /// // SOMEDAY /// ``` #[inline] -pub const fn get_blockchain_pruning_seed() -> Result { +pub const fn get_blockchain_pruning_seed() -> DbResult { // SOMEDAY: impl pruning. // We need a DB properties table. Ok(PruningSeed::NotPruned) @@ -36,7 +35,7 @@ pub const fn get_blockchain_pruning_seed() -> Result /// // SOMEDAY /// ``` #[inline] -pub const fn db_version() -> Result { +pub const fn db_version() -> DbResult { // SOMEDAY: We need a DB properties table. Ok(crate::constants::DATABASE_VERSION) } diff --git a/storage/blockchain/src/ops/tx.rs b/storage/blockchain/src/ops/tx.rs index 6edfbb2..0312f21 100644 --- a/storage/blockchain/src/ops/tx.rs +++ b/storage/blockchain/src/ops/tx.rs @@ -2,10 +2,10 @@ //---------------------------------------------------------------------------------------------------- Import use bytemuck::TransparentWrapper; -use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar}; use monero_serai::transaction::{Input, Timelock, Transaction}; -use cuprate_database::{DatabaseRo, DatabaseRw, RuntimeError, StorableVec}; +use cuprate_database::{DatabaseRo, DatabaseRw, DbResult, RuntimeError, StorableVec}; +use cuprate_helper::crypto::compute_zero_commitment; use crate::{ ops::{ @@ -52,7 +52,7 @@ pub fn add_tx( tx_hash: &TxHash, block_height: &BlockHeight, tables: &mut impl TablesMut, -) -> Result { +) -> DbResult { let tx_id = get_num_tx(tables.tx_ids_mut())?; //------------------------------------------------------ Transaction data @@ -68,7 +68,7 @@ pub fn add_tx( // so the `u64/usize` is stored without any tag. // // - match tx.prefix.timelock { + match tx.prefix().additional_timelock { Timelock::None => (), Timelock::Block(height) => tables.tx_unlock_time_mut().put(&tx_id, &(height as u64))?, Timelock::Time(time) => tables.tx_unlock_time_mut().put(&tx_id, &time)?, @@ -92,7 +92,7 @@ pub fn add_tx( let mut miner_tx = false; // Key images. - for inputs in &tx.prefix.inputs { + for inputs in &tx.prefix().inputs { match inputs { // Key images. Input::ToKey { key_image, .. } => { @@ -106,70 +106,61 @@ pub fn add_tx( //------------------------------------------------------ Outputs // Output bit flags. // Set to a non-zero bit value if the unlock time is non-zero. - let output_flags = match tx.prefix.timelock { + let output_flags = match tx.prefix().additional_timelock { Timelock::None => OutputFlags::empty(), Timelock::Block(_) | Timelock::Time(_) => OutputFlags::NON_ZERO_UNLOCK_TIME, }; - let mut amount_indices = Vec::with_capacity(tx.prefix.outputs.len()); - - for (i, output) in tx.prefix.outputs.iter().enumerate() { - let key = *output.key.as_bytes(); - - // Outputs with clear amounts. - let amount_index = if let Some(amount) = output.amount { - // RingCT (v2 transaction) miner outputs. - if miner_tx && tx.prefix.version == 2 { - // Create commitment. - // - // FIXME: implement lookup table for common values: - // - let commitment = (ED25519_BASEPOINT_POINT - + monero_serai::H() * Scalar::from(amount)) - .compress() - .to_bytes(); - - add_rct_output( - &RctOutput { - key, - height, - output_flags, - tx_idx: tx_id, - commitment, - }, - tables.rct_outputs_mut(), - )? - // Pre-RingCT outputs. - } else { - add_output( - amount, + let amount_indices = match &tx { + Transaction::V1 { prefix, .. } => prefix + .outputs + .iter() + .map(|output| { + // Pre-RingCT outputs. + Ok(add_output( + output.amount.unwrap_or(0), &Output { - key, + key: output.key.0, height, output_flags, tx_idx: tx_id, }, tables, )? - .amount_index - } - // RingCT outputs. - } else { - let commitment = tx.rct_signatures.base.commitments[i].compress().to_bytes(); - add_rct_output( - &RctOutput { - key, - height, - output_flags, - tx_idx: tx_id, - commitment, - }, - tables.rct_outputs_mut(), - )? - }; + .amount_index) + }) + .collect::>>()?, + Transaction::V2 { prefix, proofs } => prefix + .outputs + .iter() + .enumerate() + .map(|(i, output)| { + // Create commitment. - amount_indices.push(amount_index); - } // for each output + let commitment = if miner_tx { + compute_zero_commitment(output.amount.unwrap_or(0)) + } else { + proofs + .as_ref() + .expect("A V2 transaction with no RCT proofs is a miner tx") + .base + .commitments[i] + }; + + // Add the RCT output. + add_rct_output( + &RctOutput { + key: output.key.0, + height, + output_flags, + tx_idx: tx_id, + commitment: commitment.compress().0, + }, + tables.rct_outputs_mut(), + ) + }) + .collect::, _>>()?, + }; tables .tx_outputs_mut() @@ -195,10 +186,7 @@ pub fn add_tx( /// #[doc = doc_error!()] #[inline] -pub fn remove_tx( - tx_hash: &TxHash, - tables: &mut impl TablesMut, -) -> Result<(TxId, Transaction), RuntimeError> { +pub fn remove_tx(tx_hash: &TxHash, tables: &mut impl TablesMut) -> DbResult<(TxId, Transaction)> { //------------------------------------------------------ Transaction data let tx_id = tables.tx_ids_mut().take(tx_hash)?; let tx_blob = tables.tx_blobs_mut().take(&tx_id)?; @@ -227,7 +215,7 @@ pub fn remove_tx( //------------------------------------------------------ Key Images // Is this a miner transaction? let mut miner_tx = false; - for inputs in &tx.prefix.inputs { + for inputs in &tx.prefix().inputs { match inputs { // Key images. Input::ToKey { key_image, .. } => { @@ -240,11 +228,11 @@ pub fn remove_tx( //------------------------------------------------------ Outputs // Remove each output in the transaction. - for output in &tx.prefix.outputs { + for output in &tx.prefix().outputs { // Outputs with clear amounts. if let Some(amount) = output.amount { // RingCT miner outputs. - if miner_tx && tx.prefix.version == 2 { + if miner_tx && tx.version() == 2 { let amount_index = get_rct_num_outputs(tables.rct_outputs())? - 1; remove_rct_output(&amount_index, tables.rct_outputs_mut())?; // Pre-RingCT outputs. @@ -276,7 +264,7 @@ pub fn get_tx( tx_hash: &TxHash, table_tx_ids: &impl DatabaseRo, table_tx_blobs: &impl DatabaseRo, -) -> Result { +) -> DbResult { get_tx_from_id(&table_tx_ids.get(tx_hash)?, table_tx_blobs) } @@ -286,7 +274,7 @@ pub fn get_tx( pub fn get_tx_from_id( tx_id: &TxId, table_tx_blobs: &impl DatabaseRo, -) -> Result { +) -> DbResult { let tx_blob = table_tx_blobs.get(tx_id)?.0; Ok(Transaction::read(&mut tx_blob.as_slice())?) } @@ -303,7 +291,7 @@ pub fn get_tx_from_id( /// - etc #[doc = doc_error!()] #[inline] -pub fn get_num_tx(table_tx_ids: &impl DatabaseRo) -> Result { +pub fn get_num_tx(table_tx_ids: &impl DatabaseRo) -> DbResult { table_tx_ids.len() } @@ -313,10 +301,7 @@ pub fn get_num_tx(table_tx_ids: &impl DatabaseRo) -> Result, -) -> Result { +pub fn tx_exists(tx_hash: &TxHash, table_tx_ids: &impl DatabaseRo) -> DbResult { table_tx_ids.contains(tx_hash) } @@ -328,11 +313,10 @@ mod test { use pretty_assertions::assert_eq; use cuprate_database::{Env, EnvInner, TxRw}; - use cuprate_test_utils::data::{tx_v1_sig0, tx_v1_sig2, tx_v2_rct3}; + use cuprate_test_utils::data::{TX_V1_SIG0, TX_V1_SIG2, TX_V2_RCT3}; use crate::{ - open_tables::OpenTables, - tables::Tables, + tables::{OpenTables, Tables}, tests::{assert_all_tables_are_empty, tmp_concrete_env, AssertTableLen}, }; @@ -344,7 +328,7 @@ mod test { assert_all_tables_are_empty(&env); // Monero `Transaction`, not database tx. - let txs = [tx_v1_sig0(), tx_v1_sig2(), tx_v2_rct3()]; + let txs = [&*TX_V1_SIG0, &*TX_V1_SIG2, &*TX_V2_RCT3]; // Add transactions. let tx_ids = { @@ -373,7 +357,8 @@ mod test { // Assert only the proper tables were added to. AssertTableLen { block_infos: 0, - block_blobs: 0, + block_header_blobs: 0, + block_txs_hashes: 0, block_heights: 0, key_images: 4, // added to key images pruned_tx_blobs: 0, diff --git a/storage/blockchain/src/service/free.rs b/storage/blockchain/src/service/free.rs index 3ff8d6e..7cc8da8 100644 --- a/storage/blockchain/src/service/free.rs +++ b/storage/blockchain/src/service/free.rs @@ -3,11 +3,15 @@ //---------------------------------------------------------------------------------------------------- Import use std::sync::Arc; -use cuprate_database::InitError; +use cuprate_database::{ConcreteEnv, InitError}; +use cuprate_types::{AltBlockInformation, VerifiedBlockInformation}; use crate::{ config::Config, - service::{DatabaseReadHandle, DatabaseWriteHandle}, + service::{ + init_read_service, init_write_service, + types::{BlockchainReadHandle, BlockchainWriteHandle}, + }, }; //---------------------------------------------------------------------------------------------------- Init @@ -20,21 +24,131 @@ use crate::{ /// /// # Errors /// This will forward the error if [`crate::open`] failed. -pub fn init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), InitError> { +pub fn init( + config: Config, +) -> Result< + ( + BlockchainReadHandle, + BlockchainWriteHandle, + Arc, + ), + InitError, +> { let reader_threads = config.reader_threads; // Initialize the database itself. let db = Arc::new(crate::open(config)?); // Spawn the Reader thread pool and Writer. - let readers = DatabaseReadHandle::init(&db, reader_threads); - let writer = DatabaseWriteHandle::init(db); + let readers = init_read_service(Arc::clone(&db), reader_threads); + let writer = init_write_service(Arc::clone(&db)); - Ok((readers, writer)) + Ok((readers, writer, db)) +} + +//---------------------------------------------------------------------------------------------------- Compact history +/// Given a position in the compact history, returns the height offset that should be in that position. +/// +/// The height offset is the difference between the top block's height and the block height that should be in that position. +#[inline] +pub(super) const fn compact_history_index_to_height_offset( + i: usize, +) -> usize { + // If the position is below the initial blocks just return the position back + if i <= INITIAL_BLOCKS { + i + } else { + // Otherwise we go with power of 2 offsets, the same as monerod. + // So (INITIAL_BLOCKS + 2), (INITIAL_BLOCKS + 2 + 4), (INITIAL_BLOCKS + 2 + 4 + 8) + // ref: + INITIAL_BLOCKS + (2 << (i - INITIAL_BLOCKS)) - 2 + } +} + +/// Returns if the genesis block was _NOT_ included when calculating the height offsets. +/// +/// The genesis must always be included in the compact history. +#[inline] +pub(super) const fn compact_history_genesis_not_included( + top_block_height: usize, +) -> bool { + // If the top block height is less than the initial blocks then it will always be included. + // Otherwise, we use the fact that to reach the genesis block this statement must be true (for a + // single `i`): + // + // `top_block_height - INITIAL_BLOCKS - 2^i + 2 == 0` + // which then means: + // `top_block_height - INITIAL_BLOCKS + 2 == 2^i` + // So if `top_block_height - INITIAL_BLOCKS + 2` is a power of 2 then the genesis block is in + // the compact history already. + top_block_height > INITIAL_BLOCKS && !(top_block_height - INITIAL_BLOCKS + 2).is_power_of_two() +} + +//---------------------------------------------------------------------------------------------------- Map Block +/// Maps [`AltBlockInformation`] to [`VerifiedBlockInformation`] +/// +/// # Panics +/// This will panic if the block is invalid, so should only be used on blocks that have been popped from +/// the main-chain. +pub(super) fn map_valid_alt_block_to_verified_block( + alt_block: AltBlockInformation, +) -> VerifiedBlockInformation { + let total_fees = alt_block.txs.iter().map(|tx| tx.fee).sum::(); + let total_miner_output = alt_block + .block + .miner_transaction + .prefix() + .outputs + .iter() + .map(|out| out.amount.unwrap_or(0)) + .sum::(); + + VerifiedBlockInformation { + block: alt_block.block, + block_blob: alt_block.block_blob, + txs: alt_block + .txs + .into_iter() + .map(TryInto::try_into) + .collect::>() + .unwrap(), + block_hash: alt_block.block_hash, + pow_hash: alt_block.pow_hash, + height: alt_block.height, + generated_coins: total_miner_output - total_fees, + weight: alt_block.weight, + long_term_weight: alt_block.long_term_weight, + cumulative_difficulty: alt_block.cumulative_difficulty, + } } //---------------------------------------------------------------------------------------------------- Tests + #[cfg(test)] -mod test { - // use super::*; +mod tests { + use proptest::prelude::*; + + use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE; + + use super::*; + + proptest! { + #[test] + fn compact_history(top_height in 0..MAX_BLOCK_HEIGHT_USIZE) { + let mut heights = (0..) + .map(compact_history_index_to_height_offset::<11>) + .map_while(|i| top_height.checked_sub(i)) + .collect::>(); + + if compact_history_genesis_not_included::<11>(top_height) { + heights.push(0); + } + + // Make sure the genesis and top block are always included. + assert_eq!(*heights.last().unwrap(), 0); + assert_eq!(*heights.first().unwrap(), top_height); + + heights.windows(2).for_each(|window| assert_ne!(window[0], window[1])); + } + } } diff --git a/storage/blockchain/src/service/mod.rs b/storage/blockchain/src/service/mod.rs index 1d9d10b..d6a811b 100644 --- a/storage/blockchain/src/service/mod.rs +++ b/storage/blockchain/src/service/mod.rs @@ -10,19 +10,17 @@ //! //! The system is managed by this crate, and only requires [`init`] by the user. //! -//! This module must be enabled with the `service` feature. -//! //! ## Handles //! The 2 handles to the database are: -//! - [`DatabaseReadHandle`] -//! - [`DatabaseWriteHandle`] +//! - [`BlockchainReadHandle`] +//! - [`BlockchainWriteHandle`] //! //! The 1st allows any caller to send [`ReadRequest`][req_r]s. //! //! The 2nd allows any caller to send [`WriteRequest`][req_w]s. //! -//! The `DatabaseReadHandle` can be shared as it is cheaply [`Clone`]able, however, -//! the `DatabaseWriteHandle` cannot be cloned. There is only 1 place in Cuprate that +//! The [`BlockchainReadHandle`] can be shared as it is cheaply [`Clone`]able, however, +//! the [`BlockchainWriteHandle`] cannot be cloned. There is only 1 place in Cuprate that //! writes, so it is passed there and used. //! //! ## Initialization @@ -33,8 +31,10 @@ //! //! ## Shutdown //! Upon the above handles being dropped, the corresponding thread(s) will automatically exit, i.e: -//! - The last [`DatabaseReadHandle`] is dropped => reader thread-pool exits -//! - The last [`DatabaseWriteHandle`] is dropped => writer thread exits +//! - The last [`BlockchainReadHandle`] is dropped => reader thread-pool exits +//! - The last [`BlockchainWriteHandle`] is dropped => writer thread exits +//! +//! TODO: update this when `ConcreteEnv` is removed //! //! Upon dropping the [`cuprate_database::ConcreteEnv`]: //! - All un-processed database transactions are completed @@ -50,11 +50,11 @@ //! This channel can be `.await`ed upon to (eventually) receive //! the corresponding `Response` to your `Request`. //! -//! [req_r]: cuprate_types::blockchain::BCReadRequest +//! [req_r]: cuprate_types::blockchain::BlockchainReadRequest //! -//! [req_w]: cuprate_types::blockchain::BCWriteRequest +//! [req_w]: cuprate_types::blockchain::BlockchainWriteRequest //! -//! [resp]: cuprate_types::blockchain::BCResponse +//! [resp]: cuprate_types::blockchain::BlockchainResponse //! //! # Example //! Simple usage of `service`. @@ -63,8 +63,8 @@ //! use hex_literal::hex; //! use tower::{Service, ServiceExt}; //! -//! use cuprate_types::blockchain::{BCReadRequest, BCWriteRequest, BCResponse}; -//! use cuprate_test_utils::data::block_v16_tx0; +//! use cuprate_types::{blockchain::{BlockchainReadRequest, BlockchainWriteRequest, BlockchainResponse}, Chain}; +//! use cuprate_test_utils::data::BLOCK_V16_TX0; //! //! use cuprate_blockchain::{ //! cuprate_database::Env, @@ -77,16 +77,16 @@ //! let tmp_dir = tempfile::tempdir()?; //! let db_dir = tmp_dir.path().to_owned(); //! let config = ConfigBuilder::new() -//! .db_directory(db_dir.into()) +//! .data_directory(db_dir.into()) //! .build(); //! //! // Initialize the database thread-pool. -//! let (mut read_handle, mut write_handle) = cuprate_blockchain::service::init(config)?; +//! let (mut read_handle, mut write_handle, _) = cuprate_blockchain::service::init(config)?; //! //! // Prepare a request to write block. -//! let mut block = block_v16_tx0().clone(); -//! # block.height = 0 as u64; // must be 0th height or panic in `add_block()` -//! let request = BCWriteRequest::WriteBlock(block); +//! let mut block = BLOCK_V16_TX0.clone(); +//! # block.height = 0_usize; // must be 0th height or panic in `add_block()` +//! let request = BlockchainWriteRequest::WriteBlock(block); //! //! // Send the request. //! // We receive back an `async` channel that will @@ -96,16 +96,16 @@ //! //! // Block write was OK. //! let response = response_channel.await?; -//! assert_eq!(response, BCResponse::WriteBlockOk); +//! assert_eq!(response, BlockchainResponse::Ok); //! //! // Now, let's try getting the block hash //! // of the block we just wrote. -//! let request = BCReadRequest::BlockHash(0); +//! let request = BlockchainReadRequest::BlockHash(0, Chain::Main); //! let response_channel = read_handle.ready().await?.call(request); //! let response = response_channel.await?; //! assert_eq!( //! response, -//! BCResponse::BlockHash( +//! BlockchainResponse::BlockHash( //! hex!("43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428") //! ) //! ); @@ -118,17 +118,19 @@ //! # Ok(()) } //! ``` +// needed for docs +use tower as _; + mod read; -pub use read::DatabaseReadHandle; +pub use read::{init_read_service, init_read_service_with_pool}; mod write; -pub use write::DatabaseWriteHandle; +pub use write::init_write_service; mod free; pub use free::init; - -// Internal type aliases for `service`. mod types; +pub use types::{BlockchainReadHandle, BlockchainWriteHandle}; #[cfg(test)] mod tests; diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 20aebf9..84b1b92 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -1,184 +1,87 @@ //! Database reader thread-pool definitions and logic. +#![expect( + unreachable_code, + unused_variables, + clippy::unnecessary_wraps, + clippy::needless_pass_by_value, + reason = "TODO: finish implementing the signatures from " +)] + //---------------------------------------------------------------------------------------------------- Import use std::{ + cmp::min, collections::{HashMap, HashSet}, sync::Arc, - task::{Context, Poll}, }; -use futures::{channel::oneshot, ready}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rayon::{ + iter::{Either, IntoParallelIterator, ParallelIterator}, + prelude::*, + ThreadPool, +}; use thread_local::ThreadLocal; -use tokio::sync::{OwnedSemaphorePermit, Semaphore}; -use tokio_util::sync::PollSemaphore; -use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError}; -use cuprate_helper::asynch::InfallibleOneshotReceiver; +use cuprate_database::{ + ConcreteEnv, DatabaseIter, DatabaseRo, DbResult, Env, EnvInner, RuntimeError, +}; +use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads}; +use cuprate_helper::map::combine_low_high_bits_to_u128; use cuprate_types::{ - blockchain::{BCReadRequest, BCResponse}, - ExtendedBlockHeader, OutputOnChain, + blockchain::{BlockchainReadRequest, BlockchainResponse}, + Chain, ChainId, ExtendedBlockHeader, OutputHistogramInput, OutputOnChain, TxsInBlock, }; use crate::{ - config::ReaderThreads, - open_tables::OpenTables, - ops::block::block_exists, ops::{ - block::{get_block_extended_header_from_height, get_block_info}, - blockchain::{cumulative_generated_coins, top_block_height}, + alt_block::{ + get_alt_block, get_alt_block_extended_header_from_height, get_alt_block_hash, + get_alt_chain_history_ranges, + }, + block::{ + block_exists, get_block_blob_with_tx_indexes, get_block_complete_entry, + get_block_extended_header_from_height, get_block_height, get_block_info, + }, + blockchain::{cumulative_generated_coins, find_split_point, top_block_height}, key_image::key_image_exists, output::id_to_output_on_chain, }, - service::types::{ResponseReceiver, ResponseResult, ResponseSender}, - tables::{BlockHeights, BlockInfos, Tables}, - types::BlockHash, - types::{Amount, AmountIndex, BlockHeight, KeyImage, PreRctOutputId}, + service::{ + free::{compact_history_genesis_not_included, compact_history_index_to_height_offset}, + types::{BlockchainReadHandle, ResponseResult}, + }, + tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables, TablesIter}, + types::{ + AltBlockHeight, Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId, + }, }; -//---------------------------------------------------------------------------------------------------- DatabaseReadHandle -/// Read handle to the database. +//---------------------------------------------------------------------------------------------------- init_read_service +/// Initialize the [`BlockchainReadHandle`] thread-pool backed by [`rayon`]. /// -/// This is cheaply [`Clone`]able handle that -/// allows `async`hronously reading from the database. +/// This spawns `threads` amount of reader threads +/// attached to `env` and returns a handle to the pool. /// -/// Calling [`tower::Service::call`] with a [`DatabaseReadHandle`] & [`BCReadRequest`] -/// will return an `async`hronous channel that can be `.await`ed upon -/// to receive the corresponding [`BCResponse`]. -pub struct DatabaseReadHandle { - /// Handle to the custom `rayon` DB reader thread-pool. - /// - /// Requests are [`rayon::ThreadPool::spawn`]ed in this thread-pool, - /// and responses are returned via a channel we (the caller) provide. - pool: Arc, +/// Should be called _once_ per actual database. Calling this function more than once will create +/// multiple unnecessary rayon thread-pools. +#[cold] +#[inline(never)] // Only called once. +pub fn init_read_service(env: Arc, threads: ReaderThreads) -> BlockchainReadHandle { + init_read_service_with_pool(env, init_thread_pool(threads)) +} - /// Counting semaphore asynchronous permit for database access. - /// Each [`tower::Service::poll_ready`] will acquire a permit - /// before actually sending a request to the `rayon` DB threadpool. - semaphore: PollSemaphore, - - /// An owned permit. - /// This will be set to [`Some`] in `poll_ready()` when we successfully acquire - /// the permit, and will be [`Option::take()`]n after `tower::Service::call()` is called. - /// - /// The actual permit will be dropped _after_ the rayon DB thread has finished - /// the request, i.e., after [`map_request()`] finishes. - permit: Option, - - /// Access to the database. +/// Initialize the blockchain database read service, with a specific rayon thread-pool instead of +/// creating a new one. +/// +/// Should be called _once_ per actual database, although nothing bad will happen, cloning the [`BlockchainReadHandle`] +/// is the correct way to get multiple handles to the database. +#[cold] +#[inline(never)] // Only called once. +pub fn init_read_service_with_pool( env: Arc, -} - -// `OwnedSemaphorePermit` does not implement `Clone`, -// so manually clone all elements, while keeping `permit` -// `None` across clones. -impl Clone for DatabaseReadHandle { - fn clone(&self) -> Self { - Self { - pool: Arc::clone(&self.pool), - semaphore: self.semaphore.clone(), - permit: None, - env: Arc::clone(&self.env), - } - } -} - -impl DatabaseReadHandle { - /// Initialize the `DatabaseReader` thread-pool backed by `rayon`. - /// - /// This spawns `N` amount of `DatabaseReader`'s - /// attached to `env` and returns a handle to the pool. - /// - /// Should be called _once_ per actual database. - #[cold] - #[inline(never)] // Only called once. - pub(super) fn init(env: &Arc, reader_threads: ReaderThreads) -> Self { - // How many reader threads to spawn? - let reader_count = reader_threads.as_threads().get(); - - // Spawn `rayon` reader threadpool. - let pool = rayon::ThreadPoolBuilder::new() - .num_threads(reader_count) - .thread_name(|i| format!("cuprate_helper::service::read::DatabaseReader{i}")) - .build() - .unwrap(); - - // Create a semaphore with the same amount of - // permits as the amount of reader threads. - let semaphore = PollSemaphore::new(Arc::new(Semaphore::new(reader_count))); - - // Return a handle to the pool. - Self { - pool: Arc::new(pool), - semaphore, - permit: None, - env: Arc::clone(env), - } - } - - /// Access to the actual database environment. - /// - /// # ⚠️ Warning - /// This function gives you access to the actual - /// underlying database connected to by `self`. - /// - /// I.e. it allows you to read/write data _directly_ - /// instead of going through a request. - /// - /// Be warned that using the database directly - /// in this manner has not been tested. - #[inline] - pub const fn env(&self) -> &Arc { - &self.env - } -} - -impl tower::Service for DatabaseReadHandle { - type Response = BCResponse; - type Error = RuntimeError; - type Future = ResponseReceiver; - - #[inline] - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - // Check if we already have a permit. - if self.permit.is_some() { - return Poll::Ready(Ok(())); - } - - // Acquire a permit before returning `Ready`. - let permit = - ready!(self.semaphore.poll_acquire(cx)).expect("this semaphore is never closed"); - - self.permit = Some(permit); - Poll::Ready(Ok(())) - } - - #[inline] - fn call(&mut self, request: BCReadRequest) -> Self::Future { - let permit = self - .permit - .take() - .expect("poll_ready() should have acquire a permit before calling call()"); - - // Response channel we `.await` on. - let (response_sender, receiver) = oneshot::channel(); - - // Spawn the request in the rayon DB thread-pool. - // - // Note that this uses `self.pool` instead of `rayon::spawn` - // such that any `rayon` parallel code that runs within - // the passed closure uses the same `rayon` threadpool. - // - // INVARIANT: - // The below `DatabaseReader` function impl block relies on this behavior. - let env = Arc::clone(&self.env); - self.pool.spawn(move || { - let _permit: OwnedSemaphorePermit = permit; - map_request(&env, request, response_sender); - }); // drop(permit/env); - - InfallibleOneshotReceiver::from(receiver) - } + pool: Arc, +) -> BlockchainReadHandle { + DatabaseReadService::new(env, pool, map_request) } //---------------------------------------------------------------------------------------------------- Request Mapping @@ -191,31 +94,45 @@ impl tower::Service for DatabaseReadHandle { /// The basic structure is: /// 1. `Request` is mapped to a handler function /// 2. Handler function is called -/// 3. [`BCResponse`] is sent +/// 3. [`BlockchainResponse`] is returned fn map_request( - env: &ConcreteEnv, // Access to the database - request: BCReadRequest, // The request we must fulfill - response_sender: ResponseSender, // The channel we must send the response back to -) { - use BCReadRequest as R; + env: &ConcreteEnv, // Access to the database + request: BlockchainReadRequest, // The request we must fulfill +) -> ResponseResult { + use BlockchainReadRequest as R; /* SOMEDAY: pre-request handling, run some code for each request? */ - let response = match request { + match request { + R::BlockCompleteEntries(block_hashes) => block_complete_entries(env, block_hashes), R::BlockExtendedHeader(block) => block_extended_header(env, block), - R::BlockHash(block) => block_hash(env, block), - R::FilterUnknownHashes(hashes) => filter_unknown_hahses(env, hashes), - R::BlockExtendedHeaderInRange(range) => block_extended_header_in_range(env, range), + R::BlockHash(block, chain) => block_hash(env, block, chain), + R::FindBlock(block_hash) => find_block(env, block_hash), + R::FilterUnknownHashes(hashes) => filter_unknown_hashes(env, hashes), + R::BlockExtendedHeaderInRange(range, chain) => { + block_extended_header_in_range(env, range, chain) + } R::ChainHeight => chain_height(env), - R::GeneratedCoins => generated_coins(env), + R::GeneratedCoins(height) => generated_coins(env, height), R::Outputs(map) => outputs(env, map), R::NumberOutputsWithAmount(vec) => number_outputs_with_amount(env, vec), R::KeyImagesSpent(set) => key_images_spent(env, set), - }; - - if let Err(e) = response_sender.send(response) { - // TODO: use tracing. - println!("database reader failed to send response: {e:?}"); + R::CompactChainHistory => compact_chain_history(env), + R::NextChainEntry(block_hashes, amount) => next_chain_entry(env, &block_hashes, amount), + R::FindFirstUnknown(block_ids) => find_first_unknown(env, &block_ids), + R::TxsInBlock { + block_hash, + tx_indexes, + } => txs_in_block(env, block_hash, tx_indexes), + R::AltBlocksInChain(chain_id) => alt_blocks_in_chain(env, chain_id), + R::Block { height } => block(env, height), + R::BlockByHash(hash) => block_by_hash(env, hash), + R::TotalTxCount => total_tx_count(env), + R::DatabaseSize => database_size(env), + R::OutputHistogram(input) => output_histogram(env, input), + R::CoinbaseTxSum { height, count } => coinbase_tx_sum(env, height, count), + R::AltChains => alt_chains(env), + R::AltChainCount => alt_chain_count(env), } /* SOMEDAY: post-request handling, run some code for each request? */ @@ -259,7 +176,6 @@ fn thread_local(env: &impl Env) -> ThreadLocal { macro_rules! get_tables { ($env_inner:ident, $tx_ro:ident, $tables:ident) => {{ $tables.get_or_try(|| { - #[allow(clippy::significant_drop_in_scrutinee)] match $env_inner.open_tables($tx_ro) { // SAFETY: see above macro doc comment. Ok(tables) => Ok(unsafe { crate::unsafe_sendable::UnsafeSendable::new(tables) }), @@ -292,7 +208,39 @@ macro_rules! get_tables { // TODO: The overhead of parallelism may be too much for every request, perfomace test to find optimal // amount of parallelism. -/// [`BCReadRequest::BlockExtendedHeader`]. +/// [`BlockchainReadRequest::BlockCompleteEntries`]. +fn block_complete_entries(env: &ConcreteEnv, block_hashes: Vec) -> ResponseResult { + // Prepare tx/tables in `ThreadLocal`. + let env_inner = env.env_inner(); + let tx_ro = thread_local(env); + let tables = thread_local(env); + + let (missing_hashes, blocks) = block_hashes + .into_par_iter() + .map(|block_hash| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + match get_block_complete_entry(&block_hash, tables) { + Err(RuntimeError::KeyNotFound) => Ok(Either::Left(block_hash)), + res => res.map(Either::Right), + } + }) + .collect::>()?; + + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + let blockchain_height = crate::ops::blockchain::chain_height(tables.block_heights())?; + + Ok(BlockchainResponse::BlockCompleteEntries { + blocks, + missing_hashes, + blockchain_height, + }) +} + +/// [`BlockchainReadRequest::BlockExtendedHeader`]. #[inline] fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. @@ -300,27 +248,59 @@ fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> Respon let tx_ro = env_inner.tx_ro()?; let tables = env_inner.open_tables(&tx_ro)?; - Ok(BCResponse::BlockExtendedHeader( + Ok(BlockchainResponse::BlockExtendedHeader( get_block_extended_header_from_height(&block_height, &tables)?, )) } -/// [`BCReadRequest::BlockHash`]. +/// [`BlockchainReadRequest::BlockHash`]. #[inline] -fn block_hash(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { +fn block_hash(env: &ConcreteEnv, block_height: BlockHeight, chain: Chain) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; let table_block_infos = env_inner.open_db_ro::(&tx_ro)?; - Ok(BCResponse::BlockHash( - get_block_info(&block_height, &table_block_infos)?.block_hash, - )) + let block_hash = match chain { + Chain::Main => get_block_info(&block_height, &table_block_infos)?.block_hash, + Chain::Alt(chain) => { + get_alt_block_hash(&block_height, chain, &env_inner.open_tables(&tx_ro)?)? + } + }; + + Ok(BlockchainResponse::BlockHash(block_hash)) } -/// [`BCReadRequest::FilterUnknownHashes`]. +/// [`BlockchainReadRequest::FindBlock`] +fn find_block(env: &ConcreteEnv, block_hash: BlockHash) -> ResponseResult { + // Single-threaded, no `ThreadLocal` required. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro()?; + + let table_block_heights = env_inner.open_db_ro::(&tx_ro)?; + + // Check the main chain first. + match table_block_heights.get(&block_hash) { + Ok(height) => return Ok(BlockchainResponse::FindBlock(Some((Chain::Main, height)))), + Err(RuntimeError::KeyNotFound) => (), + Err(e) => return Err(e), + } + + let table_alt_block_heights = env_inner.open_db_ro::(&tx_ro)?; + + match table_alt_block_heights.get(&block_hash) { + Ok(height) => Ok(BlockchainResponse::FindBlock(Some(( + Chain::Alt(height.chain_id.into()), + height.height, + )))), + Err(RuntimeError::KeyNotFound) => Ok(BlockchainResponse::FindBlock(None)), + Err(e) => Err(e), + } +} + +/// [`BlockchainReadRequest::FilterUnknownHashes`]. #[inline] -fn filter_unknown_hahses(env: &ConcreteEnv, mut hashes: HashSet) -> ResponseResult { +fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -342,15 +322,16 @@ fn filter_unknown_hahses(env: &ConcreteEnv, mut hashes: HashSet) -> R if let Some(e) = err { Err(e) } else { - Ok(BCResponse::FilterUnknownHashes(hashes)) + Ok(BlockchainResponse::FilterUnknownHashes(hashes)) } } -/// [`BCReadRequest::BlockExtendedHeaderInRange`]. +/// [`BlockchainReadRequest::BlockExtendedHeaderInRange`]. #[inline] fn block_extended_header_in_range( env: &ConcreteEnv, range: std::ops::Range, + chain: Chain, ) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. let env_inner = env.env_inner(); @@ -358,19 +339,52 @@ fn block_extended_header_in_range( let tables = thread_local(env); // Collect results using `rayon`. - let vec = range - .into_par_iter() - .map(|block_height| { - let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; - let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); - get_block_extended_header_from_height(&block_height, tables) - }) - .collect::, RuntimeError>>()?; + let vec = match chain { + Chain::Main => range + .into_par_iter() + .map(|block_height| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + get_block_extended_header_from_height(&block_height, tables) + }) + .collect::>>()?, + Chain::Alt(chain_id) => { + let ranges = { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + let alt_chains = tables.alt_chain_infos(); - Ok(BCResponse::BlockExtendedHeaderInRange(vec)) + get_alt_chain_history_ranges(range, chain_id, alt_chains)? + }; + + ranges + .par_iter() + .rev() + .flat_map(|(chain, range)| { + range.clone().into_par_iter().map(|height| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + match *chain { + Chain::Main => get_block_extended_header_from_height(&height, tables), + Chain::Alt(chain_id) => get_alt_block_extended_header_from_height( + &AltBlockHeight { + chain_id: chain_id.into(), + height, + }, + tables, + ), + } + }) + }) + .collect::>>()? + } + }; + + Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec)) } -/// [`BCReadRequest::ChainHeight`]. +/// [`BlockchainReadRequest::ChainHeight`]. #[inline] fn chain_height(env: &ConcreteEnv) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. @@ -383,27 +397,23 @@ fn chain_height(env: &ConcreteEnv) -> ResponseResult { let block_hash = get_block_info(&chain_height.saturating_sub(1), &table_block_infos)?.block_hash; - Ok(BCResponse::ChainHeight(chain_height, block_hash)) + Ok(BlockchainResponse::ChainHeight(chain_height, block_hash)) } -/// [`BCReadRequest::GeneratedCoins`]. +/// [`BlockchainReadRequest::GeneratedCoins`]. #[inline] -fn generated_coins(env: &ConcreteEnv) -> ResponseResult { +fn generated_coins(env: &ConcreteEnv, height: usize) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; - let table_block_heights = env_inner.open_db_ro::(&tx_ro)?; let table_block_infos = env_inner.open_db_ro::(&tx_ro)?; - let top_height = top_block_height(&table_block_heights)?; - - Ok(BCResponse::GeneratedCoins(cumulative_generated_coins( - &top_height, - &table_block_infos, - )?)) + Ok(BlockchainResponse::GeneratedCoins( + cumulative_generated_coins(&height, &table_block_infos)?, + )) } -/// [`BCReadRequest::Outputs`]. +/// [`BlockchainReadRequest::Outputs`]. #[inline] fn outputs(env: &ConcreteEnv, outputs: HashMap>) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. @@ -413,7 +423,7 @@ fn outputs(env: &ConcreteEnv, outputs: HashMap>) -> // The 2nd mapping function. // This is pulled out from the below `map()` for readability. - let inner_map = |amount, amount_index| -> Result<(AmountIndex, OutputOnChain), RuntimeError> { + let inner_map = |amount, amount_index| -> DbResult<(AmountIndex, OutputOnChain)> { let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); @@ -436,15 +446,15 @@ fn outputs(env: &ConcreteEnv, outputs: HashMap>) -> amount_index_set .into_par_iter() .map(|amount_index| inner_map(amount, amount_index)) - .collect::, RuntimeError>>()?, + .collect::>>()?, )) }) - .collect::>, RuntimeError>>()?; + .collect::>>>()?; - Ok(BCResponse::Outputs(map)) + Ok(BlockchainResponse::Outputs(map)) } -/// [`BCReadRequest::NumberOutputsWithAmount`]. +/// [`BlockchainReadRequest::NumberOutputsWithAmount`]. #[inline] fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. @@ -453,8 +463,10 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon let tables = thread_local(env); // Cache the amount of RCT outputs once. - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - #[allow(clippy::cast_possible_truncation)] + #[expect( + clippy::cast_possible_truncation, + reason = "INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`" + )] let num_rct_outputs = { let tx_ro = env_inner.tx_ro()?; let tables = env_inner.open_tables(&tx_ro)?; @@ -474,8 +486,10 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon } else { // v1 transactions. match tables.num_outputs().get(&amount) { - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - #[allow(clippy::cast_possible_truncation)] + #[expect( + clippy::cast_possible_truncation, + reason = "INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`" + )] Ok(count) => Ok((amount, count as usize)), // If we get a request for an `amount` that doesn't exist, // we return `0` instead of an error. @@ -484,12 +498,12 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon } } }) - .collect::, RuntimeError>>()?; + .collect::>>()?; - Ok(BCResponse::NumberOutputsWithAmount(map)) + Ok(BlockchainResponse::NumberOutputsWithAmount(map)) } -/// [`BCReadRequest::KeyImagesSpent`]. +/// [`BlockchainReadRequest::KeyImagesSpent`]. #[inline] fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. @@ -520,8 +534,251 @@ fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> Respons // Else, `Ok(false)` will continue the iterator. .find_any(|result| !matches!(result, Ok(false))) { - None | Some(Ok(false)) => Ok(BCResponse::KeyImagesSpent(false)), // Key image was NOT found. - Some(Ok(true)) => Ok(BCResponse::KeyImagesSpent(true)), // Key image was found. + None | Some(Ok(false)) => Ok(BlockchainResponse::KeyImagesSpent(false)), // Key image was NOT found. + Some(Ok(true)) => Ok(BlockchainResponse::KeyImagesSpent(true)), // Key image was found. Some(Err(e)) => Err(e), // A database error occurred. } } + +/// [`BlockchainReadRequest::CompactChainHistory`] +fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult { + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro()?; + + let table_block_heights = env_inner.open_db_ro::(&tx_ro)?; + let table_block_infos = env_inner.open_db_ro::(&tx_ro)?; + + let top_block_height = top_block_height(&table_block_heights)?; + + let top_block_info = get_block_info(&top_block_height, &table_block_infos)?; + let cumulative_difficulty = combine_low_high_bits_to_u128( + top_block_info.cumulative_difficulty_low, + top_block_info.cumulative_difficulty_high, + ); + + /// The amount of top block IDs in the compact chain. + const INITIAL_BLOCKS: usize = 11; + + // rayon is not used here because the amount of block IDs is expected to be small. + let mut block_ids = (0..) + .map(compact_history_index_to_height_offset::) + .map_while(|i| top_block_height.checked_sub(i)) + .map(|height| Ok(get_block_info(&height, &table_block_infos)?.block_hash)) + .collect::>>()?; + + if compact_history_genesis_not_included::(top_block_height) { + block_ids.push(get_block_info(&0, &table_block_infos)?.block_hash); + } + + Ok(BlockchainResponse::CompactChainHistory { + cumulative_difficulty, + block_ids, + }) +} + +/// [`BlockchainReadRequest::NextChainEntry`] +/// +/// # Invariant +/// `block_ids` must be sorted in reverse chronological block order, or else +/// the returned result is unspecified and meaningless, as this function +/// performs a binary search. +fn next_chain_entry( + env: &ConcreteEnv, + block_ids: &[BlockHash], + next_entry_size: usize, +) -> ResponseResult { + // Single-threaded, no `ThreadLocal` required. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro()?; + + let tables = env_inner.open_tables(&tx_ro)?; + let table_block_heights = tables.block_heights(); + let table_block_infos = tables.block_infos_iter(); + + let idx = find_split_point(block_ids, false, table_block_heights)?; + + // This will happen if we have a different genesis block. + if idx == block_ids.len() { + return Ok(BlockchainResponse::NextChainEntry { + start_height: None, + chain_height: 0, + block_ids: vec![], + block_weights: vec![], + cumulative_difficulty: 0, + first_block_blob: None, + }); + } + + // The returned chain entry must overlap with one of the blocks we were told about. + let first_known_block_hash = block_ids[idx]; + let first_known_height = table_block_heights.get(&first_known_block_hash)?; + + let chain_height = crate::ops::blockchain::chain_height(table_block_heights)?; + let last_height_in_chain_entry = min(first_known_height + next_entry_size, chain_height); + + let (block_ids, block_weights) = table_block_infos + .get_range(first_known_height..last_height_in_chain_entry)? + .map(|block_info| { + let block_info = block_info?; + + Ok((block_info.block_hash, block_info.weight)) + }) + .collect::, Vec<_>)>>()?; + + let top_block_info = table_block_infos.get(&(chain_height - 1))?; + + let first_block_blob = if block_ids.len() >= 2 { + Some(get_block_blob_with_tx_indexes(&(first_known_height + 1), &tables)?.0) + } else { + None + }; + + Ok(BlockchainResponse::NextChainEntry { + start_height: std::num::NonZero::new(first_known_height), + chain_height, + block_ids, + block_weights, + cumulative_difficulty: combine_low_high_bits_to_u128( + top_block_info.cumulative_difficulty_low, + top_block_info.cumulative_difficulty_high, + ), + first_block_blob, + }) +} + +/// [`BlockchainReadRequest::FindFirstUnknown`] +/// +/// # Invariant +/// `block_ids` must be sorted in chronological block order, or else +/// the returned result is unspecified and meaningless, as this function +/// performs a binary search. +fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseResult { + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro()?; + + let table_block_heights = env_inner.open_db_ro::(&tx_ro)?; + + let idx = find_split_point(block_ids, true, &table_block_heights)?; + + Ok(if idx == block_ids.len() { + BlockchainResponse::FindFirstUnknown(None) + } else if idx == 0 { + BlockchainResponse::FindFirstUnknown(Some((0, 0))) + } else { + let last_known_height = get_block_height(&block_ids[idx - 1], &table_block_heights)?; + + BlockchainResponse::FindFirstUnknown(Some((idx, last_known_height + 1))) + }) +} + +/// [`BlockchainReadRequest::TxsInBlock`] +fn txs_in_block(env: &ConcreteEnv, block_hash: [u8; 32], missing_txs: Vec) -> ResponseResult { + // Single-threaded, no `ThreadLocal` required. + let env_inner = env.env_inner(); + let tx_ro = env_inner.tx_ro()?; + let tables = env_inner.open_tables(&tx_ro)?; + + let block_height = tables.block_heights().get(&block_hash)?; + + let (block, miner_tx_index, numb_txs) = get_block_blob_with_tx_indexes(&block_height, &tables)?; + let first_tx_index = miner_tx_index + 1; + + if numb_txs < missing_txs.len() { + return Ok(BlockchainResponse::TxsInBlock(None)); + } + + let txs = missing_txs + .into_iter() + .map(|index_offset| Ok(tables.tx_blobs().get(&(first_tx_index + index_offset))?.0)) + .collect::>()?; + + Ok(BlockchainResponse::TxsInBlock(Some(TxsInBlock { + block, + txs, + }))) +} + +/// [`BlockchainReadRequest::AltBlocksInChain`] +fn alt_blocks_in_chain(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult { + // Prepare tx/tables in `ThreadLocal`. + let env_inner = env.env_inner(); + let tx_ro = thread_local(env); + let tables = thread_local(env); + + // Get the history of this alt-chain. + let history = { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + get_alt_chain_history_ranges(0..usize::MAX, chain_id, tables.alt_chain_infos())? + }; + + // Get all the blocks until we join the main-chain. + let blocks = history + .par_iter() + .rev() + .skip(1) + .flat_map(|(chain_id, range)| { + let Chain::Alt(chain_id) = chain_id else { + panic!("Should not have main chain blocks here we skipped last range"); + }; + + range.clone().into_par_iter().map(|height| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + get_alt_block( + &AltBlockHeight { + chain_id: (*chain_id).into(), + height, + }, + tables, + ) + }) + }) + .collect::>()?; + + Ok(BlockchainResponse::AltBlocksInChain(blocks)) +} + +/// [`BlockchainReadRequest::Block`] +fn block(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { + Ok(BlockchainResponse::Block(todo!())) +} + +/// [`BlockchainReadRequest::BlockByHash`] +fn block_by_hash(env: &ConcreteEnv, block_hash: BlockHash) -> ResponseResult { + Ok(BlockchainResponse::Block(todo!())) +} + +/// [`BlockchainReadRequest::TotalTxCount`] +fn total_tx_count(env: &ConcreteEnv) -> ResponseResult { + Ok(BlockchainResponse::TotalTxCount(todo!())) +} + +/// [`BlockchainReadRequest::DatabaseSize`] +fn database_size(env: &ConcreteEnv) -> ResponseResult { + Ok(BlockchainResponse::DatabaseSize { + database_size: todo!(), + free_space: todo!(), + }) +} + +/// [`BlockchainReadRequest::OutputHistogram`] +fn output_histogram(env: &ConcreteEnv, input: OutputHistogramInput) -> ResponseResult { + Ok(BlockchainResponse::OutputHistogram(todo!())) +} + +/// [`BlockchainReadRequest::CoinbaseTxSum`] +fn coinbase_tx_sum(env: &ConcreteEnv, height: usize, count: u64) -> ResponseResult { + Ok(BlockchainResponse::CoinbaseTxSum(todo!())) +} + +/// [`BlockchainReadRequest::AltChains`] +fn alt_chains(env: &ConcreteEnv) -> ResponseResult { + Ok(BlockchainResponse::AltChains(todo!())) +} + +/// [`BlockchainReadRequest::AltChainCount`] +fn alt_chain_count(env: &ConcreteEnv) -> ResponseResult { + Ok(BlockchainResponse::AltChainCount(todo!())) +} diff --git a/storage/blockchain/src/service/tests.rs b/storage/blockchain/src/service/tests.rs index d163474..38db665 100644 --- a/storage/blockchain/src/service/tests.rs +++ b/storage/blockchain/src/service/tests.rs @@ -7,50 +7,48 @@ //---------------------------------------------------------------------------------------------------- Use use std::{ - borrow::Cow, collections::{HashMap, HashSet}, sync::Arc, }; use pretty_assertions::assert_eq; +use rand::Rng; use tower::{Service, ServiceExt}; use cuprate_database::{ConcreteEnv, DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError}; -use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3}; +use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3}; use cuprate_types::{ - blockchain::{BCReadRequest, BCResponse, BCWriteRequest}, - OutputOnChain, VerifiedBlockInformation, + blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest}, + Chain, ChainId, OutputOnChain, VerifiedBlockInformation, }; use crate::{ config::ConfigBuilder, - open_tables::OpenTables, ops::{ block::{get_block_extended_header_from_height, get_block_info}, blockchain::chain_height, output::id_to_output_on_chain, }, - service::{init, DatabaseReadHandle, DatabaseWriteHandle}, - tables::{Tables, TablesIter}, - tests::AssertTableLen, + service::{init, BlockchainReadHandle, BlockchainWriteHandle}, + tables::{OpenTables, Tables, TablesIter}, + tests::{map_verified_block_to_alt, AssertTableLen}, types::{Amount, AmountIndex, PreRctOutputId}, }; //---------------------------------------------------------------------------------------------------- Helper functions /// Initialize the `service`. fn init_service() -> ( - DatabaseReadHandle, - DatabaseWriteHandle, + BlockchainReadHandle, + BlockchainWriteHandle, Arc, tempfile::TempDir, ) { let tempdir = tempfile::tempdir().unwrap(); let config = ConfigBuilder::new() - .db_directory(Cow::Owned(tempdir.path().into())) + .data_directory(tempdir.path().into()) .low_power() .build(); - let (reader, writer) = init(config).unwrap(); - let env = reader.env().clone(); + let (reader, writer, env) = init(config).unwrap(); (reader, writer, env, tempdir) } @@ -60,10 +58,13 @@ fn init_service() -> ( /// - Receive response(s) /// - Assert proper tables were mutated /// - Assert read requests lead to expected responses -#[allow(clippy::future_not_send)] // INVARIANT: tests are using a single threaded runtime +#[expect( + clippy::future_not_send, + reason = "INVARIANT: tests are using a single threaded runtime" +)] async fn test_template( // Which block(s) to add? - block_fns: &[fn() -> &'static VerifiedBlockInformation], + blocks: &[&VerifiedBlockInformation], // Total amount of generated coins after the block(s) have been added. cumulative_generated_coins: u64, // What are the table lengths be after the block(s) have been added? @@ -78,15 +79,15 @@ async fn test_template( // HACK: `add_block()` asserts blocks with non-sequential heights // cannot be added, to get around this, manually edit the block height. - for (i, block_fn) in block_fns.iter().enumerate() { - let mut block = block_fn().clone(); - block.height = i as u64; + for (i, block) in blocks.iter().enumerate() { + let mut block = (*block).clone(); + block.height = i; // Request a block to be written, assert it was written. - let request = BCWriteRequest::WriteBlock(block); + let request = BlockchainWriteRequest::WriteBlock(block); let response_channel = writer.call(request); let response = response_channel.await.unwrap(); - assert_eq!(response, BCResponse::WriteBlockOk); + assert_eq!(response, BlockchainResponse::Ok); } //----------------------------------------------------------------------- Reset the transaction @@ -102,36 +103,36 @@ async fn test_template( // Next few lines are just for preparing the expected responses, // see further below for usage. - let extended_block_header_0 = Ok(BCResponse::BlockExtendedHeader( + let extended_block_header_0 = Ok(BlockchainResponse::BlockExtendedHeader( get_block_extended_header_from_height(&0, &tables).unwrap(), )); - let extended_block_header_1 = if block_fns.len() > 1 { - Ok(BCResponse::BlockExtendedHeader( + let extended_block_header_1 = if blocks.len() > 1 { + Ok(BlockchainResponse::BlockExtendedHeader( get_block_extended_header_from_height(&1, &tables).unwrap(), )) } else { Err(RuntimeError::KeyNotFound) }; - let block_hash_0 = Ok(BCResponse::BlockHash( + let block_hash_0 = Ok(BlockchainResponse::BlockHash( get_block_info(&0, tables.block_infos()).unwrap().block_hash, )); - let block_hash_1 = if block_fns.len() > 1 { - Ok(BCResponse::BlockHash( + let block_hash_1 = if blocks.len() > 1 { + Ok(BlockchainResponse::BlockHash( get_block_info(&1, tables.block_infos()).unwrap().block_hash, )) } else { Err(RuntimeError::KeyNotFound) }; - let range_0_1 = Ok(BCResponse::BlockExtendedHeaderInRange(vec![ + let range_0_1 = Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec![ get_block_extended_header_from_height(&0, &tables).unwrap(), ])); - let range_0_2 = if block_fns.len() >= 2 { - Ok(BCResponse::BlockExtendedHeaderInRange(vec![ + let range_0_2 = if blocks.len() >= 2 { + Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec![ get_block_extended_header_from_height(&0, &tables).unwrap(), get_block_extended_header_from_height(&1, &tables).unwrap(), ])) @@ -139,13 +140,20 @@ async fn test_template( Err(RuntimeError::KeyNotFound) }; + let test_chain_height = chain_height(tables.block_heights()).unwrap(); + let chain_height = { - let height = chain_height(tables.block_heights()).unwrap(); - let block_info = get_block_info(&height.saturating_sub(1), tables.block_infos()).unwrap(); - Ok(BCResponse::ChainHeight(height, block_info.block_hash)) + let block_info = + get_block_info(&test_chain_height.saturating_sub(1), tables.block_infos()).unwrap(); + Ok(BlockchainResponse::ChainHeight( + test_chain_height, + block_info.block_hash, + )) }; - let cumulative_generated_coins = Ok(BCResponse::GeneratedCoins(cumulative_generated_coins)); + let cumulative_generated_coins = Ok(BlockchainResponse::GeneratedCoins( + cumulative_generated_coins, + )); let num_req = tables .outputs_iter() @@ -155,12 +163,14 @@ async fn test_template( .map(|key| key.amount) .collect::>(); - let num_resp = Ok(BCResponse::NumberOutputsWithAmount( + let num_resp = Ok(BlockchainResponse::NumberOutputsWithAmount( num_req .iter() .map(|amount| match tables.num_outputs().get(amount) { - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - #[allow(clippy::cast_possible_truncation)] + #[expect( + clippy::cast_possible_truncation, + reason = "INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`" + )] Ok(count) => (*amount, count as usize), Err(RuntimeError::KeyNotFound) => (*amount, 0), Err(e) => panic!("{e:?}"), @@ -170,27 +180,45 @@ async fn test_template( // Contains a fake non-spent key-image. let ki_req = HashSet::from([[0; 32]]); - let ki_resp = Ok(BCResponse::KeyImagesSpent(false)); + let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(false)); //----------------------------------------------------------------------- Assert expected response // Assert read requests lead to the expected responses. for (request, expected_response) in [ ( - BCReadRequest::BlockExtendedHeader(0), + BlockchainReadRequest::BlockExtendedHeader(0), extended_block_header_0, ), ( - BCReadRequest::BlockExtendedHeader(1), + BlockchainReadRequest::BlockExtendedHeader(1), extended_block_header_1, ), - (BCReadRequest::BlockHash(0), block_hash_0), - (BCReadRequest::BlockHash(1), block_hash_1), - (BCReadRequest::BlockExtendedHeaderInRange(0..1), range_0_1), - (BCReadRequest::BlockExtendedHeaderInRange(0..2), range_0_2), - (BCReadRequest::ChainHeight, chain_height), - (BCReadRequest::GeneratedCoins, cumulative_generated_coins), - (BCReadRequest::NumberOutputsWithAmount(num_req), num_resp), - (BCReadRequest::KeyImagesSpent(ki_req), ki_resp), + ( + BlockchainReadRequest::BlockHash(0, Chain::Main), + block_hash_0, + ), + ( + BlockchainReadRequest::BlockHash(1, Chain::Main), + block_hash_1, + ), + ( + BlockchainReadRequest::BlockExtendedHeaderInRange(0..1, Chain::Main), + range_0_1, + ), + ( + BlockchainReadRequest::BlockExtendedHeaderInRange(0..2, Chain::Main), + range_0_2, + ), + (BlockchainReadRequest::ChainHeight, chain_height), + ( + BlockchainReadRequest::GeneratedCoins(test_chain_height), + cumulative_generated_coins, + ), + ( + BlockchainReadRequest::NumberOutputsWithAmount(num_req), + num_resp, + ), + (BlockchainReadRequest::KeyImagesSpent(ki_req), ki_resp), ] { let response = reader.clone().oneshot(request).await; println!("response: {response:#?}, expected_response: {expected_response:#?}"); @@ -204,50 +232,46 @@ async fn test_template( // Assert each key image we inserted comes back as "spent". for key_image in tables.key_images_iter().keys().unwrap() { let key_image = key_image.unwrap(); - let request = BCReadRequest::KeyImagesSpent(HashSet::from([key_image])); + let request = BlockchainReadRequest::KeyImagesSpent(HashSet::from([key_image])); let response = reader.clone().oneshot(request).await; println!("response: {response:#?}, key_image: {key_image:#?}"); - assert_eq!(response.unwrap(), BCResponse::KeyImagesSpent(true)); + assert_eq!(response.unwrap(), BlockchainResponse::KeyImagesSpent(true)); } //----------------------------------------------------------------------- Output checks // Create the map of amounts and amount indices. - // - // FIXME: There's definitely a better way to map - // `Vec` -> `HashMap>` let (map, output_count) = { - let mut ids = tables - .outputs_iter() - .keys() - .unwrap() - .map(Result::unwrap) - .collect::>(); - - ids.extend( - tables - .rct_outputs_iter() - .keys() - .unwrap() - .map(Result::unwrap) - .map(|amount_index| PreRctOutputId { - amount: 0, - amount_index, - }), - ); + let mut map = HashMap::>::new(); // Used later to compare the amount of Outputs // returned in the Response is equal to the amount // we asked for. - let output_count = ids.len(); + let mut output_count: usize = 0; - let mut map = HashMap::>::new(); - for id in ids { - map.entry(id.amount) - .and_modify(|set| { - set.insert(id.amount_index); - }) - .or_insert_with(|| HashSet::from([id.amount_index])); - } + tables + .outputs_iter() + .keys() + .unwrap() + .map(Result::unwrap) + .chain( + tables + .rct_outputs_iter() + .keys() + .unwrap() + .map(Result::unwrap) + .map(|amount_index| PreRctOutputId { + amount: 0, + amount_index, + }), + ) + .for_each(|id| { + output_count += 1; + map.entry(id.amount) + .and_modify(|set| { + set.insert(id.amount_index); + }) + .or_insert_with(|| HashSet::from([id.amount_index])); + }); (map, output_count) }; @@ -268,10 +292,10 @@ async fn test_template( .collect::>(); // Send a request for every output we inserted before. - let request = BCReadRequest::Outputs(map.clone()); + let request = BlockchainReadRequest::Outputs(map.clone()); let response = reader.clone().oneshot(request).await; println!("Response::Outputs response: {response:#?}"); - let Ok(BCResponse::Outputs(response)) = response else { + let Ok(BlockchainResponse::Outputs(response)) = response else { panic!("{response:#?}") }; @@ -281,8 +305,12 @@ async fn test_template( // Assert we get back the same map of // `Amount`'s and `AmountIndex`'s. let mut response_output_count = 0; + #[expect( + clippy::iter_over_hash_type, + reason = "order doesn't matter in this test" + )] for (amount, output_map) in response { - let amount_index_set = map.get(&amount).unwrap(); + let amount_index_set = &map[&amount]; for (amount_index, output) in output_map { response_output_count += 1; @@ -310,11 +338,12 @@ fn init_drop() { #[tokio::test] async fn v1_tx2() { test_template( - &[block_v1_tx2], + &[&*BLOCK_V1_TX2], 14_535_350_982_449, AssertTableLen { block_infos: 1, - block_blobs: 1, + block_header_blobs: 1, + block_txs_hashes: 1, block_heights: 1, key_images: 65, num_outputs: 41, @@ -336,11 +365,12 @@ async fn v1_tx2() { #[tokio::test] async fn v9_tx3() { test_template( - &[block_v9_tx3], + &[&*BLOCK_V9_TX3], 3_403_774_022_163, AssertTableLen { block_infos: 1, - block_blobs: 1, + block_header_blobs: 1, + block_txs_hashes: 1, block_heights: 1, key_images: 4, num_outputs: 0, @@ -362,11 +392,12 @@ async fn v9_tx3() { #[tokio::test] async fn v16_tx0() { test_template( - &[block_v16_tx0], + &[&*BLOCK_V16_TX0], 600_000_000_000, AssertTableLen { block_infos: 1, - block_blobs: 1, + block_header_blobs: 1, + block_txs_hashes: 1, block_heights: 1, key_images: 0, num_outputs: 0, @@ -383,3 +414,92 @@ async fn v16_tx0() { ) .await; } + +/// Tests the alt-chain requests and responses. +#[tokio::test] +async fn alt_chain_requests() { + let (reader, mut writer, _, _tempdir) = init_service(); + + // Set up the test by adding blocks to the main-chain. + for (i, mut block) in [BLOCK_V9_TX3.clone(), BLOCK_V16_TX0.clone()] + .into_iter() + .enumerate() + { + block.height = i; + + let request = BlockchainWriteRequest::WriteBlock(block); + writer.call(request).await.unwrap(); + } + + // Generate the alt-blocks. + let mut prev_hash = BLOCK_V9_TX3.block_hash; + let mut chain_id = 1; + let alt_blocks = [&BLOCK_V16_TX0, &BLOCK_V9_TX3, &BLOCK_V1_TX2] + .into_iter() + .enumerate() + .map(|(i, block)| { + let mut block = (**block).clone(); + block.height = i + 1; + block.block.header.previous = prev_hash; + block.block_blob = block.block.serialize(); + + prev_hash = block.block_hash; + // Randomly either keep the [`ChainId`] the same or change it to a new value. + chain_id += rand::thread_rng().gen_range(0..=1); + + map_verified_block_to_alt(block, ChainId(chain_id.try_into().unwrap())) + }) + .collect::>(); + + for block in &alt_blocks { + // Request a block to be written, assert it was written. + let request = BlockchainWriteRequest::WriteAltBlock(block.clone()); + let response_channel = writer.call(request); + let response = response_channel.await.unwrap(); + assert_eq!(response, BlockchainResponse::Ok); + } + + // Get the full alt-chain + let request = BlockchainReadRequest::AltBlocksInChain(ChainId(chain_id.try_into().unwrap())); + let response = reader.clone().oneshot(request).await.unwrap(); + + let BlockchainResponse::AltBlocksInChain(blocks) = response else { + panic!("Wrong response type was returned"); + }; + + assert_eq!(blocks.len(), alt_blocks.len()); + for (got_block, alt_block) in blocks.into_iter().zip(alt_blocks) { + assert_eq!(got_block.block_blob, alt_block.block_blob); + assert_eq!(got_block.block_hash, alt_block.block_hash); + assert_eq!(got_block.chain_id, alt_block.chain_id); + assert_eq!(got_block.txs, alt_block.txs); + } + + // Flush all alt blocks. + let request = BlockchainWriteRequest::FlushAltBlocks; + let response = writer.ready().await.unwrap().call(request).await.unwrap(); + assert_eq!(response, BlockchainResponse::Ok); + + // Pop blocks from the main chain + let request = BlockchainWriteRequest::PopBlocks(1); + let response = writer.ready().await.unwrap().call(request).await.unwrap(); + + let BlockchainResponse::PopBlocks(old_main_chain_id) = response else { + panic!("Wrong response type was returned"); + }; + + // Check we have popped the top block. + let request = BlockchainReadRequest::ChainHeight; + let response = reader.clone().oneshot(request).await.unwrap(); + assert!(matches!(response, BlockchainResponse::ChainHeight(1, _))); + + // Attempt to add the popped block back. + let request = BlockchainWriteRequest::ReverseReorg(old_main_chain_id); + let response = writer.ready().await.unwrap().call(request).await.unwrap(); + assert_eq!(response, BlockchainResponse::Ok); + + // Check we have the popped block back. + let request = BlockchainReadRequest::ChainHeight; + let response = reader.clone().oneshot(request).await.unwrap(); + assert!(matches!(response, BlockchainResponse::ChainHeight(2, _))); +} diff --git a/storage/blockchain/src/service/types.rs b/storage/blockchain/src/service/types.rs index c6ee67e..190e9f6 100644 --- a/storage/blockchain/src/service/types.rs +++ b/storage/blockchain/src/service/types.rs @@ -1,30 +1,20 @@ //! Database service type aliases. -//! -//! Only used internally for our `tower::Service` impls. //---------------------------------------------------------------------------------------------------- Use -use futures::channel::oneshot::Sender; - -use cuprate_database::RuntimeError; -use cuprate_helper::asynch::InfallibleOneshotReceiver; -use cuprate_types::blockchain::BCResponse; +use cuprate_database::DbResult; +use cuprate_database_service::{DatabaseReadService, DatabaseWriteHandle}; +use cuprate_types::blockchain::{ + BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest, +}; //---------------------------------------------------------------------------------------------------- Types /// The actual type of the response. /// -/// Either our [`BCResponse`], or a database error occurred. -pub(super) type ResponseResult = Result; +/// Either our [`BlockchainResponse`], or a database error occurred. +pub(super) type ResponseResult = DbResult; -/// The `Receiver` channel that receives the read response. -/// -/// This is owned by the caller (the reader/writer thread) -/// who `.await`'s for the response. -/// -/// The channel itself should never fail, -/// but the actual database operation might. -pub(super) type ResponseReceiver = InfallibleOneshotReceiver; +/// The blockchain database write service. +pub type BlockchainWriteHandle = DatabaseWriteHandle; -/// The `Sender` channel for the response. -/// -/// The database reader/writer thread uses this to send the database result to the caller. -pub(super) type ResponseSender = Sender; +/// The blockchain database read service. +pub type BlockchainReadHandle = DatabaseReadService; diff --git a/storage/blockchain/src/service/write.rs b/storage/blockchain/src/service/write.rs index 42d9694..84c2538 100644 --- a/storage/blockchain/src/service/write.rs +++ b/storage/blockchain/src/service/write.rs @@ -1,209 +1,50 @@ //! Database writer thread definitions and logic. - //---------------------------------------------------------------------------------------------------- Import -use std::{ - sync::Arc, - task::{Context, Poll}, -}; +use std::sync::Arc; -use futures::channel::oneshot; - -use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw}; -use cuprate_helper::asynch::InfallibleOneshotReceiver; +use cuprate_database::{ConcreteEnv, DatabaseRo, DbResult, Env, EnvInner, TxRw}; +use cuprate_database_service::DatabaseWriteHandle; use cuprate_types::{ - blockchain::{BCResponse, BCWriteRequest}, - VerifiedBlockInformation, + blockchain::{BlockchainResponse, BlockchainWriteRequest}, + AltBlockInformation, Chain, ChainId, VerifiedBlockInformation, }; use crate::{ - open_tables::OpenTables, - service::types::{ResponseReceiver, ResponseResult, ResponseSender}, + service::{ + free::map_valid_alt_block_to_verified_block, + types::{BlockchainWriteHandle, ResponseResult}, + }, + tables::{OpenTables, Tables}, + types::AltBlockHeight, }; -//---------------------------------------------------------------------------------------------------- Constants -/// Name of the writer thread. -const WRITER_THREAD_NAME: &str = concat!(module_path!(), "::DatabaseWriter"); - -//---------------------------------------------------------------------------------------------------- DatabaseWriteHandle -/// Write handle to the database. +/// Write functions within this module abort if the write transaction +/// could not be aborted successfully to maintain atomicity. /// -/// This is handle that allows `async`hronously writing to the database, -/// it is not [`Clone`]able as there is only ever 1 place within Cuprate -/// that writes. -/// -/// Calling [`tower::Service::call`] with a [`DatabaseWriteHandle`] & [`BCWriteRequest`] -/// will return an `async`hronous channel that can be `.await`ed upon -/// to receive the corresponding [`BCResponse`]. -#[derive(Debug)] -pub struct DatabaseWriteHandle { - /// Sender channel to the database write thread-pool. - /// - /// We provide the response channel for the thread-pool. - pub(super) sender: crossbeam::channel::Sender<(BCWriteRequest, ResponseSender)>, +/// This is the panic message if the `abort()` fails. +const TX_RW_ABORT_FAIL: &str = + "Could not maintain blockchain database atomicity by aborting write transaction"; + +//---------------------------------------------------------------------------------------------------- init_write_service +/// Initialize the blockchain write service from a [`ConcreteEnv`]. +pub fn init_write_service(env: Arc) -> BlockchainWriteHandle { + DatabaseWriteHandle::init(env, handle_blockchain_request) } -impl DatabaseWriteHandle { - /// Initialize the single `DatabaseWriter` thread. - #[cold] - #[inline(never)] // Only called once. - pub(super) fn init(env: Arc) -> Self { - // Initialize `Request/Response` channels. - let (sender, receiver) = crossbeam::channel::unbounded(); - - // Spawn the writer. - std::thread::Builder::new() - .name(WRITER_THREAD_NAME.into()) - .spawn(move || { - let this = DatabaseWriter { receiver, env }; - DatabaseWriter::main(this); - }) - .unwrap(); - - Self { sender } - } -} - -impl tower::Service for DatabaseWriteHandle { - type Response = BCResponse; - type Error = RuntimeError; - type Future = ResponseReceiver; - - #[inline] - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - #[inline] - fn call(&mut self, request: BCWriteRequest) -> Self::Future { - // Response channel we `.await` on. - let (response_sender, receiver) = oneshot::channel(); - - // Send the write request. - self.sender.send((request, response_sender)).unwrap(); - - InfallibleOneshotReceiver::from(receiver) - } -} - -//---------------------------------------------------------------------------------------------------- DatabaseWriter -/// The single database writer thread. -pub(super) struct DatabaseWriter { - /// Receiver side of the database request channel. - /// - /// Any caller can send some requests to this channel. - /// They send them alongside another `Response` channel, - /// which we will eventually send to. - receiver: crossbeam::channel::Receiver<(BCWriteRequest, ResponseSender)>, - - /// Access to the database. - env: Arc, -} - -impl Drop for DatabaseWriter { - fn drop(&mut self) { - // TODO: log the writer thread has exited? - } -} - -impl DatabaseWriter { - /// The `DatabaseWriter`'s main function. - /// - /// The writer just loops in this function, handling requests forever - /// until the request channel is dropped or a panic occurs. - #[cold] - #[inline(never)] // Only called once. - fn main(self) { - // 1. Hang on request channel - // 2. Map request to some database function - // 3. Execute that function, get the result - // 4. Return the result via channel - 'main: loop { - let Ok((request, response_sender)) = self.receiver.recv() else { - // If this receive errors, it means that the channel is empty - // and disconnected, meaning the other side (all senders) have - // been dropped. This means "shutdown", and we return here to - // exit the thread. - // - // Since the channel is empty, it means we've also processed - // all requests. Since it is disconnected, it means future - // ones cannot come in. - return; - }; - - /// How many times should we retry handling the request on resize errors? - /// - /// This is 1 on automatically resizing databases, meaning there is only 1 iteration. - const REQUEST_RETRY_LIMIT: usize = if ConcreteEnv::MANUAL_RESIZE { 3 } else { 1 }; - - // Map [`Request`]'s to specific database functions. - // - // Both will: - // 1. Map the request to a function - // 2. Call the function - // 3. (manual resize only) If resize is needed, resize and retry - // 4. (manual resize only) Redo step {1, 2} - // 5. Send the function's `Result` back to the requester - // - // FIXME: there's probably a more elegant way - // to represent this retry logic with recursive - // functions instead of a loop. - 'retry: for retry in 0..REQUEST_RETRY_LIMIT { - // FIXME: will there be more than 1 write request? - // this won't have to be an enum. - let response = match &request { - BCWriteRequest::WriteBlock(block) => write_block(&self.env, block), - }; - - // If the database needs to resize, do so. - if ConcreteEnv::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded)) - { - // If this is the last iteration of the outer `for` loop and we - // encounter a resize error _again_, it means something is wrong. - assert_ne!( - retry, REQUEST_RETRY_LIMIT, - "database resize failed maximum of {REQUEST_RETRY_LIMIT} times" - ); - - // Resize the map, and retry the request handling loop. - // - // FIXME: - // We could pass in custom resizes to account for - // batches, i.e., we're about to add ~5GB of data, - // add that much instead of the default 1GB. - // - let old = self.env.current_map_size(); - let new = self.env.resize_map(None); - - // TODO: use tracing. - println!("resizing database memory map, old: {old}B, new: {new}B"); - - // Try handling the request again. - continue 'retry; - } - - // Automatically resizing databases should not be returning a resize error. - #[cfg(debug_assertions)] - if !ConcreteEnv::MANUAL_RESIZE { - assert!( - !matches!(response, Err(RuntimeError::ResizeNeeded)), - "auto-resizing database returned a ResizeNeeded error" - ); - } - - // Send the response back, whether if it's an `Ok` or `Err`. - if let Err(e) = response_sender.send(response) { - // TODO: use tracing. - println!("database writer failed to send response: {e:?}"); - } - - continue 'main; - } - - // Above retry loop should either: - // - continue to the next ['main] loop or... - // - ...retry until panic - unreachable!(); +//---------------------------------------------------------------------------------------------------- handle_bc_request +/// Handle an incoming [`BlockchainWriteRequest`], returning a [`BlockchainResponse`]. +fn handle_blockchain_request( + env: &ConcreteEnv, + req: &BlockchainWriteRequest, +) -> DbResult { + match req { + BlockchainWriteRequest::WriteBlock(block) => write_block(env, block), + BlockchainWriteRequest::WriteAltBlock(alt_block) => write_alt_block(env, alt_block), + BlockchainWriteRequest::PopBlocks(numb_blocks) => pop_blocks(env, *numb_blocks), + BlockchainWriteRequest::ReverseReorg(old_main_chain_id) => { + reverse_reorg(env, *old_main_chain_id) } + BlockchainWriteRequest::FlushAltBlocks => flush_alt_blocks(env), } } @@ -216,7 +57,7 @@ impl DatabaseWriter { // Each function will return the [`Response`] that we // should send back to the caller in [`map_request()`]. -/// [`BCWriteRequest::WriteBlock`]. +/// [`BlockchainWriteRequest::WriteBlock`]. #[inline] fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseResult { let env_inner = env.env_inner(); @@ -230,13 +71,140 @@ fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseR match result { Ok(()) => { TxRw::commit(tx_rw)?; - Ok(BCResponse::WriteBlockOk) + Ok(BlockchainResponse::Ok) } Err(e) => { - // INVARIANT: ensure database atomicity by aborting - // the transaction on `add_block()` failures. - TxRw::abort(tx_rw) - .expect("could not maintain database atomicity by aborting write transaction"); + TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL); + Err(e) + } + } +} + +/// [`BlockchainWriteRequest::WriteAltBlock`]. +#[inline] +fn write_alt_block(env: &ConcreteEnv, block: &AltBlockInformation) -> ResponseResult { + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw()?; + + let result = { + let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; + crate::ops::alt_block::add_alt_block(block, &mut tables_mut) + }; + + match result { + Ok(()) => { + TxRw::commit(tx_rw)?; + Ok(BlockchainResponse::Ok) + } + Err(e) => { + TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL); + Err(e) + } + } +} + +/// [`BlockchainWriteRequest::PopBlocks`]. +fn pop_blocks(env: &ConcreteEnv, numb_blocks: usize) -> ResponseResult { + let env_inner = env.env_inner(); + let mut tx_rw = env_inner.tx_rw()?; + + // FIXME: turn this function into a try block once stable. + let mut result = || { + // flush all the current alt blocks as they may reference blocks to be popped. + crate::ops::alt_block::flush_alt_blocks(&env_inner, &mut tx_rw)?; + + let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; + // generate a `ChainId` for the popped blocks. + let old_main_chain_id = ChainId(rand::random()); + + // pop the blocks + for _ in 0..numb_blocks { + crate::ops::block::pop_block(Some(old_main_chain_id), &mut tables_mut)?; + } + + Ok(old_main_chain_id) + }; + + match result() { + Ok(old_main_chain_id) => { + TxRw::commit(tx_rw)?; + Ok(BlockchainResponse::PopBlocks(old_main_chain_id)) + } + Err(e) => { + TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL); + Err(e) + } + } +} + +/// [`BlockchainWriteRequest::ReverseReorg`]. +fn reverse_reorg(env: &ConcreteEnv, chain_id: ChainId) -> ResponseResult { + let env_inner = env.env_inner(); + let mut tx_rw = env_inner.tx_rw()?; + + // FIXME: turn this function into a try block once stable. + let mut result = || { + let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; + + let chain_info = tables_mut.alt_chain_infos().get(&chain_id.into())?; + // Although this doesn't guarantee the chain was popped from the main-chain, it's an easy + // thing for us to check. + assert_eq!(Chain::from(chain_info.parent_chain), Chain::Main); + + let top_block_height = + crate::ops::blockchain::top_block_height(tables_mut.block_heights())?; + + // pop any blocks that were added as part of a re-org. + for _ in chain_info.common_ancestor_height..top_block_height { + crate::ops::block::pop_block(None, &mut tables_mut)?; + } + + // Add the old main chain blocks back to the main chain. + for height in (chain_info.common_ancestor_height + 1)..chain_info.chain_height { + let alt_block = crate::ops::alt_block::get_alt_block( + &AltBlockHeight { + chain_id: chain_id.into(), + height, + }, + &tables_mut, + )?; + let verified_block = map_valid_alt_block_to_verified_block(alt_block); + crate::ops::block::add_block(&verified_block, &mut tables_mut)?; + } + + drop(tables_mut); + crate::ops::alt_block::flush_alt_blocks(&env_inner, &mut tx_rw)?; + + Ok(()) + }; + + match result() { + Ok(()) => { + TxRw::commit(tx_rw)?; + Ok(BlockchainResponse::Ok) + } + Err(e) => { + TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL); + Err(e) + } + } +} + +/// [`BlockchainWriteRequest::FlushAltBlocks`]. +#[inline] +fn flush_alt_blocks(env: &ConcreteEnv) -> ResponseResult { + let env_inner = env.env_inner(); + let mut tx_rw = env_inner.tx_rw()?; + + let result = crate::ops::alt_block::flush_alt_blocks(&env_inner, &mut tx_rw); + + match result { + Ok(()) => { + TxRw::commit(tx_rw)?; + Ok(BlockchainResponse::Ok) + } + Err(e) => { + TxRw::abort(tx_rw).expect(TX_RW_ABORT_FAIL); Err(e) } } diff --git a/storage/blockchain/src/tables.rs b/storage/blockchain/src/tables.rs index 447faa6..b9fc5ed 100644 --- a/storage/blockchain/src/tables.rs +++ b/storage/blockchain/src/tables.rs @@ -4,345 +4,54 @@ //! This module contains all the table definitions used by `cuprate_blockchain`. //! //! The zero-sized structs here represents the table type; -//! they all are essentially marker types that implement [`Table`]. +//! they all are essentially marker types that implement [`cuprate_database::Table`]. //! //! Table structs are `CamelCase`, and their static string //! names used by the actual database backend are `snake_case`. //! -//! For example: [`BlockBlobs`] -> `block_blobs`. +//! For example: [`BlockHeaderBlobs`] -> `block_header_blobs`. //! //! # Traits //! This module also contains a set of traits for //! accessing _all_ tables defined here at once. -//! -//! For example, this is the object returned by [`OpenTables::open_tables`](crate::OpenTables::open_tables). //---------------------------------------------------------------------------------------------------- Import -use cuprate_database::{DatabaseIter, DatabaseRo, DatabaseRw, Table}; - use crate::types::{ - Amount, AmountIndex, AmountIndices, BlockBlob, BlockHash, BlockHeight, BlockInfo, KeyImage, - Output, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, RctOutput, TxBlob, TxHash, - TxId, UnlockTime, + AltBlockHeight, AltChainInfo, AltTransactionInfo, Amount, AmountIndex, AmountIndices, + BlockBlob, BlockHash, BlockHeaderBlob, BlockHeight, BlockInfo, BlockTxHashes, + CompactAltBlockInfo, KeyImage, Output, PreRctOutputId, PrunableBlob, PrunableHash, PrunedBlob, + RawChainId, RctOutput, TxBlob, TxHash, TxId, UnlockTime, }; -//---------------------------------------------------------------------------------------------------- Sealed -/// Private module, should not be accessible outside this crate. -pub(super) mod private { - /// Private sealed trait. - /// - /// Cannot be implemented outside this crate. - pub trait Sealed {} -} - -//---------------------------------------------------------------------------------------------------- `trait Tables[Mut]` -/// Creates: -/// - `pub trait Tables` -/// - `pub trait TablesIter` -/// - `pub trait TablesMut` -/// - Blanket implementation for `(tuples, containing, all, open, database, tables, ...)` -/// -/// For why this exists, see: . -macro_rules! define_trait_tables { - ($( - // The `T: Table` type The index in a tuple - // | containing all tables - // v v - $table:ident => $index:literal - ),* $(,)?) => { paste::paste! { - /// Object containing all opened [`Table`]s in read-only mode. - /// - /// This is an encapsulated object that contains all - /// available [`Table`]'s in read-only mode. - /// - /// It is a `Sealed` trait and is only implemented on a - /// `(tuple, containing, all, table, types, ...)`. - /// - /// This is used to return a _single_ object from functions like - /// [`OpenTables::open_tables`](crate::OpenTables::open_tables) rather - /// than the tuple containing the tables itself. - /// - /// To replace `tuple.0` style indexing, `field_accessor_functions()` - /// are provided on this trait, which essentially map the object to - /// fields containing the particular database table, for example: - /// ```rust,ignore - /// let tables = open_tables(); - /// - /// // The accessor function `block_infos()` returns the field - /// // containing an open database table for `BlockInfos`. - /// let _ = tables.block_infos(); - /// ``` - /// - /// See also: - /// - [`TablesMut`] - /// - [`TablesIter`] - pub trait Tables: private::Sealed { - // This expands to creating `fn field_accessor_functions()` - // for each passed `$table` type. - // - // It is essentially a mapping to the field - // containing the proper opened database table. - // - // The function name of the function is - // the table type in `snake_case`, e.g., `block_info_v1s()`. - $( - /// Access an opened - #[doc = concat!("[`", stringify!($table), "`]")] - /// database. - fn [<$table:snake>](&self) -> &impl DatabaseRo<$table>; - )* - - /// This returns `true` if all tables are empty. - /// - /// # Errors - /// This returns errors on regular database errors. - fn all_tables_empty(&self) -> Result; - } - - /// Object containing all opened [`Table`]s in read + iter mode. - /// - /// This is the same as [`Tables`] but includes `_iter()` variants. - /// - /// Note that this trait is a supertrait of `Tables`, - /// as in it can use all of its functions as well. - /// - /// See [`Tables`] for documentation - this trait exists for the same reasons. - pub trait TablesIter: private::Sealed + Tables { - $( - /// Access an opened read-only + iterable - #[doc = concat!("[`", stringify!($table), "`]")] - /// database. - fn [<$table:snake _iter>](&self) -> &(impl DatabaseRo<$table> + DatabaseIter<$table>); - )* - } - - /// Object containing all opened [`Table`]s in write mode. - /// - /// This is the same as [`Tables`] but for mutable accesses. - /// - /// Note that this trait is a supertrait of `Tables`, - /// as in it can use all of its functions as well. - /// - /// See [`Tables`] for documentation - this trait exists for the same reasons. - pub trait TablesMut: private::Sealed + Tables { - $( - /// Access an opened - #[doc = concat!("[`", stringify!($table), "`]")] - /// database. - fn [<$table:snake _mut>](&mut self) -> &mut impl DatabaseRw<$table>; - )* - } - - // Implement `Sealed` for all table types. - impl<$([<$table:upper>]),*> private::Sealed for ($([<$table:upper>]),*) {} - - // This creates a blanket-implementation for - // `(tuple, containing, all, table, types)`. - // - // There is a generic defined here _for each_ `$table` input. - // Specifically, the generic letters are just the table types in UPPERCASE. - // Concretely, this expands to something like: - // ```rust - // impl - // ``` - impl<$([<$table:upper>]),*> Tables - // We are implementing `Tables` on a tuple that - // contains all those generics specified, i.e., - // a tuple containing all open table types. - // - // Concretely, this expands to something like: - // ```rust - // (BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...]) - // ``` - // which is just a tuple of the generics defined above. - for ($([<$table:upper>]),*) - where - // This expands to a where bound that asserts each element - // in the tuple implements some database table type. - // - // Concretely, this expands to something like: - // ```rust - // BLOCKINFOSV1S: DatabaseRo + DatabaseIter, - // BLOCKINFOSV2S: DatabaseRo + DatabaseIter, - // [...] - // ``` - $( - [<$table:upper>]: DatabaseRo<$table>, - )* - { - $( - // The function name of the accessor function is - // the table type in `snake_case`, e.g., `block_info_v1s()`. - #[inline] - fn [<$table:snake>](&self) -> &impl DatabaseRo<$table> { - // The index of the database table in - // the tuple implements the table trait. - &self.$index - } - )* - - fn all_tables_empty(&self) -> Result { - $( - if !DatabaseRo::is_empty(&self.$index)? { - return Ok(false); - } - )* - Ok(true) - } - } - - // This is the same as the above - // `Tables`, but for `TablesIter`. - impl<$([<$table:upper>]),*> TablesIter - for ($([<$table:upper>]),*) - where - $( - [<$table:upper>]: DatabaseRo<$table> + DatabaseIter<$table>, - )* - { - $( - // The function name of the accessor function is - // the table type in `snake_case` + `_iter`, e.g., `block_info_v1s_iter()`. - #[inline] - fn [<$table:snake _iter>](&self) -> &(impl DatabaseRo<$table> + DatabaseIter<$table>) { - &self.$index - } - )* - } - - // This is the same as the above - // `Tables`, but for `TablesMut`. - impl<$([<$table:upper>]),*> TablesMut - for ($([<$table:upper>]),*) - where - $( - [<$table:upper>]: DatabaseRw<$table>, - )* - { - $( - // The function name of the mutable accessor function is - // the table type in `snake_case` + `_mut`, e.g., `block_info_v1s_mut()`. - #[inline] - fn [<$table:snake _mut>](&mut self) -> &mut impl DatabaseRw<$table> { - &mut self.$index - } - )* - } - }}; -} - -// Input format: $table_type => $index -// -// The $index: -// - Simply increments by 1 for each table -// - Must be 0.. -// - Must end at the total amount of table types - 1 -// -// Compile errors will occur if these aren't satisfied. -// -// $index is just the `tuple.$index`, as the above [`define_trait_tables`] -// macro has a blanket impl for `(all, table, types, ...)` and we must map -// each type to a tuple index explicitly. -// -// FIXME: there's definitely an automatic way to this :) -define_trait_tables! { - BlockInfos => 0, - BlockBlobs => 1, - BlockHeights => 2, - KeyImages => 3, - NumOutputs => 4, - PrunedTxBlobs => 5, - PrunableHashes => 6, - Outputs => 7, - PrunableTxBlobs => 8, - RctOutputs => 9, - TxBlobs => 10, - TxIds => 11, - TxHeights => 12, - TxOutputs => 13, - TxUnlockTime => 14, -} - -//---------------------------------------------------------------------------------------------------- Table macro -/// Create all tables, should be used _once_. -/// -/// Generating this macro once and using `$()*` is probably -/// faster for compile times than calling the macro _per_ table. -/// -/// All tables are zero-sized table structs, and implement the `Table` trait. -/// -/// Table structs are automatically `CamelCase`, -/// and their static string names are automatically `snake_case`. -macro_rules! tables { - ( - $( - $(#[$attr:meta])* // Documentation and any `derive`'s. - $table:ident, // The table name + doubles as the table struct name. - $key:ty => // Key type. - $value:ty // Value type. - ),* $(,)? - ) => { - paste::paste! { $( - // Table struct. - $(#[$attr])* - // The below test show the `snake_case` table name in cargo docs. - #[doc = concat!("- Key: [`", stringify!($key), "`]")] - #[doc = concat!("- Value: [`", stringify!($value), "`]")] - /// - /// ## Table Name - /// ```rust - /// # use cuprate_blockchain::{*,tables::*}; - /// use cuprate_database::Table; - #[doc = concat!( - "assert_eq!(", - stringify!([<$table:camel>]), - "::NAME, \"", - stringify!([<$table:snake>]), - "\");", - )] - /// ``` - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)] - pub struct [<$table:camel>]; - - // Implement the `Sealed` in this file. - // Required by `Table`. - impl private::Sealed for [<$table:camel>] {} - - // Table trait impl. - impl Table for [<$table:camel>] { - const NAME: &'static str = stringify!([<$table:snake>]); - type Key = $key; - type Value = $value; - } - )* } - }; -} - //---------------------------------------------------------------------------------------------------- Tables // Notes: // - Keep this sorted A-Z (by table name) // - Tables are defined in plural to avoid name conflicts with types // - If adding/changing a table also edit: // - the tests in `src/backend/tests.rs` -// - `call_fn_on_all_tables_or_early_return!()` macro in `src/open_tables.rs` -tables! { - /// Serialized block blobs (bytes). +cuprate_database::define_tables! { + /// Serialized block header blobs (bytes). /// - /// Contains the serialized version of all blocks. - BlockBlobs, - BlockHeight => BlockBlob, + /// Contains the serialized version of all blocks headers. + 0 => BlockHeaderBlobs, + BlockHeight => BlockHeaderBlob, + + /// Block transactions hashes + /// + /// Contains all the transaction hashes of all blocks. + 1 => BlockTxsHashes, + BlockHeight => BlockTxHashes, /// Block heights. /// /// Contains the height of all blocks. - BlockHeights, + 2 => BlockHeights, BlockHash => BlockHeight, /// Block information. /// /// Contains metadata of all blocks. - BlockInfos, + 3 => BlockInfos, BlockHeight => BlockInfo, /// Set of key images. @@ -351,38 +60,38 @@ tables! { /// /// This table has `()` as the value type, as in, /// it is a set of key images. - KeyImages, + 4 => KeyImages, KeyImage => (), /// Maps an output's amount to the number of outputs with that amount. /// /// For example, if there are 5 outputs with `amount = 123` /// then calling `get(123)` on this table will return 5. - NumOutputs, + 5 => NumOutputs, Amount => u64, /// Pre-RCT output data. - Outputs, + 6 => Outputs, PreRctOutputId => Output, /// Pruned transaction blobs (bytes). /// /// Contains the pruned portion of serialized transaction data. - PrunedTxBlobs, + 7 => PrunedTxBlobs, TxId => PrunedBlob, /// Prunable transaction blobs (bytes). /// /// Contains the prunable portion of serialized transaction data. // SOMEDAY: impl when `monero-serai` supports pruning - PrunableTxBlobs, + 8 => PrunableTxBlobs, TxId => PrunableBlob, /// Prunable transaction hashes. /// /// Contains the prunable portion of transaction hashes. // SOMEDAY: impl when `monero-serai` supports pruning - PrunableHashes, + 9 => PrunableHashes, TxId => PrunableHash, // SOMEDAY: impl a properties table: @@ -392,41 +101,75 @@ tables! { // StorableString => StorableVec, /// RCT output data. - RctOutputs, + 10 => RctOutputs, AmountIndex => RctOutput, /// Transaction blobs (bytes). /// /// Contains the serialized version of all transactions. // SOMEDAY: remove when `monero-serai` supports pruning - TxBlobs, + 11 => TxBlobs, TxId => TxBlob, /// Transaction indices. /// /// Contains the indices all transactions. - TxIds, + 12 => TxIds, TxHash => TxId, /// Transaction heights. /// /// Contains the block height associated with all transactions. - TxHeights, + 13 => TxHeights, TxId => BlockHeight, /// Transaction outputs. /// /// Contains the list of `AmountIndex`'s of the /// outputs associated with all transactions. - TxOutputs, + 14 => TxOutputs, TxId => AmountIndices, /// Transaction unlock time. /// /// Contains the unlock time of transactions IF they have one. /// Transactions without unlock times will not exist in this table. - TxUnlockTime, + 15 => TxUnlockTime, TxId => UnlockTime, + + /// Information on alt-chains. + 16 => AltChainInfos, + RawChainId => AltChainInfo, + + /// Alt-block heights. + /// + /// Contains the height of all alt-blocks. + 17 => AltBlockHeights, + BlockHash => AltBlockHeight, + + /// Alt-block information. + /// + /// Contains information on all alt-blocks. + 18 => AltBlocksInfo, + AltBlockHeight => CompactAltBlockInfo, + + /// Alt-block blobs. + /// + /// Contains the raw bytes of all alt-blocks. + 19 => AltBlockBlobs, + AltBlockHeight => BlockBlob, + + /// Alt-block transaction blobs. + /// + /// Contains the raw bytes of alt transactions, if those transactions are not in the main-chain. + 20 => AltTransactionBlobs, + TxHash => TxBlob, + + /// Alt-block transaction information. + /// + /// Contains information on all alt transactions, even if they are in the main-chain. + 21 => AltTransactionInfos, + TxHash => AltTransactionInfo, } //---------------------------------------------------------------------------------------------------- Tests diff --git a/storage/blockchain/src/tests.rs b/storage/blockchain/src/tests.rs index ec2f18e..4192f81 100644 --- a/storage/blockchain/src/tests.rs +++ b/storage/blockchain/src/tests.rs @@ -5,13 +5,17 @@ //! - only used internally //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, fmt::Debug}; +use std::fmt::Debug; use pretty_assertions::assert_eq; -use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner}; +use cuprate_database::{DatabaseRo, Env, EnvInner}; +use cuprate_types::{AltBlockInformation, ChainId, VerifiedBlockInformation}; -use crate::{config::ConfigBuilder, open_tables::OpenTables, tables::Tables}; +use crate::{ + config::ConfigBuilder, + tables::{OpenTables, Tables}, +}; //---------------------------------------------------------------------------------------------------- Struct /// Named struct to assert the length of all tables. @@ -22,7 +26,8 @@ use crate::{config::ConfigBuilder, open_tables::OpenTables, tables::Tables}; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct AssertTableLen { pub(crate) block_infos: u64, - pub(crate) block_blobs: u64, + pub(crate) block_header_blobs: u64, + pub(crate) block_txs_hashes: u64, pub(crate) block_heights: u64, pub(crate) key_images: u64, pub(crate) num_outputs: u64, @@ -42,7 +47,8 @@ impl AssertTableLen { pub(crate) fn assert(self, tables: &impl Tables) { let other = Self { block_infos: tables.block_infos().len().unwrap(), - block_blobs: tables.block_blobs().len().unwrap(), + block_header_blobs: tables.block_header_blobs().len().unwrap(), + block_txs_hashes: tables.block_txs_hashes().len().unwrap(), block_heights: tables.block_heights().len().unwrap(), key_images: tables.key_images().len().unwrap(), num_outputs: tables.num_outputs().len().unwrap(), @@ -65,11 +71,10 @@ impl AssertTableLen { /// Create an `Env` in a temporarily directory. /// The directory is automatically removed after the `TempDir` is dropped. /// -/// FIXME: changing this to `-> impl Env` causes lifetime errors... -pub(crate) fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) { +pub(crate) fn tmp_concrete_env() -> (impl Env, tempfile::TempDir) { let tempdir = tempfile::tempdir().unwrap(); let config = ConfigBuilder::new() - .db_directory(Cow::Owned(tempdir.path().into())) + .data_directory(tempdir.path().into()) .low_power() .build(); let env = crate::open(config).unwrap(); @@ -78,10 +83,28 @@ pub(crate) fn tmp_concrete_env() -> (ConcreteEnv, tempfile::TempDir) { } /// Assert all the tables in the environment are empty. -pub(crate) fn assert_all_tables_are_empty(env: &ConcreteEnv) { +pub(crate) fn assert_all_tables_are_empty(env: &impl Env) { let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro().unwrap(); let tables = env_inner.open_tables(&tx_ro).unwrap(); assert!(tables.all_tables_empty().unwrap()); assert_eq!(crate::ops::tx::get_num_tx(tables.tx_ids()).unwrap(), 0); } + +pub(crate) fn map_verified_block_to_alt( + verified_block: VerifiedBlockInformation, + chain_id: ChainId, +) -> AltBlockInformation { + AltBlockInformation { + block: verified_block.block, + block_blob: verified_block.block_blob, + txs: verified_block.txs, + block_hash: verified_block.block_hash, + pow_hash: verified_block.pow_hash, + height: verified_block.height, + weight: verified_block.weight, + long_term_weight: verified_block.long_term_weight, + cumulative_difficulty: verified_block.cumulative_difficulty, + chain_id, + } +} diff --git a/storage/blockchain/src/types.rs b/storage/blockchain/src/types.rs index a1f28f0..86ef91c 100644 --- a/storage/blockchain/src/types.rs +++ b/storage/blockchain/src/types.rs @@ -1,4 +1,4 @@ -//! Database [table](crate::tables) types. +//! Blockchain [table](crate::tables) types. //! //! This module contains all types used by the database tables, //! and aliases for common Monero-related types that use the @@ -41,12 +41,14 @@ #![forbid(unsafe_code)] // if you remove this line i will steal your monero //---------------------------------------------------------------------------------------------------- Import -use bytemuck::{Pod, Zeroable}; +use std::num::NonZero; +use bytemuck::{Pod, Zeroable}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use cuprate_database::{Key, StorableVec}; +use cuprate_types::{Chain, ChainId}; //---------------------------------------------------------------------------------------------------- Aliases // These type aliases exist as many Monero-related types are the exact same. @@ -64,11 +66,17 @@ pub type AmountIndices = StorableVec; /// A serialized block. pub type BlockBlob = StorableVec; +/// A serialized block header +pub type BlockHeaderBlob = StorableVec; + +/// A block transaction hashes +pub type BlockTxHashes = StorableVec<[u8; 32]>; + /// A block's hash. pub type BlockHash = [u8; 32]; /// A block's height. -pub type BlockHeight = u64; +pub type BlockHeight = usize; /// A key image. pub type KeyImage = [u8; 32]; @@ -121,7 +129,6 @@ pub type UnlockTime = u64; /// # Size & Alignment /// ```rust /// # use cuprate_blockchain::types::*; -/// # use std::mem::*; /// assert_eq!(size_of::(), 16); /// assert_eq!(align_of::(), 8); /// ``` @@ -165,6 +172,7 @@ impl Key for PreRctOutputId {} /// block_hash: [54; 32], /// cumulative_rct_outs: 2389, /// long_term_weight: 2389, +/// mining_tx_index: 23 /// }; /// let b = Storable::as_bytes(&a); /// let c: BlockInfo = Storable::from_bytes(b); @@ -174,8 +182,7 @@ impl Key for PreRctOutputId {} /// # Size & Alignment /// ```rust /// # use cuprate_blockchain::types::*; -/// # use std::mem::*; -/// assert_eq!(size_of::(), 88); +/// assert_eq!(size_of::(), 96); /// assert_eq!(align_of::(), 8); /// ``` #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -189,7 +196,7 @@ pub struct BlockInfo { /// The adjusted block size, in bytes. /// /// See [`block_weight`](https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#blocks-weight). - pub weight: u64, + pub weight: usize, /// Least-significant 64 bits of the 128-bit cumulative difficulty. pub cumulative_difficulty_low: u64, /// Most-significant 64 bits of the 128-bit cumulative difficulty. @@ -201,7 +208,9 @@ pub struct BlockInfo { /// The long term block weight, based on the median weight of the preceding `100_000` blocks. /// /// See [`long_term_weight`](https://monero-book.cuprate.org/consensus_rules/blocks/weights.html#long-term-block-weight). - pub long_term_weight: u64, + pub long_term_weight: usize, + /// [`TxId`] (u64) of the block coinbase transaction. + pub mining_tx_index: TxId, } //---------------------------------------------------------------------------------------------------- OutputFlags @@ -226,7 +235,6 @@ bitflags::bitflags! { /// # Size & Alignment /// ```rust /// # use cuprate_blockchain::types::*; - /// # use std::mem::*; /// assert_eq!(size_of::(), 4); /// assert_eq!(align_of::(), 4); /// ``` @@ -262,7 +270,6 @@ bitflags::bitflags! { /// # Size & Alignment /// ```rust /// # use cuprate_blockchain::types::*; -/// # use std::mem::*; /// assert_eq!(size_of::(), 48); /// assert_eq!(align_of::(), 8); /// ``` @@ -306,7 +313,6 @@ pub struct Output { /// # Size & Alignment /// ```rust /// # use cuprate_blockchain::types::*; -/// # use std::mem::*; /// assert_eq!(size_of::(), 80); /// assert_eq!(align_of::(), 8); /// ``` @@ -329,6 +335,259 @@ pub struct RctOutput { } // TODO: local_index? +//---------------------------------------------------------------------------------------------------- RawChain +/// [`Chain`] in a format which can be stored in the DB. +/// +/// Implements [`Into`] and [`From`] for [`Chain`]. +/// +/// ```rust +/// # use std::borrow::*; +/// # use cuprate_blockchain::{*, types::*}; +/// use cuprate_database::Storable; +/// use cuprate_types::Chain; +/// +/// // Assert Storable is correct. +/// let a: RawChain = Chain::Main.into(); +/// let b = Storable::as_bytes(&a); +/// let c: RawChain = Storable::from_bytes(b); +/// assert_eq!(a, c); +/// ``` +/// +/// # Size & Alignment +/// ```rust +/// # use cuprate_blockchain::types::*; +/// assert_eq!(size_of::(), 8); +/// assert_eq!(align_of::(), 8); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(transparent)] +pub struct RawChain(u64); + +impl From for RawChain { + fn from(value: Chain) -> Self { + match value { + Chain::Main => Self(0), + Chain::Alt(chain_id) => Self(chain_id.0.get()), + } + } +} + +impl From for Chain { + fn from(value: RawChain) -> Self { + NonZero::new(value.0).map_or(Self::Main, |id| Self::Alt(ChainId(id))) + } +} + +impl From for RawChain { + fn from(value: RawChainId) -> Self { + // A [`ChainID`] with an inner value of `0` is invalid. + assert_ne!(value.0, 0); + + Self(value.0) + } +} + +//---------------------------------------------------------------------------------------------------- RawChainId +/// [`ChainId`] in a format which can be stored in the DB. +/// +/// Implements [`Into`] and [`From`] for [`ChainId`]. +/// +/// ```rust +/// # use std::borrow::*; +/// # use cuprate_blockchain::{*, types::*}; +/// use cuprate_database::Storable; +/// use cuprate_types::ChainId; +/// +/// // Assert Storable is correct. +/// let a: RawChainId = ChainId(10.try_into().unwrap()).into(); +/// let b = Storable::as_bytes(&a); +/// let c: RawChainId = Storable::from_bytes(b); +/// assert_eq!(a, c); +/// ``` +/// +/// # Size & Alignment +/// ```rust +/// # use cuprate_blockchain::types::*; +/// assert_eq!(size_of::(), 8); +/// assert_eq!(align_of::(), 8); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(transparent)] +pub struct RawChainId(u64); + +impl From for RawChainId { + fn from(value: ChainId) -> Self { + Self(value.0.get()) + } +} + +impl From for ChainId { + fn from(value: RawChainId) -> Self { + Self(NonZero::new(value.0).expect("RawChainId cannot have a value of `0`")) + } +} + +impl Key for RawChainId {} + +//---------------------------------------------------------------------------------------------------- AltChainInfo +/// Information on an alternative chain. +/// +/// ```rust +/// # use std::borrow::*; +/// # use cuprate_blockchain::{*, types::*}; +/// use cuprate_database::Storable; +/// use cuprate_types::Chain; +/// +/// // Assert Storable is correct. +/// let a: AltChainInfo = AltChainInfo { +/// parent_chain: Chain::Main.into(), +/// common_ancestor_height: 0, +/// chain_height: 1, +/// }; +/// let b = Storable::as_bytes(&a); +/// let c: AltChainInfo = Storable::from_bytes(b); +/// assert_eq!(a, c); +/// ``` +/// +/// # Size & Alignment +/// ```rust +/// # use cuprate_blockchain::types::*; +/// assert_eq!(size_of::(), 24); +/// assert_eq!(align_of::(), 8); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(C)] +pub struct AltChainInfo { + /// The chain this alt chain forks from. + pub parent_chain: RawChain, + /// The height of the first block we share with the parent chain. + pub common_ancestor_height: usize, + /// The chain height of the blocks in this alt chain. + pub chain_height: usize, +} + +//---------------------------------------------------------------------------------------------------- AltBlockHeight +/// Represents the height of a block on an alt-chain. +/// +/// ```rust +/// # use std::borrow::*; +/// # use cuprate_blockchain::{*, types::*}; +/// use cuprate_database::Storable; +/// use cuprate_types::ChainId; +/// +/// // Assert Storable is correct. +/// let a: AltBlockHeight = AltBlockHeight { +/// chain_id: ChainId(1.try_into().unwrap()).into(), +/// height: 1, +/// }; +/// let b = Storable::as_bytes(&a); +/// let c: AltBlockHeight = Storable::from_bytes(b); +/// assert_eq!(a, c); +/// ``` +/// +/// # Size & Alignment +/// ```rust +/// # use cuprate_blockchain::types::*; +/// assert_eq!(size_of::(), 16); +/// assert_eq!(align_of::(), 8); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(C)] +pub struct AltBlockHeight { + /// The [`ChainId`] of the chain this alt block is on, in raw form. + pub chain_id: RawChainId, + /// The height of this alt-block. + pub height: usize, +} + +impl Key for AltBlockHeight {} + +//---------------------------------------------------------------------------------------------------- CompactAltBlockInfo +/// Represents information on an alt-chain. +/// +/// ```rust +/// # use std::borrow::*; +/// # use cuprate_blockchain::{*, types::*}; +/// use cuprate_database::Storable; +/// +/// // Assert Storable is correct. +/// let a: CompactAltBlockInfo = CompactAltBlockInfo { +/// block_hash: [1; 32], +/// pow_hash: [2; 32], +/// height: 10, +/// weight: 20, +/// long_term_weight: 30, +/// cumulative_difficulty_low: 40, +/// cumulative_difficulty_high: 50, +/// }; +/// +/// let b = Storable::as_bytes(&a); +/// let c: CompactAltBlockInfo = Storable::from_bytes(b); +/// assert_eq!(a, c); +/// ``` +/// +/// # Size & Alignment +/// ```rust +/// # use cuprate_blockchain::types::*; +/// assert_eq!(size_of::(), 104); +/// assert_eq!(align_of::(), 8); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(C)] +pub struct CompactAltBlockInfo { + /// The block's hash. + pub block_hash: [u8; 32], + /// The block's proof-of-work hash. + pub pow_hash: [u8; 32], + /// The block's height. + pub height: usize, + /// The adjusted block size, in bytes. + pub weight: usize, + /// The long term block weight, which is the weight factored in with previous block weights. + pub long_term_weight: usize, + /// The low 64 bits of the cumulative difficulty. + pub cumulative_difficulty_low: u64, + /// The high 64 bits of the cumulative difficulty. + pub cumulative_difficulty_high: u64, +} + +//---------------------------------------------------------------------------------------------------- AltTransactionInfo +/// Represents information on an alt transaction. +/// +/// ```rust +/// # use std::borrow::*; +/// # use cuprate_blockchain::{*, types::*}; +/// use cuprate_database::Storable; +/// +/// // Assert Storable is correct. +/// let a: AltTransactionInfo = AltTransactionInfo { +/// tx_weight: 1, +/// fee: 6, +/// tx_hash: [6; 32], +/// }; +/// +/// let b = Storable::as_bytes(&a); +/// let c: AltTransactionInfo = Storable::from_bytes(b); +/// assert_eq!(a, c); +/// ``` +/// +/// # Size & Alignment +/// ```rust +/// # use cuprate_blockchain::types::*; +/// assert_eq!(size_of::(), 48); +/// assert_eq!(align_of::(), 8); +/// ``` +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(C)] +pub struct AltTransactionInfo { + /// The transaction's weight. + pub tx_weight: usize, + /// The transaction's total fees. + pub fee: u64, + /// The transaction's hash. + pub tx_hash: [u8; 32], +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/storage/blockchain/src/unsafe_sendable.rs b/storage/blockchain/src/unsafe_sendable.rs index 9447293..76c7899 100644 --- a/storage/blockchain/src/unsafe_sendable.rs +++ b/storage/blockchain/src/unsafe_sendable.rs @@ -26,7 +26,7 @@ use bytemuck::TransparentWrapper; /// Notably, `heed`'s table type uses this inside `service`. pub(crate) struct UnsafeSendable(T); -#[allow(clippy::non_send_fields_in_send_ty)] +#[expect(clippy::non_send_fields_in_send_ty)] // SAFETY: Users ensure that their usage of this type is safe. unsafe impl Send for UnsafeSendable {} @@ -41,7 +41,7 @@ impl UnsafeSendable { } /// Extract the inner `T`. - #[allow(dead_code)] + #[expect(dead_code)] pub(crate) fn into_inner(self) -> T { self.0 } diff --git a/storage/database/Cargo.toml b/storage/database/Cargo.toml index 77fcdbd..d855c8b 100644 --- a/storage/database/Cargo.toml +++ b/storage/database/Cargo.toml @@ -12,23 +12,27 @@ keywords = ["cuprate", "database"] default = ["heed"] # default = ["redb"] # default = ["redb-memory"] -heed = ["dep:heed"] +heed = [] redb = ["dep:redb"] redb-memory = ["redb"] [dependencies] -bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } +bytemuck = { version = "1.18.0", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } bytes = { workspace = true } cfg-if = { workspace = true } page_size = { version = "0.6.0" } # Needed for database resizes, they must be a multiple of the OS page size. +paste = { workspace = true } thiserror = { workspace = true } # Optional features. -heed = { version = "0.20.3", features = ["read-txn-no-tls"], optional = true } -redb = { version = "2.1.1", optional = true } +heed = { version = "0.20.5", features = ["read-txn-no-tls"] } +redb = { version = "2.2.0", optional = true } serde = { workspace = true, optional = true } [dev-dependencies] -bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } +bytemuck = { version = "1.18.0", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } page_size = { version = "0.6.0" } -tempfile = { version = "3.10.0" } \ No newline at end of file +tempfile = { version = "3.12.0" } + +[lints] +workspace = true diff --git a/storage/database/README.md b/storage/database/README.md index d7a9b92..fe22247 100644 --- a/storage/database/README.md +++ b/storage/database/README.md @@ -6,10 +6,10 @@ For a high-level overview, see the database section in [Cuprate's architecture book](https://architecture.cuprate.org). If you need blockchain specific capabilities, consider using the higher-level -`cuprate-blockchain` crate which builds upon this one. +[`cuprate-blockchain`](https://doc.cuprate.org/cuprate_blockchain) crate which builds upon this one. # Purpose -This crate abstracts various database backends with traits. The databases are: +This crate abstracts various database backends with traits. All backends have the following attributes: - [Embedded](https://en.wikipedia.org/wiki/Embedded_database) @@ -19,6 +19,10 @@ All backends have the following attributes: - Are table oriented (`"table_name" -> (key, value)`) - Allows concurrent readers +The currently implemented backends are: +- [`heed`](https://github.com/meilisearch/heed) (LMDB) +- [`redb`](https://github.com/cberner/redb) + # Terminology To be more clear on some terms used in this crate: @@ -26,17 +30,17 @@ To be more clear on some terms used in this crate: |------------------|--------------------------------------| | `Env` | The 1 database environment, the "whole" thing | `DatabaseR{o,w}` | A _actively open_ readable/writable `key/value` store -| `Table` | Solely the metadata of a `cuprate_database` (the `key` and `value` types, and the name) +| `Table` | Solely the metadata of a `Database` (the `key` and `value` types, and the name) | `TxR{o,w}` | A read/write transaction -| `Storable` | A data that type can be stored in the database +| `Storable` | A data type that can be stored in the database -The dataflow is `Env` -> `Tx` -> `cuprate_database` +The flow is `Env` -> `Tx` -> `Database` Which reads as: 1. You have a database `Environment` 1. You open up a `Transaction` -1. You open a particular `Table` from that `Environment`, getting a `cuprate_database` -1. You can now read/write data from/to that `cuprate_database` +1. You open a particular `Table` from that `Environment`, getting a `Database` +1. You can now read/write data from/to that `Database` # Concrete types You should _not_ rely on the concrete type of any abstracted backend. @@ -62,8 +66,8 @@ As `ConcreteEnv` is just a re-exposed type which has varying inner types, it means some properties will change depending on the backend used. For example: -- [`std::mem::size_of::`] -- [`std::mem::align_of::`] +- [`size_of::`] +- [`align_of::`] Things like these functions are affected by the backend and inner data, and should not be relied upon. This extends to any `struct/enum` that contains `ConcreteEnv`. @@ -72,7 +76,7 @@ and should not be relied upon. This extends to any `struct/enum` that contains ` - It implements [`Env`] - Upon [`Drop::drop`], all database data will sync to disk -Note that `ConcreteEnv` itself is not a clonable type, +Note that `ConcreteEnv` itself is not a cloneable type, it should be wrapped in [`std::sync::Arc`]. +# Defining tables +Most likely, your crate building on-top of `cuprate_database` will +want to define all tables used at compile time. + +If this is the case, consider using the [`define_tables`] macro +to bulk generate zero-sized marker types that implement [`Table`]. + +This macro also generates other convenient traits specific to _your_ tables. + # Feature flags Different database backends are enabled by the feature flags: - `heed` (LMDB) diff --git a/storage/database/src/backend/heed/database.rs b/storage/database/src/backend/heed/database.rs index c985d0d..15f16b4 100644 --- a/storage/database/src/backend/heed/database.rs +++ b/storage/database/src/backend/heed/database.rs @@ -6,7 +6,7 @@ use std::{cell::RefCell, ops::RangeBounds}; use crate::{ backend::heed::types::HeedDb, database::{DatabaseIter, DatabaseRo, DatabaseRw}, - error::RuntimeError, + error::{DbResult, RuntimeError}, table::Table, }; @@ -54,16 +54,13 @@ fn get( db: &HeedDb, tx_ro: &heed::RoTxn<'_>, key: &T::Key, -) -> Result { +) -> DbResult { db.get(tx_ro, key)?.ok_or(RuntimeError::KeyNotFound) } /// Shared [`DatabaseRo::len()`]. #[inline] -fn len( - db: &HeedDb, - tx_ro: &heed::RoTxn<'_>, -) -> Result { +fn len(db: &HeedDb, tx_ro: &heed::RoTxn<'_>) -> DbResult { Ok(db.len(tx_ro)?) } @@ -72,7 +69,7 @@ fn len( fn first( db: &HeedDb, tx_ro: &heed::RoTxn<'_>, -) -> Result<(T::Key, T::Value), RuntimeError> { +) -> DbResult<(T::Key, T::Value)> { db.first(tx_ro)?.ok_or(RuntimeError::KeyNotFound) } @@ -81,16 +78,13 @@ fn first( fn last( db: &HeedDb, tx_ro: &heed::RoTxn<'_>, -) -> Result<(T::Key, T::Value), RuntimeError> { +) -> DbResult<(T::Key, T::Value)> { db.last(tx_ro)?.ok_or(RuntimeError::KeyNotFound) } /// Shared [`DatabaseRo::is_empty()`]. #[inline] -fn is_empty( - db: &HeedDb, - tx_ro: &heed::RoTxn<'_>, -) -> Result { +fn is_empty(db: &HeedDb, tx_ro: &heed::RoTxn<'_>) -> DbResult { Ok(db.is_empty(tx_ro)?) } @@ -100,7 +94,7 @@ impl DatabaseIter for HeedTableRo<'_, T> { fn get_range<'a, Range>( &'a self, range: Range, - ) -> Result> + 'a, RuntimeError> + ) -> DbResult> + 'a> where Range: RangeBounds + 'a, { @@ -108,24 +102,17 @@ impl DatabaseIter for HeedTableRo<'_, T> { } #[inline] - fn iter( - &self, - ) -> Result> + '_, RuntimeError> - { + fn iter(&self) -> DbResult> + '_> { Ok(self.db.iter(self.tx_ro)?.map(|res| Ok(res?))) } #[inline] - fn keys( - &self, - ) -> Result> + '_, RuntimeError> { + fn keys(&self) -> DbResult> + '_> { Ok(self.db.iter(self.tx_ro)?.map(|res| Ok(res?.0))) } #[inline] - fn values( - &self, - ) -> Result> + '_, RuntimeError> { + fn values(&self) -> DbResult> + '_> { Ok(self.db.iter(self.tx_ro)?.map(|res| Ok(res?.1))) } } @@ -134,27 +121,27 @@ impl DatabaseIter for HeedTableRo<'_, T> { // SAFETY: `HeedTableRo: !Send` as it holds a reference to `heed::RoTxn: Send + !Sync`. unsafe impl DatabaseRo for HeedTableRo<'_, T> { #[inline] - fn get(&self, key: &T::Key) -> Result { + fn get(&self, key: &T::Key) -> DbResult { get::(&self.db, self.tx_ro, key) } #[inline] - fn len(&self) -> Result { + fn len(&self) -> DbResult { len::(&self.db, self.tx_ro) } #[inline] - fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn first(&self) -> DbResult<(T::Key, T::Value)> { first::(&self.db, self.tx_ro) } #[inline] - fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn last(&self) -> DbResult<(T::Key, T::Value)> { last::(&self.db, self.tx_ro) } #[inline] - fn is_empty(&self) -> Result { + fn is_empty(&self) -> DbResult { is_empty::(&self.db, self.tx_ro) } } @@ -164,45 +151,45 @@ unsafe impl DatabaseRo for HeedTableRo<'_, T> { // `HeedTableRw`'s write transaction is `!Send`. unsafe impl DatabaseRo for HeedTableRw<'_, '_, T> { #[inline] - fn get(&self, key: &T::Key) -> Result { + fn get(&self, key: &T::Key) -> DbResult { get::(&self.db, &self.tx_rw.borrow(), key) } #[inline] - fn len(&self) -> Result { + fn len(&self) -> DbResult { len::(&self.db, &self.tx_rw.borrow()) } #[inline] - fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn first(&self) -> DbResult<(T::Key, T::Value)> { first::(&self.db, &self.tx_rw.borrow()) } #[inline] - fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn last(&self) -> DbResult<(T::Key, T::Value)> { last::(&self.db, &self.tx_rw.borrow()) } #[inline] - fn is_empty(&self) -> Result { + fn is_empty(&self) -> DbResult { is_empty::(&self.db, &self.tx_rw.borrow()) } } impl DatabaseRw for HeedTableRw<'_, '_, T> { #[inline] - fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> { + fn put(&mut self, key: &T::Key, value: &T::Value) -> DbResult<()> { Ok(self.db.put(&mut self.tx_rw.borrow_mut(), key, value)?) } #[inline] - fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> { + fn delete(&mut self, key: &T::Key) -> DbResult<()> { self.db.delete(&mut self.tx_rw.borrow_mut(), key)?; Ok(()) } #[inline] - fn take(&mut self, key: &T::Key) -> Result { + fn take(&mut self, key: &T::Key) -> DbResult { // LMDB/heed does not return the value on deletion. // So, fetch it first - then delete. let value = get::(&self.db, &self.tx_rw.borrow(), key)?; @@ -216,7 +203,7 @@ impl DatabaseRw for HeedTableRw<'_, '_, T> { } #[inline] - fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + fn pop_first(&mut self) -> DbResult<(T::Key, T::Value)> { let tx_rw = &mut self.tx_rw.borrow_mut(); // Get the value first... @@ -235,7 +222,7 @@ impl DatabaseRw for HeedTableRw<'_, '_, T> { } #[inline] - fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + fn pop_last(&mut self) -> DbResult<(T::Key, T::Value)> { let tx_rw = &mut self.tx_rw.borrow_mut(); // Get the value first... diff --git a/storage/database/src/backend/heed/env.rs b/storage/database/src/backend/heed/env.rs index 69e3b17..b603013 100644 --- a/storage/database/src/backend/heed/env.rs +++ b/storage/database/src/backend/heed/env.rs @@ -18,7 +18,7 @@ use crate::{ config::{Config, SyncMode}, database::{DatabaseIter, DatabaseRo, DatabaseRw}, env::{Env, EnvInner}, - error::{InitError, RuntimeError}, + error::{DbResult, InitError, RuntimeError}, key::{Key, KeyCompare}, resize::ResizeAlgorithm, table::Table, @@ -70,7 +70,7 @@ impl Drop for ConcreteEnv { // We need to do `mdb_env_set_flags(&env, MDB_NOSYNC|MDB_ASYNCMAP, 0)` // to clear the no sync and async flags such that the below `self.sync()` // _actually_ synchronously syncs. - if let Err(_e) = crate::Env::sync(self) { + if let Err(_e) = Env::sync(self) { // TODO: log error? } @@ -144,7 +144,7 @@ impl Env for ConcreteEnv { // (current disk size) + (a bit of leeway) // to account for empty databases where we // need to write same tables. - #[allow(clippy::cast_possible_truncation)] // only 64-bit targets + #[expect(clippy::cast_possible_truncation, reason = "only 64-bit targets")] let disk_size_bytes = match std::fs::File::open(&config.db_file) { Ok(file) => file.metadata()?.len() as usize, // The database file doesn't exist, 0 bytes. @@ -203,7 +203,7 @@ impl Env for ConcreteEnv { &self.config } - fn sync(&self) -> Result<(), RuntimeError> { + fn sync(&self) -> DbResult<()> { Ok(self.env.read().unwrap().force_sync()?) } @@ -244,26 +244,29 @@ impl Env for ConcreteEnv { } //---------------------------------------------------------------------------------------------------- EnvInner Impl -impl<'env> EnvInner<'env, heed::RoTxn<'env>, RefCell>> - for RwLockReadGuard<'env, heed::Env> +impl<'env> EnvInner<'env> for RwLockReadGuard<'env, heed::Env> where Self: 'env, { + type Ro<'a> = heed::RoTxn<'a>; + + type Rw<'a> = RefCell>; + #[inline] - fn tx_ro(&'env self) -> Result, RuntimeError> { + fn tx_ro(&self) -> DbResult> { Ok(self.read_txn()?) } #[inline] - fn tx_rw(&'env self) -> Result>, RuntimeError> { + fn tx_rw(&self) -> DbResult> { Ok(RefCell::new(self.write_txn()?)) } #[inline] fn open_db_ro( &self, - tx_ro: &heed::RoTxn<'env>, - ) -> Result + DatabaseIter, RuntimeError> { + tx_ro: &Self::Ro<'_>, + ) -> DbResult + DatabaseIter> { // Open up a read-only database using our table's const metadata. // // INVARIANT: LMDB caches the ordering / comparison function from [`EnvInner::create_db`], @@ -278,10 +281,7 @@ where } #[inline] - fn open_db_rw( - &self, - tx_rw: &RefCell>, - ) -> Result, RuntimeError> { + fn open_db_rw(&self, tx_rw: &Self::Rw<'_>) -> DbResult> { // Open up a read/write database using our table's const metadata. // // INVARIANT: LMDB caches the ordering / comparison function from [`EnvInner::create_db`], @@ -293,7 +293,7 @@ where }) } - fn create_db(&self, tx_rw: &RefCell>) -> Result<(), RuntimeError> { + fn create_db(&self, tx_rw: &Self::Rw<'_>) -> DbResult<()> { // Create a database using our: // - [`Table`]'s const metadata. // - (potentially) our [`Key`] comparison function @@ -325,10 +325,7 @@ where } #[inline] - fn clear_db( - &self, - tx_rw: &mut RefCell>, - ) -> Result<(), RuntimeError> { + fn clear_db(&self, tx_rw: &mut Self::Rw<'_>) -> DbResult<()> { let tx_rw = tx_rw.get_mut(); // Open the table. We don't care about flags or key diff --git a/storage/database/src/backend/heed/error.rs b/storage/database/src/backend/heed/error.rs index bbaeaf0..fdeab70 100644 --- a/storage/database/src/backend/heed/error.rs +++ b/storage/database/src/backend/heed/error.rs @@ -57,7 +57,10 @@ impl From for crate::InitError { } //---------------------------------------------------------------------------------------------------- RuntimeError -#[allow(clippy::fallible_impl_from)] // We need to panic sometimes. +#[expect( + clippy::fallible_impl_from, + reason = "We need to panic sometimes for safety" +)] impl From for crate::RuntimeError { /// # Panics /// This will panic on unrecoverable errors for safety. diff --git a/storage/database/src/backend/heed/storable.rs b/storage/database/src/backend/heed/storable.rs index 3566e88..da0e0cb 100644 --- a/storage/database/src/backend/heed/storable.rs +++ b/storage/database/src/backend/heed/storable.rs @@ -78,8 +78,8 @@ mod test { println!("left: {left:?}, right: {right:?}, expected: {expected:?}"); assert_eq!( as heed::Comparator>::compare( - & as heed::BytesEncode>::bytes_encode(&left).unwrap(), - & as heed::BytesEncode>::bytes_encode(&right).unwrap() + & as BytesEncode>::bytes_encode(&left).unwrap(), + & as BytesEncode>::bytes_encode(&right).unwrap() ), expected ); diff --git a/storage/database/src/backend/heed/transaction.rs b/storage/database/src/backend/heed/transaction.rs index d32f370..b7c0f54 100644 --- a/storage/database/src/backend/heed/transaction.rs +++ b/storage/database/src/backend/heed/transaction.rs @@ -4,31 +4,31 @@ use std::cell::RefCell; //---------------------------------------------------------------------------------------------------- Import use crate::{ - error::RuntimeError, + error::DbResult, transaction::{TxRo, TxRw}, }; //---------------------------------------------------------------------------------------------------- TxRo impl TxRo<'_> for heed::RoTxn<'_> { - fn commit(self) -> Result<(), RuntimeError> { + fn commit(self) -> DbResult<()> { Ok(heed::RoTxn::commit(self)?) } } //---------------------------------------------------------------------------------------------------- TxRw impl TxRo<'_> for RefCell> { - fn commit(self) -> Result<(), RuntimeError> { + fn commit(self) -> DbResult<()> { TxRw::commit(self) } } impl TxRw<'_> for RefCell> { - fn commit(self) -> Result<(), RuntimeError> { + fn commit(self) -> DbResult<()> { Ok(heed::RwTxn::commit(self.into_inner())?) } /// This function is infallible. - fn abort(self) -> Result<(), RuntimeError> { + fn abort(self) -> DbResult<()> { heed::RwTxn::abort(self.into_inner()); Ok(()) } diff --git a/storage/database/src/backend/mod.rs b/storage/database/src/backend/mod.rs index 11ae40b..ebe12d8 100644 --- a/storage/database/src/backend/mod.rs +++ b/storage/database/src/backend/mod.rs @@ -4,6 +4,8 @@ cfg_if::cfg_if! { // If both backends are enabled, fallback to `heed`. // This is useful when using `--all-features`. if #[cfg(all(feature = "redb", not(feature = "heed")))] { + use heed as _; + mod redb; pub use redb::ConcreteEnv; } else { diff --git a/storage/database/src/backend/redb/database.rs b/storage/database/src/backend/redb/database.rs index cd9a0be..0be58ef 100644 --- a/storage/database/src/backend/redb/database.rs +++ b/storage/database/src/backend/redb/database.rs @@ -11,7 +11,7 @@ use crate::{ types::{RedbTableRo, RedbTableRw}, }, database::{DatabaseIter, DatabaseRo, DatabaseRw}, - error::RuntimeError, + error::{DbResult, RuntimeError}, table::Table, }; @@ -23,25 +23,25 @@ use crate::{ /// Shared [`DatabaseRo::get()`]. #[inline] fn get( - db: &impl redb::ReadableTable, StorableRedb>, + db: &impl ReadableTable, StorableRedb>, key: &T::Key, -) -> Result { +) -> DbResult { Ok(db.get(key)?.ok_or(RuntimeError::KeyNotFound)?.value()) } /// Shared [`DatabaseRo::len()`]. #[inline] fn len( - db: &impl redb::ReadableTable, StorableRedb>, -) -> Result { + db: &impl ReadableTable, StorableRedb>, +) -> DbResult { Ok(db.len()?) } /// Shared [`DatabaseRo::first()`]. #[inline] fn first( - db: &impl redb::ReadableTable, StorableRedb>, -) -> Result<(T::Key, T::Value), RuntimeError> { + db: &impl ReadableTable, StorableRedb>, +) -> DbResult<(T::Key, T::Value)> { let (key, value) = db.first()?.ok_or(RuntimeError::KeyNotFound)?; Ok((key.value(), value.value())) } @@ -49,8 +49,8 @@ fn first( /// Shared [`DatabaseRo::last()`]. #[inline] fn last( - db: &impl redb::ReadableTable, StorableRedb>, -) -> Result<(T::Key, T::Value), RuntimeError> { + db: &impl ReadableTable, StorableRedb>, +) -> DbResult<(T::Key, T::Value)> { let (key, value) = db.last()?.ok_or(RuntimeError::KeyNotFound)?; Ok((key.value(), value.value())) } @@ -58,8 +58,8 @@ fn last( /// Shared [`DatabaseRo::is_empty()`]. #[inline] fn is_empty( - db: &impl redb::ReadableTable, StorableRedb>, -) -> Result { + db: &impl ReadableTable, StorableRedb>, +) -> DbResult { Ok(db.is_empty()?) } @@ -69,7 +69,7 @@ impl DatabaseIter for RedbTableRo { fn get_range<'a, Range>( &'a self, range: Range, - ) -> Result> + 'a, RuntimeError> + ) -> DbResult> + 'a> where Range: RangeBounds + 'a, { @@ -80,10 +80,7 @@ impl DatabaseIter for RedbTableRo { } #[inline] - fn iter( - &self, - ) -> Result> + '_, RuntimeError> - { + fn iter(&self) -> DbResult> + '_> { Ok(ReadableTable::iter(self)?.map(|result| { let (key, value) = result?; Ok((key.value(), value.value())) @@ -91,9 +88,7 @@ impl DatabaseIter for RedbTableRo { } #[inline] - fn keys( - &self, - ) -> Result> + '_, RuntimeError> { + fn keys(&self) -> DbResult> + '_> { Ok(ReadableTable::iter(self)?.map(|result| { let (key, _value) = result?; Ok(key.value()) @@ -101,9 +96,7 @@ impl DatabaseIter for RedbTableRo { } #[inline] - fn values( - &self, - ) -> Result> + '_, RuntimeError> { + fn values(&self) -> DbResult> + '_> { Ok(ReadableTable::iter(self)?.map(|result| { let (_key, value) = result?; Ok(value.value()) @@ -115,27 +108,27 @@ impl DatabaseIter for RedbTableRo { // SAFETY: Both `redb`'s transaction and table types are `Send + Sync`. unsafe impl DatabaseRo for RedbTableRo { #[inline] - fn get(&self, key: &T::Key) -> Result { + fn get(&self, key: &T::Key) -> DbResult { get::(self, key) } #[inline] - fn len(&self) -> Result { + fn len(&self) -> DbResult { len::(self) } #[inline] - fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn first(&self) -> DbResult<(T::Key, T::Value)> { first::(self) } #[inline] - fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn last(&self) -> DbResult<(T::Key, T::Value)> { last::(self) } #[inline] - fn is_empty(&self) -> Result { + fn is_empty(&self) -> DbResult { is_empty::(self) } } @@ -144,27 +137,27 @@ unsafe impl DatabaseRo for RedbTableRo // SAFETY: Both `redb`'s transaction and table types are `Send + Sync`. unsafe impl DatabaseRo for RedbTableRw<'_, T::Key, T::Value> { #[inline] - fn get(&self, key: &T::Key) -> Result { + fn get(&self, key: &T::Key) -> DbResult { get::(self, key) } #[inline] - fn len(&self) -> Result { + fn len(&self) -> DbResult { len::(self) } #[inline] - fn first(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn first(&self) -> DbResult<(T::Key, T::Value)> { first::(self) } #[inline] - fn last(&self) -> Result<(T::Key, T::Value), RuntimeError> { + fn last(&self) -> DbResult<(T::Key, T::Value)> { last::(self) } #[inline] - fn is_empty(&self) -> Result { + fn is_empty(&self) -> DbResult { is_empty::(self) } } @@ -173,19 +166,19 @@ impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { // `redb` returns the value after function calls so we end with Ok(()) instead. #[inline] - fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError> { + fn put(&mut self, key: &T::Key, value: &T::Value) -> DbResult<()> { redb::Table::insert(self, key, value)?; Ok(()) } #[inline] - fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError> { + fn delete(&mut self, key: &T::Key) -> DbResult<()> { redb::Table::remove(self, key)?; Ok(()) } #[inline] - fn take(&mut self, key: &T::Key) -> Result { + fn take(&mut self, key: &T::Key) -> DbResult { if let Some(value) = redb::Table::remove(self, key)? { Ok(value.value()) } else { @@ -194,13 +187,13 @@ impl DatabaseRw for RedbTableRw<'_, T::Key, T::Value> { } #[inline] - fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + fn pop_first(&mut self) -> DbResult<(T::Key, T::Value)> { let (key, value) = redb::Table::pop_first(self)?.ok_or(RuntimeError::KeyNotFound)?; Ok((key.value(), value.value())) } #[inline] - fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError> { + fn pop_last(&mut self) -> DbResult<(T::Key, T::Value)> { let (key, value) = redb::Table::pop_last(self)?.ok_or(RuntimeError::KeyNotFound)?; Ok((key.value(), value.value())) } diff --git a/storage/database/src/backend/redb/env.rs b/storage/database/src/backend/redb/env.rs index 65e3e05..4bd49d6 100644 --- a/storage/database/src/backend/redb/env.rs +++ b/storage/database/src/backend/redb/env.rs @@ -6,7 +6,7 @@ use crate::{ config::{Config, SyncMode}, database::{DatabaseIter, DatabaseRo, DatabaseRw}, env::{Env, EnvInner}, - error::{InitError, RuntimeError}, + error::{DbResult, InitError, RuntimeError}, table::Table, TxRw, }; @@ -56,8 +56,9 @@ impl Env for ConcreteEnv { // // should we use that instead of Immediate? SyncMode::Safe => redb::Durability::Immediate, - SyncMode::Async => redb::Durability::Eventual, - SyncMode::Fast => redb::Durability::None, + // FIXME: `Fast` maps to `Eventual` instead of `None` because of: + // + SyncMode::Async | SyncMode::Fast => redb::Durability::Eventual, // SOMEDAY: dynamic syncs are not implemented. SyncMode::FastThenSafe | SyncMode::Threshold(_) => unimplemented!(), }; @@ -104,7 +105,7 @@ impl Env for ConcreteEnv { &self.config } - fn sync(&self) -> Result<(), RuntimeError> { + fn sync(&self) -> DbResult<()> { // `redb`'s syncs are tied with write transactions, // so just create one, don't do anything and commit. let mut tx_rw = self.env.begin_write()?; @@ -118,18 +119,20 @@ impl Env for ConcreteEnv { } //---------------------------------------------------------------------------------------------------- EnvInner Impl -impl<'env> EnvInner<'env, redb::ReadTransaction, redb::WriteTransaction> - for (&'env redb::Database, redb::Durability) +impl<'env> EnvInner<'env> for (&'env redb::Database, redb::Durability) where Self: 'env, { + type Ro<'a> = redb::ReadTransaction; + type Rw<'a> = redb::WriteTransaction; + #[inline] - fn tx_ro(&'env self) -> Result { + fn tx_ro(&self) -> DbResult { Ok(self.0.begin_read()?) } #[inline] - fn tx_rw(&'env self) -> Result { + fn tx_rw(&self) -> DbResult { // `redb` has sync modes on the TX level, unlike heed, // which sets it at the Environment level. // @@ -142,8 +145,8 @@ where #[inline] fn open_db_ro( &self, - tx_ro: &redb::ReadTransaction, - ) -> Result + DatabaseIter, RuntimeError> { + tx_ro: &Self::Ro<'_>, + ) -> DbResult + DatabaseIter> { // Open up a read-only database using our `T: Table`'s const metadata. let table: redb::TableDefinition<'static, StorableRedb, StorableRedb> = redb::TableDefinition::new(T::NAME); @@ -152,10 +155,7 @@ where } #[inline] - fn open_db_rw( - &self, - tx_rw: &redb::WriteTransaction, - ) -> Result, RuntimeError> { + fn open_db_rw(&self, tx_rw: &Self::Rw<'_>) -> DbResult> { // Open up a read/write database using our `T: Table`'s const metadata. let table: redb::TableDefinition<'static, StorableRedb, StorableRedb> = redb::TableDefinition::new(T::NAME); @@ -165,14 +165,14 @@ where Ok(tx_rw.open_table(table)?) } - fn create_db(&self, tx_rw: &redb::WriteTransaction) -> Result<(), RuntimeError> { + fn create_db(&self, tx_rw: &redb::WriteTransaction) -> DbResult<()> { // INVARIANT: `redb` creates tables if they don't exist. self.open_db_rw::(tx_rw)?; Ok(()) } #[inline] - fn clear_db(&self, tx_rw: &mut redb::WriteTransaction) -> Result<(), RuntimeError> { + fn clear_db(&self, tx_rw: &mut redb::WriteTransaction) -> DbResult<()> { let table: redb::TableDefinition< 'static, StorableRedb<::Key>, diff --git a/storage/database/src/backend/redb/storable.rs b/storage/database/src/backend/redb/storable.rs index abf2e71..f0412ef 100644 --- a/storage/database/src/backend/redb/storable.rs +++ b/storage/database/src/backend/redb/storable.rs @@ -34,8 +34,14 @@ impl redb::Value for StorableRedb where T: Storable + 'static, { - type SelfType<'a> = T where Self: 'a; - type AsBytes<'a> = &'a [u8] where Self: 'a; + type SelfType<'a> + = T + where + Self: 'a; + type AsBytes<'a> + = &'a [u8] + where + Self: 'a; #[inline] fn fixed_width() -> Option { diff --git a/storage/database/src/backend/redb/transaction.rs b/storage/database/src/backend/redb/transaction.rs index 5048851..8d93986 100644 --- a/storage/database/src/backend/redb/transaction.rs +++ b/storage/database/src/backend/redb/transaction.rs @@ -2,14 +2,14 @@ //---------------------------------------------------------------------------------------------------- Import use crate::{ - error::RuntimeError, + error::DbResult, transaction::{TxRo, TxRw}, }; //---------------------------------------------------------------------------------------------------- TxRo impl TxRo<'_> for redb::ReadTransaction { /// This function is infallible. - fn commit(self) -> Result<(), RuntimeError> { + fn commit(self) -> DbResult<()> { // `redb`'s read transactions cleanup automatically when all references are dropped. // // There is `close()`: @@ -22,11 +22,11 @@ impl TxRo<'_> for redb::ReadTransaction { //---------------------------------------------------------------------------------------------------- TxRw impl TxRw<'_> for redb::WriteTransaction { - fn commit(self) -> Result<(), RuntimeError> { + fn commit(self) -> DbResult<()> { Ok(self.commit()?) } - fn abort(self) -> Result<(), RuntimeError> { + fn abort(self) -> DbResult<()> { Ok(self.abort()?) } } diff --git a/storage/database/src/backend/tests.rs b/storage/database/src/backend/tests.rs index ac6b592..0c0fe05 100644 --- a/storage/database/src/backend/tests.rs +++ b/storage/database/src/backend/tests.rs @@ -126,7 +126,7 @@ fn resize() { let (env, _tempdir) = tmp_concrete_env(); // Resize by the OS page size. - let page_size = crate::resize::page_size(); + let page_size = *crate::resize::PAGE_SIZE; let old_size = env.current_map_size(); env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size))); @@ -194,7 +194,7 @@ fn db_read_write() { // Insert keys. let mut key = KEY; - #[allow(clippy::explicit_counter_loop)] // we need the +1 side effect + #[expect(clippy::explicit_counter_loop, reason = "we need the +1 side effect")] for _ in 0..N { table.put(&key, &VALUE).unwrap(); key += 1; @@ -269,7 +269,7 @@ fn db_read_write() { assert_ne!(table.get(&KEY).unwrap(), NEW_VALUE); - #[allow(unused_assignments)] + #[expect(unused_assignments)] table .update(&KEY, |mut value| { value = NEW_VALUE; diff --git a/storage/database/src/config/config.rs b/storage/database/src/config/config.rs index a5ecbb2..8a4ddbf 100644 --- a/storage/database/src/config/config.rs +++ b/storage/database/src/config/config.rs @@ -160,7 +160,7 @@ pub struct Config { /// Set the number of slots in the reader table. /// /// This is only used in LMDB, see - /// . + /// [here](https://github.com/LMDB/lmdb/blob/b8e54b4c31378932b69f1298972de54a565185b1/libraries/liblmdb/mdb.c#L794-L799). /// /// By default, this value is [`READER_THREADS_DEFAULT`]. pub reader_threads: NonZeroUsize, diff --git a/storage/database/src/config/mod.rs b/storage/database/src/config/mod.rs index 19a324e..7d65233 100644 --- a/storage/database/src/config/mod.rs +++ b/storage/database/src/config/mod.rs @@ -33,6 +33,7 @@ //! # Ok(()) } //! ``` +#[expect(clippy::module_inception)] mod config; pub use config::{Config, ConfigBuilder, READER_THREADS_DEFAULT}; diff --git a/storage/database/src/config/sync_mode.rs b/storage/database/src/config/sync_mode.rs index 1d20339..dbb34e7 100644 --- a/storage/database/src/config/sync_mode.rs +++ b/storage/database/src/config/sync_mode.rs @@ -9,7 +9,6 @@ //! based on these values. //---------------------------------------------------------------------------------------------------- Import - #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -84,7 +83,7 @@ pub enum SyncMode { /// /// This is expected to be very slow. /// - /// This matches: + /// This maps to: /// - LMDB without any special sync flags /// - [`redb::Durability::Immediate`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Immediate) Safe, @@ -96,7 +95,7 @@ pub enum SyncMode { /// each transaction commit will sync to disk, /// but only eventually, not necessarily immediately. /// - /// This matches: + /// This maps to: /// - [`MDB_MAPASYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#gab034ed0d8e5938090aef5ee0997f7e94) /// - [`redb::Durability::Eventual`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Eventual) Async, @@ -115,17 +114,25 @@ pub enum SyncMode { /// This is the fastest, yet unsafest option. /// /// It will cause the database to never _actively_ sync, - /// letting the OS decide when to flush data to disk. + /// letting the OS decide when to flush data to disk[^1]. /// - /// This matches: + /// This maps to: /// - [`MDB_NOSYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#ga5791dd1adb09123f82dd1f331209e12e) + [`MDB_MAPASYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#gab034ed0d8e5938090aef5ee0997f7e94) - /// - [`redb::Durability::None`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.None) + /// - [`redb::Durability::Eventual`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Eventual) /// - /// `monerod` reference: + /// [`monerod` reference](https://github.com/monero-project/monero/blob/7b7958bbd9d76375c47dc418b4adabba0f0b1785/src/blockchain_db/lmdb/db_lmdb.cpp#L1380-L1381). /// /// # Corruption /// In the case of a system crash, the database /// may become corrupted when using this option. + /// + /// + /// [^1]: Semantically, this variant would actually map to + /// [`redb::Durability::None`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.None), + /// however due to [`#149`](https://github.com/Cuprate/cuprate/issues/149), + /// this is not possible. As such, when using the `redb` backend, + /// transaction writes "should be persistent some time after `WriteTransaction::commit` returns." + /// Thus, [`SyncMode::Async`] will map to the same `redb::Durability::Eventual` as [`SyncMode::Fast`]. // // FIXME: we could call this `unsafe` // and use that terminology in the config file diff --git a/storage/database/src/database.rs b/storage/database/src/database.rs index 4a45f7c..c019972 100644 --- a/storage/database/src/database.rs +++ b/storage/database/src/database.rs @@ -3,7 +3,10 @@ //---------------------------------------------------------------------------------------------------- Import use std::ops::RangeBounds; -use crate::{error::RuntimeError, table::Table}; +use crate::{ + error::{DbResult, RuntimeError}, + table::Table, +}; //---------------------------------------------------------------------------------------------------- DatabaseIter /// Generic post-fix documentation for `DatabaseIter` methods. @@ -48,27 +51,22 @@ pub trait DatabaseIter { fn get_range<'a, Range>( &'a self, range: Range, - ) -> Result> + 'a, RuntimeError> + ) -> DbResult> + 'a> where Range: RangeBounds + 'a; /// Get an [`Iterator`] that returns the `(key, value)` types for this database. #[doc = doc_iter!()] - #[allow(clippy::iter_not_returning_iterator)] - fn iter( - &self, - ) -> Result> + '_, RuntimeError>; + #[expect(clippy::iter_not_returning_iterator)] + fn iter(&self) -> DbResult> + '_>; /// Get an [`Iterator`] that returns _only_ the `key` type for this database. #[doc = doc_iter!()] - fn keys(&self) - -> Result> + '_, RuntimeError>; + fn keys(&self) -> DbResult> + '_>; /// Get an [`Iterator`] that returns _only_ the `value` type for this database. #[doc = doc_iter!()] - fn values( - &self, - ) -> Result> + '_, RuntimeError>; + fn values(&self) -> DbResult> + '_>; } //---------------------------------------------------------------------------------------------------- DatabaseRo @@ -76,7 +74,7 @@ pub trait DatabaseIter { macro_rules! doc_database { () => { r"# Errors -This will return [`RuntimeError::KeyNotFound`] if: +This will return [`crate::RuntimeError::KeyNotFound`] if: - Input does not exist OR - Database is empty" }; @@ -111,7 +109,7 @@ This will return [`RuntimeError::KeyNotFound`] if: pub unsafe trait DatabaseRo { /// Get the value corresponding to a key. #[doc = doc_database!()] - fn get(&self, key: &T::Key) -> Result; + fn get(&self, key: &T::Key) -> DbResult; /// Returns `true` if the database contains a value for the specified key. /// @@ -120,7 +118,7 @@ pub unsafe trait DatabaseRo { /// as in that case, `Ok(false)` will be returned. /// /// Other errors may still occur. - fn contains(&self, key: &T::Key) -> Result { + fn contains(&self, key: &T::Key) -> DbResult { match self.get(key) { Ok(_) => Ok(true), Err(RuntimeError::KeyNotFound) => Ok(false), @@ -132,21 +130,21 @@ pub unsafe trait DatabaseRo { /// /// # Errors /// This will never return [`RuntimeError::KeyNotFound`]. - fn len(&self) -> Result; + fn len(&self) -> DbResult; /// Returns the first `(key, value)` pair in the database. #[doc = doc_database!()] - fn first(&self) -> Result<(T::Key, T::Value), RuntimeError>; + fn first(&self) -> DbResult<(T::Key, T::Value)>; /// Returns the last `(key, value)` pair in the database. #[doc = doc_database!()] - fn last(&self) -> Result<(T::Key, T::Value), RuntimeError>; + fn last(&self) -> DbResult<(T::Key, T::Value)>; /// Returns `true` if the database contains no `(key, value)` pairs. /// /// # Errors /// This can only return [`RuntimeError::Io`] on errors. - fn is_empty(&self) -> Result; + fn is_empty(&self) -> DbResult; } //---------------------------------------------------------------------------------------------------- DatabaseRw @@ -161,7 +159,7 @@ pub trait DatabaseRw: DatabaseRo { #[doc = doc_database!()] /// /// This will never [`RuntimeError::KeyExists`]. - fn put(&mut self, key: &T::Key, value: &T::Value) -> Result<(), RuntimeError>; + fn put(&mut self, key: &T::Key, value: &T::Value) -> DbResult<()>; /// Delete a key-value pair in the database. /// @@ -170,7 +168,7 @@ pub trait DatabaseRw: DatabaseRo { #[doc = doc_database!()] /// /// This will never [`RuntimeError::KeyExists`]. - fn delete(&mut self, key: &T::Key) -> Result<(), RuntimeError>; + fn delete(&mut self, key: &T::Key) -> DbResult<()>; /// Delete and return a key-value pair in the database. /// @@ -178,7 +176,7 @@ pub trait DatabaseRw: DatabaseRo { /// it will serialize the `T::Value` and return it. /// #[doc = doc_database!()] - fn take(&mut self, key: &T::Key) -> Result; + fn take(&mut self, key: &T::Key) -> DbResult; /// Fetch the value, and apply a function to it - or delete the entry. /// @@ -192,7 +190,7 @@ pub trait DatabaseRw: DatabaseRo { /// - If `f` returns `None`, the entry will be [`DatabaseRw::delete`]d /// #[doc = doc_database!()] - fn update(&mut self, key: &T::Key, mut f: F) -> Result<(), RuntimeError> + fn update(&mut self, key: &T::Key, mut f: F) -> DbResult<()> where F: FnMut(T::Value) -> Option, { @@ -207,10 +205,10 @@ pub trait DatabaseRw: DatabaseRo { /// Removes and returns the first `(key, value)` pair in the database. /// #[doc = doc_database!()] - fn pop_first(&mut self) -> Result<(T::Key, T::Value), RuntimeError>; + fn pop_first(&mut self) -> DbResult<(T::Key, T::Value)>; /// Removes and returns the last `(key, value)` pair in the database. /// #[doc = doc_database!()] - fn pop_last(&mut self) -> Result<(T::Key, T::Value), RuntimeError>; + fn pop_last(&mut self) -> DbResult<(T::Key, T::Value)>; } diff --git a/storage/database/src/env.rs b/storage/database/src/env.rs index 291ac9d..56b92cb 100644 --- a/storage/database/src/env.rs +++ b/storage/database/src/env.rs @@ -6,7 +6,7 @@ use std::num::NonZeroUsize; use crate::{ config::Config, database::{DatabaseIter, DatabaseRo, DatabaseRw}, - error::{InitError, RuntimeError}, + error::{DbResult, InitError}, resize::ResizeAlgorithm, table::Table, transaction::{TxRo, TxRw}, @@ -24,15 +24,14 @@ use crate::{ /// /// # Lifetimes /// The lifetimes associated with `Env` have a sequential flow: -/// 1. `ConcreteEnv` -/// 2. `'env` -/// 3. `'tx` -/// 4. `'db` +/// ```text +/// Env -> Tx -> Database +/// ``` /// /// As in: /// - open database tables only live as long as... /// - transactions which only live as long as the... -/// - environment ([`EnvInner`]) +/// - database environment pub trait Env: Sized { //------------------------------------------------ Constants /// Does the database backend need to be manually @@ -40,7 +39,7 @@ pub trait Env: Sized { /// /// # Invariant /// If this is `false`, that means this [`Env`] - /// must _never_ return a [`RuntimeError::ResizeNeeded`]. + /// must _never_ return a [`crate::RuntimeError::ResizeNeeded`]. /// /// If this is `true`, [`Env::resize_map`] & [`Env::current_map_size`] /// _must_ be re-implemented, as it just panics by default. @@ -62,17 +61,17 @@ pub trait Env: Sized { // For `heed`, this is just `heed::Env`, for `redb` this is // `(redb::Database, redb::Durability)` as each transaction // needs the sync mode set during creation. - type EnvInner<'env>: EnvInner<'env, Self::TxRo<'env>, Self::TxRw<'env>> + type EnvInner<'env>: EnvInner<'env> where Self: 'env; /// The read-only transaction type of the backend. - type TxRo<'env>: TxRo<'env> + 'env + type TxRo<'env>: TxRo<'env> where Self: 'env; /// The read/write transaction type of the backend. - type TxRw<'env>: TxRw<'env> + 'env + type TxRw<'env>: TxRw<'env> where Self: 'env; @@ -89,7 +88,7 @@ pub trait Env: Sized { /// This will error if the database file could not be opened. /// /// This is the only [`Env`] function that will return - /// an [`InitError`] instead of a [`RuntimeError`]. + /// an [`InitError`] instead of a [`crate::RuntimeError`]. fn open(config: Config) -> Result; /// Return the [`Config`] that this database was [`Env::open`]ed with. @@ -108,7 +107,7 @@ pub trait Env: Sized { /// /// # Errors /// If there is a synchronization error, this should return an error. - fn sync(&self) -> Result<(), RuntimeError>; + fn sync(&self) -> DbResult<()>; /// Resize the database's memory map to a /// new (bigger) size using a [`ResizeAlgorithm`]. @@ -123,7 +122,7 @@ pub trait Env: Sized { /// This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`. /// /// Otherwise, this function will panic with `unreachable!()`. - #[allow(unused_variables)] + #[expect(unused_variables)] fn resize_map(&self, resize_algorithm: Option) -> NonZeroUsize { unreachable!() } @@ -164,7 +163,7 @@ pub trait Env: Sized { // We have the direct PATH to the file, // no need to use backend-specific functions. // - // SAFETY: as we are only accessing the metadata of + // INVARIANT: as we are only accessing the metadata of // the file and not reading the bytes, it should be // fine even with a memory mapped file being actively // written to. @@ -202,30 +201,31 @@ Subsequent table opens will follow the flags/ordering, but only if /// Note that when opening tables with [`EnvInner::open_db_ro`], /// they must be created first or else it will return error. /// -/// Note that when opening tables with [`EnvInner::open_db_ro`], -/// they must be created first or else it will return error. -/// /// See [`EnvInner::create_db`] for creating tables. /// /// # Invariant #[doc = doc_heed_create_db_invariant!()] -pub trait EnvInner<'env, Ro, Rw> -where - Self: 'env, - Ro: TxRo<'env>, - Rw: TxRw<'env>, -{ +pub trait EnvInner<'env> { + /// The read-only transaction type of the backend. + /// + /// `'tx` is the lifetime of the transaction itself. + type Ro<'tx>: TxRo<'tx>; + /// The read-write transaction type of the backend. + /// + /// `'tx` is the lifetime of the transaction itself. + type Rw<'tx>: TxRw<'tx>; + /// Create a read-only transaction. /// /// # Errors - /// This will only return [`RuntimeError::Io`] if it errors. - fn tx_ro(&'env self) -> Result; + /// This will only return [`crate::RuntimeError::Io`] if it errors. + fn tx_ro(&self) -> DbResult>; /// Create a read/write transaction. /// /// # Errors - /// This will only return [`RuntimeError::Io`] if it errors. - fn tx_rw(&'env self) -> Result; + /// This will only return [`crate::RuntimeError::Io`] if it errors. + fn tx_rw(&self) -> DbResult>; /// Open a database in read-only mode. /// @@ -235,25 +235,51 @@ where /// This will open the database [`Table`] /// passed as a generic to this function. /// - /// ```rust,ignore - /// let db = env.open_db_ro::
(&tx_ro); - /// // ^ ^ - /// // database table table metadata - /// // (name, key/value type) + /// ```rust + /// # use cuprate_database::{ + /// # ConcreteEnv, + /// # config::ConfigBuilder, + /// # Env, EnvInner, + /// # DatabaseRo, DatabaseRw, TxRo, TxRw, + /// # }; + /// # fn main() -> Result<(), Box> { + /// # let tmp_dir = tempfile::tempdir()?; + /// # let db_dir = tmp_dir.path().to_owned(); + /// # let config = ConfigBuilder::new(db_dir.into()).build(); + /// # let env = ConcreteEnv::open(config)?; + /// # + /// # struct Table; + /// # impl cuprate_database::Table for Table { + /// # const NAME: &'static str = "table"; + /// # type Key = u8; + /// # type Value = u64; + /// # } + /// # + /// # let env_inner = env.env_inner(); + /// # let tx_rw = env_inner.tx_rw()?; + /// # env_inner.create_db::
(&tx_rw)?; + /// # TxRw::commit(tx_rw); + /// # + /// # let tx_ro = env_inner.tx_ro()?; + /// let db = env_inner.open_db_ro::
(&tx_ro); + /// // ^ ^ + /// // database table table metadata + /// // (name, key/value type) + /// # Ok(()) } /// ``` /// /// # Errors - /// This will only return [`RuntimeError::Io`] on normal errors. + /// This will only return [`crate::RuntimeError::Io`] on normal errors. /// /// If the specified table is not created upon before this function is called, - /// this will return [`RuntimeError::TableNotFound`]. + /// this will return [`crate::RuntimeError::TableNotFound`]. /// /// # Invariant #[doc = doc_heed_create_db_invariant!()] fn open_db_ro( &self, - tx_ro: &Ro, - ) -> Result + DatabaseIter, RuntimeError>; + tx_ro: &Self::Ro<'_>, + ) -> DbResult + DatabaseIter>; /// Open a database in read/write mode. /// @@ -267,22 +293,22 @@ where /// passed as a generic to this function. /// /// # Errors - /// This will only return [`RuntimeError::Io`] on errors. + /// This will only return [`crate::RuntimeError::Io`] on errors. /// /// # Invariant #[doc = doc_heed_create_db_invariant!()] - fn open_db_rw(&self, tx_rw: &Rw) -> Result, RuntimeError>; + fn open_db_rw(&self, tx_rw: &Self::Rw<'_>) -> DbResult>; /// Create a database table. /// /// This will create the database [`Table`] passed as a generic to this function. /// /// # Errors - /// This will only return [`RuntimeError::Io`] on errors. + /// This will only return [`crate::RuntimeError::Io`] on errors. /// /// # Invariant #[doc = doc_heed_create_db_invariant!()] - fn create_db(&self, tx_rw: &Rw) -> Result<(), RuntimeError>; + fn create_db(&self, tx_rw: &Self::Rw<'_>) -> DbResult<()>; /// Clear all `(key, value)`'s from a database table. /// @@ -293,9 +319,9 @@ where /// function's effects can be aborted using [`TxRw::abort`]. /// /// # Errors - /// This will return [`RuntimeError::Io`] on normal errors. + /// This will return [`crate::RuntimeError::Io`] on normal errors. /// /// If the specified table is not created upon before this function is called, - /// this will return [`RuntimeError::TableNotFound`]. - fn clear_db(&self, tx_rw: &mut Rw) -> Result<(), RuntimeError>; + /// this will return [`crate::RuntimeError::TableNotFound`]. + fn clear_db(&self, tx_rw: &mut Self::Rw<'_>) -> DbResult<()>; } diff --git a/storage/database/src/error.rs b/storage/database/src/error.rs index 386091d..82f80b9 100644 --- a/storage/database/src/error.rs +++ b/storage/database/src/error.rs @@ -7,6 +7,9 @@ use std::fmt::Debug; /// Alias for a thread-safe boxed error. type BoxError = Box; +/// [`Result`] with [`RuntimeError`] as the error. +pub type DbResult = Result; + //---------------------------------------------------------------------------------------------------- InitError /// Errors that occur during ([`Env::open`](crate::env::Env::open)). /// @@ -59,18 +62,16 @@ pub enum InitError { } //---------------------------------------------------------------------------------------------------- RuntimeError -/// Errors that occur _after_ successful ([`Env::open`](crate::env::Env::open)). +/// Errors that occur _after_ successful [`Env::open`](crate::env::Env::open). /// /// There are no errors for: /// 1. Missing tables /// 2. (De)serialization -/// 3. Shutdown errors /// /// as `cuprate_database` upholds the invariant that: /// /// 1. All tables exist /// 2. (De)serialization never fails -/// 3. The database (thread-pool) only shuts down when all channels are dropped #[derive(thiserror::Error, Debug)] pub enum RuntimeError { /// The given key already existed in the database. diff --git a/storage/database/src/key.rs b/storage/database/src/key.rs index 3273d4e..2f3855a 100644 --- a/storage/database/src/key.rs +++ b/storage/database/src/key.rs @@ -163,11 +163,11 @@ impl KeyCompare { #[inline] pub const fn as_compare_fn(self) -> fn(&[u8], &[u8]) -> Ordering { match self { - Self::Default => std::cmp::Ord::cmp, + Self::Default => Ord::cmp, Self::Number => |left, right| { let left = ::from_bytes(left); let right = ::from_bytes(right); - std::cmp::Ord::cmp(&left, &right) + Ord::cmp(&left, &right) }, Self::Custom(f) => f, } diff --git a/storage/database/src/lib.rs b/storage/database/src/lib.rs index 31c5c95..8e48fca 100644 --- a/storage/database/src/lib.rs +++ b/storage/database/src/lib.rs @@ -1,92 +1,18 @@ #![doc = include_str!("../README.md")] -//---------------------------------------------------------------------------------------------------- Lints -// Forbid lints. -// Our code, and code generated (e.g macros) cannot overrule these. -#![forbid( - // `unsafe` is allowed but it _must_ be - // commented with `SAFETY: reason`. - clippy::undocumented_unsafe_blocks, - - // Never. - unused_unsafe, - redundant_semicolons, - unused_allocation, - coherence_leak_check, - while_true, - clippy::missing_docs_in_private_items, - - // Maybe can be put into `#[deny]`. - unconditional_recursion, - for_loops_over_fallibles, - unused_braces, - unused_labels, - keyword_idents, - non_ascii_idents, - variant_size_differences, - single_use_lifetimes, - - // Probably can be put into `#[deny]`. - future_incompatible, - let_underscore, - break_with_label_and_loop, - duplicate_macro_attributes, - exported_private_dependencies, - large_assignments, - overlapping_range_endpoints, - semicolon_in_expressions_from_macros, - noop_method_call, - unreachable_pub, -)] -// Deny lints. -// Some of these are `#[allow]`'ed on a per-case basis. -#![deny( - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, - clippy::nursery, - clippy::cargo, - unused_crate_dependencies, - unused_doc_comments, - unused_mut, - missing_docs, - deprecated, - unused_comparisons, - nonstandard_style -)] #![allow( - // FIXME: this lint affects crates outside of - // `database/` for some reason, allow for now. - clippy::cargo_common_metadata, - - // FIXME: adding `#[must_use]` onto everything - // might just be more annoying than useful... - // although it is sometimes nice. - clippy::must_use_candidate, - - // FIXME: good lint but too many false positives - // with our `Env` + `RwLock` setup. - clippy::significant_drop_tightening, - - // FIXME: good lint but is less clear in most cases. - clippy::items_after_statements, - - clippy::module_name_repetitions, - clippy::module_inception, - clippy::redundant_pub_crate, - clippy::option_if_let_else, -)] -// Allow some lints when running in debug mode. -#![cfg_attr( - debug_assertions, - allow( - clippy::todo, - clippy::multiple_crate_versions, - // unused_crate_dependencies, - ) + // This lint is allowed because the following + // code exists a lot in this crate: + // + // ```rust + // let env_inner = env.env_inner(); + // let tx_rw = env_inner.tx_rw()?; + // OpenTables::create_tables(&env_inner, &tx_rw)?; + // ``` + // + // Rust thinks `env_inner` can be dropped earlier + // but it cannot, we need it for the lifetime of + // the database transaction + tables. + clippy::significant_drop_tightening )] // Allow some lints in tests. #![cfg_attr( @@ -105,42 +31,39 @@ // Documentation for each module is located in the respective file. mod backend; -pub use backend::ConcreteEnv; +mod constants; +mod database; +mod env; +mod error; +mod key; +mod storable; +mod table; +mod tables; +mod transaction; pub mod config; +pub mod resize; -mod constants; +pub use backend::ConcreteEnv; pub use constants::{ DATABASE_BACKEND, DATABASE_CORRUPT_MSG, DATABASE_DATA_FILENAME, DATABASE_LOCK_FILENAME, }; - -mod database; pub use database::{DatabaseIter, DatabaseRo, DatabaseRw}; - -mod env; pub use env::{Env, EnvInner}; - -mod error; -pub use error::{InitError, RuntimeError}; - -pub mod resize; - -mod key; +pub use error::{DbResult, InitError, RuntimeError}; pub use key::{Key, KeyCompare}; - -mod storable; pub use storable::{Storable, StorableBytes, StorableStr, StorableVec}; - -mod table; pub use table::Table; - -mod transaction; pub use transaction::{TxRo, TxRw}; //---------------------------------------------------------------------------------------------------- Private #[cfg(test)] pub(crate) mod tests; +// Used inside public facing macros. +#[doc(hidden)] +pub use paste; + //---------------------------------------------------------------------------------------------------- // HACK: needed to satisfy the `unused_crate_dependencies` lint. cfg_if::cfg_if! { diff --git a/storage/database/src/resize.rs b/storage/database/src/resize.rs index 99d6d7e..b217478 100644 --- a/storage/database/src/resize.rs +++ b/storage/database/src/resize.rs @@ -10,7 +10,7 @@ //! //! # Page size //! All free functions in this module will -//! return a multiple of the OS page size ([`page_size()`]), +//! return a multiple of the OS page size ([`PAGE_SIZE`]), //! [LMDB will error](http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5) //! if this is not the case. //! @@ -18,10 +18,10 @@ //! All returned [`NonZeroUsize`] values of the free functions in this module //! (including [`ResizeAlgorithm::resize`]) uphold the following invariants: //! 1. It will always be `>=` the input `current_size_bytes` -//! 2. It will always be a multiple of [`page_size()`] +//! 2. It will always be a multiple of [`PAGE_SIZE`] //---------------------------------------------------------------------------------------------------- Import -use std::{num::NonZeroUsize, sync::OnceLock}; +use std::{num::NonZeroUsize, sync::LazyLock}; //---------------------------------------------------------------------------------------------------- ResizeAlgorithm /// The function/algorithm used by the @@ -85,21 +85,14 @@ impl Default for ResizeAlgorithm { } //---------------------------------------------------------------------------------------------------- Free functions -/// This function retrieves the system’s memory page size. +/// This retrieves the system’s memory page size. /// /// It is just [`page_size::get`](https://docs.rs/page_size) internally. /// -/// This caches the result, so this function is cheap after the 1st call. -/// /// # Panics -/// This function will panic if the OS returns of page size of `0` (impossible?). -#[inline] -pub fn page_size() -> NonZeroUsize { - /// Cached result of [`page_size()`]. - static PAGE_SIZE: OnceLock = OnceLock::new(); - *PAGE_SIZE - .get_or_init(|| NonZeroUsize::new(page_size::get()).expect("page_size::get() returned 0")) -} +/// Accessing this [`LazyLock`] will panic if the OS returns of page size of `0` (impossible?). +pub static PAGE_SIZE: LazyLock = + LazyLock::new(|| NonZeroUsize::new(page_size::get()).expect("page_size::get() returned 0")); /// Memory map resize closely matching `monerod`. /// @@ -122,7 +115,7 @@ pub fn page_size() -> NonZeroUsize { /// assert_eq!(monero(0).get(), N); /// /// // Rounds up to nearest OS page size. -/// assert_eq!(monero(1).get(), N + page_size().get()); +/// assert_eq!(monero(1).get(), N + PAGE_SIZE.get()); /// ``` /// /// # Panics @@ -143,7 +136,7 @@ pub fn monero(current_size_bytes: usize) -> NonZeroUsize { /// const ADD_SIZE: usize = 1_usize << 30; - let page_size = page_size().get(); + let page_size = PAGE_SIZE.get(); let new_size_bytes = current_size_bytes + ADD_SIZE; // Round up the new size to the @@ -167,7 +160,7 @@ pub fn monero(current_size_bytes: usize) -> NonZeroUsize { /// /// ```rust /// # use cuprate_database::resize::*; -/// let page_size: usize = page_size().get(); +/// let page_size: usize = PAGE_SIZE.get(); /// /// // Anything below the page size will round up to the page size. /// for i in 0..=page_size { @@ -190,7 +183,7 @@ pub fn monero(current_size_bytes: usize) -> NonZeroUsize { /// fixed_bytes(1, usize::MAX); /// ``` pub fn fixed_bytes(current_size_bytes: usize, add_bytes: usize) -> NonZeroUsize { - let page_size = page_size(); + let page_size = *PAGE_SIZE; let new_size_bytes = current_size_bytes + add_bytes; // Guard against < page_size. @@ -222,7 +215,7 @@ pub fn fixed_bytes(current_size_bytes: usize, add_bytes: usize) -> NonZeroUsize /// /// ```rust /// # use cuprate_database::resize::*; -/// let page_size: usize = page_size().get(); +/// let page_size: usize = PAGE_SIZE.get(); /// /// // Anything below the page size will round up to the page size. /// for i in 0..=page_size { @@ -265,10 +258,10 @@ pub fn percent(current_size_bytes: usize, percent: f32) -> NonZeroUsize { _ => 1.0, }; - let page_size = page_size(); + let page_size = *PAGE_SIZE; // INVARIANT: Allow `f32` <-> `usize` casting, we handle all cases. - #[allow( + #[expect( clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_precision_loss diff --git a/storage/database/src/storable.rs b/storage/database/src/storable.rs index 8842af7..b153568 100644 --- a/storage/database/src/storable.rs +++ b/storage/database/src/storable.rs @@ -109,7 +109,7 @@ impl Storable for T where Self: Pod + Debug, { - const BYTE_LENGTH: Option = Some(std::mem::size_of::()); + const BYTE_LENGTH: Option = Some(size_of::()); #[inline] fn as_bytes(&self) -> &[u8] { @@ -129,7 +129,7 @@ where /// /// Slice types are owned both: /// - when returned from the database -/// - in `put()` +/// - in [`crate::DatabaseRw::put()`] /// /// This is needed as `impl Storable for Vec` runs into impl conflicts. /// diff --git a/storage/database/src/table.rs b/storage/database/src/table.rs index 56e84dd..6d0daa2 100644 --- a/storage/database/src/table.rs +++ b/storage/database/src/table.rs @@ -1,13 +1,14 @@ //! Database table abstraction; `trait Table`. //---------------------------------------------------------------------------------------------------- Import - use crate::{key::Key, storable::Storable}; //---------------------------------------------------------------------------------------------------- Table /// Database table metadata. /// /// Purely compile time information for database tables. +/// +/// See [`crate::define_tables`] for bulk table generation. pub trait Table: 'static { /// Name of the database table. const NAME: &'static str; diff --git a/storage/database/src/tables.rs b/storage/database/src/tables.rs new file mode 100644 index 0000000..56203ad --- /dev/null +++ b/storage/database/src/tables.rs @@ -0,0 +1,429 @@ +//! Database table definition macro. + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Table macro +/// Define all table types. +/// +/// # Purpose +/// This macro allows you to define all database tables in one place. +/// +/// A by-product of this macro is that it defines some +/// convenient traits specific to _your_ tables +/// (see [Output](#output)). +/// +/// # Inputs +/// This macro expects a list of tables, and their key/value types. +/// +/// This syntax is as follows: +/// +/// ```rust +/// cuprate_database::define_tables! { +/// /// Any extra attributes you'd like to add to +/// /// this table type, e.g. docs or derives. +/// +/// 0 => TableName, +/// // ▲ ▲ +/// // │ └─ Table struct name. The macro generates this for you. +/// // │ +/// // Incrementing index. This must start at 0 +/// // and increment by 1 per table added. +/// +/// u8 => u64, +/// // ▲ ▲ +/// // │ └─ Table value type. +/// // │ +/// // Table key type. +/// +/// // Another table. +/// 1 => TableName2, +/// i8 => i64, +/// } +/// ``` +/// +/// An example: +/// ```rust +/// use cuprate_database::{ +/// ConcreteEnv, Table, +/// config::ConfigBuilder, +/// Env, EnvInner, +/// DatabaseRo, DatabaseRw, TxRo, TxRw, +/// }; +/// +/// // This generates `pub struct Table{1,2,3}` +/// // where all those implement `Table` with +/// // the defined name and key/value types. +/// // +/// // It also generate traits specific to our tables. +/// cuprate_database::define_tables! { +/// 0 => Table1, +/// u32 => i32, +/// +/// /// This one has extra docs. +/// 1 => Table2, +/// u64 => (), +/// +/// 2 => Table3, +/// i32 => i32, +/// } +/// +/// # fn main() -> Result<(), Box> { +/// # let tmp_dir = tempfile::tempdir()?; +/// # let db_dir = tmp_dir.path().to_owned(); +/// # let config = ConfigBuilder::new(db_dir.into()).build(); +/// // Open the database. +/// let env = ConcreteEnv::open(config)?; +/// let env_inner = env.env_inner(); +/// +/// // Open the table we just defined. +/// { +/// let tx_rw = env_inner.tx_rw()?; +/// env_inner.create_db::(&tx_rw)?; +/// let mut table = env_inner.open_db_rw::(&tx_rw)?; +/// +/// // Write data to the table. +/// table.put(&0, &1)?; +/// +/// drop(table); +/// TxRw::commit(tx_rw)?; +/// } +/// +/// // Read the data, assert it is correct. +/// { +/// let tx_ro = env_inner.tx_ro()?; +/// let table = env_inner.open_db_ro::(&tx_ro)?; +/// assert_eq!(table.first()?, (0, 1)); +/// } +/// +/// // Create all tables at once using the +/// // `OpenTables` trait generated with the +/// // macro above. +/// { +/// let tx_rw = env_inner.tx_rw()?; +/// env_inner.create_tables(&tx_rw)?; +/// TxRw::commit(tx_rw)?; +/// } +/// +/// // Open all tables at once. +/// { +/// let tx_ro = env_inner.tx_ro()?; +/// let all_tables = env_inner.open_tables(&tx_ro)?; +/// } +/// # Ok(()) } +/// ``` +/// +/// # Output +/// This macro: +/// 1. Implements [`Table`](crate::Table) on all your table types +/// 1. Creates a `pub trait Tables` trait (in scope) +/// 1. Creates a `pub trait TablesIter` trait (in scope) +/// 1. Creates a `pub trait TablesMut` trait (in scope) +/// 1. Blanket implements a `(tuples, containing, all, open, database, tables, ...)` for the above traits +/// 1. Creates a `pub trait OpenTables` trait (in scope) +/// +/// All table types are zero-sized structs that implement the `Table` trait. +/// +/// Table structs are automatically `CamelCase`, and their +/// static string names are automatically `snake_case`. +/// +/// For why the table traits + blanket implementation on the tuple exists, see: +/// . +/// +/// The `OpenTables` trait lets you open all tables you've defined, at once. +/// +/// # Example +/// For examples of usage & output, see +/// [`cuprate_blockchain::tables`](https://github.com/Cuprate/cuprate/blob/main/storage/blockchain/src/tables.rs). +#[macro_export] +macro_rules! define_tables { + ( + $( + // Documentation and any `derive`'s. + $(#[$attr:meta])* + + // The table name + doubles as the table struct name. + $index:literal => $table:ident, + + // Key type => Value type. + $key:ty => $value:ty + ),* $(,)? + ) => { $crate::paste::paste! { + $( + // Table struct. + $(#[$attr])* + #[doc = concat!("- Key: [`", stringify!($key), "`]")] + #[doc = concat!("- Value: [`", stringify!($value), "`]")] + #[doc = concat!("- Name: `", stringify!([<$table:snake>]), "`")] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)] + pub struct [<$table:camel>]; + + // Table trait impl. + impl $crate::Table for [<$table:camel>] { + const NAME: &'static str = stringify!([<$table:snake>]); + type Key = $key; + type Value = $value; + } + )* + + /// Object containing all opened [`Table`](cuprate_database::Table)s in read-only mode. + /// + /// This is an encapsulated object that contains all + /// available `Table`'s in read-only mode. + /// + /// It is a `Sealed` trait and is only implemented on a + /// `(tuple, containing, all, table, types, ...)`. + /// + /// This is used to return a _single_ object from functions like + /// [`OpenTables::open_tables`] rather than the tuple containing the tables itself. + /// + /// To replace `tuple.0` style indexing, `field_accessor_functions()` + /// are provided on this trait, which essentially map the object to + /// fields containing the particular database table, for example: + /// ```rust,ignore + /// let tables = open_tables(); + /// + /// // The accessor function `block_infos()` returns the field + /// // containing an open database table for `BlockInfos`. + /// let _ = tables.block_infos(); + /// ``` + /// + /// See also: + /// - [`TablesMut`] + /// - [`TablesIter`] + pub trait Tables { + // This expands to creating `fn field_accessor_functions()` + // for each passed `$table` type. + // + // It is essentially a mapping to the field + // containing the proper opened database table. + // + // The function name of the function is + // the table type in `snake_case`, e.g., `block_info_v1s()`. + $( + /// Access an opened + #[doc = concat!("[`", stringify!($table), "`]")] + /// database. + fn [<$table:snake>](&self) -> &impl $crate::DatabaseRo<$table>; + )* + + /// This returns `true` if all tables are empty. + /// + /// # Errors + /// This returns errors on regular database errors. + fn all_tables_empty(&self) -> $crate::DbResult; + } + + /// Object containing all opened [`Table`](cuprate_database::Table)s in read + iter mode. + /// + /// This is the same as [`Tables`] but includes `_iter()` variants. + /// + /// Note that this trait is a supertrait of `Tables`, + /// as in it can use all of its functions as well. + /// + /// See [`Tables`] for documentation - this trait exists for the same reasons. + pub trait TablesIter: Tables { + $( + /// Access an opened read-only + iterable + #[doc = concat!("[`", stringify!($table), "`]")] + /// database. + fn [<$table:snake _iter>](&self) -> &(impl $crate::DatabaseRo<$table> + $crate::DatabaseIter<$table>); + )* + } + + /// Object containing all opened [`Table`](cuprate_database::Table)s in write mode. + /// + /// This is the same as [`Tables`] but for mutable accesses. + /// + /// Note that this trait is a supertrait of `Tables`, + /// as in it can use all of its functions as well. + /// + /// See [`Tables`] for documentation - this trait exists for the same reasons. + pub trait TablesMut: Tables { + $( + /// Access an opened + #[doc = concat!("[`", stringify!($table), "`]")] + /// database. + fn [<$table:snake _mut>](&mut self) -> &mut impl $crate::DatabaseRw<$table>; + )* + } + + // This creates a blanket-implementation for + // `(tuple, containing, all, table, types)`. + // + // There is a generic defined here _for each_ `$table` input. + // Specifically, the generic letters are just the table types in UPPERCASE. + // Concretely, this expands to something like: + // ```rust + // impl + // ``` + impl<$([<$table:upper>]),*> Tables + // We are implementing `Tables` on a tuple that + // contains all those generics specified, i.e., + // a tuple containing all open table types. + // + // Concretely, this expands to something like: + // ```rust + // (BLOCKINFOSV1S, BLOCKINFOSV2S, BLOCKINFOSV3S, [...]) + // ``` + // which is just a tuple of the generics defined above. + for ($([<$table:upper>]),*) + where + // This expands to a where bound that asserts each element + // in the tuple implements some database table type. + // + // Concretely, this expands to something like: + // ```rust + // BLOCKINFOSV1S: DatabaseRo + DatabaseIter, + // BLOCKINFOSV2S: DatabaseRo + DatabaseIter, + // [...] + // ``` + $( + [<$table:upper>]: $crate::DatabaseRo<$table>, + )* + { + $( + // The function name of the accessor function is + // the table type in `snake_case`, e.g., `block_info_v1s()`. + #[inline] + fn [<$table:snake>](&self) -> &impl $crate::DatabaseRo<$table> { + // The index of the database table in + // the tuple implements the table trait. + &self.$index + } + )* + + fn all_tables_empty(&self) -> $crate::DbResult { + $( + if !$crate::DatabaseRo::is_empty(&self.$index)? { + return Ok(false); + } + )* + Ok(true) + } + } + + // This is the same as the above + // `Tables`, but for `TablesIter`. + impl<$([<$table:upper>]),*> TablesIter + for ($([<$table:upper>]),*) + where + $( + [<$table:upper>]: $crate::DatabaseRo<$table> + $crate::DatabaseIter<$table>, + )* + { + $( + // The function name of the accessor function is + // the table type in `snake_case` + `_iter`, e.g., `block_info_v1s_iter()`. + #[inline] + fn [<$table:snake _iter>](&self) -> &(impl $crate::DatabaseRo<$table> + $crate::DatabaseIter<$table>) { + &self.$index + } + )* + } + + // This is the same as the above + // `Tables`, but for `TablesMut`. + impl<$([<$table:upper>]),*> TablesMut + for ($([<$table:upper>]),*) + where + $( + [<$table:upper>]: $crate::DatabaseRw<$table>, + )* + { + $( + // The function name of the mutable accessor function is + // the table type in `snake_case` + `_mut`, e.g., `block_info_v1s_mut()`. + #[inline] + fn [<$table:snake _mut>](&mut self) -> &mut impl $crate::DatabaseRw<$table> { + &mut self.$index + } + )* + } + + /// Open all tables at once. + /// + /// This trait encapsulates the functionality of opening all tables at once. + /// It can be seen as the "constructor" for the [`Tables`] object. + /// + /// Note that this is already implemented on [`cuprate_database::EnvInner`], thus: + /// - You don't need to implement this + /// - It can be called using `env_inner.open_tables()` notation + /// + /// # Creation before opening + /// As [`cuprate_database::EnvInner`] documentation states, + /// tables must be created before they are opened. + /// + /// I.e. [`OpenTables::create_tables`] must be called before + /// [`OpenTables::open_tables`] or else panics may occur. + pub trait OpenTables<'env> { + /// The read-only transaction type of the backend. + type Ro<'tx>; + /// The read-write transaction type of the backend. + type Rw<'tx>; + + /// Open all tables in read/iter mode. + /// + /// This calls [`cuprate_database::EnvInner::open_db_ro`] on all database tables + /// and returns a structure that allows access to all tables. + /// + /// # Errors + /// This will only return [`cuprate_database::RuntimeError::Io`] if it errors. + fn open_tables(&self, tx_ro: &Self::Ro<'_>) -> $crate::DbResult; + + /// Open all tables in read-write mode. + /// + /// This calls [`cuprate_database::EnvInner::open_db_rw`] on all database tables + /// and returns a structure that allows access to all tables. + /// + /// # Errors + /// This will only return [`cuprate_database::RuntimeError::Io`] on errors. + fn open_tables_mut(&self, tx_rw: &Self::Rw<'_>) -> $crate::DbResult; + + /// Create all database tables. + /// + /// This will create all the defined [`Table`](cuprate_database::Table)s. + /// + /// # Errors + /// This will only return [`cuprate_database::RuntimeError::Io`] on errors. + fn create_tables(&self, tx_rw: &Self::Rw<'_>) -> $crate::DbResult<()>; + } + + impl<'env, Ei> OpenTables<'env> for Ei + where + Ei: $crate::EnvInner<'env>, + { + type Ro<'tx> = >::Ro<'tx>; + type Rw<'tx> = >::Rw<'tx>; + + fn open_tables(&self, tx_ro: &Self::Ro<'_>) -> $crate::DbResult { + Ok(($( + Self::open_db_ro::<[<$table:camel>]>(self, tx_ro)?, + )*)) + } + + fn open_tables_mut(&self, tx_rw: &Self::Rw<'_>) -> $crate::DbResult { + Ok(($( + Self::open_db_rw::<[<$table:camel>]>(self, tx_rw)?, + )*)) + } + + fn create_tables(&self, tx_rw: &Self::Rw<'_>) -> $crate::DbResult<()> { + let result = Ok(($( + Self::create_db::<[<$table:camel>]>(self, tx_rw), + )*)); + + match result { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + } + }}; +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/storage/database/src/transaction.rs b/storage/database/src/transaction.rs index e4c310a..16d1c51 100644 --- a/storage/database/src/transaction.rs +++ b/storage/database/src/transaction.rs @@ -1,7 +1,7 @@ //! Database transaction abstraction; `trait TxRo`, `trait TxRw`. //---------------------------------------------------------------------------------------------------- Import -use crate::error::RuntimeError; +use crate::error::DbResult; //---------------------------------------------------------------------------------------------------- TxRo /// Read-only database transaction. @@ -11,19 +11,19 @@ use crate::error::RuntimeError; /// # Commit /// It's recommended but may not be necessary to call [`TxRo::commit`] in certain cases: /// - -pub trait TxRo<'env> { +pub trait TxRo<'tx> { /// Commit the read-only transaction. /// /// # Errors /// This operation will always return `Ok(())` with the `redb` backend. - fn commit(self) -> Result<(), RuntimeError>; + fn commit(self) -> DbResult<()>; } //---------------------------------------------------------------------------------------------------- TxRw /// Read/write database transaction. /// /// Returned from [`EnvInner::tx_rw`](crate::EnvInner::tx_rw). -pub trait TxRw<'env> { +pub trait TxRw<'tx> { /// Commit the read/write transaction. /// /// Note that this doesn't necessarily sync the database caches to disk. @@ -32,12 +32,12 @@ pub trait TxRw<'env> { /// This operation will always return `Ok(())` with the `redb` backend. /// /// If `Env::MANUAL_RESIZE == true`, - /// [`RuntimeError::ResizeNeeded`] may be returned. - fn commit(self) -> Result<(), RuntimeError>; + /// [`crate::RuntimeError::ResizeNeeded`] may be returned. + fn commit(self) -> DbResult<()>; /// Abort the transaction, erasing any writes that have occurred. /// /// # Errors /// This operation will always return `Ok(())` with the `heed` backend. - fn abort(self) -> Result<(), RuntimeError>; + fn abort(self) -> DbResult<()>; } diff --git a/storage/service/Cargo.toml b/storage/service/Cargo.toml new file mode 100644 index 0000000..ebdb13e --- /dev/null +++ b/storage/service/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "cuprate-database-service" +version = "0.1.0" +edition = "2021" +description = "Cuprate's database service abstraction" +license = "MIT" +authors = ["Boog900"] +repository = "https://github.com/Cuprate/cuprate/tree/main/storage/service" +keywords = ["cuprate", "service", "database"] + +[features] +default = ["heed"] +heed = ["cuprate-database/heed"] +redb = ["cuprate-database/redb"] +redb-memorey = ["cuprate-database/redb-memory"] + +[dependencies] +cuprate-database = { workspace = true } +cuprate-helper = { workspace = true, features = ["fs", "thread", "map", "asynch"] } + +serde = { workspace = true, optional = true } +rayon = { workspace = true } +tower = { workspace = true } +futures = { workspace = true, features = ["std"] } +crossbeam = { workspace = true, features = ["std"] } + +[lints] +workspace = true diff --git a/storage/service/README.md b/storage/service/README.md new file mode 100644 index 0000000..32e743c --- /dev/null +++ b/storage/service/README.md @@ -0,0 +1,7 @@ +# Cuprate's `tower::Service` database abstraction. + +This crate contains the building blocks for creating a [`tower::Service`] interface to [`cuprate_blockchain`](https://doc.cuprate.org/cuprate_blockchain). + +It is split into 2 `tower::Service`s: +1. A [read service](crate::DatabaseReadService) which is backed by a [`rayon::ThreadPool`] +1. A [write service](crate::DatabaseWriteHandle) which spawns a single thread to handle write requests diff --git a/storage/service/src/lib.rs b/storage/service/src/lib.rs new file mode 100644 index 0000000..51d896a --- /dev/null +++ b/storage/service/src/lib.rs @@ -0,0 +1,8 @@ +#![doc = include_str!("../README.md")] + +mod reader_threads; +mod service; + +pub use reader_threads::{init_thread_pool, ReaderThreads}; + +pub use service::{DatabaseReadService, DatabaseWriteHandle}; diff --git a/storage/blockchain/src/config/reader_threads.rs b/storage/service/src/reader_threads.rs similarity index 73% rename from storage/blockchain/src/config/reader_threads.rs rename to storage/service/src/reader_threads.rs index 04216e3..a182e48 100644 --- a/storage/blockchain/src/config/reader_threads.rs +++ b/storage/service/src/reader_threads.rs @@ -1,31 +1,43 @@ -//! Database [`Env`](crate::Env) configuration. +//! Reader thread-pool configuration and initiation. //! -//! This module contains the main [`Config`]uration struct -//! for the database [`Env`](crate::Env)ironment, and data -//! structures related to any configuration setting. +//! This module contains [`ReaderThreads`] which allow specifying the amount of +//! reader threads for the [`rayon::ThreadPool`]. //! -//! These configurations are processed at runtime, meaning -//! the `Env` can/will dynamically adjust its behavior -//! based on these values. +//! It also contains [`init_thread_pool`] which initiates the thread-pool. //---------------------------------------------------------------------------------------------------- Import -use std::num::NonZeroUsize; +use std::{num::NonZeroUsize, sync::Arc}; +use rayon::ThreadPool; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +//---------------------------------------------------------------------------------------------------- init_thread_pool +/// Initialize the reader thread-pool backed by `rayon`. +pub fn init_thread_pool(reader_threads: ReaderThreads) -> Arc { + // How many reader threads to spawn? + let reader_count = reader_threads.as_threads().get(); + + Arc::new( + rayon::ThreadPoolBuilder::new() + .num_threads(reader_count) + .thread_name(|i| format!("{}::DatabaseReader({i})", module_path!())) + .build() + .unwrap(), + ) +} + //---------------------------------------------------------------------------------------------------- ReaderThreads -/// Amount of database reader threads to spawn when using [`service`](crate::service). +/// Amount of database reader threads to spawn. /// -/// This controls how many reader thread `service`'s +/// This controls how many reader threads the [`DatabaseReadService`](crate::DatabaseReadService) /// thread-pool will spawn to receive and send requests/responses. /// -/// It does nothing outside of `service`. -/// -/// It will always be at least 1, up until the amount of threads on the machine. -/// +/// # Invariant /// The main function used to extract an actual /// usable thread count out of this is [`ReaderThreads::as_threads`]. +/// +/// This will always return at least 1, up until the amount of threads on the machine. #[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ReaderThreads { @@ -49,7 +61,7 @@ pub enum ReaderThreads { /// as such, it is equal to [`ReaderThreads::OnePerThread`]. /// /// ```rust - /// # use cuprate_blockchain::config::*; + /// # use cuprate_database_service::*; /// let reader_threads = ReaderThreads::from(0_usize); /// assert!(matches!(reader_threads, ReaderThreads::OnePerThread)); /// ``` @@ -81,7 +93,7 @@ pub enum ReaderThreads { /// non-zero, but not 1 thread, the minimum value 1 will be returned. /// /// ```rust - /// # use cuprate_blockchain::config::*; + /// # use cuprate_database_service::ReaderThreads; /// assert_eq!(ReaderThreads::Percent(0.000000001).as_threads().get(), 1); /// ``` Percent(f32), @@ -97,30 +109,30 @@ impl ReaderThreads { /// /// # Example /// ```rust - /// use cuprate_blockchain::config::ReaderThreads as Rt; + /// use cuprate_database_service::ReaderThreads as R; /// /// let total_threads: std::num::NonZeroUsize = /// cuprate_helper::thread::threads(); /// - /// assert_eq!(Rt::OnePerThread.as_threads(), total_threads); + /// assert_eq!(R::OnePerThread.as_threads(), total_threads); /// - /// assert_eq!(Rt::One.as_threads().get(), 1); + /// assert_eq!(R::One.as_threads().get(), 1); /// - /// assert_eq!(Rt::Number(0).as_threads(), total_threads); - /// assert_eq!(Rt::Number(1).as_threads().get(), 1); - /// assert_eq!(Rt::Number(usize::MAX).as_threads(), total_threads); + /// assert_eq!(R::Number(0).as_threads(), total_threads); + /// assert_eq!(R::Number(1).as_threads().get(), 1); + /// assert_eq!(R::Number(usize::MAX).as_threads(), total_threads); /// - /// assert_eq!(Rt::Percent(0.01).as_threads().get(), 1); - /// assert_eq!(Rt::Percent(0.0).as_threads(), total_threads); - /// assert_eq!(Rt::Percent(1.0).as_threads(), total_threads); - /// assert_eq!(Rt::Percent(f32::NAN).as_threads(), total_threads); - /// assert_eq!(Rt::Percent(f32::INFINITY).as_threads(), total_threads); - /// assert_eq!(Rt::Percent(f32::NEG_INFINITY).as_threads(), total_threads); + /// assert_eq!(R::Percent(0.01).as_threads().get(), 1); + /// assert_eq!(R::Percent(0.0).as_threads(), total_threads); + /// assert_eq!(R::Percent(1.0).as_threads(), total_threads); + /// assert_eq!(R::Percent(f32::NAN).as_threads(), total_threads); + /// assert_eq!(R::Percent(f32::INFINITY).as_threads(), total_threads); + /// assert_eq!(R::Percent(f32::NEG_INFINITY).as_threads(), total_threads); /// /// // Percentage only works on more than 1 thread. /// if total_threads.get() > 1 { /// assert_eq!( - /// Rt::Percent(0.5).as_threads().get(), + /// R::Percent(0.5).as_threads().get(), /// (total_threads.get() as f32 / 2.0) as usize, /// ); /// } @@ -141,7 +153,7 @@ impl ReaderThreads { }, // We handle the casting loss. - #[allow( + #[expect( clippy::cast_precision_loss, clippy::cast_possible_truncation, clippy::cast_sign_loss diff --git a/storage/service/src/service.rs b/storage/service/src/service.rs new file mode 100644 index 0000000..cd4957f --- /dev/null +++ b/storage/service/src/service.rs @@ -0,0 +1,5 @@ +mod read; +mod write; + +pub use read::DatabaseReadService; +pub use write::DatabaseWriteHandle; diff --git a/storage/service/src/service/read.rs b/storage/service/src/service/read.rs new file mode 100644 index 0000000..187ffa4 --- /dev/null +++ b/storage/service/src/service/read.rs @@ -0,0 +1,95 @@ +use std::{ + sync::Arc, + task::{Context, Poll}, +}; + +use futures::channel::oneshot; +use rayon::ThreadPool; +use tower::Service; + +use cuprate_database::{ConcreteEnv, DbResult, RuntimeError}; +use cuprate_helper::asynch::InfallibleOneshotReceiver; + +/// The [`rayon::ThreadPool`] service. +/// +/// Uses an inner request handler and a rayon thread-pool to asynchronously handle requests. +/// +/// - `Req` is the request type +/// - `Res` is the response type +pub struct DatabaseReadService { + /// Handle to the custom `rayon` DB reader thread-pool. + /// + /// Requests are [`rayon::ThreadPool::spawn`]ed in this thread-pool, + /// and responses are returned via a channel we (the caller) provide. + pool: Arc, + + /// The function used to handle request. + inner_handler: Arc DbResult + Send + Sync + 'static>, +} + +// Deriving [`Clone`] means `Req` & `Res` need to be `Clone`, even if they aren't. +impl Clone for DatabaseReadService { + fn clone(&self) -> Self { + Self { + pool: Arc::clone(&self.pool), + inner_handler: Arc::clone(&self.inner_handler), + } + } +} + +impl DatabaseReadService +where + Req: Send + 'static, + Res: Send + 'static, +{ + /// Creates the [`DatabaseReadService`] with the provided backing thread-pool. + /// + /// Should be called _once_ per actual database, although nothing bad will happen, cloning the [`DatabaseReadService`] + /// is the correct way to get multiple handles to the database. + #[cold] + #[inline(never)] // Only called once. + pub fn new( + env: Arc, + pool: Arc, + req_handler: impl Fn(&ConcreteEnv, Req) -> DbResult + Send + Sync + 'static, + ) -> Self { + let inner_handler = Arc::new(move |req| req_handler(&env, req)); + + Self { + pool, + inner_handler, + } + } +} + +impl Service for DatabaseReadService +where + Req: Send + 'static, + Res: Send + 'static, +{ + type Response = Res; + type Error = RuntimeError; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Req) -> Self::Future { + // Response channel we `.await` on. + let (response_sender, receiver) = oneshot::channel(); + + let handler = Arc::clone(&self.inner_handler); + + // Spawn the request in the rayon DB thread-pool. + // + // Note that this uses `self.pool` instead of `rayon::spawn` + // such that any `rayon` parallel code that runs within + // the passed closure uses the same `rayon` threadpool. + self.pool.spawn(move || { + drop(response_sender.send(handler(req))); + }); + + InfallibleOneshotReceiver::from(receiver) + } +} diff --git a/storage/service/src/service/write.rs b/storage/service/src/service/write.rs new file mode 100644 index 0000000..6bcd725 --- /dev/null +++ b/storage/service/src/service/write.rs @@ -0,0 +1,185 @@ +use std::{ + fmt::Debug, + sync::Arc, + task::{Context, Poll}, +}; + +use futures::channel::oneshot; + +use cuprate_database::{ConcreteEnv, DbResult, Env, RuntimeError}; +use cuprate_helper::asynch::InfallibleOneshotReceiver; + +//---------------------------------------------------------------------------------------------------- Constants +/// Name of the writer thread. +const WRITER_THREAD_NAME: &str = concat!(module_path!(), "::DatabaseWriter"); + +//---------------------------------------------------------------------------------------------------- DatabaseWriteHandle +/// Write handle to the database. +/// +/// This is handle that allows `async`hronously writing to the database. +/// +/// Calling [`tower::Service::call`] with a [`DatabaseWriteHandle`] +/// will return an `async`hronous channel that can be `.await`ed upon +/// to receive the corresponding response. +#[derive(Debug)] +pub struct DatabaseWriteHandle { + /// Sender channel to the database write thread-pool. + /// + /// We provide the response channel for the thread-pool. + pub(super) sender: crossbeam::channel::Sender<(Req, oneshot::Sender>)>, +} + +impl Clone for DatabaseWriteHandle { + fn clone(&self) -> Self { + Self { + sender: self.sender.clone(), + } + } +} + +impl DatabaseWriteHandle +where + Req: Send + 'static, + Res: Debug + Send + 'static, +{ + /// Initialize the single `DatabaseWriter` thread. + #[cold] + #[inline(never)] // Only called once. + pub fn init( + env: Arc, + inner_handler: impl Fn(&ConcreteEnv, &Req) -> DbResult + Send + 'static, + ) -> Self { + // Initialize `Request/Response` channels. + let (sender, receiver) = crossbeam::channel::unbounded(); + + // Spawn the writer. + std::thread::Builder::new() + .name(WRITER_THREAD_NAME.into()) + .spawn(move || database_writer(&env, &receiver, inner_handler)) + .unwrap(); + + Self { sender } + } +} + +impl tower::Service for DatabaseWriteHandle { + type Response = Res; + type Error = RuntimeError; + type Future = InfallibleOneshotReceiver>; + + #[inline] + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, request: Req) -> Self::Future { + // Response channel we `.await` on. + let (response_sender, receiver) = oneshot::channel(); + + // Send the write request. + self.sender.send((request, response_sender)).unwrap(); + + InfallibleOneshotReceiver::from(receiver) + } +} + +//---------------------------------------------------------------------------------------------------- database_writer +/// The main function of the writer thread. +fn database_writer( + env: &ConcreteEnv, + receiver: &crossbeam::channel::Receiver<(Req, oneshot::Sender>)>, + inner_handler: impl Fn(&ConcreteEnv, &Req) -> DbResult, +) where + Req: Send + 'static, + Res: Debug + Send + 'static, +{ + // 1. Hang on request channel + // 2. Map request to some database function + // 3. Execute that function, get the result + // 4. Return the result via channel + 'main: loop { + let Ok((request, response_sender)) = receiver.recv() else { + // If this receive errors, it means that the channel is empty + // and disconnected, meaning the other side (all senders) have + // been dropped. This means "shutdown", and we return here to + // exit the thread. + // + // Since the channel is empty, it means we've also processed + // all requests. Since it is disconnected, it means future + // ones cannot come in. + return; + }; + + /// How many times should we retry handling the request on resize errors? + /// + /// This is 1 on automatically resizing databases, meaning there is only 1 iteration. + const REQUEST_RETRY_LIMIT: usize = if ConcreteEnv::MANUAL_RESIZE { 3 } else { 1 }; + + // Map [`Request`]'s to specific database functions. + // + // Both will: + // 1. Map the request to a function + // 2. Call the function + // 3. (manual resize only) If resize is needed, resize and retry + // 4. (manual resize only) Redo step {1, 2} + // 5. Send the function's `Result` back to the requester + // + // FIXME: there's probably a more elegant way + // to represent this retry logic with recursive + // functions instead of a loop. + 'retry: for retry in 0..REQUEST_RETRY_LIMIT { + // FIXME: will there be more than 1 write request? + // this won't have to be an enum. + let response = inner_handler(env, &request); + + // If the database needs to resize, do so. + if ConcreteEnv::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded)) { + // If this is the last iteration of the outer `for` loop and we + // encounter a resize error _again_, it means something is wrong. + assert_ne!( + retry, REQUEST_RETRY_LIMIT, + "database resize failed maximum of {REQUEST_RETRY_LIMIT} times" + ); + + // Resize the map, and retry the request handling loop. + // + // FIXME: + // We could pass in custom resizes to account for + // batches, i.e., we're about to add ~5GB of data, + // add that much instead of the default 1GB. + // + let old = env.current_map_size(); + let new = env.resize_map(None); + + // TODO: use tracing. + println!("resizing database memory map, old: {old}B, new: {new}B"); + + // Try handling the request again. + continue 'retry; + } + + // Automatically resizing databases should not be returning a resize error. + #[cfg(debug_assertions)] + if !ConcreteEnv::MANUAL_RESIZE { + assert!( + !matches!(response, Err(RuntimeError::ResizeNeeded)), + "auto-resizing database returned a ResizeNeeded error" + ); + } + + // Send the response back, whether if it's an `Ok` or `Err`. + if let Err(e) = response_sender.send(response) { + // TODO: use tracing. + println!("database writer failed to send response: {e:?}"); + } + + continue 'main; + } + + // Above retry loop should either: + // - continue to the next ['main] loop or... + // - ...retry until panic + unreachable!(); + } +} diff --git a/storage/txpool/Cargo.toml b/storage/txpool/Cargo.toml index 536d445..0fb43b2 100644 --- a/storage/txpool/Cargo.toml +++ b/storage/txpool/Cargo.toml @@ -4,12 +4,43 @@ version = "0.0.0" edition = "2021" description = "Cuprate's transaction pool database" license = "MIT" -authors = ["hinto-janai"] -repository = "https://github.com/Cuprate/cuprate/tree/main/storage/cuprate-txpool" +authors = ["Boog900"] +repository = "https://github.com/Cuprate/cuprate/tree/main/storage/txpool" keywords = ["cuprate", "txpool", "transaction", "pool", "database"] [features] +default = ["heed"] +# default = ["redb", "service"] +# default = ["redb-memory", "service"] +heed = ["cuprate-database/heed"] +redb = ["cuprate-database/redb"] +redb-memory = ["cuprate-database/redb-memory"] +serde = ["dep:serde", "cuprate-database/serde", "cuprate-database-service/serde", "cuprate-helper/serde"] [dependencies] +cuprate-database = { workspace = true, features = ["heed"] } +cuprate-database-service = { workspace = true } +cuprate-types = { workspace = true } +cuprate-helper = { workspace = true, default-features = false, features = ["constants"] } + +monero-serai = { workspace = true, features = ["std"] } +bytemuck = { workspace = true, features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] } +bitflags = { workspace = true, features = ["std", "serde", "bytemuck"] } +thiserror = { workspace = true } +hex = { workspace = true, features = ["std"] } +blake3 = { workspace = true, features = ["std"] } + +tower = { workspace = true } +rayon = { workspace = true } + +serde = { workspace = true, optional = true } [dev-dependencies] +cuprate-test-utils = { workspace = true } + +tokio = { workspace = true } +tempfile = { workspace = true } +hex-literal = { workspace = true } + +[lints] +workspace = true diff --git a/storage/txpool/README.md b/storage/txpool/README.md new file mode 100644 index 0000000..ca4f737 --- /dev/null +++ b/storage/txpool/README.md @@ -0,0 +1,110 @@ +Cuprate's tx-pool database. + +This documentation is mostly for practical usage of `cuprate_txpool`. + +For a high-level overview, see the database section in +[Cuprate's architecture book](https://architecture.cuprate.org). + +If you're looking for a database crate, consider using the lower-level +[`cuprate-database`](https://doc.cuprate.org/cuprate_database) +crate that this crate is built on-top of. + +# Purpose + +This crate does 3 things: + +1. Uses [`cuprate_database`] as a base database layer +1. Implements various transaction pool related [operations](ops), [tables], and [types] +1. Exposes a [`tower::Service`] backed by a thread-pool + +Each layer builds on-top of the previous. + +As a user of `cuprate_txpool`, consider using the higher-level [`service`] module, +or at the very least the [`ops`] module instead of interacting with the `cuprate_database` traits directly. + +# `cuprate_database` + +Consider reading `cuprate_database`'s crate documentation before this crate, as it is the first layer. + +If/when this crate needs is used, be sure to use the version that this crate re-exports, e.g.: + +```rust +use cuprate_txpool::{ + cuprate_database::RuntimeError, +}; +``` + +This ensures the types/traits used from `cuprate_database` are the same ones used by `cuprate_txpool` internally. + +# Feature flags +Different database backends are enabled by the feature flags: + +- `heed` (LMDB) +- `redb` + +The default is `heed`. + +`tracing` is always enabled and cannot be disabled via feature-flag. + + +# Invariants when not using `service` + +See [`cuprate_blockchain`](https://doc.cuprate.org/cuprate_blockchain), the invariants are the same. + +# Examples + +The below is an example of using `cuprate_txpool`'s +lowest API, i.e. using a mix of this crate and `cuprate_database`'s traits directly - +**this is NOT recommended.** + +For examples of the higher-level APIs, see: + +- [`ops`] +- [`service`] + +```rust +use cuprate_txpool::{ + cuprate_database::{ + ConcreteEnv, + Env, EnvInner, + DatabaseRo, DatabaseRw, TxRo, TxRw, + }, + config::ConfigBuilder, + tables::{Tables, TablesMut, OpenTables}, +}; + +# fn main() -> Result<(), Box> { + // Create a configuration for the database environment. + let tmp_dir = tempfile::tempdir()?; + let db_dir = tmp_dir.path().to_owned(); + let config = ConfigBuilder::new() + .data_directory(db_dir.into()) + .build(); + + // Initialize the database environment. + let env = cuprate_txpool::open(config)?; + + // Open up a transaction + tables for writing. + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw()?; + let mut tables = env_inner.open_tables_mut(&tx_rw)?; + + // ⚠️ Write data to the tables directly. + // (not recommended, use `ops` or `service`). + const KEY_IMAGE: [u8; 32] = [88; 32]; + const TX_HASH: [u8; 32] = [88; 32]; + tables.spent_key_images_mut().put(&KEY_IMAGE, &TX_HASH)?; + + // Commit the data written. + drop(tables); + TxRw::commit(tx_rw)?; + + // Read the data, assert it is correct. + let tx_ro = env_inner.tx_ro()?; + let tables = env_inner.open_tables(&tx_ro)?; + let (key_image, tx_hash) = tables.spent_key_images().first()?; + assert_eq!(key_image, KEY_IMAGE); + assert_eq!(tx_hash, TX_HASH); + # Ok(()) +} +``` diff --git a/storage/txpool/src/config.rs b/storage/txpool/src/config.rs new file mode 100644 index 0000000..724ae21 --- /dev/null +++ b/storage/txpool/src/config.rs @@ -0,0 +1,240 @@ +//! The transaction pool [`Config`]. +use std::{borrow::Cow, path::PathBuf}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use cuprate_database::{ + config::{Config as DbConfig, SyncMode}, + resize::ResizeAlgorithm, +}; +use cuprate_database_service::ReaderThreads; +use cuprate_helper::{ + fs::{txpool_path, CUPRATE_DATA_DIR}, + network::Network, +}; + +/// The default transaction pool weight limit. +const DEFAULT_TXPOOL_WEIGHT_LIMIT: usize = 600 * 1024 * 1024; + +//---------------------------------------------------------------------------------------------------- ConfigBuilder +/// Builder for [`Config`]. +/// +// SOMEDAY: there's are many more options to add in the future. +#[derive(Debug, Clone, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ConfigBuilder { + network: Network, + + data_dir: Option, + + /// [`Config::cuprate_database_config`]. + db_config: cuprate_database::config::ConfigBuilder, + + /// [`Config::reader_threads`]. + reader_threads: Option, + + /// [`Config::max_txpool_weight`]. + max_txpool_weight: Option, +} + +impl ConfigBuilder { + /// Create a new [`ConfigBuilder`]. + /// + /// [`ConfigBuilder::build`] can be called immediately + /// after this function to use default values. + pub fn new() -> Self { + Self { + network: Network::default(), + data_dir: None, + db_config: cuprate_database::config::ConfigBuilder::new(Cow::Owned(txpool_path( + &CUPRATE_DATA_DIR, + Network::Mainnet, + ))), + reader_threads: None, + max_txpool_weight: None, + } + } + + /// Build into a [`Config`]. + /// + /// # Default values + /// If [`ConfigBuilder::data_directory`] was not called, + /// [`txpool_path`] with [`CUPRATE_DATA_DIR`] and [`Network::Mainnet`] will be used. + /// + /// For all other values, [`Default::default`] is used. + pub fn build(self) -> Config { + // INVARIANT: all PATH safety checks are done + // in `helper::fs`. No need to do them here. + let data_dir = self + .data_dir + .unwrap_or_else(|| CUPRATE_DATA_DIR.to_path_buf()); + + let reader_threads = self.reader_threads.unwrap_or_default(); + + let max_txpool_weight = self + .max_txpool_weight + .unwrap_or(DEFAULT_TXPOOL_WEIGHT_LIMIT); + + let db_config = self + .db_config + .db_directory(Cow::Owned(txpool_path(&data_dir, self.network))) + .reader_threads(reader_threads.as_threads()) + .build(); + + Config { + db_config, + reader_threads, + max_txpool_weight, + } + } + + /// Change the network this database is for. + #[must_use] + pub const fn network(mut self, network: Network) -> Self { + self.network = network; + self + } + + /// Sets a new maximum weight for the transaction pool. + #[must_use] + pub const fn max_txpool_weight(mut self, max_txpool_weight: usize) -> Self { + self.max_txpool_weight = Some(max_txpool_weight); + self + } + + /// Set a custom data directory [`PathBuf`]. + #[must_use] + pub fn data_directory(mut self, db_directory: PathBuf) -> Self { + self.data_dir = Some(db_directory); + self + } + + /// Calls [`cuprate_database::config::ConfigBuilder::sync_mode`]. + #[must_use] + pub fn sync_mode(mut self, sync_mode: SyncMode) -> Self { + self.db_config = self.db_config.sync_mode(sync_mode); + self + } + + /// Calls [`cuprate_database::config::ConfigBuilder::resize_algorithm`]. + #[must_use] + pub fn resize_algorithm(mut self, resize_algorithm: ResizeAlgorithm) -> Self { + self.db_config = self.db_config.resize_algorithm(resize_algorithm); + self + } + + /// Set a custom [`ReaderThreads`]. + #[must_use] + pub const fn reader_threads(mut self, reader_threads: ReaderThreads) -> Self { + self.reader_threads = Some(reader_threads); + self + } + + /// Tune the [`ConfigBuilder`] for the highest performing, + /// but also most resource-intensive & maybe risky settings. + /// + /// Good default for testing, and resource-available machines. + #[must_use] + pub fn fast(mut self) -> Self { + self.db_config = self.db_config.fast(); + + self.reader_threads = Some(ReaderThreads::OnePerThread); + self + } + + /// Tune the [`ConfigBuilder`] for the lowest performing, + /// but also least resource-intensive settings. + /// + /// Good default for resource-limited machines, e.g. a cheap VPS. + #[must_use] + pub fn low_power(mut self) -> Self { + self.db_config = self.db_config.low_power(); + + self.reader_threads = Some(ReaderThreads::One); + self + } +} + +impl Default for ConfigBuilder { + fn default() -> Self { + Self { + network: Network::default(), + data_dir: Some(CUPRATE_DATA_DIR.to_path_buf()), + db_config: cuprate_database::config::ConfigBuilder::new(Cow::Owned(txpool_path( + &CUPRATE_DATA_DIR, + Network::Mainnet, + ))), + reader_threads: Some(ReaderThreads::default()), + max_txpool_weight: Some(DEFAULT_TXPOOL_WEIGHT_LIMIT), + } + } +} + +//---------------------------------------------------------------------------------------------------- Config +/// `cuprate_txpool` configuration. +/// +/// This is a configuration built on-top of [`DbConfig`]. +/// +/// It contains configuration specific to this crate, plus the database config. +/// +/// For construction, either use [`ConfigBuilder`] or [`Config::default`]. +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Config { + /// The database configuration. + pub db_config: DbConfig, + + /// Database reader thread count. + pub reader_threads: ReaderThreads, + + /// The maximum weight of the transaction pool, after which we will start dropping transactions. + // TODO: enforce this max size. + pub max_txpool_weight: usize, +} + +impl Config { + /// Create a new [`Config`] with sane default settings. + /// + /// The [`DbConfig::db_directory`] + /// will be set to [`txpool_path`] with [`CUPRATE_DATA_DIR`] and [`Network::Mainnet`]. + /// + /// All other values will be [`Default::default`]. + /// + /// Same as [`Config::default`]. + /// + /// ```rust + /// use cuprate_database::{ + /// config::SyncMode, + /// resize::ResizeAlgorithm, + /// DATABASE_DATA_FILENAME, + /// }; + /// use cuprate_database_service::ReaderThreads; + /// use cuprate_helper::{fs::*, network::Network}; + /// + /// use cuprate_txpool::Config; + /// + /// let config = Config::new(); + /// + /// assert_eq!(config.db_config.db_directory(), txpool_path(&CUPRATE_DATA_DIR, Network::Mainnet).as_path()); + /// assert!(config.db_config.db_file().starts_with(&*CUPRATE_DATA_DIR)); + /// assert!(config.db_config.db_file().ends_with(DATABASE_DATA_FILENAME)); + /// assert_eq!(config.db_config.sync_mode, SyncMode::default()); + /// assert_eq!(config.db_config.resize_algorithm, ResizeAlgorithm::default()); + /// assert_eq!(config.reader_threads, ReaderThreads::default()); + /// ``` + pub fn new() -> Self { + ConfigBuilder::new().build() + } +} + +impl Default for Config { + /// Same as [`Config::new`]. + /// + /// ```rust + /// # use cuprate_txpool::Config; + /// assert_eq!(Config::default(), Config::new()); + /// ``` + fn default() -> Self { + Self::new() + } +} diff --git a/storage/txpool/src/free.rs b/storage/txpool/src/free.rs new file mode 100644 index 0000000..d0f9a31 --- /dev/null +++ b/storage/txpool/src/free.rs @@ -0,0 +1,72 @@ +//! General free functions (related to the tx-pool database). + +//---------------------------------------------------------------------------------------------------- Import +use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw}; + +use crate::{config::Config, tables::OpenTables, types::TransactionBlobHash}; + +//---------------------------------------------------------------------------------------------------- Free functions +/// Open the txpool database using the passed [`Config`]. +/// +/// This calls [`cuprate_database::Env::open`] and prepares the +/// database to be ready for txpool-related usage, e.g. +/// table creation, table sort order, etc. +/// +/// All tables found in [`crate::tables`] will be +/// ready for usage in the returned [`ConcreteEnv`]. +/// +/// # Errors +/// This will error if: +/// - The database file could not be opened +/// - A write transaction could not be opened +/// - A table could not be created/opened +#[cold] +#[inline(never)] // only called once +pub fn open(config: Config) -> Result { + // Attempt to open the database environment. + let env = ::open(config.db_config)?; + + /// Convert runtime errors to init errors. + /// + /// INVARIANT: + /// [`cuprate_database`]'s functions mostly return the former + /// so we must convert them. We have knowledge of which errors + /// makes sense in this functions context so we panic on + /// unexpected ones. + fn runtime_to_init_error(runtime: RuntimeError) -> InitError { + match runtime { + RuntimeError::Io(io_error) => io_error.into(), + + // These errors shouldn't be happening here. + RuntimeError::KeyExists + | RuntimeError::KeyNotFound + | RuntimeError::ResizeNeeded + | RuntimeError::TableNotFound => unreachable!(), + } + } + + // INVARIANT: We must ensure that all tables are created, + // `cuprate_database` has no way of knowing _which_ tables + // we want since it is agnostic, so we are responsible for this. + { + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw().map_err(runtime_to_init_error)?; + + // Create all tables. + OpenTables::create_tables(&env_inner, &tx_rw).map_err(runtime_to_init_error)?; + + TxRw::commit(tx_rw).map_err(runtime_to_init_error)?; + } + + Ok(env) +} + +/// Calculate the transaction blob hash. +/// +/// This value is supposed to be quick to compute just based of the tx-blob without needing to parse the tx. +/// +/// The exact way the hash is calculated is not stable and is subject to change, as such it should not be exposed +/// as a way to interact with Cuprate externally. +pub fn transaction_blob_hash(tx_blob: &[u8]) -> TransactionBlobHash { + blake3::hash(tx_blob).into() +} diff --git a/storage/txpool/src/lib.rs b/storage/txpool/src/lib.rs index 8b13789..53e53ec 100644 --- a/storage/txpool/src/lib.rs +++ b/storage/txpool/src/lib.rs @@ -1 +1,31 @@ +#![doc = include_str!("../README.md")] +#![allow( + // See `cuprate-database` for reasoning. + clippy::significant_drop_tightening +)] +// Used in docs: . +use tower as _; + +pub mod config; +mod free; +pub mod ops; +pub mod service; +pub mod tables; +mod tx; +pub mod types; + +pub use config::Config; +pub use free::{open, transaction_blob_hash}; +pub use tx::TxEntry; + +//re-exports +pub use cuprate_database; + +#[cfg(test)] +mod test { + use cuprate_test_utils as _; + use hex_literal as _; + use tempfile as _; + use tokio as _; +} diff --git a/storage/txpool/src/ops.rs b/storage/txpool/src/ops.rs new file mode 100644 index 0000000..badc4f6 --- /dev/null +++ b/storage/txpool/src/ops.rs @@ -0,0 +1,102 @@ +//! Abstracted Monero tx-pool database operations. +//! +//! This module contains many free functions that use the +//! traits in [`cuprate_database`] to generically call Monero-related +//! tx-pool database operations. +//! +//! # `impl Table` +//! Functions in this module take [`Tables`](crate::tables::Tables) and +//! [`TablesMut`](crate::tables::TablesMut) directly - these are +//! _already opened_ database tables. +//! +//! As such, the responsibility of +//! transactions, tables, etc, are on the caller. +//! +//! Notably, this means that these functions are as lean +//! as possible, so calling them in a loop should be okay. +//! +//! # Atomicity +//! As transactions are handled by the _caller_ of these functions, +//! it is up to the caller to decide what happens if one them return +//! an error. +//! +//! To maintain atomicity, transactions should be [`abort`](cuprate_database::TxRw::abort)ed +//! if one of the functions failed. +//! +//! For example, if [`add_transaction`] is called and returns an [`Err`], +//! `abort`ing the transaction that opened the input `TableMut` would reverse all tables +//! mutated by [`add_transaction`] up until the error, leaving it in the state it was in before +//! [`add_transaction`] was called. +//! +//! # Example +//! Simple usage of `ops`. +//! +//! ```rust +//! use hex_literal::hex; +//! +//! use cuprate_test_utils::data::TX_V1_SIG2; +//! use cuprate_txpool::{ +//! cuprate_database::{ +//! ConcreteEnv, +//! Env, EnvInner, +//! DatabaseRo, DatabaseRw, TxRo, TxRw, +//! }, +//! config::ConfigBuilder, +//! tables::{Tables, TablesMut, OpenTables}, +//! ops::{add_transaction, get_transaction_verification_data}, +//! }; +//! +//! # fn main() -> Result<(), Box> { +//! // Create a configuration for the database environment. +//! let tmp_dir = tempfile::tempdir()?; +//! let db_dir = tmp_dir.path().to_owned(); +//! let config = ConfigBuilder::new() +//! .data_directory(db_dir.into()) +//! .build(); +//! +//! // Initialize the database environment. +//! let env = cuprate_txpool::open(config)?; +//! +//! // Open up a transaction + tables for writing. +//! let env_inner = env.env_inner(); +//! let tx_rw = env_inner.tx_rw()?; +//! let mut tables = env_inner.open_tables_mut(&tx_rw)?; +//! +//! // Write a tx to the database. +//! let mut tx = TX_V1_SIG2.clone(); +//! let tx_hash = tx.tx_hash; +//! add_transaction(&tx.try_into().unwrap(), true, &mut tables)?; +//! +//! // Commit the data written. +//! drop(tables); +//! TxRw::commit(tx_rw)?; +//! +//! // Read the data, assert it is correct. +//! let tx_rw = env_inner.tx_rw()?; +//! let mut tables = env_inner.open_tables_mut(&tx_rw)?; +//! let tx = get_transaction_verification_data(&tx_hash, &mut tables)?; +//! +//! assert_eq!(tx.tx_hash, tx_hash); +//! assert_eq!(tx.tx, TX_V1_SIG2.tx); +//! # Ok(()) } +//! ``` + +mod key_images; +mod tx_read; +mod tx_write; + +pub use tx_read::{get_transaction_verification_data, in_stem_pool}; +pub use tx_write::{add_transaction, remove_transaction}; + +/// An error that can occur on some tx-write ops. +#[derive(thiserror::Error, Debug)] +pub enum TxPoolWriteError { + /// The transaction could not be added as it double spends another tx in the pool. + /// + /// The inner value is the hash of the transaction that was double spent. + #[error("Transaction doubles spent transaction already in the pool ({}).", hex::encode(.0))] + DoubleSpend(crate::types::TransactionHash), + /// A database error. + #[error("Database error: {0}")] + Database(#[from] cuprate_database::RuntimeError), +} diff --git a/storage/txpool/src/ops/key_images.rs b/storage/txpool/src/ops/key_images.rs new file mode 100644 index 0000000..76cae14 --- /dev/null +++ b/storage/txpool/src/ops/key_images.rs @@ -0,0 +1,54 @@ +//! Tx-pool key image ops. +use monero_serai::transaction::Input; + +use cuprate_database::{DatabaseRw, DbResult}; + +use crate::{ops::TxPoolWriteError, tables::SpentKeyImages, types::TransactionHash}; + +/// Adds the transaction key images to the [`SpentKeyImages`] table. +/// +/// This function will return an error if any of the key images are already spent. +/// +/// # Panics +/// This function will panic if any of the [`Input`]s are not [`Input::ToKey`] +pub(super) fn add_tx_key_images( + inputs: &[Input], + tx_hash: &TransactionHash, + kis_table: &mut impl DatabaseRw, +) -> Result<(), TxPoolWriteError> { + for ki in inputs.iter().map(ki_from_input) { + if let Ok(double_spend_tx_hash) = kis_table.get(&ki) { + return Err(TxPoolWriteError::DoubleSpend(double_spend_tx_hash)); + } + + kis_table.put(&ki, tx_hash)?; + } + + Ok(()) +} + +/// Removes key images from the [`SpentKeyImages`] table. +/// +/// # Panics +/// This function will panic if any of the [`Input`]s are not [`Input::ToKey`] +pub(super) fn remove_tx_key_images( + inputs: &[Input], + kis_table: &mut impl DatabaseRw, +) -> DbResult<()> { + for ki in inputs.iter().map(ki_from_input) { + kis_table.delete(&ki)?; + } + + Ok(()) +} + +/// Maps an input to a key image. +/// +/// # Panics +/// This function will panic if the [`Input`] is not [`Input::ToKey`] +fn ki_from_input(input: &Input) -> [u8; 32] { + match input { + Input::ToKey { key_image, .. } => key_image.compress().0, + Input::Gen(_) => panic!("miner tx cannot be added to the txpool"), + } +} diff --git a/storage/txpool/src/ops/tx_read.rs b/storage/txpool/src/ops/tx_read.rs new file mode 100644 index 0000000..24101f7 --- /dev/null +++ b/storage/txpool/src/ops/tx_read.rs @@ -0,0 +1,53 @@ +//! Transaction read ops. +//! +//! This module handles reading full transaction data, like getting a transaction from the pool. +use std::sync::Mutex; + +use monero_serai::transaction::Transaction; + +use cuprate_database::{DatabaseRo, DbResult}; +use cuprate_types::{TransactionVerificationData, TxVersion}; + +use crate::{ + tables::{Tables, TransactionInfos}, + types::{TransactionHash, TxStateFlags}, +}; + +/// Gets the [`TransactionVerificationData`] of a transaction in the tx-pool, leaving the tx in the pool. +pub fn get_transaction_verification_data( + tx_hash: &TransactionHash, + tables: &impl Tables, +) -> DbResult { + let tx_blob = tables.transaction_blobs().get(tx_hash)?.0; + + let tx_info = tables.transaction_infos().get(tx_hash)?; + + let cached_verification_state = tables.cached_verification_state().get(tx_hash)?.into(); + + let tx = + Transaction::read(&mut tx_blob.as_slice()).expect("Tx in the tx-pool must be parseable"); + + Ok(TransactionVerificationData { + version: TxVersion::from_raw(tx.version()).expect("Tx in tx-pool has invalid version"), + tx, + tx_blob, + tx_weight: tx_info.weight, + fee: tx_info.fee, + tx_hash: *tx_hash, + cached_verification_state: Mutex::new(cached_verification_state), + }) +} + +/// Returns `true` if the transaction with the given hash is in the stem pool. +/// +/// # Errors +/// This will return an [`Err`] if the transaction is not in the pool. +pub fn in_stem_pool( + tx_hash: &TransactionHash, + tx_infos: &impl DatabaseRo, +) -> DbResult { + Ok(tx_infos + .get(tx_hash)? + .flags + .contains(TxStateFlags::STATE_STEM)) +} diff --git a/storage/txpool/src/ops/tx_write.rs b/storage/txpool/src/ops/tx_write.rs new file mode 100644 index 0000000..8f426fb --- /dev/null +++ b/storage/txpool/src/ops/tx_write.rs @@ -0,0 +1,91 @@ +//! Transaction writing ops. +//! +//! This module handles writing full transaction data, like removing or adding a transaction. +use bytemuck::TransparentWrapper; +use monero_serai::transaction::{NotPruned, Transaction}; + +use cuprate_database::{DatabaseRw, DbResult, StorableVec}; +use cuprate_types::TransactionVerificationData; + +use crate::{ + free::transaction_blob_hash, + ops::{ + key_images::{add_tx_key_images, remove_tx_key_images}, + TxPoolWriteError, + }, + tables::TablesMut, + types::{TransactionHash, TransactionInfo, TxStateFlags}, +}; + +/// Adds a transaction to the tx-pool. +/// +/// This function fills in all tables necessary to add the transaction to the pool. +/// +/// # Panics +/// This function will panic if the transactions inputs are not all of type [`Input::ToKey`](monero_serai::transaction::Input::ToKey). +pub fn add_transaction( + tx: &TransactionVerificationData, + state_stem: bool, + tables: &mut impl TablesMut, +) -> Result<(), TxPoolWriteError> { + // Add the tx blob to table 0. + tables + .transaction_blobs_mut() + .put(&tx.tx_hash, StorableVec::wrap_ref(&tx.tx_blob))?; + + let mut flags = TxStateFlags::empty(); + flags.set(TxStateFlags::STATE_STEM, state_stem); + + // Add the tx info to table 1. + tables.transaction_infos_mut().put( + &tx.tx_hash, + &TransactionInfo { + fee: tx.fee, + weight: tx.tx_weight, + flags, + _padding: [0; 7], + }, + )?; + + // Add the cached verification state to table 2. + let cached_verification_state = (*tx.cached_verification_state.lock().unwrap()).into(); + tables + .cached_verification_state_mut() + .put(&tx.tx_hash, &cached_verification_state)?; + + // Add the tx key images to table 3. + let kis_table = tables.spent_key_images_mut(); + add_tx_key_images(&tx.tx.prefix().inputs, &tx.tx_hash, kis_table)?; + + // Add the blob hash to table 4. + let blob_hash = transaction_blob_hash(&tx.tx_blob); + tables + .known_blob_hashes_mut() + .put(&blob_hash, &tx.tx_hash)?; + + Ok(()) +} + +/// Removes a transaction from the transaction pool. +pub fn remove_transaction(tx_hash: &TransactionHash, tables: &mut impl TablesMut) -> DbResult<()> { + // Remove the tx blob from table 0. + let tx_blob = tables.transaction_blobs_mut().take(tx_hash)?.0; + + // Remove the tx info from table 1. + tables.transaction_infos_mut().delete(tx_hash)?; + + // Remove the cached verification state from table 2. + tables.cached_verification_state_mut().delete(tx_hash)?; + + // Remove the tx key images from table 3. + let tx = Transaction::::read(&mut tx_blob.as_slice()) + .expect("Tx in the tx-pool must be parseable"); + let kis_table = tables.spent_key_images_mut(); + remove_tx_key_images(&tx.prefix().inputs, kis_table)?; + + // Remove the blob hash from table 4. + let blob_hash = transaction_blob_hash(&tx_blob); + tables.known_blob_hashes_mut().delete(&blob_hash)?; + + Ok(()) +} diff --git a/storage/txpool/src/service.rs b/storage/txpool/src/service.rs new file mode 100644 index 0000000..03ce2f0 --- /dev/null +++ b/storage/txpool/src/service.rs @@ -0,0 +1,132 @@ +//! [`tower::Service`] integeration + thread-pool. +//! +//! ## `service` +//! The `service` module implements the [`tower`] integration, +//! along with the reader/writer thread-pool system. +//! +//! The thread-pool allows outside crates to communicate with it by +//! sending database [`Request`][req_r]s and receiving [`Response`][resp]s `async`hronously - +//! without having to actually worry and handle the database themselves. +//! +//! The system is managed by this crate, and only requires [`init`] by the user. +//! +//! ## Handles +//! The 2 handles to the database are: +//! - [`TxpoolReadHandle`] +//! - [`TxpoolWriteHandle`] +//! +//! The 1st allows any caller to send [`ReadRequest`][req_r]s. +//! +//! The 2nd allows any caller to send [`WriteRequest`][req_w]s. +//! +//! Both the handles are cheaply [`Clone`]able. +//! +//! ## Initialization +//! The database & thread-pool system can be initialized with [`init()`]. +//! +//! This causes the underlying database/threads to be setup +//! and returns a read/write handle to that database. +//! +//! ## Shutdown +//! Upon the above handles being dropped, the corresponding thread(s) will automatically exit, i.e: +//! - The last [`TxpoolReadHandle`] is dropped => reader thread-pool exits +//! - The last [`TxpoolWriteHandle`] is dropped => writer thread exits +//! +//! Upon dropping the [`cuprate_database::Env`]: +//! - All un-processed database transactions are completed +//! - All data gets flushed to disk (caused by [`Drop::drop`] impl on `Env`) +//! +//! ## Request and Response +//! To interact with the database (whether reading or writing data), +//! a `Request` can be sent using one of the above handles. +//! +//! Both the handles implement [`tower::Service`], so they can be [`tower::Service::call`]ed. +//! +//! An `async`hronous channel will be returned from the call. +//! This channel can be `.await`ed upon to (eventually) receive +//! the corresponding `Response` to your `Request`. +//! +//! [req_r]: interface::TxpoolReadRequest +//! +//! [req_w]: interface::TxpoolWriteRequest +//! +//! // TODO: we have 2 responses +//! +//! [resp]: interface::TxpoolWriteResponse +//! +//! # Example +//! Simple usage of `service`. +//! +//! ```rust +//! use std::sync::Arc; +//! +//! use hex_literal::hex; +//! use tower::{Service, ServiceExt}; +//! +//! use cuprate_test_utils::data::TX_V1_SIG2; +//! +//! use cuprate_txpool::{ +//! cuprate_database::Env, +//! config::ConfigBuilder, +//! service::interface::{ +//! TxpoolWriteRequest, +//! TxpoolWriteResponse, +//! TxpoolReadRequest, +//! TxpoolReadResponse +//! } +//! }; +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! // Create a configuration for the database environment. +//! use cuprate_test_utils::data::TX_V1_SIG2; +//! let tmp_dir = tempfile::tempdir()?; +//! let db_dir = tmp_dir.path().to_owned(); +//! let config = ConfigBuilder::new() +//! .data_directory(db_dir.into()) +//! .build(); +//! +//! // Initialize the database thread-pool. +//! let (mut read_handle, mut write_handle, _) = cuprate_txpool::service::init(config)?; +//! +//! // Prepare a request to write block. +//! let tx = TX_V1_SIG2.clone(); +//! let request = TxpoolWriteRequest::AddTransaction { +//! tx: Arc::new(tx.try_into().unwrap()), +//! state_stem: false, +//! }; +//! +//! // Send the request. +//! // We receive back an `async` channel that will +//! // eventually yield the result when `service` +//! // is done writing the tx. +//! let response_channel = write_handle.ready().await?.call(request); +//! +//! // Block write was OK. +//! let TxpoolWriteResponse::AddTransaction(double_spent) = response_channel.await? else { +//! panic!("tx-pool returned wrong response!"); +//! }; +//! assert!(double_spent.is_none()); +//! +//! // Now, let's try getting the block hash +//! // of the block we just wrote. +//! let request = TxpoolReadRequest::TxBlob(TX_V1_SIG2.tx_hash); +//! let response_channel = read_handle.ready().await?.call(request); +//! let response = response_channel.await?; +//! +//! // This causes the writer thread on the +//! // other side of this handle to exit... +//! drop(write_handle); +//! // ...and this causes the reader thread-pool to exit. +//! drop(read_handle); +//! # Ok(()) } +//! ``` + +mod free; +pub mod interface; +mod read; +mod types; +mod write; + +pub use free::init; +pub use types::{TxpoolReadHandle, TxpoolWriteHandle}; diff --git a/storage/txpool/src/service/free.rs b/storage/txpool/src/service/free.rs new file mode 100644 index 0000000..003da55 --- /dev/null +++ b/storage/txpool/src/service/free.rs @@ -0,0 +1,37 @@ +use std::sync::Arc; + +use cuprate_database::{ConcreteEnv, InitError}; + +use crate::{ + service::{ + read::init_read_service, + types::{TxpoolReadHandle, TxpoolWriteHandle}, + write::init_write_service, + }, + Config, +}; + +//---------------------------------------------------------------------------------------------------- Init +#[cold] +#[inline(never)] // Only called once (?) +/// Initialize a database & thread-pool, and return a read/write handle to it. +/// +/// Once the returned handles are [`Drop::drop`]ed, the reader +/// thread-pool and writer thread will exit automatically. +/// +/// # Errors +/// This will forward the error if [`crate::open`] failed. +pub fn init( + config: Config, +) -> Result<(TxpoolReadHandle, TxpoolWriteHandle, Arc), InitError> { + let reader_threads = config.reader_threads; + + // Initialize the database itself. + let db = Arc::new(crate::open(config)?); + + // Spawn the Reader thread pool and Writer. + let readers = init_read_service(Arc::clone(&db), reader_threads); + let writer = init_write_service(Arc::clone(&db)); + + Ok((readers, writer, db)) +} diff --git a/storage/txpool/src/service/interface.rs b/storage/txpool/src/service/interface.rs new file mode 100644 index 0000000..a27c630 --- /dev/null +++ b/storage/txpool/src/service/interface.rs @@ -0,0 +1,128 @@ +//! Tx-pool [`service`](super) interface. +//! +//! This module contains `cuprate_txpool`'s [`tower::Service`] request and response enums. +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use cuprate_types::TransactionVerificationData; + +use crate::{ + tx::TxEntry, + types::{KeyImage, TransactionBlobHash, TransactionHash}, +}; + +//---------------------------------------------------------------------------------------------------- TxpoolReadRequest +/// The transaction pool [`tower::Service`] read request type. +#[derive(Clone)] +pub enum TxpoolReadRequest { + /// Get the blob (raw bytes) of a transaction with the given hash. + TxBlob(TransactionHash), + + /// Get the [`TransactionVerificationData`] of a transaction in the tx pool. + TxVerificationData(TransactionHash), + + /// Filter (remove) all **known** transactions from the set. + /// + /// The hash is **not** the transaction hash, it is the hash of the serialized tx-blob. + FilterKnownTxBlobHashes(HashSet), + + /// Get some transactions for an incoming block. + TxsForBlock(Vec), + + /// Get information on all transactions in the pool. + Backlog, + + /// Get the number of transactions in the pool. + Size { + /// If this is [`true`], the size returned will + /// include private transactions in the pool. + include_sensitive_txs: bool, + }, +} + +//---------------------------------------------------------------------------------------------------- TxpoolReadResponse +/// The transaction pool [`tower::Service`] read response type. +#[expect(clippy::large_enum_variant)] +pub enum TxpoolReadResponse { + /// The response for [`TxpoolReadRequest::TxBlob`]. + TxBlob { tx_blob: Vec, state_stem: bool }, + + /// The response for [`TxpoolReadRequest::TxVerificationData`]. + TxVerificationData(TransactionVerificationData), + + /// The response for [`TxpoolReadRequest::FilterKnownTxBlobHashes`]. + FilterKnownTxBlobHashes { + /// The blob hashes that are unknown. + unknown_blob_hashes: HashSet, + /// The tx hashes of the blob hashes that were known but were in the stem pool. + stem_pool_hashes: Vec, + }, + + /// The response for [`TxpoolReadRequest::TxsForBlock`]. + TxsForBlock { + /// The txs we had in the txpool. + txs: HashMap<[u8; 32], TransactionVerificationData>, + /// The indexes of the missing txs. + missing: Vec, + }, + + /// Response to [`TxpoolReadRequest::Backlog`]. + /// + /// The inner [`Vec`] contains information on all + /// the transactions currently in the pool. + Backlog(Vec), + + /// Response to [`TxpoolReadRequest::Size`]. + /// + /// The inner value is the amount of + /// transactions currently in the pool. + Size(usize), +} + +//---------------------------------------------------------------------------------------------------- TxpoolWriteRequest +/// The transaction pool [`tower::Service`] write request type. +#[derive(Clone)] +pub enum TxpoolWriteRequest { + /// Add a transaction to the pool. + /// + /// Returns [`TxpoolWriteResponse::AddTransaction`]. + AddTransaction { + /// The tx to add. + tx: Arc, + /// A [`bool`] denoting the routing state of this tx. + /// + /// [`true`] if this tx is in the stem state. + state_stem: bool, + }, + + /// Remove a transaction with the given hash from the pool. + RemoveTransaction(TransactionHash), + + /// Promote a transaction from the stem pool to the fluff pool. + /// If the tx is already in the fluff pool this does nothing. + Promote(TransactionHash), + + /// Tell the tx-pool about a new block. + NewBlock { + /// The spent key images in the new block. + spent_key_images: Vec, + }, +} + +//---------------------------------------------------------------------------------------------------- TxpoolWriteResponse +/// The transaction pool [`tower::Service`] write response type. +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +pub enum TxpoolWriteResponse { + /// Response to: + /// - [`TxpoolWriteRequest::RemoveTransaction`] + /// - [`TxpoolWriteRequest::Promote`] + /// - [`TxpoolWriteRequest::NewBlock`] + Ok, + + /// Response to [`TxpoolWriteRequest::AddTransaction`]. + /// + /// If the inner value is [`Some`] the tx was not added to the pool as it double spends a tx with the given hash. + AddTransaction(Option), +} diff --git a/storage/txpool/src/service/read.rs b/storage/txpool/src/service/read.rs new file mode 100644 index 0000000..44a29b3 --- /dev/null +++ b/storage/txpool/src/service/read.rs @@ -0,0 +1,208 @@ +#![expect( + unreachable_code, + unused_variables, + clippy::unnecessary_wraps, + reason = "TODO: finish implementing the signatures from " +)] +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use rayon::ThreadPool; + +use cuprate_database::{ConcreteEnv, DatabaseRo, DbResult, Env, EnvInner, RuntimeError}; +use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads}; + +use crate::{ + ops::{get_transaction_verification_data, in_stem_pool}, + service::{ + interface::{TxpoolReadRequest, TxpoolReadResponse}, + types::{ReadResponseResult, TxpoolReadHandle}, + }, + tables::{KnownBlobHashes, OpenTables, TransactionBlobs, TransactionInfos}, + types::{TransactionBlobHash, TransactionHash}, +}; + +// TODO: update the docs here +//---------------------------------------------------------------------------------------------------- init_read_service +/// Initialize the [`TxpoolReadHandle`] thread-pool backed by `rayon`. +/// +/// This spawns `threads` amount of reader threads +/// attached to `env` and returns a handle to the pool. +/// +/// Should be called _once_ per actual database. +#[cold] +#[inline(never)] // Only called once. +pub(super) fn init_read_service(env: Arc, threads: ReaderThreads) -> TxpoolReadHandle { + init_read_service_with_pool(env, init_thread_pool(threads)) +} + +/// Initialize the [`TxpoolReadHandle`], with a specific rayon thread-pool instead of +/// creating a new one. +/// +/// Should be called _once_ per actual database. +#[cold] +#[inline(never)] // Only called once. +fn init_read_service_with_pool(env: Arc, pool: Arc) -> TxpoolReadHandle { + DatabaseReadService::new(env, pool, map_request) +} + +//---------------------------------------------------------------------------------------------------- Request Mapping +// This function maps [`Request`]s to function calls +// executed by the rayon DB reader threadpool. + +/// Map [`TxpoolReadRequest`]'s to specific database handler functions. +/// +/// This is the main entrance into all `Request` handler functions. +/// The basic structure is: +/// 1. `Request` is mapped to a handler function +/// 2. Handler function is called +/// 3. [`TxpoolReadResponse`] is returned +fn map_request( + env: &ConcreteEnv, // Access to the database + request: TxpoolReadRequest, // The request we must fulfill +) -> ReadResponseResult { + match request { + TxpoolReadRequest::TxBlob(tx_hash) => tx_blob(env, &tx_hash), + TxpoolReadRequest::TxVerificationData(tx_hash) => tx_verification_data(env, &tx_hash), + TxpoolReadRequest::FilterKnownTxBlobHashes(blob_hashes) => { + filter_known_tx_blob_hashes(env, blob_hashes) + } + TxpoolReadRequest::TxsForBlock(txs_needed) => txs_for_block(env, txs_needed), + TxpoolReadRequest::Backlog => backlog(env), + TxpoolReadRequest::Size { + include_sensitive_txs, + } => size(env, include_sensitive_txs), + } +} + +//---------------------------------------------------------------------------------------------------- Handler functions +// These are the actual functions that do stuff according to the incoming [`TxpoolReadRequest`]. +// +// Each function name is a 1-1 mapping (from CamelCase -> snake_case) to +// the enum variant name, e.g: `TxBlob` -> `tx_blob`. +// +// Each function will return the [`TxpoolReadResponse`] that we +// should send back to the caller in [`map_request()`]. +// +// INVARIANT: +// These functions are called above in `tower::Service::call()` +// using a custom threadpool which means any call to `par_*()` functions +// will be using the custom rayon DB reader thread-pool, not the global one. +// +// All functions below assume that this is the case, such that +// `par_*()` functions will not block the _global_ rayon thread-pool. + +/// [`TxpoolReadRequest::TxBlob`]. +#[inline] +fn tx_blob(env: &ConcreteEnv, tx_hash: &TransactionHash) -> ReadResponseResult { + let inner_env = env.env_inner(); + let tx_ro = inner_env.tx_ro()?; + + let tx_blobs_table = inner_env.open_db_ro::(&tx_ro)?; + let tx_infos_table = inner_env.open_db_ro::(&tx_ro)?; + + let tx_blob = tx_blobs_table.get(tx_hash)?.0; + + Ok(TxpoolReadResponse::TxBlob { + tx_blob, + state_stem: in_stem_pool(tx_hash, &tx_infos_table)?, + }) +} + +/// [`TxpoolReadRequest::TxVerificationData`]. +#[inline] +fn tx_verification_data(env: &ConcreteEnv, tx_hash: &TransactionHash) -> ReadResponseResult { + let inner_env = env.env_inner(); + let tx_ro = inner_env.tx_ro()?; + + let tables = inner_env.open_tables(&tx_ro)?; + + get_transaction_verification_data(tx_hash, &tables).map(TxpoolReadResponse::TxVerificationData) +} + +/// [`TxpoolReadRequest::FilterKnownTxBlobHashes`]. +fn filter_known_tx_blob_hashes( + env: &ConcreteEnv, + mut blob_hashes: HashSet, +) -> ReadResponseResult { + let inner_env = env.env_inner(); + let tx_ro = inner_env.tx_ro()?; + + let tx_blob_hashes = inner_env.open_db_ro::(&tx_ro)?; + let tx_infos = inner_env.open_db_ro::(&tx_ro)?; + + let mut stem_pool_hashes = Vec::new(); + + // A closure that returns `true` if a tx with a certain blob hash is unknown. + // This also fills in `stem_tx_hashes`. + let mut tx_unknown = |blob_hash| -> DbResult { + match tx_blob_hashes.get(&blob_hash) { + Ok(tx_hash) => { + if in_stem_pool(&tx_hash, &tx_infos)? { + stem_pool_hashes.push(tx_hash); + } + Ok(false) + } + Err(RuntimeError::KeyNotFound) => Ok(true), + Err(e) => Err(e), + } + }; + + let mut err = None; + blob_hashes.retain(|blob_hash| match tx_unknown(*blob_hash) { + Ok(res) => res, + Err(e) => { + err = Some(e); + false + } + }); + + if let Some(e) = err { + return Err(e); + } + + Ok(TxpoolReadResponse::FilterKnownTxBlobHashes { + unknown_blob_hashes: blob_hashes, + stem_pool_hashes, + }) +} + +/// [`TxpoolReadRequest::TxsForBlock`]. +fn txs_for_block(env: &ConcreteEnv, txs: Vec) -> ReadResponseResult { + let inner_env = env.env_inner(); + let tx_ro = inner_env.tx_ro()?; + + let tables = inner_env.open_tables(&tx_ro)?; + + let mut missing_tx_indexes = Vec::with_capacity(txs.len()); + let mut txs_verification_data = HashMap::with_capacity(txs.len()); + + for (i, tx_hash) in txs.into_iter().enumerate() { + match get_transaction_verification_data(&tx_hash, &tables) { + Ok(tx) => { + txs_verification_data.insert(tx_hash, tx); + } + Err(RuntimeError::KeyNotFound) => missing_tx_indexes.push(i), + Err(e) => return Err(e), + } + } + + Ok(TxpoolReadResponse::TxsForBlock { + txs: txs_verification_data, + missing: missing_tx_indexes, + }) +} + +/// [`TxpoolReadRequest::Backlog`]. +#[inline] +fn backlog(env: &ConcreteEnv) -> ReadResponseResult { + Ok(TxpoolReadResponse::Backlog(todo!())) +} + +/// [`TxpoolReadRequest::Size`]. +#[inline] +fn size(env: &ConcreteEnv, include_sensitive_txs: bool) -> ReadResponseResult { + Ok(TxpoolReadResponse::Size(todo!())) +} diff --git a/storage/txpool/src/service/types.rs b/storage/txpool/src/service/types.rs new file mode 100644 index 0000000..af1ca98 --- /dev/null +++ b/storage/txpool/src/service/types.rs @@ -0,0 +1,21 @@ +//! Database service type aliases. +//! +//! Only used internally for our [`tower::Service`] impls. + +use cuprate_database::DbResult; +use cuprate_database_service::{DatabaseReadService, DatabaseWriteHandle}; + +use crate::service::interface::{ + TxpoolReadRequest, TxpoolReadResponse, TxpoolWriteRequest, TxpoolWriteResponse, +}; + +/// The actual type of the response. +/// +/// Either our [`TxpoolReadResponse`], or a database error occurred. +pub(super) type ReadResponseResult = DbResult; + +/// The transaction pool database write service. +pub type TxpoolWriteHandle = DatabaseWriteHandle; + +/// The transaction pool database read service. +pub type TxpoolReadHandle = DatabaseReadService; diff --git a/storage/txpool/src/service/write.rs b/storage/txpool/src/service/write.rs new file mode 100644 index 0000000..23c5a8a --- /dev/null +++ b/storage/txpool/src/service/write.rs @@ -0,0 +1,166 @@ +use std::sync::Arc; + +use cuprate_database::{ + ConcreteEnv, DatabaseRo, DatabaseRw, DbResult, Env, EnvInner, RuntimeError, TxRw, +}; +use cuprate_database_service::DatabaseWriteHandle; +use cuprate_types::TransactionVerificationData; + +use crate::{ + ops::{self, TxPoolWriteError}, + service::{ + interface::{TxpoolWriteRequest, TxpoolWriteResponse}, + types::TxpoolWriteHandle, + }, + tables::{OpenTables, Tables, TransactionInfos}, + types::{KeyImage, TransactionHash, TxStateFlags}, +}; + +//---------------------------------------------------------------------------------------------------- init_write_service +/// Initialize the txpool write service from a [`ConcreteEnv`]. +pub(super) fn init_write_service(env: Arc) -> TxpoolWriteHandle { + DatabaseWriteHandle::init(env, handle_txpool_request) +} + +//---------------------------------------------------------------------------------------------------- handle_txpool_request +/// Handle an incoming [`TxpoolWriteRequest`], returning a [`TxpoolWriteResponse`]. +fn handle_txpool_request( + env: &ConcreteEnv, + req: &TxpoolWriteRequest, +) -> DbResult { + match req { + TxpoolWriteRequest::AddTransaction { tx, state_stem } => { + add_transaction(env, tx, *state_stem) + } + TxpoolWriteRequest::RemoveTransaction(tx_hash) => remove_transaction(env, tx_hash), + TxpoolWriteRequest::Promote(tx_hash) => promote(env, tx_hash), + TxpoolWriteRequest::NewBlock { spent_key_images } => new_block(env, spent_key_images), + } +} + +//---------------------------------------------------------------------------------------------------- Handler functions +// These are the actual functions that do stuff according to the incoming [`TxpoolWriteRequest`]. +// +// Each function name is a 1-1 mapping (from CamelCase -> snake_case) to +// the enum variant name, e.g: `BlockExtendedHeader` -> `block_extended_header`. +// +// Each function will return the [`Response`] that we +// should send back to the caller in [`map_request()`]. + +/// [`TxpoolWriteRequest::AddTransaction`] +fn add_transaction( + env: &ConcreteEnv, + tx: &TransactionVerificationData, + state_stem: bool, +) -> DbResult { + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw()?; + + let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; + + if let Err(e) = ops::add_transaction(tx, state_stem, &mut tables_mut) { + drop(tables_mut); + // error adding the tx, abort the DB transaction. + TxRw::abort(tx_rw) + .expect("could not maintain database atomicity by aborting write transaction"); + + return match e { + TxPoolWriteError::DoubleSpend(tx_hash) => { + // If we couldn't add the tx due to a double spend still return ok, but include the tx + // this double spent. + // TODO: mark the double spent tx? + Ok(TxpoolWriteResponse::AddTransaction(Some(tx_hash))) + } + TxPoolWriteError::Database(e) => Err(e), + }; + }; + + drop(tables_mut); + // The tx was added to the pool successfully. + TxRw::commit(tx_rw)?; + Ok(TxpoolWriteResponse::AddTransaction(None)) +} + +/// [`TxpoolWriteRequest::RemoveTransaction`] +fn remove_transaction( + env: &ConcreteEnv, + tx_hash: &TransactionHash, +) -> DbResult { + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw()?; + + let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; + + if let Err(e) = ops::remove_transaction(tx_hash, &mut tables_mut) { + drop(tables_mut); + // error removing the tx, abort the DB transaction. + TxRw::abort(tx_rw) + .expect("could not maintain database atomicity by aborting write transaction"); + + return Err(e); + } + + drop(tables_mut); + + TxRw::commit(tx_rw)?; + Ok(TxpoolWriteResponse::Ok) +} + +/// [`TxpoolWriteRequest::Promote`] +fn promote(env: &ConcreteEnv, tx_hash: &TransactionHash) -> DbResult { + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw()?; + + let res = || { + let mut tx_infos = env_inner.open_db_rw::(&tx_rw)?; + + tx_infos.update(tx_hash, |mut info| { + info.flags.remove(TxStateFlags::STATE_STEM); + Some(info) + }) + }; + + if let Err(e) = res() { + // error promoting the tx, abort the DB transaction. + TxRw::abort(tx_rw) + .expect("could not maintain database atomicity by aborting write transaction"); + + return Err(e); + } + + TxRw::commit(tx_rw)?; + Ok(TxpoolWriteResponse::Ok) +} + +/// [`TxpoolWriteRequest::NewBlock`] +fn new_block(env: &ConcreteEnv, spent_key_images: &[KeyImage]) -> DbResult { + let env_inner = env.env_inner(); + let tx_rw = env_inner.tx_rw()?; + + // FIXME: use try blocks once stable. + let result = || { + let mut tables_mut = env_inner.open_tables_mut(&tx_rw)?; + + // Remove all txs which spend key images that were spent in the new block. + for key_image in spent_key_images { + match tables_mut + .spent_key_images() + .get(key_image) + .and_then(|tx_hash| ops::remove_transaction(&tx_hash, &mut tables_mut)) + { + Ok(()) | Err(RuntimeError::KeyNotFound) => (), + Err(e) => return Err(e), + } + } + + Ok(()) + }; + + if let Err(e) = result() { + TxRw::abort(tx_rw)?; + return Err(e); + } + + TxRw::commit(tx_rw)?; + Ok(TxpoolWriteResponse::Ok) +} diff --git a/storage/txpool/src/tables.rs b/storage/txpool/src/tables.rs new file mode 100644 index 0000000..1f2d449 --- /dev/null +++ b/storage/txpool/src/tables.rs @@ -0,0 +1,51 @@ +//! Tx-pool Database tables. +//! +//! # Table marker structs +//! This module contains all the table definitions used by [`cuprate_txpool`](crate). +//! +//! The zero-sized structs here represents the table type; +//! they all are essentially marker types that implement [`cuprate_database::Table`]. +//! +//! Table structs are `CamelCase`, and their static string +//! names used by the actual database backend are `snake_case`. +//! +//! For example: [`TransactionBlobs`] -> `transaction_blobs`. +//! +//! # Traits +//! This module also contains a set of traits for +//! accessing _all_ tables defined here at once. +use cuprate_database::{define_tables, StorableVec}; + +use crate::types::{ + KeyImage, RawCachedVerificationState, TransactionBlobHash, TransactionHash, TransactionInfo, +}; + +define_tables! { + /// Serialized transaction blobs. + /// + /// This table contains the transaction blobs of all the transactions in the pool. + 0 => TransactionBlobs, + TransactionHash => StorableVec, + + /// Transaction information. + /// + /// This table contains information of all transactions currently in the pool. + 1 => TransactionInfos, + TransactionHash => TransactionInfo, + + /// Cached transaction verification state. + /// + /// This table contains the cached verification state of all translations in the pool. + 2 => CachedVerificationState, + TransactionHash => RawCachedVerificationState, + + /// Spent key images. + /// + /// This table contains the spent key images from all transactions in the pool. + 3 => SpentKeyImages, + KeyImage => TransactionHash, + + /// Transaction blob hashes that are in the pool. + 4 => KnownBlobHashes, + TransactionBlobHash => TransactionHash, +} diff --git a/storage/txpool/src/tx.rs b/storage/txpool/src/tx.rs new file mode 100644 index 0000000..29afae8 --- /dev/null +++ b/storage/txpool/src/tx.rs @@ -0,0 +1,16 @@ +//! Transaction metadata. + +/// Data about a transaction in the pool. +/// +/// Used in [`TxpoolReadResponse::Backlog`](crate::service::interface::TxpoolReadResponse::Backlog). +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct TxEntry { + /// The transaction's ID (hash). + pub id: [u8; 32], + /// The transaction's weight. + pub weight: u64, + /// The transaction's fee. + pub fee: u64, + /// How long the transaction has been in the pool. + pub time_in_pool: std::time::Duration, +} diff --git a/storage/txpool/src/types.rs b/storage/txpool/src/types.rs new file mode 100644 index 0000000..2acb819 --- /dev/null +++ b/storage/txpool/src/types.rs @@ -0,0 +1,128 @@ +//! Tx-pool [table](crate::tables) types. +//! +//! This module contains all types used by the database tables, +//! and aliases for common types that use the same underlying +//! primitive type. +//! +//! +use bytemuck::{Pod, Zeroable}; +use monero_serai::transaction::Timelock; + +use cuprate_types::{CachedVerificationState, HardFork}; + +/// An inputs key image. +pub type KeyImage = [u8; 32]; + +/// A transaction hash. +pub type TransactionHash = [u8; 32]; + +/// A transaction blob hash. +pub type TransactionBlobHash = [u8; 32]; + +bitflags::bitflags! { + /// Flags representing the state of the transaction in the pool. + #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] + #[repr(transparent)] + pub struct TxStateFlags: u8 { + /// A flag for if the transaction is in the stem state. + const STATE_STEM = 0b0000_0001; + /// A flag for if we have seen another tx double spending this tx. + const DOUBLE_SPENT = 0b0000_0010; + } +} + +/// Information on a tx-pool transaction. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(C)] +pub struct TransactionInfo { + /// The transaction's fee. + pub fee: u64, + /// The transaction's weight. + pub weight: usize, + /// [`TxStateFlags`] of this transaction. + pub flags: TxStateFlags, + #[expect(clippy::pub_underscore_fields)] + /// Explicit padding so that we have no implicit padding bytes in `repr(C)`. + /// + /// Allows potential future expansion of this type. + pub _padding: [u8; 7], +} + +/// [`CachedVerificationState`] in a format that can be stored into the database. +/// +/// This type impls [`Into`] & [`From`] [`CachedVerificationState`]. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Pod, Zeroable)] +#[repr(C)] +pub struct RawCachedVerificationState { + /// The raw hash, will be all `0`s if there is no block hash that this is valid for. + raw_valid_at_hash: [u8; 32], + /// The raw hard-fork, will be `0` if there is no hf this was validated at. + raw_hf: u8, + /// The raw [`u64`] timestamp as little endian bytes ([`u64::to_le_bytes`]). + /// + /// This will be `0` if there is no timestamp that needs to be passed for this to + /// be valid. + /// + /// Not a [`u64`] as if it was this type would have an alignment requirement. + raw_valid_past_timestamp: [u8; 8], +} + +impl From for CachedVerificationState { + fn from(value: RawCachedVerificationState) -> Self { + // if the hash is all `0`s then there is no hash this is valid at. + if value.raw_valid_at_hash == [0; 32] { + return Self::NotVerified; + } + + let raw_valid_past_timestamp = u64::from_le_bytes(value.raw_valid_past_timestamp); + + // if the timestamp is 0, there is no timestamp that needs to be passed. + if raw_valid_past_timestamp == 0 { + return Self::ValidAtHashAndHF { + block_hash: value.raw_valid_at_hash, + hf: HardFork::from_version(value.raw_hf) + .expect("hard-fork values stored in the DB should always be valid"), + }; + } + + Self::ValidAtHashAndHFWithTimeBasedLock { + block_hash: value.raw_valid_at_hash, + hf: HardFork::from_version(value.raw_hf) + .expect("hard-fork values stored in the DB should always be valid"), + time_lock: Timelock::Time(raw_valid_past_timestamp), + } + } +} + +#[expect(clippy::fallible_impl_from, reason = "only panics in invalid states")] +impl From for RawCachedVerificationState { + fn from(value: CachedVerificationState) -> Self { + match value { + CachedVerificationState::NotVerified => Self { + raw_valid_at_hash: [0; 32], + raw_hf: 0, + raw_valid_past_timestamp: [0; 8], + }, + CachedVerificationState::ValidAtHashAndHF { block_hash, hf } => Self { + raw_valid_at_hash: block_hash, + raw_hf: hf.as_u8(), + raw_valid_past_timestamp: [0; 8], + }, + CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock { + block_hash, + hf, + time_lock, + } => { + let Timelock::Time(time) = time_lock else { + panic!("ValidAtHashAndHFWithTimeBasedLock timelock was not time-based"); + }; + + Self { + raw_valid_at_hash: block_hash, + raw_hf: hf.as_u8(), + raw_valid_past_timestamp: time.to_le_bytes(), + } + } + } + } +} diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index f9a5c6d..4eb5684 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -1,30 +1,34 @@ [package] -name = "cuprate-test-utils" +name = "cuprate-test-utils" version = "0.1.0" edition = "2021" license = "MIT" authors = ["Boog900", "hinto-janai"] [dependencies] -cuprate-types = { path = "../types" } -cuprate-helper = { path = "../helper", features = ["map"] } -cuprate-wire = { path = "../net/wire" } -cuprate-p2p-core = { path = "../p2p/p2p-core", features = ["borsh"] } +cuprate-types = { workspace = true } +cuprate-helper = { workspace = true, features = ["map", "tx"] } +cuprate-wire = { workspace = true } +cuprate-p2p-core = { workspace = true, features = ["borsh"] } -hex = { workspace = true } -hex-literal = { workspace = true } -monero-serai = { workspace = true, features = ["std", "http-rpc"] } -futures = { workspace = true, features = ["std"] } -async-trait = { workspace = true } -tokio = { workspace = true, features = ["full"] } -tokio-util = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -bytes = { workspace = true, features = ["std"] } -tempfile = { workspace = true } - -borsh = { workspace = true, features = ["derive"]} +hex = { workspace = true } +hex-literal = { workspace = true } +monero-serai = { workspace = true, features = ["std"] } +monero-simple-request-rpc = { workspace = true } +monero-rpc = { workspace = true } +futures = { workspace = true, features = ["std"] } +async-trait = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tokio-util = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tempfile = { workspace = true } +paste = { workspace = true } +borsh = { workspace = true, features = ["derive"]} [dev-dependencies] -hex = { workspace = true } -pretty_assertions = { workspace = true } \ No newline at end of file +hex = { workspace = true } +pretty_assertions = { workspace = true } + +[lints] +workspace = true diff --git a/test-utils/README.md b/test-utils/README.md index c210686..3c71c0a 100644 --- a/test-utils/README.md +++ b/test-utils/README.md @@ -7,3 +7,4 @@ It currently contains: - Code to spawn monerod instances and a testing network zone - Real raw and typed Monero data, e.g. `Block, Transaction` - An RPC client to generate types from `cuprate_types` +- Raw RPC request/response strings and binary data \ No newline at end of file diff --git a/test-utils/src/data/constants.rs b/test-utils/src/data/constants.rs index c1da6d0..78413ed 100644 --- a/test-utils/src/data/constants.rs +++ b/test-utils/src/data/constants.rs @@ -34,12 +34,12 @@ macro_rules! const_block_blob { #[doc = ""] #[doc = concat!("let block = Block::read(&mut ", stringify!($name), ").unwrap();")] #[doc = ""] - #[doc = concat!("assert_eq!(block.header.major_version, ", $major_version, ");")] - #[doc = concat!("assert_eq!(block.header.minor_version, ", $minor_version, ");")] + #[doc = concat!("assert_eq!(block.header.hardfork_version, ", $major_version, ");")] + #[doc = concat!("assert_eq!(block.header.hardfork_signal, ", $minor_version, ");")] #[doc = concat!("assert_eq!(block.header.timestamp, ", $timestamp, ");")] #[doc = concat!("assert_eq!(block.header.nonce, ", $nonce, ");")] - #[doc = concat!("assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(", $height, ")));")] - #[doc = concat!("assert_eq!(block.txs.len(), ", $tx_len, ");")] + #[doc = concat!("assert!(matches!(block.miner_transaction.prefix().inputs[0], Input::Gen(", $height, ")));")] + #[doc = concat!("assert_eq!(block.transactions.len(), ", $tx_len, ");")] #[doc = concat!("assert_eq!(hex::encode(block.hash()), \"", $hash, "\")")] /// ``` pub const $name: &[u8] = include_bytes!($data_path); @@ -104,10 +104,9 @@ macro_rules! const_tx_blob { hash: $hash:literal, // Transaction hash data_path: $data_path:literal, // Path to the transaction blob version: $version:literal, // Transaction version - timelock: $timelock:expr, // Transaction's timelock (use the real type `Timelock`) + timelock: $timelock:expr_2021, // Transaction's timelock (use the real type `Timelock`) input_len: $input_len:literal, // Amount of inputs output_len: $output_len:literal, // Amount of outputs - signatures_len: $signatures_len:literal, // Amount of signatures ) => { #[doc = concat!("Transaction with hash `", $hash, "`.")] /// @@ -117,11 +116,10 @@ macro_rules! const_tx_blob { #[doc = ""] #[doc = concat!("let tx = Transaction::read(&mut ", stringify!($name), ").unwrap();")] #[doc = ""] - #[doc = concat!("assert_eq!(tx.prefix.version, ", $version, ");")] - #[doc = concat!("assert_eq!(tx.prefix.timelock, ", stringify!($timelock), ");")] - #[doc = concat!("assert_eq!(tx.prefix.inputs.len(), ", $input_len, ");")] - #[doc = concat!("assert_eq!(tx.prefix.outputs.len(), ", $output_len, ");")] - #[doc = concat!("assert_eq!(tx.signatures.len(), ", $signatures_len, ");")] + #[doc = concat!("assert_eq!(tx.version(), ", $version, ");")] + #[doc = concat!("assert_eq!(tx.prefix().additional_timelock, ", stringify!($timelock), ");")] + #[doc = concat!("assert_eq!(tx.prefix().inputs.len(), ", $input_len, ");")] + #[doc = concat!("assert_eq!(tx.prefix().outputs.len(), ", $output_len, ");")] #[doc = concat!("assert_eq!(hex::encode(tx.hash()), \"", $hash, "\")")] /// ``` pub const $name: &[u8] = include_bytes!($data_path); @@ -136,7 +134,6 @@ const_tx_blob! { timelock: Timelock::Block(100_081), input_len: 1, output_len: 5, - signatures_len: 0, } const_tx_blob! { @@ -147,7 +144,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 19, output_len: 61, - signatures_len: 19, } const_tx_blob! { @@ -158,7 +154,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 46, output_len: 46, - signatures_len: 46, } const_tx_blob! { @@ -169,7 +164,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 1, output_len: 2, - signatures_len: 0, } const_tx_blob! { @@ -180,7 +174,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 1, output_len: 2, - signatures_len: 0, } const_tx_blob! { @@ -191,7 +184,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 2, output_len: 2, - signatures_len: 0, } const_tx_blob! { @@ -202,7 +194,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 2, output_len: 5, - signatures_len: 2, } const_tx_blob! { @@ -213,7 +204,6 @@ const_tx_blob! { timelock: Timelock::None, input_len: 2, output_len: 2, - signatures_len: 0, } //---------------------------------------------------------------------------------------------------- Tests diff --git a/test-utils/src/data/mod.rs b/test-utils/src/data/mod.rs index 49ea89a..3be409f 100644 --- a/test-utils/src/data/mod.rs +++ b/test-utils/src/data/mod.rs @@ -15,21 +15,21 @@ //! let tx: Transaction = Transaction::read(&mut TX_E57440).unwrap(); //! ``` //! -//! ## Functions -//! The free functions provide access to typed data found in `cuprate_types`: +//! ## Statics +//! The statics provide access to typed data found in `cuprate_types`: //! ```rust //! # use cuprate_test_utils::data::*; //! use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; //! -//! let block: VerifiedBlockInformation = block_v16_tx0().clone(); -//! let tx: VerifiedTransactionInformation = tx_v1_sig0().clone(); +//! let block: VerifiedBlockInformation = BLOCK_V16_TX0.clone(); +//! let tx: VerifiedTransactionInformation = TX_V1_SIG0.clone(); //! ``` -mod constants; pub use constants::{ BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_BBD604, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73, TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440, }; +pub use statics::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3, TX_V1_SIG0, TX_V1_SIG2, TX_V2_RCT3}; -mod free; -pub use free::{block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3}; +mod constants; +mod statics; diff --git a/test-utils/src/data/free.rs b/test-utils/src/data/statics.rs similarity index 78% rename from test-utils/src/data/free.rs rename to test-utils/src/data/statics.rs index e80bdda..c67c7eb 100644 --- a/test-utils/src/data/free.rs +++ b/test-utils/src/data/statics.rs @@ -1,4 +1,4 @@ -//! Free functions to access data. +//! `static LazyLock`s to access data. #![allow( const_item_mutation, // `R: Read` needs `&mut self` @@ -6,12 +6,12 @@ )] //---------------------------------------------------------------------------------------------------- Import -use std::sync::OnceLock; +use std::sync::LazyLock; use hex_literal::hex; use monero_serai::{block::Block, transaction::Transaction}; -use cuprate_helper::map::combine_low_high_bits_to_u128; +use cuprate_helper::{map::combine_low_high_bits_to_u128, tx::tx_fee}; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; use crate::data::constants::{ @@ -31,7 +31,7 @@ use crate::data::constants::{ struct VerifiedBlockMap { block_blob: &'static [u8], pow_hash: [u8; 32], - height: u64, + height: usize, generated_coins: u64, weight: usize, long_term_weight: usize, @@ -68,11 +68,11 @@ impl VerifiedBlockMap { assert_eq!( txs.len(), - block.txs.len(), + block.transactions.len(), "(deserialized txs).len() != (txs hashes in block).len()" ); - for (tx, tx_hash_in_block) in txs.iter().zip(&block.txs) { + for (tx, tx_hash_in_block) in txs.iter().zip(&block.transactions) { assert_eq!( &tx.tx_hash, tx_hash_in_block, "deserialized tx hash is not the same as the one in the parent block" @@ -103,7 +103,7 @@ fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> VerifiedTransactionInfo let tx = Transaction::read(&mut tx_blob.as_slice()).unwrap(); VerifiedTransactionInformation { tx_weight: tx.weight(), - fee: tx.rct_signatures.base.fee, + fee: tx_fee(&tx), tx_hash: tx.hash(), tx_blob, tx, @@ -111,21 +111,20 @@ fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> VerifiedTransactionInfo } //---------------------------------------------------------------------------------------------------- Blocks -/// Generate a block accessor function with this signature: -/// `fn() -> &'static VerifiedBlockInformation` +/// Generate a `static LazyLock`. /// /// This will use `VerifiedBlockMap` type above to do various /// checks on the input data and makes sure it seems correct. /// /// This requires some static block/tx input (from data) and some fields. /// This data can be accessed more easily via: -/// - A block explorer (https://xmrchain.net) -/// - Monero RPC (see cuprate_test_utils::rpc for this) +/// - A block explorer () +/// - Monero RPC (see `cuprate_test_utils::rpc` for this) /// /// See below for actual usage. -macro_rules! verified_block_information_fn { +macro_rules! verified_block_information { ( - fn_name: $fn_name:ident, // Name of the function created + name: $name:ident, // Name of the `LazyLock` created block_blob: $block_blob:ident, // Block blob ([u8], found in `constants.rs`) tx_blobs: [$($tx_blob:ident),*], // Array of contained transaction blobs pow_hash: $pow_hash:literal, // PoW hash as a string literal @@ -153,7 +152,7 @@ macro_rules! verified_block_information_fn { #[doc = "# use hex_literal::hex;"] #[doc = "use cuprate_helper::map::combine_low_high_bits_to_u128;"] #[doc = ""] - #[doc = concat!("let block = ", stringify!($fn_name), "();")] + #[doc = concat!("let block = &*", stringify!($name), ";")] #[doc = concat!("assert_eq!(&block.block.serialize(), ", stringify!($block_blob), ");")] #[doc = concat!("assert_eq!(block.pow_hash, hex!(\"", $pow_hash, "\"));")] #[doc = concat!("assert_eq!(block.height, ", $height, ");")] @@ -171,28 +170,25 @@ macro_rules! verified_block_information_fn { "));" )] /// ``` - pub fn $fn_name() -> &'static VerifiedBlockInformation { - static BLOCK: OnceLock = OnceLock::new(); - BLOCK.get_or_init(|| { - VerifiedBlockMap { - block_blob: $block_blob, - pow_hash: hex!($pow_hash), - height: $height, - generated_coins: $generated_coins, - weight: $weight, - long_term_weight: $long_term_weight, - cumulative_difficulty_low: $cumulative_difficulty_low, - cumulative_difficulty_high: $cumulative_difficulty_high, - txs: &[$($tx_blob),*], - } - .into_verified() - }) - } + pub static $name: LazyLock = LazyLock::new(|| { + VerifiedBlockMap { + block_blob: $block_blob, + pow_hash: hex!($pow_hash), + height: $height, + generated_coins: $generated_coins, + weight: $weight, + long_term_weight: $long_term_weight, + cumulative_difficulty_low: $cumulative_difficulty_low, + cumulative_difficulty_high: $cumulative_difficulty_high, + txs: &[$($tx_blob),*], + } + .into_verified() + }); }; } -verified_block_information_fn! { - fn_name: block_v1_tx2, +verified_block_information! { + name: BLOCK_V1_TX2, block_blob: BLOCK_5ECB7E, tx_blobs: [TX_2180A8, TX_D7FEBD], pow_hash: "c960d540000459480560b7816de968c7470083e5874e10040bdd4cc501000000", @@ -205,8 +201,8 @@ verified_block_information_fn! { tx_len: 2, } -verified_block_information_fn! { - fn_name: block_v9_tx3, +verified_block_information! { + name: BLOCK_V9_TX3, block_blob: BLOCK_F91043, tx_blobs: [TX_E2D393, TX_E57440, TX_B6B439], pow_hash: "7c78b5b67a112a66ea69ea51477492057dba9cfeaa2942ee7372c61800000000", @@ -219,8 +215,8 @@ verified_block_information_fn! { tx_len: 3, } -verified_block_information_fn! { - fn_name: block_v16_tx0, +verified_block_information! { + name: BLOCK_V16_TX0, block_blob: BLOCK_43BD1F, tx_blobs: [], pow_hash: "10b473b5d097d6bfa0656616951840724dfe38c6fb9c4adf8158800300000000", @@ -234,13 +230,12 @@ verified_block_information_fn! { } //---------------------------------------------------------------------------------------------------- Transactions -/// Generate a transaction accessor function with this signature: -/// `fn() -> &'static VerifiedTransactionInformation` +/// Generate a `const LazyLock`. /// -/// Same as [`verified_block_information_fn`] but for transactions. -macro_rules! transaction_verification_data_fn { +/// Same as [`verified_block_information`] but for transactions. +macro_rules! transaction_verification_data { ( - fn_name: $fn_name:ident, // Name of the function created + name: $name:ident, // Name of the `LazyLock` created tx_blobs: $tx_blob:ident, // Transaction blob ([u8], found in `constants.rs`) weight: $weight:literal, // Transaction weight hash: $hash:literal, // Transaction hash as a string literal @@ -250,36 +245,34 @@ macro_rules! transaction_verification_data_fn { /// ```rust #[doc = "# use cuprate_test_utils::data::*;"] #[doc = "# use hex_literal::hex;"] - #[doc = concat!("let tx = ", stringify!($fn_name), "();")] + #[doc = concat!("let tx = &*", stringify!($name), ";")] #[doc = concat!("assert_eq!(&tx.tx.serialize(), ", stringify!($tx_blob), ");")] #[doc = concat!("assert_eq!(tx.tx_blob, ", stringify!($tx_blob), ");")] #[doc = concat!("assert_eq!(tx.tx_weight, ", $weight, ");")] #[doc = concat!("assert_eq!(tx.tx_hash, hex!(\"", $hash, "\"));")] - #[doc = "assert_eq!(tx.fee, tx.tx.rct_signatures.base.fee);"] /// ``` - pub fn $fn_name() -> &'static VerifiedTransactionInformation { - static TX: OnceLock = OnceLock::new(); - TX.get_or_init(|| to_tx_verification_data($tx_blob)) - } + pub static $name: LazyLock = LazyLock::new(|| { + to_tx_verification_data($tx_blob) + }); }; } -transaction_verification_data_fn! { - fn_name: tx_v1_sig0, +transaction_verification_data! { + name: TX_V1_SIG0, tx_blobs: TX_3BC7FF, weight: 248, hash: "3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1", } -transaction_verification_data_fn! { - fn_name: tx_v1_sig2, +transaction_verification_data! { + name: TX_V1_SIG2, tx_blobs: TX_9E3F73, weight: 448, hash: "9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34", } -transaction_verification_data_fn! { - fn_name: tx_v2_rct3, +transaction_verification_data! { + name: TX_V2_RCT3, tx_blobs: TX_84D48D, weight: 2743, hash: "84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66", @@ -288,18 +281,18 @@ transaction_verification_data_fn! { //---------------------------------------------------------------------------------------------------- TESTS #[cfg(test)] mod tests { - use super::*; - use pretty_assertions::assert_eq; - use crate::rpc::HttpRpcClient; + use crate::rpc::client::HttpRpcClient; + + use super::*; /// Assert the defined blocks are the same compared to ones received from a local RPC call. #[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node #[tokio::test] async fn block_same_as_rpc() { let rpc = HttpRpcClient::new(None).await; - for block in [block_v1_tx2(), block_v9_tx3(), block_v16_tx0()] { + for block in [&*BLOCK_V1_TX2, &*BLOCK_V9_TX3, &*BLOCK_V16_TX0] { println!("block_height: {}", block.height); let block_rpc = rpc.get_verified_block_information(block.height).await; assert_eq!(block, &block_rpc); @@ -313,16 +306,12 @@ mod tests { async fn tx_same_as_rpc() { let rpc = HttpRpcClient::new(None).await; - let mut txs = [block_v1_tx2(), block_v9_tx3(), block_v16_tx0()] + let mut txs = [&*BLOCK_V1_TX2, &*BLOCK_V9_TX3, &*BLOCK_V16_TX0] .into_iter() .flat_map(|block| block.txs.iter().cloned()) .collect::>(); - txs.extend([ - tx_v1_sig0().clone(), - tx_v1_sig2().clone(), - tx_v2_rct3().clone(), - ]); + txs.extend([TX_V1_SIG0.clone(), TX_V1_SIG2.clone(), TX_V2_RCT3.clone()]); for tx in txs { println!("tx_hash: {:?}", tx.tx_hash); diff --git a/test-utils/src/monerod.rs b/test-utils/src/monerod.rs index 9ffa08d..abad4c9 100644 --- a/test-utils/src/monerod.rs +++ b/test-utils/src/monerod.rs @@ -178,6 +178,7 @@ impl Drop for SpawnedMoneroD { println!("------END-MONEROD-LOGS------"); } + #[expect(clippy::manual_assert, reason = "`if` is more clear")] if error && !panicking() { // `println` only outputs in a test when panicking so if there is an error while // dropping monerod but not an error in the test then we need to panic to make sure diff --git a/test-utils/src/rpc/client.rs b/test-utils/src/rpc/client.rs index 22ae11f..ce7fb09 100644 --- a/test-utils/src/rpc/client.rs +++ b/test-utils/src/rpc/client.rs @@ -1,24 +1,25 @@ //! HTTP RPC client. //---------------------------------------------------------------------------------------------------- Use +use monero_rpc::Rpc; +use monero_serai::block::Block; +use monero_simple_request_rpc::SimpleRequestRpc; use serde::Deserialize; use serde_json::json; use tokio::task::spawn_blocking; -use monero_serai::{ - block::Block, - rpc::{HttpRpc, Rpc}, -}; - +use cuprate_helper::tx::tx_fee; use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; -use crate::rpc::constants::LOCALHOST_RPC_URL; +//---------------------------------------------------------------------------------------------------- Constants +/// The default URL used for Monero RPC connections. +pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081"; //---------------------------------------------------------------------------------------------------- HttpRpcClient /// An HTTP RPC client for Monero. pub struct HttpRpcClient { address: String, - rpc: Rpc, + rpc: SimpleRequestRpc, } impl HttpRpcClient { @@ -38,20 +39,20 @@ impl HttpRpcClient { let address = address.unwrap_or_else(|| LOCALHOST_RPC_URL.to_string()); Self { - rpc: HttpRpc::new(address.clone()).await.unwrap(), + rpc: SimpleRequestRpc::new(address.clone()).await.unwrap(), address, } } /// The address used for this [`HttpRpcClient`]. - #[allow(dead_code)] + #[allow(clippy::allow_attributes, dead_code, reason = "expect doesn't work")] const fn address(&self) -> &String { &self.address } /// Access to the inner RPC client for other usage. - #[allow(dead_code)] - const fn rpc(&self) -> &Rpc { + #[expect(dead_code)] + const fn rpc(&self) -> &SimpleRequestRpc { &self.rpc } @@ -60,7 +61,7 @@ impl HttpRpcClient { /// # Panics /// This function will panic at any error point, e.g., /// if the node cannot be connected to, if deserialization fails, etc. - pub async fn get_verified_block_information(&self, height: u64) -> VerifiedBlockInformation { + pub async fn get_verified_block_information(&self, height: usize) -> VerifiedBlockInformation { #[derive(Debug, Deserialize)] struct Result { blob: String, @@ -73,7 +74,7 @@ impl HttpRpcClient { long_term_weight: usize, cumulative_difficulty: u128, hash: String, - height: u64, + height: usize, pow_hash: String, reward: u64, // generated_coins + total_tx_fees } @@ -109,7 +110,7 @@ impl HttpRpcClient { .unwrap(); let txs: Vec = self - .get_transaction_verification_data(&block.txs) + .get_transaction_verification_data(&block.transactions) .await .collect(); @@ -122,8 +123,8 @@ impl HttpRpcClient { let total_tx_fees = txs.iter().map(|tx| tx.fee).sum::(); let generated_coins = block - .miner_tx - .prefix + .miner_transaction + .prefix() .outputs .iter() .map(|output| output.amount.expect("miner_tx amount was None")) @@ -171,7 +172,7 @@ impl HttpRpcClient { tx_blob: tx.serialize(), tx_weight: tx.weight(), tx_hash, - fee: tx.rct_signatures.base.fee, + fee: tx_fee(&tx), tx, } }) @@ -181,9 +182,10 @@ impl HttpRpcClient { //---------------------------------------------------------------------------------------------------- TESTS #[cfg(test)] mod tests { - use super::*; use hex_literal::hex; + use super::*; + /// Assert the default address is localhost. #[tokio::test] async fn localhost() { @@ -194,10 +196,10 @@ mod tests { #[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node #[tokio::test] async fn get() { - #[allow(clippy::too_many_arguments)] + #[expect(clippy::too_many_arguments)] async fn assert_eq( rpc: &HttpRpcClient, - height: u64, + height: usize, block_hash: [u8; 32], pow_hash: [u8; 32], generated_coins: u64, diff --git a/test-utils/src/rpc/constants.rs b/test-utils/src/rpc/constants.rs deleted file mode 100644 index ce44a88..0000000 --- a/test-utils/src/rpc/constants.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! RPC-related Constants. - -//---------------------------------------------------------------------------------------------------- Use - -//---------------------------------------------------------------------------------------------------- Constants -/// The default URL used for Monero RPC connections. -pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081"; diff --git a/test-utils/src/rpc/data/bin.rs b/test-utils/src/rpc/data/bin.rs new file mode 100644 index 0000000..cf98a4a --- /dev/null +++ b/test-utils/src/rpc/data/bin.rs @@ -0,0 +1,55 @@ +//! Binary data from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). +//! +//! TODO: Not implemented yet. + +//---------------------------------------------------------------------------------------------------- Import +use crate::rpc::data::macros::define_request_and_response; + +//---------------------------------------------------------------------------------------------------- TODO +define_request_and_response! { + get_blocksbin, + GET_BLOCKS: &[u8], + Request = &[]; + Response = &[]; +} + +define_request_and_response! { + get_blocks_by_heightbin, + GET_BLOCKS_BY_HEIGHT: &[u8], + Request = &[]; + Response = &[]; +} + +define_request_and_response! { + get_hashesbin, + GET_HASHES: &[u8], + Request = &[]; + Response = &[]; +} + +define_request_and_response! { + get_o_indexesbin, + GET_O_INDEXES: &[u8], + Request = &[]; + Response = &[]; +} + +define_request_and_response! { + get_outsbin, + GET_OUTS: &[u8], + Request = &[]; + Response = &[]; +} + +define_request_and_response! { + get_transaction_pool_hashesbin, + GET_TRANSACTION_POOL_HASHES: &[u8], + Request = &[]; + Response = &[]; +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs new file mode 100644 index 0000000..3d46306 --- /dev/null +++ b/test-utils/src/rpc/data/json.rs @@ -0,0 +1,1299 @@ +//! JSON data from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. + +//---------------------------------------------------------------------------------------------------- Import +use crate::rpc::data::macros::define_request_and_response; + +//---------------------------------------------------------------------------------------------------- Struct definitions +// This generates 2 const strings: +// +// - `const GET_BLOCK_TEMPLATE_REQUEST: &str = "..."` +// - `const GET_BLOCK_TEMPLATE_RESPONSE: &str = "..."` +// +// with some interconnected documentation. +define_request_and_response! { + // The markdown tag for Monero RPC documentation. Not necessarily the endpoint (json). + // + // Adding `(json_rpc)` after this will trigger the macro to automatically + // add a `serde_json` test for the request/response data. + get_block_template (json_rpc), + + // The base const name: the type of the request/response. + GET_BLOCK_TEMPLATE: &str, + + // The request data. + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_template", + "params": { + "wallet_address": "44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns", + "reserve_size": 60 + } +}"#; + + // The response data. + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blockhashing_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401", + "blocktemplate_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": 283305047039, + "difficulty_top64": 0, + "expected_reward": 600000000000, + "height": 3195018, + "next_seed_hash": "", + "prev_hash": "9d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a", + "reserved_offset": 131, + "seed_hash": "e2aa0b7b55042cd48b02e395d78fa66a29815ccc1584e38db2d1f0e8485cd44f", + "seed_height": 3194880, + "status": "OK", + "untrusted": false, + "wide_difficulty": "0x41f64bf3ff" + } +}"#; +} + +define_request_and_response! { + get_block_count (json_rpc), + GET_BLOCK_COUNT: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_count" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "count": 3195019, + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + on_get_block_hash (json_rpc), + ON_GET_BLOCK_HASH: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "on_get_block_hash", + "params": [912345] +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6" +}"#; +} + +define_request_and_response! { + submit_block (json_rpc), + SUBMIT_BLOCK: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "submit_block", + "params": ["0707e6bdfedc053771512f1bc27c62731ae9e8f2443db64ce742f4e57f5cf8d393de28551e441a0000000002fb830a01ffbf830a018cfe88bee283060274c0aae2ef5730e680308d9c00b6da59187ad0352efe3c71d36eeeb28782f29f2501bd56b952c3ddc3e350c2631d3a5086cac172c56893831228b17de296ff4669de020200000000"] +}"#; + Response = +r#"{ + "error": { + "code": -7, + "message": "Block not accepted" + }, + "id": "0", + "jsonrpc": "2.0" +}"#; +} + +define_request_and_response! { + generateblocks (json_rpc), + GENERATE_BLOCKS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "generateblocks", + "params": { + "amount_of_blocks": 1, + "wallet_address": "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A", + "starting_nonce": 0 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blocks": ["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4"], + "height": 9783, + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_last_block_header (json_rpc), + GET_LAST_BLOCK_HEADER: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_last_block_header" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "block_header": { + "block_size": 200419, + "block_weight": 200419, + "cumulative_difficulty": 366125734645190820, + "cumulative_difficulty_top64": 0, + "depth": 0, + "difficulty": 282052561854, + "difficulty_top64": 0, + "hash": "57238217820195ac4c08637a144a885491da167899cf1d20e8e7ce0ae0a3434e", + "height": 3195020, + "long_term_weight": 200419, + "major_version": 16, + "miner_tx_hash": "7a42667237d4f79891bb407c49c712a9299fb87fce799833a7b633a3a9377dbd", + "minor_version": 16, + "nonce": 1885649739, + "num_txes": 37, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "22c72248ae9c5a2863c94735d710a3525c499f70707d1c2f395169bc5c8a0da3", + "reward": 615702960000, + "timestamp": 1721245548, + "wide_cumulative_difficulty": "0x514bd6a74a7d0a4", + "wide_difficulty": "0x41aba48bbe" + }, + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block_header_by_hash (json_rpc), + GET_BLOCK_HEADER_BY_HASH: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_header_by_hash", + "params": { + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "block_header": { + "block_size": 210, + "block_weight": 210, + "cumulative_difficulty": 754734824984346, + "cumulative_difficulty_top64": 0, + "depth": 2282676, + "difficulty": 815625611, + "difficulty_top64": 0, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "long_term_weight": 210, + "major_version": 1, + "miner_tx_hash": "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30", + "minor_version": 2, + "nonce": 1646, + "num_txes": 0, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716, + "wide_cumulative_difficulty": "0x2ae6d65248f1a", + "wide_difficulty": "0x309d758b" + }, + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block_header_by_height (json_rpc), + GET_BLOCK_HEADER_BY_HEIGHT: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_header_by_height", + "params": { + "height": 912345 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "block_header": { + "block_size": 210, + "block_weight": 210, + "cumulative_difficulty": 754734824984346, + "cumulative_difficulty_top64": 0, + "depth": 2282677, + "difficulty": 815625611, + "difficulty_top64": 0, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "long_term_weight": 210, + "major_version": 1, + "miner_tx_hash": "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30", + "minor_version": 2, + "nonce": 1646, + "num_txes": 0, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716, + "wide_cumulative_difficulty": "0x2ae6d65248f1a", + "wide_difficulty": "0x309d758b" + }, + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block_headers_range (json_rpc), + GET_BLOCK_HEADERS_RANGE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_headers_range", + "params": { + "start_height": 1545999, + "end_height": 1546000 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "headers": [{ + "block_size": 301413, + "block_weight": 301413, + "cumulative_difficulty": 13185267971483472, + "cumulative_difficulty_top64": 0, + "depth": 1649024, + "difficulty": 134636057921, + "difficulty_top64": 0, + "hash": "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a", + "height": 1545999, + "long_term_weight": 301413, + "major_version": 6, + "miner_tx_hash": "9909c6f8a5267f043c3b2b079fb4eacc49ef9c1dee1c028eeb1a259b95e6e1d9", + "minor_version": 6, + "nonce": 3246403956, + "num_txes": 20, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "0ef6e948f77b8f8806621003f5de24b1bcbea150bc0e376835aea099674a5db5", + "reward": 5025593029981, + "timestamp": 1523002893, + "wide_cumulative_difficulty": "0x2ed7ee6db56750", + "wide_difficulty": "0x1f58ef3541" + },{ + "block_size": 13322, + "block_weight": 13322, + "cumulative_difficulty": 13185402687569710, + "cumulative_difficulty_top64": 0, + "depth": 1649023, + "difficulty": 134716086238, + "difficulty_top64": 0, + "hash": "b408bf4cfcd7de13e7e370c84b8314c85b24f0ba4093ca1d6eeb30b35e34e91a", + "height": 1546000, + "long_term_weight": 13322, + "major_version": 7, + "miner_tx_hash": "7f749c7c64acb35ef427c7454c45e6688781fbead9bbf222cb12ad1a96a4e8f6", + "minor_version": 7, + "nonce": 3737164176, + "num_txes": 1, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a", + "reward": 4851952181070, + "timestamp": 1523002931, + "wide_cumulative_difficulty": "0x2ed80dcb69bf2e", + "wide_difficulty": "0x1f5db457de" + }], + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block (json_rpc), + GET_BLOCK: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block", + "params": { + "height": 2751506 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blob": "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000", + "block_header": { + "block_size": 106, + "block_weight": 106, + "cumulative_difficulty": 236046001376524168, + "cumulative_difficulty_top64": 0, + "depth": 443517, + "difficulty": 313732272488, + "difficulty_top64": 0, + "hash": "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428", + "height": 2751506, + "long_term_weight": 176470, + "major_version": 16, + "miner_tx_hash": "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd", + "minor_version": 16, + "nonce": 4110909056, + "num_txes": 0, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7", + "reward": 600000000000, + "timestamp": 1667941829, + "wide_cumulative_difficulty": "0x3469a966eb2f788", + "wide_difficulty": "0x490be69168" + }, + "credits": 0, + "json": "{\n \"major_version\": 16, \n \"minor_version\": 16, \n \"timestamp\": 1667941829, \n \"prev_id\": \"b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7\", \n \"nonce\": 4110909056, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2751566, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2751506\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 600000000000, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85\", \n \"view_tag\": \"d0\"\n }\n }\n }\n ], \n \"extra\": [ 1, 159, 98, 157, 139, 54, 189, 22, 162, 191, 206, 62, 168, 12, 49, 220, 77, 135, 98, 198, 113, 101, 174, 194, 24, 69, 73, 78, 50, 183, 88, 47, 224, 2, 17, 0, 0, 0, 41, 122, 120, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ ]\n}", + "miner_tx_hash": "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd", + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block (json_rpc), + /// This is the same as [`GET_BLOCK_REQUEST`] and + /// [`GET_BLOCK_RESPONSE`] but it uses the `hash` parameter. + GET_BLOCK_HASH: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block", + "params": { + "hash": "86d421322b700166dde2d7eba1cc8600925ef640abf6c0a2cc8ce0d6dd90abfd" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blob": "1010d8faa89b06f8a36d0dbe4d27d2f52160000563896048d71067c31e99a3869bf9b7142227bb5328010b02a6f6a70101ffeaf5a70101a08bc8b3bb11036d6713f5aa552a1aaf33baed7591f795b86daf339e51029a9062dfe09f0f909b312b0124d6023d591c4d434000e5e31c6db718a1e96e865939930e90a7042a1cd4cbd202083786a78452fdfc000002a89e380a44d8dfc64b551baa171447a0f9c9262255be6e8f8ef10896e36e2bf90c4d343e416e394ad9cc10b7d2df7b2f39370a554730f75dfcb04944bd62c299", + "block_header": { + "block_size": 3166, + "block_weight": 3166, + "cumulative_difficulty": 235954020187853162, + "cumulative_difficulty_top64": 0, + "depth": 443814, + "difficulty": 312527777859, + "difficulty_top64": 0, + "hash": "86d421322b700166dde2d7eba1cc8600925ef640abf6c0a2cc8ce0d6dd90abfd", + "height": 2751210, + "long_term_weight": 176470, + "major_version": 16, + "miner_tx_hash": "dabe07900d3123ed895612f4a151adb3e39681b145f0f85bfee23ea1fe47acf2", + "minor_version": 16, + "nonce": 184625235, + "num_txes": 2, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "f8a36d0dbe4d27d2f52160000563896048d71067c31e99a3869bf9b7142227bb", + "reward": 600061380000, + "timestamp": 1667906904, + "wide_cumulative_difficulty": "0x34646ee649f516a", + "wide_difficulty": "0x48c41b7043" + }, + "credits": 0, + "json": "{\n \"major_version\": 16, \n \"minor_version\": 16, \n \"timestamp\": 1667906904, \n \"prev_id\": \"f8a36d0dbe4d27d2f52160000563896048d71067c31e99a3869bf9b7142227bb\", \n \"nonce\": 184625235, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2751270, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2751210\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 600061380000, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"6d6713f5aa552a1aaf33baed7591f795b86daf339e51029a9062dfe09f0f909b\", \n \"view_tag\": \"31\"\n }\n }\n }\n ], \n \"extra\": [ 1, 36, 214, 2, 61, 89, 28, 77, 67, 64, 0, 229, 227, 28, 109, 183, 24, 161, 233, 110, 134, 89, 57, 147, 14, 144, 167, 4, 42, 28, 212, 203, 210, 2, 8, 55, 134, 167, 132, 82, 253, 252, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ \"a89e380a44d8dfc64b551baa171447a0f9c9262255be6e8f8ef10896e36e2bf9\", \"0c4d343e416e394ad9cc10b7d2df7b2f39370a554730f75dfcb04944bd62c299\"\n ]\n}", + "miner_tx_hash": "dabe07900d3123ed895612f4a151adb3e39681b145f0f85bfee23ea1fe47acf2", + "status": "OK", + "top_hash": "", + "tx_hashes": ["a89e380a44d8dfc64b551baa171447a0f9c9262255be6e8f8ef10896e36e2bf9","0c4d343e416e394ad9cc10b7d2df7b2f39370a554730f75dfcb04944bd62c299"], + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_connections (json_rpc), + GET_CONNECTIONS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_connections" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "connections": [{ + "address": "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083", + "address_type": 4, + "avg_download": 0, + "avg_upload": 0, + "connection_id": "22ef856d0f1d44cc95e84fecfd065fe2", + "current_download": 0, + "current_upload": 0, + "height": 3195026, + "host": "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion", + "incoming": false, + "ip": "", + "live_time": 76651, + "local_ip": false, + "localhost": false, + "peer_id": "0000000000000001", + "port": "", + "pruning_seed": 0, + "recv_count": 240328, + "recv_idle_time": 34, + "rpc_credits_per_hash": 0, + "rpc_port": 0, + "send_count": 3406572, + "send_idle_time": 30, + "state": "normal", + "support_flags": 0 + },{ + "address": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083", + "address_type": 4, + "avg_download": 0, + "avg_upload": 0, + "connection_id": "c7734e15936f485a86d2b0534f87e499", + "current_download": 0, + "current_upload": 0, + "height": 3195024, + "host": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion", + "incoming": false, + "ip": "", + "live_time": 76755, + "local_ip": false, + "localhost": false, + "peer_id": "0000000000000001", + "port": "", + "pruning_seed": 389, + "recv_count": 237657, + "recv_idle_time": 120, + "rpc_credits_per_hash": 0, + "rpc_port": 0, + "send_count": 3370566, + "send_idle_time": 120, + "state": "normal", + "support_flags": 0 + }], + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_info (json_rpc), + GET_INFO: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_info" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "adjusted_time": 1721245289, + "alt_blocks_count": 16, + "block_size_limit": 600000, + "block_size_median": 300000, + "block_weight_limit": 600000, + "block_weight_median": 300000, + "bootstrap_daemon_address": "", + "busy_syncing": false, + "credits": 0, + "cumulative_difficulty": 366127702242611947, + "cumulative_difficulty_top64": 0, + "database_size": 235169075200, + "difficulty": 280716748706, + "difficulty_top64": 0, + "free_space": 30521749504, + "grey_peerlist_size": 4996, + "height": 3195028, + "height_without_bootstrap": 3195028, + "incoming_connections_count": 62, + "mainnet": true, + "nettype": "mainnet", + "offline": false, + "outgoing_connections_count": 1143, + "restricted": false, + "rpc_connections_count": 1, + "stagenet": false, + "start_time": 1720462427, + "status": "OK", + "synchronized": true, + "target": 120, + "target_height": 0, + "testnet": false, + "top_block_hash": "bdf06d18ed1931a8ee62654e9b6478cc459bc7072628b8e36f4524d339552946", + "top_hash": "", + "tx_count": 43205750, + "tx_pool_size": 12, + "untrusted": false, + "update_available": false, + "version": "0.18.3.3-release", + "was_bootstrap_ever_used": false, + "white_peerlist_size": 1000, + "wide_cumulative_difficulty": "0x514bf349299d2eb", + "wide_difficulty": "0x415c05a7a2" + } +}"#; +} + +define_request_and_response! { + hard_fork_info (json_rpc), + HARD_FORK_INFO: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "hard_fork_info", + "params": { + "version": 16 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "earliest_height": 2689608, + "enabled": true, + "state": 0, + "status": "OK", + "threshold": 0, + "top_hash": "", + "untrusted": false, + "version": 16, + "votes": 10080, + "voting": 16, + "window": 10080 + } +}"#; +} + +define_request_and_response! { + set_bans (json_rpc), + SET_BANS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "set_bans", + "params": { + "bans": [{ + "host": "192.168.1.51", + "ban": true, + "seconds": 30 + }] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + set_bans (json_rpc), + /// This is the same as [`SET_BANS_REQUEST`] and + /// [`SET_BANS_RESPONSE`] but it uses the `ip` parameter. + SET_BANS_IP: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "set_bans", + "params": { + "bans": [{ + "ip": 838969536, + "ban": true, + "seconds": 30 + }] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_bans (json_rpc), + GET_BANS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_bans" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "bans": [{ + "host": "104.248.206.131", + "ip": 2211379304, + "seconds": 689754 + },{ + "host": "209.222.252.0\/24", + "ip": 0, + "seconds": 689754 + }], + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + banned (json_rpc), + BANNED: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "banned", + "params": { + "address": "95.216.203.255" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "banned": true, + "seconds": 689655, + "status": "OK" + } +}"#; +} + +define_request_and_response! { + flush_txpool (json_rpc), + FLUSH_TRANSACTION_POOL: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "flush_txpool", + "params": { + "txids": ["dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308"] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK" + } +}"#; +} + +define_request_and_response! { + get_output_histogram (json_rpc), + GET_OUTPUT_HISTOGRAM: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_output_histogram", + "params": { + "amounts": [20000000000] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "histogram": [{ + "amount": 20000000000, + "recent_instances": 0, + "total_instances": 381490, + "unlocked_instances": 0 + }], + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_coinbase_tx_sum (json_rpc), + GET_COINBASE_TX_SUM: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_coinbase_tx_sum", + "params": { + "height": 1563078, + "count": 2 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "emission_amount": 9387854817320, + "emission_amount_top64": 0, + "fee_amount": 83981380000, + "fee_amount_top64": 0, + "status": "OK", + "top_hash": "", + "untrusted": false, + "wide_emission_amount": "0x889c7c06828", + "wide_fee_amount": "0x138dae29a0" + } +}"#; +} + +define_request_and_response! { + get_version (json_rpc), + GET_VERSION: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_version" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "current_height": 3195051, + "hard_forks": [{ + "height": 1, + "hf_version": 1 + },{ + "height": 1009827, + "hf_version": 2 + },{ + "height": 1141317, + "hf_version": 3 + },{ + "height": 1220516, + "hf_version": 4 + },{ + "height": 1288616, + "hf_version": 5 + },{ + "height": 1400000, + "hf_version": 6 + },{ + "height": 1546000, + "hf_version": 7 + },{ + "height": 1685555, + "hf_version": 8 + },{ + "height": 1686275, + "hf_version": 9 + },{ + "height": 1788000, + "hf_version": 10 + },{ + "height": 1788720, + "hf_version": 11 + },{ + "height": 1978433, + "hf_version": 12 + },{ + "height": 2210000, + "hf_version": 13 + },{ + "height": 2210720, + "hf_version": 14 + },{ + "height": 2688888, + "hf_version": 15 + },{ + "height": 2689608, + "hf_version": 16 + }], + "release": true, + "status": "OK", + "untrusted": false, + "version": 196621 + } +}"#; +} + +define_request_and_response! { + get_fee_estimate (json_rpc), + GET_FEE_ESTIMATE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_fee_estimate" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "fee": 20000, + "fees": [20000,80000,320000,4000000], + "quantization_mask": 10000, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_alternate_chains (json_rpc), + GET_ALTERNATE_CHAINS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_alternate_chains" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "chains": [{ + "block_hash": "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce", + "block_hashes": ["4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce"], + "difficulty": 357404825113208373, + "difficulty_top64": 0, + "height": 3167471, + "length": 1, + "main_chain_parent_block": "69b5075ea627d6ba06b1c30b7e023884eeaef5282cf58ec847dab838ddbcdd86", + "wide_difficulty": "0x4f5c1cb79e22635" + },{ + "block_hash": "33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401", + "block_hashes": ["33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401"], + "difficulty": 354736121711617293, + "difficulty_top64": 0, + "height": 3157465, + "length": 1, + "main_chain_parent_block": "fd522fcc4cefe5c8c0e5c5600981b3151772c285df3a4e38e5c4011cf466d2cb", + "wide_difficulty": "0x4ec469f8b9ee50d" + }], + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + relay_tx (json_rpc), + RELAY_TX: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "relay_tx", + "params": { + "txids": ["9fd75c429cbe52da9a52f2ffc5fbd107fe7fd2099c0d8de274dc8a67e0c98613"] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK" + } +}"#; +} + +define_request_and_response! { + sync_info (json_rpc), + SYNC_INFO: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "sync_info" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "height": 3195157, + "next_needed_pruning_seed": 0, + "overview": "[]", + "peers": [{ + "info": { + "address": "142.93.128.65:44986", + "address_type": 1, + "avg_download": 1, + "avg_upload": 1, + "connection_id": "a5803c4c2dac49e7b201dccdef54c862", + "current_download": 2, + "current_upload": 1, + "height": 3195157, + "host": "142.93.128.65", + "incoming": true, + "ip": "142.93.128.65", + "live_time": 18, + "local_ip": false, + "localhost": false, + "peer_id": "6830e9764d3e5687", + "port": "44986", + "pruning_seed": 0, + "recv_count": 20340, + "recv_idle_time": 0, + "rpc_credits_per_hash": 0, + "rpc_port": 18089, + "send_count": 32235, + "send_idle_time": 6, + "state": "normal", + "support_flags": 1 + } + },{ + "info": { + "address": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083", + "address_type": 4, + "avg_download": 0, + "avg_upload": 0, + "connection_id": "277f7c821bc546878c8bd29977e780f5", + "current_download": 0, + "current_upload": 0, + "height": 3195157, + "host": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion", + "incoming": false, + "ip": "", + "live_time": 2246, + "local_ip": false, + "localhost": false, + "peer_id": "0000000000000001", + "port": "", + "pruning_seed": 389, + "recv_count": 65164, + "recv_idle_time": 15, + "rpc_credits_per_hash": 0, + "rpc_port": 0, + "send_count": 99120, + "send_idle_time": 15, + "state": "normal", + "support_flags": 0 + } + }], + "status": "OK", + "target_height": 0, + "top_hash": "", + "untrusted": false + } +}"#; +} + +// TODO: binary string. +// define_request_and_response! { +// get_txpool_backlog (json_rpc), +// GET_TRANSACTION_POOL_BACKLOG: &str, +// Request = +// r#"{ +// "jsonrpc": "2.0", +// "id": "0", +// "method": "get_txpool_backlog" +// }"#; +// Response = +// r#"{ +// "id": "0", +// "jsonrpc": "2.0", +// "result": { +// "backlog": "...Binary...", +// "status": "OK", +// "untrusted": false +// } +// }"#; +// } + +define_request_and_response! { + get_output_distribution (json_rpc), + GET_OUTPUT_DISTRIBUTION: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_output_distribution", + "params": { + "amounts": [628780000], + "from_height": 1462078 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "distributions": [{ + "amount": 2628780000, + "base": 0, + "distribution": "", + "start_height": 1462078, + "binary": false + }], + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_miner_data (json_rpc), + GET_MINER_DATA: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_miner_data" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "already_generated_coins": 18186022843595960691, + "difficulty": "0x48afae42de", + "height": 2731375, + "major_version": 16, + "median_weight": 300000, + "prev_id": "78d50c5894d187c4946d54410990ca59a75017628174a9e8c7055fa4ca5c7c6d", + "seed_hash": "a6b869d50eca3a43ec26fe4c369859cf36ae37ce6ecb76457d31ffeb8a6ca8a6", + "status": "OK", + "tx_backlog": [{ + "fee": 30700000, + "id": "9868490d6bb9207fdd9cf17ca1f6c791b92ca97de0365855ea5c089f67c22208", + "weight": 1535 + },{ + "fee": 44280000, + "id": "b6000b02bbec71e18ad704bcae09fb6e5ae86d897ced14a718753e76e86c0a0a", + "weight": 2214 + }], + "untrusted": false + } +}"#; +} + +define_request_and_response! { + prune_blockchain (json_rpc), + PRUNE_BLOCKCHAIN: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "prune_blockchain", + "params": { + "check": true + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "pruned": true, + "pruning_seed": 387, + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + calc_pow (json_rpc), + CALC_POW: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "calc_pow", + "params": { + "major_version": 14, + "height": 2286447, + "block_blob": "0e0ed286da8006ecdc1aab3033cf1716c52f13f9d8ae0051615a2453643de94643b550d543becd0000000002abc78b0101ffefc68b0101fcfcf0d4b422025014bb4a1eade6622fd781cb1063381cad396efa69719b41aa28b4fce8c7ad4b5f019ce1dc670456b24a5e03c2d9058a2df10fec779e2579753b1847b74ee644f16b023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051399a1bc46a846474f5b33db24eae173a26393b976054ee14f9feefe99925233802867097564c9db7a36af5bb5ed33ab46e63092bd8d32cef121608c3258edd55562812e21cc7e3ac73045745a72f7d74581d9a0849d6f30e8b2923171253e864f4e9ddea3acb5bc755f1c4a878130a70c26297540bc0b7a57affb6b35c1f03d8dbd54ece8457531f8cba15bb74516779c01193e212050423020e45aa2c15dcb", + "seed_hash": "d432f499205150873b2572b5f033c9c6e4b7c6f3394bd2dd93822cd7085e7307" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": "d0402d6834e26fb94a9ce38c6424d27d2069896a9b8b1ce685d79936bca6e0a8" +}"#; +} + +define_request_and_response! { + flush_cache (json_rpc), + FLUSH_CACHE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "flush_cache", + "params": { + "bad_txs": true, + "bad_blocks": true + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + add_aux_pow (json_rpc), + ADD_AUX_POW: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "add_aux_pow", + "params": { + "blocktemplate_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "aux_pow": [{ + "id": "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8", + "hash": "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a" + }] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "aux_pow": [{ + "hash": "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a", + "id": "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8" + }], + "blockhashing_blob": "1010ee97e2a106e9f8ebe8887e5b609949ac8ea6143e560ed13552b110cb009b21f0cfca1eaccf00000000b2685c1283a646bc9020c758daa443be145b7370ce5a6efacb3e614117032e2c22", + "blocktemplate_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "merkle_root": "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a", + "merkle_tree_depth": 0, + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + UNDOCUMENTED_METHOD (json_rpc), + GET_TX_IDS_LOOSE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_txids_loose", + "params": { + "txid_template": "0000000000000000aea473c43708aa50b2c9eaf0e441aa209afc9b43458fb09e", + "num_matching_bits": 192 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "txids": "", + "status": "OK", + "untrusted": false + } +}"#; +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/test-utils/src/rpc/data/macros.rs b/test-utils/src/rpc/data/macros.rs new file mode 100644 index 0000000..5f87c53 --- /dev/null +++ b/test-utils/src/rpc/data/macros.rs @@ -0,0 +1,160 @@ +//! Macros. + +//---------------------------------------------------------------------------------------------------- define_request_and_response +/// A template for generating the RPC request and response `const` data. +/// +/// See the [`crate::json`] module for example usage. +/// +/// # Macro internals +/// This macro uses: +/// - [`define_request_and_response_doc`] +/// - [`define_request_and_response_test`] +macro_rules! define_request_and_response { + ( + // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. + // + // Adding `(json)` after this will trigger the macro to automatically + // add a `serde_json` test for the request/response data. + $monero_daemon_rpc_doc_link:ident $(($test:ident))?, + + // The base name. + // Attributes added here will apply to _both_ + // request and response types. + $( #[$attr:meta] )* + $name:ident: $type:ty, + + // The request type (and any doc comments, derives, etc). + $( #[$request_attr:meta] )* + Request = $request:expr_2021; + + // The response type (and any doc comments, derives, etc). + $( #[$response_attr:meta] )* + Response = $response:expr_2021; + ) => { paste::paste! { + #[doc = $crate::rpc::data::macros::define_request_and_response_doc!( + "response" => [<$name:upper _RESPONSE>], + $monero_daemon_rpc_doc_link, + )] + /// + $( #[$attr] )* + /// + $( #[$request_attr] )* + /// + $( + #[doc = $crate::rpc::data::macros::define_request_and_response_doc_test!([<$name:upper _REQUEST>], $test)] + )? + pub const [<$name:upper _REQUEST>]: $type = $request; + + #[doc = $crate::rpc::data::macros::define_request_and_response_doc!( + "request" => [<$name:upper _REQUEST>], + $monero_daemon_rpc_doc_link, + )] + /// + $( #[$attr] )* + /// + $( #[$response_attr] )* + /// + $( + #[doc = $crate::rpc::data::macros::define_request_and_response_doc_test!([<$name:upper _RESPONSE>], $test)] + )? + pub const [<$name:upper _RESPONSE>]: $type = $response; + }}; +} +pub(super) use define_request_and_response; + +//---------------------------------------------------------------------------------------------------- define_request_and_response_doc +/// Generate documentation for the types generated +/// by the [`define_request_and_response`] macro. +/// +/// See it for more info on inputs. +macro_rules! define_request_and_response_doc { + ( + // This labels the last `[request]` or `[response]` + // hyperlink in documentation. Input is either: + // - "request" + // - "response" + // + // Remember this is linking to the _other_ type, + // so if defining a `Request` type, input should + // be "response". + $request_or_response:literal => $request_or_response_type:ident, + $monero_daemon_rpc_doc_link:ident, + ) => { + concat!( + "", + "[Documentation](", + "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html", + "#", + stringify!($monero_daemon_rpc_doc_link), + "), [", + $request_or_response, + "](", + stringify!($request_or_response_type), + ")." + ) + }; +} +pub(super) use define_request_and_response_doc; + +//---------------------------------------------------------------------------------------------------- define_request_and_response_test +/// Generate documentation for the types generated +/// by the [`define_request_and_response`] macro. +/// +/// See it for more info on inputs. +macro_rules! define_request_and_response_doc_test { + // `/json_rpc` doc test. + ( + // The ident of the `const` request/response. + $name:ident, + json_rpc + ) => { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::json::*;\n", + "use serde_json::{to_value, Value};\n", + "\n", + "let value = serde_json::from_str::(&", + stringify!($name), + ").unwrap();\n", + "let Value::Object(map) = value else {\n", + " panic!();\n", + "};\n", + "\n", + r#"assert_eq!(map.get("jsonrpc").unwrap(), "2.0");"#, + "\n", + r#"map.get("id").unwrap();"#, + "\n\n", + r#"if map.get("method").is_some() {"#, + "\n", + r#" return;"#, + "\n", + "}\n", + "\n", + r#"if map.get("result").is_none() {"#, + "\n", + r#" map.get("error").unwrap();"#, + "\n", + "}\n", + "\n", + "```\n", + ) + }; + + // Other JSON endpoint doc test. + ( + $name:ident, + other + ) => { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use serde_json::{to_value, Value};\n", + "\n", + "let value = serde_json::from_str::(&", + stringify!($name), + ");\n", + "```\n", + ) + }; +} +pub(super) use define_request_and_response_doc_test; diff --git a/test-utils/src/rpc/data/mod.rs b/test-utils/src/rpc/data/mod.rs new file mode 100644 index 0000000..09f0d60 --- /dev/null +++ b/test-utils/src/rpc/data/mod.rs @@ -0,0 +1,18 @@ +//! Monero RPC data. +//! +//! This module contains real `monerod` RPC requests/responses +//! as `const` [`str`]s and byte arrays (binary). +//! +//! The strings include the JSON-RPC 2.0 portions of the JSON. +//! - Tests exist within this crate that ensure the JSON is valid +//! - Tests exist within Cuprate's `rpc/` crates that ensure these strings (de)serialize as valid types +//! +//! # Determinism +//! Note that although both request/response data is defined, +//! they aren't necessarily tied to each other, i.e. the request +//! will not deterministically lead to the response. + +pub mod bin; +pub mod json; +mod macros; +pub mod other; diff --git a/test-utils/src/rpc/data/other.rs b/test-utils/src/rpc/data/other.rs new file mode 100644 index 0000000..9af6d8b --- /dev/null +++ b/test-utils/src/rpc/data/other.rs @@ -0,0 +1,849 @@ +//! JSON data from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. + +//---------------------------------------------------------------------------------------------------- Import +use crate::rpc::data::macros::define_request_and_response; + +//---------------------------------------------------------------------------------------------------- TODO +define_request_and_response! { + // `(other)` adds a JSON sanity-check test. + get_height (other), + GET_HEIGHT: &str, + Request = "{}"; + Response = +r#"{ + "hash": "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f", + "height": 3195160, + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + get_transactions (other), + GET_TRANSACTIONS: &str, + Request = +r#"{ + "txs_hashes": ["d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408"] +}"#; + Response = +r#"{ + "credits": 0, + "status": "OK", + "top_hash": "", + "txs": [{ + "as_hex": "0100940102ffc7afa02501b3056ebee1b651a8da723462b4891d471b990ddc226049a0866d3029b8e2f75b70120280a0b6cef785020190dd0a200bd02b70ee707441a8863c5279b4e4d9f376dc97a140b1e5bc7d72bc5080690280c0caf384a30201d0b12b751e8f6e2e31316110fa6631bf2eb02e88ac8d778ec70d42b24ef54843fd75d90280d0dbc3f40201c498358287895f16b62a000a3f2fd8fb2e70d8e376858fb9ba7d9937d3a076e36311bb0280f092cbdd0801e5a230c6250d5835877b735c71d41587082309bf593d06a78def1b4ec57355a37838b5028080bfb59dd20d01c36c6dd3a9826658642ba4d1d586366f2782c0768c7e9fb93f32e8fdfab18c0228ed0280d0b8e1981a01bfb0158a530682f78754ab5b1b81b15891b2c7a22d4d7a929a5b51c066ffd73ac360230280f092cbdd0801f9a330a1217984cc5d31bf0e76ed4f8e3d4115f470824bc214fa84929fcede137173a60280e0bcefa75701f3910e3a8b3c031e15573f7a69db9f8dda3b3f960253099d8f844169212f2de772f6ff0280d0b8e1981a01adc1157920f2c72d6140afd4b858da3f41d07fc1655f2ebe593d32f96d5335d11711ee0280d0dbc3f40201ca8635a1373fa829f58e8f46d72c8e52aa1ce53fa1d798284ed08b44849e2e9ad79b620280a094a58d1d01faf729e5ab208fa809dd2efc6f0b74d3e7eff2a66c689a3b5c31c33c8a14e2359ac484028080e983b1de1601eced0182c8d37d77ce439824ddb3c8ff7bd60642181e183c409545c9d6f9c36683908f028080d194b57401ead50b3eefebb5303e14a5087de37ad1799a4592cf0e897eafb46d9b57257b5732949e0280a094a58d1d01d3882a1e949b2d1b6fc1fd5e44df95bae9068b090677d76b6c307188da44dd4e343cef028090cad2c60e0196c73a74a60fc4ce3a7b14d1abdf7a0c70a9efb490a9de6ec6208a846f8282d878132b028080bb8b939b4401c03dbcbfd9fb02e181d99c0093e53aceecf42bf6ccc0ec611a5093fe6f2b2738a07f0280f092cbdd0801b98d30c27f297ae4cb89fb7bb29ed11adff17db9b71d39edf736172892784897488c5d0280d0dbc3f40201da9a353d39555c27a2d620bf69136e4c665aaa19557d6fc0255cbb67ec69bf2403b63e0280b09dc2df0101f8820caab7a5e736f5445b5624837de46e9ef906cb538f7c860f688a7f7d155e19e0ac0280808d93f5d77101b544e62708eb27ff140b58c521e4a90acab5eca36f1ce9516a6318306f7d48beddbc0280a0b6cef7850201abdd0a5453712326722427f66b865e67f8cdb7188001aaacb70f1a018403d3289fcb130280c0caf384a30201a2b32b2cfb06b7022668b2cd5b8263a162511c03154b259ce91c6c97270e4c19efe4710280c0caf384a302018db32bda81bfbe5f9cdf94b20047d12a7fb5f097a83099fafdfedc03397826fb4d18d50280c0fc82aa0201b2e60b825e8c0360b4b44f4fe0a30f4d2f18c80d5bbb7bfc5ddf671f27b6867461c51d028080e983b1de1601b2eb0156dd7ab6dcb0970d4a5dbcb4e04281c1db350198e31893cec9b9d77863fedaf60280e08d84ddcb0101e0960fe3cafedb154111449c5112fc1d9e065222ed0243de3207c3e6f974941a66f177028080df9ad7949f01019815c8c5032f2c28e7e6c9f9c70f6fccdece659d8df53e54ad99a0f7fa5d831cf762028090dfc04a01b4fb123d97504b9d832f7041c4d1db1cda3b7a6d307194aff104ec6b711cced2b005e2028080dd9da41701bef1179c9a459e75a0c4cf4aff1a81f31f477bd682e28a155231da1a1aa7a25ef219910280d88ee16f01facd0f043485225a1e708aa89d71f951bc092724b53942a67a35b2315bfeac4e8af0eb0280d0dbc3f40201c4e634d6e1f3a1b232ef130d4a5417379c4fcc9d078f71899f0617cec8b1e72a1844b60280f092cbdd0801f6b9300c8c94a337fefc1c19f12dee0f2551a09ee0aaa954d1762c93fec8dadae2146c0280c0f9decfae0101ce8d09f26c90144257b5462791487fd1b017eb283268b1c86c859c4194bf1a987c62bf0280c0caf384a30201cead2bbb01653d0d7ff8a42958040814c3cbf228ebb772e03956b367bace3b684b9b7f0280a0e5b9c2910101c1b40c1796904ac003f7a6dd72b4845625e99ba12bdd003e65b2dd2760a4e460821178028080e983b1de160186e9013f55160cd9166756ea8e2c9af065dcdfb16a684e9376c909d18b65fd5306f9690280a0e5b9c2910101aeb70c4433f95ff4cdc4aa54a1ede9ae725cec06350db5d3056815486e761e381ae4d00280c0a8ca9a3a01ebe2139bd558b63ebb9f4d12aca270159ccf565e9cffaadd717ce200db779f202b106f0280d0dbc3f40201b9963568acf599958be4e72f71c3446332a39c815876c185198fa2dcf13877eba3627b0280c0f4c198af0b01bccc01408cbb5a210ad152bd6138639673a6161efd2f85be310b477ae14891870985f90280a0b6cef7850201cadd0a82e7950f5d9e62d14d0f7c6af84002ea9822cdeefabbb866b7a5776c6436636b028080d287e2bc2d01d888013a7146b96a7abc5ce5249b7981fb54250eef751964ff00530915084479b5d6ba028080d287e2bc2d018c8901d8cc1933366dceb49416b2f48fd2ce297cfd8da8baadc7f63856c46130368fca0280a0b787e90501b6d242f93a6570ad920332a354b14ad93b37c0f3815bb5fa2dcc7ca5e334256bd165320280a0e5b9c2910101a3ac0c4ed5ebf0c11285c351ddfd0bb52bd225ee0f8a319025dc416a5e2ba8e84186680280c0f9decfae0101f18709daddc52dccb6e1527ac83da15e19c2272206ee0b2ac37ac478b4dd3e6bcac5dc0280f8cce2840201b7c80bc872a1e1323d61342a2a7ac480b4b061163811497e08698057a8774493a1abe50280e0bcefa75701f38b0e532d34791916b1f56c3f008b2231de5cc62bd1ec898c62d19fb1ec716d467ae20280c0fc82aa0201d6da0b7de4dc001430473852620e13e5931960c55ab6ebeff574fbddea995cbc9d7c010280c0f4c198af0b01edca017ec6af4ece2622edaf9914cfb1cc6663639285256912d7d9d70e905120a6961c028090cad2c60e01cad43abcc63a192d53fe8269ecaf2d9ca3171c2172d85956fe44fcc1ac01efe4c610dd0280809aa6eaafe30101a92fccd2bcadfa42dcbd28d483d32abde14b377b72c4e6ef31a1f1e0ff6c2c9f452f0280a0b787e9050197d9420ce413f5321a785cd5bea4952e6c32acd0b733240a2ea2835747bb916a032da7028080d287e2bc2d01c48a01a3c4afcbbdac70524b27585c68ed1f8ea3858c1c392aeb7ada3432f3eed7cab10280f092cbdd0801abb130d362484a00ea63b2a250a8ae7cf8b904522638838a460653a3c37db1b76ff3de0280e08d84ddcb0101cc980fce5c2d8b34b3039e612adeb707f9ab397c75f76c1f0da8af92c64cd021976ff3028080dd9da4170198c217e4a7b63fd645bd27aa5afc6fae7db1e66896cece0d4b84ac76428268b5c213c30280a0b6cef7850201e3e20acab086a758c56372cf461e5339c09745ee510a785b540d68c7f62c35b8b6c5c10280a094a58d1d01ab842ac0ba57e87a3f204a56cbecca405419e4c896c817c5ced715d903a104a09735660280e0a596bb1101ade921f1ef2fe56c74320ceb1f6c52506d0b835808474b928ad6309398b42434d27f3d0280d0b8e1981a01dab515d684a324cff4b00753a9ef0868f308b8121cbc79077ede84d70cf6015ec989be0280c0ee8ed20b01f99c227da8d616ce3669517282902cdf1ef75e75a427385270d1a94197b93cf6620c15028080dd9da41701d2d0175c8494f151e585f01e80c303c84eea460b69874b773ba01d20f28a05916111a0028090dfc04a01a4f312c12a9f52a99f69e43979354446fd4e2ba5e2d5fb8aaa17cd25cdf591543149da0280d0dbc3f40201969c3510fbca0efa6d5b0c45dca32a5a91b10608594a58e5154d6a453493d4a0f10cf70280f8cce2840201ddcb0b76ca6a2df4544ea2d9634223becf72b6d6a176eae609d8a496ee7c0a45bec8240280e0bcefa7570180920e95d8d04f9f7a4b678497ded16da4caca0934fc019f582d8e1db1239920914d35028090cad2c60e01c2d43a30bbb2dcbb2b6c361dc49649a6cf733b29df5f6e7504b03a55ee707ed3db2c4e028080d287e2bc2d01c38801941b46cb00712de68cebc99945dc7b472b352c9a2e582a9217ea6d0b8c3f07590280e0a596bb1101b6eb219463e6e8aa6395eefc4e7d2d7d6484b5f684e7018fe56d3d6ddca82f4b89c5840280d0dbc3f40201db9535db1fb02f4a45c21eae26f5c40c01ab1bca304deac2fb08d2b3d9ac4f65fd10c60280a094a58d1d01948c2a413da2503eb92880f02f764c2133ed6f2951ae86e8c8c17d1e9e024ca4dc72320280c0ee8ed20b01869f22d3e106082527d6f0b052106a4850801fcd59d0b6ce61b237c2321111ed8bdf47028080d194b57401acd20b9c0e61b23698c4b4d47965a597284d409f71d7f16f4997bc04ba042d3cbe044d028090cad2c60e0194b83ac3b448f0bd45f069df6a80e49778c289edeb93b9f213039e53a828e685c270f90280a094a58d1d01bdfb2984b37167dce720d3972eaa50ba42ae1c73ce8e8bc15b5b420e55c9ae96e5ca8c028090dfc04a01abf3120595fbef2082079af5448c6d0d6491aa758576881c1839f4934fa5f6276b33810280e0a596bb1101f9ea2170a571f721540ec01ae22501138fa808045bb8d86b22b1be686b258b2cc999c5028088aca3cf02019cb60d1ffda55346c6612364a9f426a8b9942d9269bef1360f20b8f3ccf57e9996b5f70280e8eda1ba0101aff90c87588ff1bb510a30907357afbf6c3292892c2d9ff41e363889af32e70891cb9b028080d49ca7981201d65ee875df2a98544318a5f4e9aa70a799374b40cff820c132a388736b86ff6c7b7d0280c0caf384a30201dab52bbf532aa44298858b0a313d0f29953ea90efd3ac3421c674dbda79530e4a6b0060280f092cbdd0801c3ab30b0fc9f93dddc6c3e4d976e9c5e4cfee5bfd58415c96a3e7ec05a3172c29f223f0280a094a58d1d01a2812a3e0ec75af0330302c35c582d9a14c8e5f00a0bf84da22eec672c4926ca6fccb10280a094a58d1d01ca842a2b03a22e56f164bae94e43d1c353217c1a1048375731c0c47bb63216e1ef6c480280e08d84ddcb0101d68b0fb2d29505b3f25a8e36f17a2fde13bce41752ecec8c2042a7e1a7d65a0fd35cdf028090cad2c60e0199ce3afa0b62692f1b87324fd4904bf9ffd45ed169d1f5096634a3ba8602919681e5660280c0f9decfae010193ed081977c266b88f1c3cb7027c0216adb506f0e929ce650cd178b81645458c3af4c6028090cad2c60e01eec13a9cce0e6750904e5649907b0bdc6c6963f62fef41ef3932765418d933fc1cd97a0280c0ee8ed20b019ea8228d467474d1073d5c906acdec6ee799b71e16141930c9d66b7f181dbd7a6e924a028080bb8b939b4401c23d3cb4e840ad6766bb0fd6d2b81462f1e4828d2eae341ce3bd8d7ce38b036ac6fb028080e983b1de1601b9e601e3cf485fa441836d83d1f1be6d8611599eccc29f3af832b922e45ab1cd7f31d00280f092cbdd0801fc9e30fff408d7b0c5d88f662dddb5d06ff382baa06191102278e54a0030f7e3246e7c0280d88ee16f01bfcd0f96a24f27ac225278701c6b54df41c6aa511dd86ce682516fb1824ff104c572fb0280f092cbdd0801cbb430bd5f343e45c62efcd6e0f62e77ceb3c95ef945b0cff7002872ea350b5dfffef10280c0caf384a30201bfb22b14dccbba4582da488aef91b530395979f73fa83511e3b3bcb311221c6832b18d0280a0b6cef7850201c4dc0a31fb268316ab21229072833191b7a77b9832afea700a1b93f2e86fb31be9480f028090cad2c60e01cab63a313af96a15c06fcf1f1cf10b69eae50e2c1d005357df457768d384b7a35fa0bb0280d0dbc3f40201fe9835fffd89020879ec3ca02db3eadbb81b0c34a6d77f9d1031438d55fd9c33827db00280d0dbc3f40201d19b354a25ddf2641fc7e000f9306df1c6bf731bddfe9ab148713781bbfab4814ed87e0280e08d84ddcb0101ba960fec80e1dcda9fae2d63e14500354f191f287811f5503e269c9ec1ae56cef4cd110280a0b787e90501acde42b0bdfd00ab0518c8bd6938f0a6efab1b1762704e86c71a154f6d6378dd63ce840280e0bcefa75701b5900eedc2ce12618978351788e46fefe8b9b776510ec32add7423f649c613b9db853a028080e983b1de1601edeb01d68b226cd6b71903a812aa6a6f0799381cf6f70870810df560042cd732b26526028080f696a6b6880101ca18a91fd53d6370a95ca2e0700aabc3784e355afcafb25656c70d780de90e30be31028090cad2c60e0184c03adc307ee3753a20f8f687344aae487639ab12276b604b1f74789d47f1371cac6b0280c0fc82aa0201a2dc0b000aa40a7e3e44d0181aaa5cc64df6307cf119798797fbf82421e3b78a0aa2760280e8eda1ba0101daf20caa8a7c80b400f4dd189e4a00ef1074e26fcc186fed46f0d97814c464aa7561e20280c0f9decfae0101a18b092ee7d48a9fb88cefb22874e5a1ed7a1bf99cc06e93e55c7f75ca4bf38ad185a60280a094a58d1d01dff92904ef53b1415cdb435a1589c072a7e6bd8e69a31bf31153c3beb07ebf585aa838028080bfb59dd20d01916c9d21b580aed847f256b4f507f562858396a9d392adc92b7ed3585d78acf9b38b028080a2a9eae80101fab80b153098181e5fabf1056b4e88db7ce5ed875132e3b7d78ed3b6fc528edda921050280d88ee16f019fcf0fd5f4d68c9afe2e543c125963043024fe557e817c279dbd0602b158fe96ec4b6f0280e0bcefa75701d1910e44b59722c588c30a65b920fc72e0e58c5acc1535b4cad4fc889a89fccfa271510280d0dbc3f40201b78b358b066d485145ada1c39153caacf843fcd9c2f4681d226d210a9a9942109314d90280e0bcefa75701a88b0e5f100858379f9edbbfe92d8f3de825990af354e38edc3b0d246d8a62f01ab3220280d0dbc3f40201959135c6a904269f0bf29fbf8cef1f705dde8c7364ba4618ad9ee378b69a3d590af5680280e0a596bb1101edeb2140e07858aa36619a74b0944c52663b7d3818ab6bf9e66ee792cda1b6dd39842e0280c0a8ca9a3a01aae213a90a6708e741f688a32fb5f1b800800e64cfd341c0f82f8e1ca822336d70c78e0280c0fc82aa02018ddf0b5e03adc078c32952c9077fee655a65a933558c610f23245cd7416669da12611e0280f092cbdd0801aca0305b7157269b35d5068d64f8d43386e8463f2893695bc94f07b4a14f9f5c85e8c50280e0bcefa75701b18f0efd26a0ad840829429252c7e6db2ff0eb7980a8f4c4e400b3a68475f6831cc5f50280b09dc2df0101a6830c2b7555fd29e82d1f0cf6a00f2c671c94c3c683254853c045519d1c5d5dc314fb028080bb8b939b4401be3d76fcfea2c6216513382a75aedaba8932f339ed56f4aad33fb04565429c7f7fa50280c0ee8ed20b01b4a322218a5fd3a13ed0847e8165a28861fb3edc0e2c1603e95e042e2cbb0040e49ab50280c0caf384a30201ecb42b7c10020495d95b3c1ea45ce8709ff4a181771fc053911e5ec51d237d585f19610280f092cbdd0801eba3309533ea103add0540f9624cb24c5cecadf4b93ceb39aa2e541668a0dd23bf3f2f028090dfc04a01a6f3121520ad99387cf4dd779410542b3f5ed9991f0fadbb40f292be0057f4a1dfbf10028090cad2c60e019ac83a125492706ba043a4e3b927ab451c8dccb4b798f83312320dcf4d306bc45c3016028080a2a9eae80101b4ba0bd413da8f7f0aad9cd41d728b4fef20e31fbc61fc397a585c6755134406680b14028080d49ca798120192600ef342c8cf4c0e9ebf52986429add3de7f7757c3d5f7c951810b2fb5352aec620280a0b787e90501afe442797256544eb3515e6fa45b1785d65816dd179bd7f0372a561709f87fae7f95f10280a094a58d1d01dc882aacbd3e13a0b97c2a08b6b6deec5e9685b94409d30c774c85a373b252169d588f028090dfc04a0184f81225e7ded2e83d4f9f0ee64f60c9c8bce2dcb110fd2f3d66c17aafdff53fbf6bbe028080d287e2bc2d01d18901e2fd0eeb4fe9223b4610e05022fcc194240e8afe5472fceda8346cb5b66a0a5902808095e789c60401cf88036cf7317af6dc47cd0ce319a51aaaf936854287c07a24afad791a1431cbd2df5c0280c0f9decfae0101999909d038b9c30a2de009813e56ba2ba17964a24d5f195aaa5f7f2f5fefacd69893e80280a0e5b9c291010199b10cf336c49e2864d07ad3c7a0b9a19e0c17aaf0e72f9fcc980180272000fe5ba1260280a0b6cef7850201a2e20a7a870af412e8fff7eba50b2f8f3be318736996a347fa1222019be9971b6f9b81028090dfc04a01bae5127889a54246328815e9819a05eea4c93bdfffaa2a2cc9747c5d8e74a9a4a8bfe10280f8cce284020191da0b24ee29cd3f554bb618f336dd2841ba23168bf123ee88ebdb48bcbb033a67a02f0280f8cce2840201e6c30b2756e87b0b6ff35103c20c1ddb3b0502f712977fd7909a0b552f1c7dfc3e0c3c028080e983b1de16018fed01a3c245ee280ff115f7e92b16dc2c25831a2da6af5321ad76a1fbbcdd6afc780c0280e0bcefa7570183920ef957193122bb2624d28c0a3cbd4370a1cfff4e1c2e0c8bb22d4c4b47e7f0a5a60280f092cbdd0801ccab30f5440aceabe0c8c408dddf755f789fae2afbf21a64bc183f2d4218a8a792f2870280e08d84ddcb0101f8870f8e26eacca06623c8291d2b29d26ca7f476f09e89c21302d0b85e144267b2712a028080aace938c0901b0b1014c9b9fab49660c2067f4b60151427cf415aa0887447da450652f83a8027524170580b09dc2df01028c792dea94dab48160e067fb681edd6247ba375281fbcfedc03cb970f3b98e2d80b081daaf14021ab33e69737e157d23e33274c42793be06a8711670e73fa10ecebc604f87cc7180a0b6cef78502020752a6308df9466f0838c978216926cb69e113761e84446d5c8453863f06a05c808095e789c60402edc8db59ee3f13d361537cb65c6c89b218e5580a8fbaf9734e9dc71c26a996d780809ce5fd9ed40a024d3ae5019faae01f3e7ae5e978ae0f6a4344976c414b617146f7e76d9c6020c52101038c6d9ccd2f949909115d5321a26e98018b4679138a0a2c06378cf27c8fdbacfd82214a59c99d9251fa00126d353f9cf502a80d8993a6c223e3c802a40ab405555637f495903d3ba558312881e586d452e6e95826d8e128345f6c0a8f9f350e8c04ef50cf34afa3a9ec19c457143496f8cf7045ed869b581f9efa2f1d65e30f1cec5272b00e9c61a34bdd3c78cf82ae8ef4df3132f70861391069b9c255cd0875496ee376e033ee44f8a2d5605a19c88c07f354483a4144f1116143bb212f02fafb5ef7190ce928d2ab32601de56eb944b76935138ca496a345b89b54526a0265614d7932ac0b99289b75b2e18eb4f2918de8cb419bf19d916569b8f90e450bb5bc0da806b7081ecf36da21737ec52379a143632ef483633059741002ee520807713c344b38f710b904c806cf93d3f604111e6565de5f4a64e9d7ea5c24140965684e03cefcb9064ecefb46b82bb5589a6b9baac1800bed502bbc6636ad92026f57fdf2839f36726b0d69615a03b35bb182ec1ef1dcd790a259127a65208e08ea0dd55c8f8cd993c32458562638cf1fb09f77aa7f40e3fecc432f16b2396d0cb7239f7e9f5600bdface5d5f5c0285a9dca1096bd033c4ccf9ceebe063c01e0ec6e2d551189a3d70ae6214a22cd79322de7710ac834c98955d93a5aeed21f900792a98210a1a4a44a17901de0d93e20863a04903e2e77eb119b31c9971653f070ddec02bd08a577bf132323ccf763d6bfc615f1a35802877c6703b70ab7216089a3d5f9b9eacb55ba430484155cb195f736d6c094528b29d3e01032fe61c2c07da6618cf5edad594056db4f6db44adb47721616c4c70e770661634d436e6e90cbcdfdb44603948338401a6ba60c64ca6b51bbf493ecd99ccddd92e6cad20160b0b983744f90cdc4260f60b0776af7c9e664eeb5394ee1182fb6881026271db0a9aad0764782ba106074a0576239681ecae941a9ef56b7b6dda7dbf08ecafac08ab8302d52ee495e4403f2c8b9b18d53ac3863e22d4181688f2bda37943afbf04a436302498f2298b50761eb6e1f43f6354bdc79671b9e97fa239f77924683904e0cf6b1351d4535393a9352d27b007dfda7a8ae8b767e2b5241313d7b5daf20523a80dd6cc9c049da66a5d23f76c132a85d772c45b4c10f2032f58b90c862f09f625cbd18c91a37bb3fc3a413a2e081618da845910cf5b2e6bffea555e883b0bb9c5f9063380a1c33ebdb764d9ffefe9e3169de40b18eeb9bfca48296457bb0b4e29d7b2b5bc4e0021ba0a1d389f77a8e253d6db149d400f675a9330f3bcfd09c7169224a947b6b4e0745ae08cd7adea4151277a94f51f85292ba082cf28300cca233ff4966b093c9cb6abcef476026040fec2b435021aff717b8bb90a40950e010f70bb416a618dc3c5c03f590c5b7ec8e0c05b85ba94078de4817918f783022364b8aa228b5df43b38fba3060c30616f265022584ab6034ddbc832450f90047d0cf41a4af8a20fb1aa66406133a17c2e905ee28d8acd186c872859c196db0474dfaaaded2d63768143cf6b5e2e34662f7bae573a08cb15069ef881892e5a0c08b5c6c7b2e6376cd2080fb29e8d3d5aa5b853662b4f1784ba7f072130e4dc00cba3cc9278fc4213f2ce2fc82bd1ea9fa91bb17b4f7c36962c78d864eab9f30ef327039da6607962a156a05c384a4a58ddd8f51a0d4fe91f64ae7b0a5199110a66f1e676392ec8d31b20a65f7a7fcff90b37a8a3962bff0c83ee6033a70c5b0af663ca48a8f22ced255839444fc51f5b6a6c1237eda5804289aa25fc93f14d0d4a63cecfa30d213eb3b2497af4a22396cc8c0e7c8b8bb57be8878bfc7fb29c038d39cf9fe0c964ebac13354a580527b1dbaced58500a292eb5f7cdafc772860f8d5c324a7079de9e0c1382228effaf2ac0278ebedad1117c5edacf08105a3f0905bca6e59cdf9fd074e1fbb53628a3d9bf3b7be28b33747438a12ae4fed62d035aa49965912839e41d35206a87fff7f79c686584cc23f38277db146dc4bebd0e612edf8b031021e88d1134188cde11bb6ea30883e6a0b0cc38ababe1eb55bf06f26955f25c25c93f40c77f27423131a7769719b09225723dd5283192f74c8a050829fc6fdec46e708111c2bcb1f562a00e831c804fad7a1f74a9be75a7e1720a552f8bd135b6d2b8e8e2a7712b562c33ec9e1030224c0cfc7a6f3b5dc2e6bd02a98d25c73b3a168daa768b70b8aef5bd12f362a89725571c4a82a06d55b22e071a30e15b006a8ea03012d2bb9a7c6a90b7fbd012efbb1c4fa4f35b2a2a2e4f0d54c4e125084208e096cdee54b2763c0f6fbd1f4f341d8829a2d551bfb889e30ca3af81b2fbecc10d0f106845b73475ec5033baab1bad777c23fa55704ba14e01d228597339f3ba6b6caaaa53a8c701b513c4272ff617494277baf9cdea37870ce0d3c03203f93a4ce87b63c577a9d41a7ccbf1c9d0bcdecd8b72a71e9b911b014e172ff24bc63ba064f6fde212df25c40e88257c92f8bc35c4139f058748b00fa511755d9ae5a7b2b2bdf7cdca13b3171ca85a0a1f75c3cae1983c7da7c748076a1c0d2669e7b2e6b71913677af2bc1a21f1c7c436509514320e248015798a050b2cbb1b076cd5eb72cc336d2aad290f959dc6636a050b0811933b01ea25ec006688da1b7e8b4bb963fbe8bc06b5f716a96b15e22be7f8b99b8feba54ac74f080b799ea3a7599daa723067bf837ca32d8921b7584b17d708971fb21cbb8a2808c7da811cff4967363fe7748f0f8378b7c14dd7a5bd10055c78ccb8b8e8b88206317f35dcad0cb2951e5eb7697d484c63764483f7bbc0ad3ca41630fc76a44006e310249d8a73d7f9ca7ce648b5602b331afb584a3e1db1ec9f2a1fc1d030650557c7dbc62008235911677709dea7b60c8d400c9da16b4b0a988b25e5cf26c00c3ef02812def049bb149ea635280e5b339db1035b7275e154b587cc50464a4c0bfd15c79f54faa10fbe571b73cf1aa4a20746b11c80c8c95899521fe5f0bb3104b0a050c55a79511e202fee30c005339694b18f4e18ab5e36ea21952a01864a0e067d9f19362e009a21c6c1a798f7c1325edd95e98fd1f9cb544909fdf9d076070d1233e183fb6d46a46fbc6e10452ef4c45fa0b88a84962ad6e91cbcc52bc000b12a82e93ae5998b20ee9000a8ef68ec8a44862cc108869fd388142692be6b0657e3fe79eff0e8b72f63aeec5874acf5fb0bfc9fa22645ed6ecaaf186eca690ecdf8a71b8f4789ac41b1f4f7539e04c53dd05e67488ea5849bf069d4eefc040273f6018819fdcbaa170c2ce078062b7bbe951d2214b077c4c836db85e1b138059c382ab408a65a3b94132136945cc4a3974c0f96d88eaa1b07cce02dce04ea0126e6210a9543129bb8296839949f6c3867243d4b0e1ff32be58c188ba905d40e32c53f7871920967210de94f71709f73e826036b4e3fa3e42c23f2912f4ea50557dff78aeb34cb35444965614812cbe14068a62be075fce6bf3310b9e8b12e0dd8379104360f728d47a327c172257134e2c0e7c32e01321f4d636f9047bd750e7993eeda7d39fc16f29696b1becee4d8026e967f8149935b947fce8517b2ce02b7831a232f3a29010129c49494ed2b84c7f881b7e4b02a00ebabf5a36023c404002d6cb88cee76c8ce97b03143ca867359d7e118d54e053b02c94998e6fd8409f8d46fc1741a2e56aebb1e7dab7ca3296a2566263d9be2f4bbef4872a49ee1082cbaf86e21b0c232c4182fc660f0c0b6aaeb0393750e553bc406e2a27842bd033da45a562ed1998ef9bd83e35ed813bef00a3e6147cb363bee63c543ba5e770b043dbacc155214a2496f91879bbc9170a2a513d7b48fad40c8c2d96f951e3a0932f6d12956789198430b352803852aa9726163fbe839979b33f8dbf7f76cd50755c1ce0c40a072aeec35057d06abaf59e878000b1d796e51908bfbf23b13900dcb30f9bd52b52994e7245a7017653a404a70d1c444b8c613ff10a2b057c02d062c5faf13cdc4445809fb6e096923cdbbdca18f59318ff86c7e449f596050b404d3cde0338dfdf9b1389178b1d4c70eefa2bbd76fefc1ee1f1688ef507821e40ae31d8d8e673d183b54563e2cbd27e0e042f61b046877d37a68c1b5784830690f2dd4ebbbd2dbdb35800b9e0ba8ea985fa106dd2ce8493e845586716c538ee9008b88a7c482f3c00c14c08468230d40cdc040e145282c4d61985cb5800306e305146204f63e96ad194bcdf1338ab8480341b6fbccf18fc32145f84bece4069c09e41096e94c24fa4f0db988e860a3bff3604143f2b17e8c219f28189e4cd49a0e506fe62dc419299bcd78c6ccb107f63eb31b4bd8ea1e2fed10e3ac17341d3505019e2376b01f7a7fcea3db110fb090c681c866ac86f13e6f8d44a32861e0580def063736b5c771b2b3b9067045867b4393f3eb2a4610bd0216e29906aaac370986451c6bf78264dda7e7a5fcbcf7bd6e024ff6003c6db780d89b97765cee8d0ff3ff25d94d4b4b919f722b26a6903a017daa62af387843087680c57952de06064de05b662af87be49b6e34cf0991cec7be3396e2eec9678ba259bd8de1c192014d02928f9113488215658df4078ed661fa4e79e58decaeb0ee5a00488b094b0b77f083b2b7844f481e7788ffe8004b96ccdf853532bfd9632a8a652c2d97d10173c90864fbb6facf47fae415df4acc0b099140a657b35d083d74dbdfbf107303e74c64471bed4b2199f2babcb4e1fc593d6f309e21f85e68ffd9904731559d0f2b673b36d3984e5d66d897dfa17d601edef3ed78cb70dc5115d4ae240c203e031263f0cf1e98075bac0361fde24cbcb852b8055d53ae01d61a0a1e1ba423d00833747e7364df7ebfd1f84598d801c249e1805279dc37d39fc7f7e27b067e4e0287aec432ed49e4d701a0ff377e88179968430d110cb20476ed4c6bf1624d1907ef24406d3295fcacde2a102cc85f4f3d0cb87a8fae7535a06e442833e58cfc04242ff85fb654d05f9874c0a6756f542db4e9d8b0366191fbb8b09a1bbcb6af04c069978417ca80d92f442b7dbd092f74e1268aa73b54e4b64e84543449ecd30b5ea392a1669a5f441d7208925e91c75df611cd26042630c6b98f160b8c0156048108d5465b71bbc54d31a9f90e34428d97590a427e1ae618d4a35fc1022d4e007c6108dcb1672b88d43ae4d886a5adcc26faf56bc5e5a0b08342fb88263fd80940d1edf794c6ad6d339b974e164b38439e11b4fa87cc793b080b4f8bf0eb56043f79ed3911da21092475fcf8320b55b9f558f194c6c8121b2e696039340d97057be2583726d762b5ae4327e5286a2d8c14ddbe0027c75aacbf7e9de13037390df7d72e13b46bc06bad0363b070e0174d034120d7fa7b4550e7dc28f7f0241f059ae266fc13dccd1d07f744208a7d6a2e565b6613d46e4550f79ef3209c46a805b97284df558719e131f44e419e690f4fc28ee4862b9d1f8f7e1a164ac18141076087693e70ac76a10f7851530d4cbc65def90d5544671ad64249569c3abf0200d09be3c63efaa7cb723b39ccffc9b3a3ba0c8426847123d2a881efbd4937a40cb3e8011c70ba427f80b3dc91a608086f8b61f8bd86fcb482ea388299a7cfbd00a3ddfadb4b6d0e51c1369276c25889a9f3592900b6502d9af1c732e1fb7db307d71e45deb1553ba1568d0480ea9e132b52564da6ac5c56eff823e7f37976cd075ce8f7a78aaef1b87f7437a0b84035677f266f7d0596e493101fec3e14fcf80b22322454587b51fda0636231c07d4e63e007f1b89137d8a37b03bf00f3a7c10169f757d9a74b7bffba797c746e3845decc3d0559d7cf6f08f3bd67dac5f33109b212582dc7df5d561ad63dddc794f2aea4e493db1a73c702d258c9b922c35d04c47f88f87c54c821a29f04abd91a079ce8cef252a21dc72d409fd1618c9be709af029ba98b0140e74666fcb01bced4f88ab68e6b63b8ed6febc0905d22cb2200493c071ce136833697406f8a04e77b21747dda997046cf3c7080096fe481790d77cf5904e7f7128ed95a6e576d109fdf10eb0c888db35a4a685b62253987b70fb1538e6c0932889460fa31c60d123266b7bcb828f846a35b2851127679b05f05a75266529c343a6075e54c455e02b2c83e6f7bf1ae23326506a5f532472d780815c5af425f7d8b543a8f014966e0538f48ca84d181695381a09701eb65c9ae084bf2a4dc84f1b2071be32be25d5f4fcdc59668fd800496ef7eb6dddf867ab908e543cb51f0451706cce4be6b9f68a537a79ea88e17fcd78965b3da68c0d9d30623a2a9e275e1c320f59e118e09c02eee527167bc06f7693e7b584e3b25ecc1093d46b80a1cacced87c2b32e2f90c5bbb9cd1b701aae69a04b16d535fac6eab0d091790fc5fdfa8a8842bfcb62dbf963cbf62c4afb4c468be98c770e6078b8c0a8cfcbae43dcfff17d3c6d587c3e4309fd39c66acd14781fea66fc57278b02302c0fa386280e67acff19955b6a428b0e22ceb1e54e913a37cd19eb6e9d2268a039f2b5fdda7d5804db79385f0e50082b128c952f8dfdedc4411d0675d95127f0bfc01710a869b10d7a8b9e632dad944062567439e6d192fb09329d058e87ecd0aa8981328f541e87ed02cfe4031f2d3a046ff517a2a80486b04ade31a647aec0884fb96ed753ffc47892431c6e6f08fd1c633a1a44e882d3d8b92c567e0fb8305327a354851464ca0f18d89c6ee2a91a4afef0c55883acf8fcb68c2c3b7402e005d8affc19c13f1f26fee0698dff181ab22cb84a2b31e0a6a81dc5d02e60a3c07090397ae58a985526b2ad6ee5725e82328062b68566b4871705ce3b9856e550d068c20fd9aaeb27740c07aad53d79fc20e46e40e7103e2d69626ee64b6aa600f6f1a86f37948ff4990d88f43c34994e2fe586cb779997be323da53329c10480aeb08fe440e9e4b979171371c73b94da9f928a3f6c8f6792f782f3d6432b86d06f54557327fef31fd6ae0a3f6d2f16c9ad947d132e14def33fa24cb4565370e0832fa50f5f5f93c9f3d65776cc22608b68a4f3719e9be47a19432991e4a2c49089c0ea20e7f7c73feaa47970da424d8543d80d622e2f2be9f4c65cc39dc369009a9d41a52bdea7cc0e8e04da87a633fd4f814fda1b646121a469ba0b5b8006d0e9118761d97b5d1856e2d690f27a81c42b176df853d07cf4a66ee83c9eb24ac0a382f5143a10a33ec3ddf17dcd8a8303fac8f279d31b4d04d74bd8804cefbb400c86174ad444e43ed33ee1e1e73f660b9814d5ca3cb1d650f1978a825a617bb05f84eab3b9b8359b991e1084cf4e8179ecb67f92398638e31227ff63427b67f0f232b454a341d85d4b56e31135c9035e231f7d9318ca12b5ab524f87bb0ca9b04b80effed202897ab016d5acc054c4fe62a5f0192f136cf2cd714998a4b164b0c2cdbace52243fdc9ea879b0d247d4fe8bd80481fad6b325cb5f2cfa2534dec0e47d41b6b99352e6e5faccb5ee28ca2fe96e04f9c83a0461ba34cfb499d864f05dc734b6c8f51cc0c994290b2a868cb8312df39fb6d457a81e62d872c65d4f3007094be42663bca3d64ebbcc8401158fce4f5a38a49c20f029c338f126451820459866e77c6984a467aad571cc7452392a9cb9f8e65099fff2f2acd170a833e01ed3d22a683356ee42dcbe6bab6d05d3edda2d40e9ba53884d430c2e0cd87c0067dc8cb68c868bd9f29db1dd73703c139ffc15e4f7264e727c70560ae02da100871f30e8a7c1275805f621752d73aafecddc2a7808b6c2ecbb8d0134a644bb603f30f8d18b3fc5efaa7f206ce180bfb14f5dbd3b0115145a227113eeaf1c1ec04244227b931388e72960833a40baa4319c5cf74aa94f7e3233e1d09f0a4f74409999684ad1cc836ac74563c85b164664dfab08ea084b25e2cbd7e7b94a781a10fcd455ee38b65126dcc52016127fd195c80b05660ed931e128b0cb92868955c0d032ada9fb951210a5442d21c1718ebc4702ad4a57967e15a63ffb05e1e072a0c41ebdf1e7205373eeaf4587f695de887fa3a8c96b8deb99e040fa1fc4dc2a402a017891943d734ae2f3798b22b1269d3d9f6d65581b9c637a6896a4fb554810bbd3db5c5737391a74150b43413b2e3824490b7911cbeb845147f1a8521620b0dd31306f13a9754a01bcdbd18bfdeade06b0ec97f48df56c45d3670a1fe18d00ef13e613c8a77aeb40401a814b377137cf44f29cb2cb94186ad1161ecb05a7c07837a5ab3474e57990cff2ab16b4d99f62e646da28e8bb712a5b561cf0e25be039c3e08583c8ebc3dd2fdb8fdc6e135ecc7851c73218a70b75e697cc84ea50504b9c34a33ed52f87230b9d192a940f3b7bb6d45b58dbf52f0afeb8dac85c77b06bdf9b70a10cb81c50055c9d8cf7e3a5c4b7dfae55beabcb3e8a8a1cb822d8d0bf6c01e32056929f853021eae6c97fdb0c5031df6b2e7c57f1318866769a9cc09c38ed62d8bf4663334c0df67c47236ed73f6ce7f54e0ada9270398c1aa558d0f993b0d25d97aea77b1635ee4832362cd590bae5fc1549402ddcd42b15efc930111a01535c0242116078d6d2d53b8612d378c4370e90d0d01b01bd7da591bec07981652a98485d8ed5c8f3def2bdac7d992ee5fc6a1ec7bd36940e1bc58c7050451248fc3ee6069e6b1b0d3ef122c6ef2a9b99aa0f145fb43341c58dbb472130b51730c956273a3ef6df9e000f6a87c2bacdefcdb5daef28b6170f61bc3a9c101f439755c86e6b85ee06a7a60688b3843eb359cd4acd9221a2ee131e2fd2e190652e5c47c0b98c41010eb99a991ec48a5de99cc8f403d6d76f8307d6657c1e007ebd64eec7bbd0d4f1ba2db7bb0efe27c7828f053e00def775943ab01a7e33d0fffcfe6f9a7285237f2c381b638758e373f8ceac672190664bb25fb5d355c240bd1773d61bda7f7ef1f4261b80ff5058ec6f7e024ab9459b1103815624b81f80c39db2f6fecb72de452b11636b0f71b16cb55f883d93bebb94328f13ef1ab6d0df449e32d27884f5139af584035547dace65ee25ba05cc461e74760d4468af90dcaa982e52cb902e2b84b3324019da575601ca54e91655913892e703257deaa01d14fd8459ff780c724161ba4d4280b70a5039dcfb5d775560714009724cb0d0b7e178c71e777b896bcfcde7d4c9c3dc6ab819d74a1a1fda8486448b1ad79be02fb134ea93a8600f1bc2a42e68d0213ab461a07cef3ad3965bc130beb76bab409102f82bf6c4cd626f6df3388e17b87584310c50832cde3191f6557f0014bdc0a68d924119e43111043bc6f26d16a5f2612dae6ab24984e2d87a71d93d5f4670dba2176d4f16633407bf7c10b51b6842dfdbc6fe3eaa4b6a12f0550700ece070ca382dec3b587e0e1fc317a48a83754d15aaf9a6971b8cb641fd8b32846d89002e6301700a0e7056e8002d8f269d29ebaf64f4493b1f1e676fc78e673067fb00625df15dc0490235b386ee14e55b335f3bc6dcedd7d3a80fd3a6e9bc2ccf3af0d89be71b5ca92bd7a9b97b9ff8976f75702419aa5bf9be34600496ca1bfa8ad0400602a23579365574252434f2bcd7efb360b0e8a495e8f7e78923b6fbf2207049e9179f0d4d7d6b4a4a10ca10f0ef4dd6cb5a74f7574e832044d6120fbc1580a68eddfbc65ab300bed960a6f24a102dc36b72937a8be4385daf5946e81ccde0619251babbff17e5685217a134d22f6130d0322483b3475227ffd27adc73ca202a6debfa37e5731747f4449ac70a33684f460eede65918c6d89acf4b50fd28d040ffbd436a944d3be0210606bfc2301e7ac66d462dba29a0489eb55af714a760e5302592cccc726e535b945ceb6126eb84e31f0f140ff54df8be0fa3a22f418036ad996787a5616a97a42049ebce351dc11857cab3dc914ef26833b0e75653004a8cafea099fb0750135255c41ef43e2f29c75714e2f0be2545e7c109b70c43004a471daa85b47befc65907d033f133b2f3ac2ad568df630ee80506610b8dc9052d442668dc06b13ea76ab1ab7b34870341d660af5d3007c21bb72512e4f8a60d8916a037b93f9e15ac9e4a6a1246d73ebb40e5fdd5a0d6dc0cf175023b891301f69fe5a3ca6f12cb8312d16333de1cd3ebb99339ab18c0715bfcd35b8365b407ad759e2c591d8270ad335381573e27ec18af7ca157b4a2bbca921db083d9b0009dc332a79dde14354a8c18bce76a1bfc1a25a1e702ccaa0feb521ee9279b8a01ceab6e237bbe4128b23cb53b1e5185f3266e20670a307ea0cfb5377025e0bb0790d48f1636c8b836c1a1f69ad61265f19057197e86cd526da6ddb94fd1ece80b60852f27ef2ce56ccb5a32d8cab6d16be06f380dfde3602ea4c1ae927173b2001ff0d9e29bc66b2b2a20c3e3ac174fcba187aacab0876c1356d30d4021e6dd0048c3bdfbf254108bb09d3ca9f2be423a92408bca52fbcd68f972c46fc8d20e0350d12c2f2d6c7da85e96bcec3ce61119793d44a210f81ece859fef6360ae3b0e1af0634fc141a8b50b3b383fb264e8a4fb84ea06db6becbf5e140edf66ee190da8968da579eb349fedea45e4c252a79570501278bab5fa984d7b1179d7c2460faa7beafee153bbae0a591701632aa94839528d3ef50cf809c1f7209b9e5c99010eaff7f921c45b6546358ee7a90948e3c710cd3e1796860839a345516fdf4f07c415029627abe1273a1f510c36a662562d18169b23305b4efadfefbfbb41a400ab533e61c14cafa49bc5d2818058ee4f3e1aeb329e150820d1de1f1eaaad31051a6dfdd3a1d5cec7b16bc0ea2c649d409917faa42138b1f824b4d534a050be0a99ea6772daf0b2e58623cc7a250ef37599bd556508f08886e663ef0917ecd3077072c3268ea5b9b89cbb6b761ee9f9c4765d8b267d9eb19728a28ce67a42ed0cad142b5dc0fc5313853860ec3f0ee2bc3d47cbe12dbe9633db809967d5b8bf0e45574eac657059530c30aeeade1e4f858a4a6e79d6e441b4af0127a13340d908d48cfec849ee93d53b1564231f048d34885e791a9d40c61a7b00f12f6f72a5050bcaabcc98480170ea6e20bef6b5c6f504c808108454fe2f3c275bf8f89a5e0a3304a7c4787e6d4fbe569930f7cfd38ab7d1d2ebd599bbb411950cec3e53b90cefb82234990d353c71ce4c21ef674a1c4070f71c90e1ea7edf35f5a421118f01b49a92ea97720e2d4df6b5885c181002656629a90eaa1904fe1c379b8291480ca15d0dc2b65a20c22f1e01d612d21ecb5738e5ebfc578a4a65066ee6e913e3030d3fdfb0168fd75022492728ee82869deb9ff2827f4e10759ecddb20f67e9808e707257a74d3dc0a6068f264066f95c9f772a3dcec0b4f0a327e3745517ad60ccbc5392890d2479b724d068fcdb83607e02291c06e1a5a1dac7604889cce2500f418da2f7080a7e9a1bdf28b87028a2bbb0c14f059f10f46d46716eac2cdfc06676cbec91b8c2c0f7c9bea7e27fa5048662398b23a9b488a49e1d3330c04e60179a4492c8b836780899899d2af17e6119a94d54a890ce8c0b550e87fd54cba0821fa7c48f6e09a60dcbddf853f82b47195aa44a5ceae14a9257296acd711c8073ff3345befca5d3ebf64901b283df96395fe9785d7090176bfe5a9f13ceae701c6c93af0e13d949bc3c7e9b06674a73e7affc508302258a27fb34569c3742201c0721aef282a31c69a5d98a67ac5c3d920d50c089896f7f8c8c237a81f803f0444f417246d695e89a3a523b62a3cd2203d42607cac7c7782dec1f9edbb806c0a7a37d1a969082a126bc726151a50233456a07d374399e74aaa8cc66821511d092615950d302e815cfcc021e1250cdea20fd9e1e4b5e88280d6e4283b918e780d12cbba59ef2ed2ce86135a48fba6c0dc2bf2efee190d9a3f9aa22a622b1953058f2bb3a371637d13e045d54e7eb54c0d25851f49283d7d34e9785d2d5c3f70086c48a8325a2083bdf5b3531fcc697cc0c9f63892a866c84585d673a2a63fd60e77995bfb0c0a44a4b63c0ff67e813027d3e84cddd393a0f4e6bc95525c5ae20eed9d0cea4a12aa748eb5209cfd75990b055f1ad0472f9f7599f569a8743a720755aa11555df4bb2e725fa93bc5dea603a964e8dc9fb1742e81825022866fc50a6b2a19b6a234a38ee27a74f2f5832b294143ca7ff8d07fd7d4e01f479e9792058871d90ee3aaa3329e82cebe41dff5e6d00a36268a7965466b80c6510ac1350cee797e1d6737f6aaff155266d2a2d611b2124affed1ac73a6a06515627b2230ce0d7fed33ecbde511f4d472cbc556cc8d9c5640e67657035112976b626847a0a4ea5fdd14d4a3eed57f0dfbe153393d8bd28c8b4f9e62940e8379790393fa20c617050c780a7d870193b4611bd7a12d26947a3cf4605e225da8b1646a76984015a5e317016a4d8301eaeec0db3ae0daa719182e2f4479154dbcccfcce1f365099de6c91934c395ce82abba8062a51d7773b418330921766cd3d275c689098e06039698db6f09accb292e7eb79e7a022d4257bb2f9ed993c519860919bc229a06ad88954c9ebf7f5b9fe95cf56e8181cb9175dac06be0be70fd28df20cdb4600ef0869668c645c9ea01360fdae7c922cb3d2b3583ae1de5ae7d899a83ff2bb00d7365c782a0fccfcba7f87bb29416469bb051f9b0755123e0f2fa76dc7644b70e452f49a84bc372b384c843b8161b7f9b63699adcadd5cb2b33b36c7eb3e1b00f25218bc16447968b939016242fceaebd796c17a24d1b9870991a9c3ae90e380302b7bb320adacc08cfb9249d29cd9275c52476dac6a7e9870ee3776cbc3352036f9c8f681d44856c6c5f90b7cde0877472ddd48719c449f59dca1f49442f7505e4809c6d323b37530ecccf3e41e19822f53d64dc90efb113405ee88799c37f0a342293b5bfc019a9057138326de6107b5613554dffc737aed7237fb16cd77e09f581d12220ac930c6ca279efd1d07a92125fb2606ec3ec35351987a15fc72806cfb3cb66fce8dcfabee5c1e586bf0f802fa12ae5ad5a708e3a5d54e1926dbd0202bf1150f1bb612b9a4590b5b520b86a90860ec3d9c2184f9975ced15ae1300882d9918021b43a1184ba88ddd7091539fe5a7017b8708d0f5c916f9c42de5103f8116863864b508f5880ca60b7492385c16a02b6ceb64d257a4838873b85d2041517c5c7c4508e4d5a5faa72729d73af0361e11828eeca992b8f20d903a5ef065976a9f322e34bd4b3984bb09e18be40e77e833c8c1a2e80093227d3f40d4a067f5e3aee9fce9bd234bb6ff4d0c34fc060d23e86b1f5a6d8d052e53e913182052a2d9c5e97bb0e0a51bb2fafbe7346bacfcbadb00ce2ba129f29d41a11f7d105cf19bb60b5f5b0dfd6a894698ef7f56a02d69cc03eb62a56563d3a77e3ac2302", + "as_json": "", + "block_height": 993442, + "block_timestamp": 1457749396, + "confirmations": 2201720, + "double_spend_seen": false, + "in_pool": false, + "output_indices": [198769,418598,176616,50345,509], + "prunable_as_hex": "", + "prunable_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "pruned_as_hex": "", + "tx_hash": "d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408" + }], + "txs_as_hex": ["0100940102ffc7afa02501b3056ebee1b651a8da723462b4891d471b990ddc226049a0866d3029b8e2f75b70120280a0b6cef785020190dd0a200bd02b70ee707441a8863c5279b4e4d9f376dc97a140b1e5bc7d72bc5080690280c0caf384a30201d0b12b751e8f6e2e31316110fa6631bf2eb02e88ac8d778ec70d42b24ef54843fd75d90280d0dbc3f40201c498358287895f16b62a000a3f2fd8fb2e70d8e376858fb9ba7d9937d3a076e36311bb0280f092cbdd0801e5a230c6250d5835877b735c71d41587082309bf593d06a78def1b4ec57355a37838b5028080bfb59dd20d01c36c6dd3a9826658642ba4d1d586366f2782c0768c7e9fb93f32e8fdfab18c0228ed0280d0b8e1981a01bfb0158a530682f78754ab5b1b81b15891b2c7a22d4d7a929a5b51c066ffd73ac360230280f092cbdd0801f9a330a1217984cc5d31bf0e76ed4f8e3d4115f470824bc214fa84929fcede137173a60280e0bcefa75701f3910e3a8b3c031e15573f7a69db9f8dda3b3f960253099d8f844169212f2de772f6ff0280d0b8e1981a01adc1157920f2c72d6140afd4b858da3f41d07fc1655f2ebe593d32f96d5335d11711ee0280d0dbc3f40201ca8635a1373fa829f58e8f46d72c8e52aa1ce53fa1d798284ed08b44849e2e9ad79b620280a094a58d1d01faf729e5ab208fa809dd2efc6f0b74d3e7eff2a66c689a3b5c31c33c8a14e2359ac484028080e983b1de1601eced0182c8d37d77ce439824ddb3c8ff7bd60642181e183c409545c9d6f9c36683908f028080d194b57401ead50b3eefebb5303e14a5087de37ad1799a4592cf0e897eafb46d9b57257b5732949e0280a094a58d1d01d3882a1e949b2d1b6fc1fd5e44df95bae9068b090677d76b6c307188da44dd4e343cef028090cad2c60e0196c73a74a60fc4ce3a7b14d1abdf7a0c70a9efb490a9de6ec6208a846f8282d878132b028080bb8b939b4401c03dbcbfd9fb02e181d99c0093e53aceecf42bf6ccc0ec611a5093fe6f2b2738a07f0280f092cbdd0801b98d30c27f297ae4cb89fb7bb29ed11adff17db9b71d39edf736172892784897488c5d0280d0dbc3f40201da9a353d39555c27a2d620bf69136e4c665aaa19557d6fc0255cbb67ec69bf2403b63e0280b09dc2df0101f8820caab7a5e736f5445b5624837de46e9ef906cb538f7c860f688a7f7d155e19e0ac0280808d93f5d77101b544e62708eb27ff140b58c521e4a90acab5eca36f1ce9516a6318306f7d48beddbc0280a0b6cef7850201abdd0a5453712326722427f66b865e67f8cdb7188001aaacb70f1a018403d3289fcb130280c0caf384a30201a2b32b2cfb06b7022668b2cd5b8263a162511c03154b259ce91c6c97270e4c19efe4710280c0caf384a302018db32bda81bfbe5f9cdf94b20047d12a7fb5f097a83099fafdfedc03397826fb4d18d50280c0fc82aa0201b2e60b825e8c0360b4b44f4fe0a30f4d2f18c80d5bbb7bfc5ddf671f27b6867461c51d028080e983b1de1601b2eb0156dd7ab6dcb0970d4a5dbcb4e04281c1db350198e31893cec9b9d77863fedaf60280e08d84ddcb0101e0960fe3cafedb154111449c5112fc1d9e065222ed0243de3207c3e6f974941a66f177028080df9ad7949f01019815c8c5032f2c28e7e6c9f9c70f6fccdece659d8df53e54ad99a0f7fa5d831cf762028090dfc04a01b4fb123d97504b9d832f7041c4d1db1cda3b7a6d307194aff104ec6b711cced2b005e2028080dd9da41701bef1179c9a459e75a0c4cf4aff1a81f31f477bd682e28a155231da1a1aa7a25ef219910280d88ee16f01facd0f043485225a1e708aa89d71f951bc092724b53942a67a35b2315bfeac4e8af0eb0280d0dbc3f40201c4e634d6e1f3a1b232ef130d4a5417379c4fcc9d078f71899f0617cec8b1e72a1844b60280f092cbdd0801f6b9300c8c94a337fefc1c19f12dee0f2551a09ee0aaa954d1762c93fec8dadae2146c0280c0f9decfae0101ce8d09f26c90144257b5462791487fd1b017eb283268b1c86c859c4194bf1a987c62bf0280c0caf384a30201cead2bbb01653d0d7ff8a42958040814c3cbf228ebb772e03956b367bace3b684b9b7f0280a0e5b9c2910101c1b40c1796904ac003f7a6dd72b4845625e99ba12bdd003e65b2dd2760a4e460821178028080e983b1de160186e9013f55160cd9166756ea8e2c9af065dcdfb16a684e9376c909d18b65fd5306f9690280a0e5b9c2910101aeb70c4433f95ff4cdc4aa54a1ede9ae725cec06350db5d3056815486e761e381ae4d00280c0a8ca9a3a01ebe2139bd558b63ebb9f4d12aca270159ccf565e9cffaadd717ce200db779f202b106f0280d0dbc3f40201b9963568acf599958be4e72f71c3446332a39c815876c185198fa2dcf13877eba3627b0280c0f4c198af0b01bccc01408cbb5a210ad152bd6138639673a6161efd2f85be310b477ae14891870985f90280a0b6cef7850201cadd0a82e7950f5d9e62d14d0f7c6af84002ea9822cdeefabbb866b7a5776c6436636b028080d287e2bc2d01d888013a7146b96a7abc5ce5249b7981fb54250eef751964ff00530915084479b5d6ba028080d287e2bc2d018c8901d8cc1933366dceb49416b2f48fd2ce297cfd8da8baadc7f63856c46130368fca0280a0b787e90501b6d242f93a6570ad920332a354b14ad93b37c0f3815bb5fa2dcc7ca5e334256bd165320280a0e5b9c2910101a3ac0c4ed5ebf0c11285c351ddfd0bb52bd225ee0f8a319025dc416a5e2ba8e84186680280c0f9decfae0101f18709daddc52dccb6e1527ac83da15e19c2272206ee0b2ac37ac478b4dd3e6bcac5dc0280f8cce2840201b7c80bc872a1e1323d61342a2a7ac480b4b061163811497e08698057a8774493a1abe50280e0bcefa75701f38b0e532d34791916b1f56c3f008b2231de5cc62bd1ec898c62d19fb1ec716d467ae20280c0fc82aa0201d6da0b7de4dc001430473852620e13e5931960c55ab6ebeff574fbddea995cbc9d7c010280c0f4c198af0b01edca017ec6af4ece2622edaf9914cfb1cc6663639285256912d7d9d70e905120a6961c028090cad2c60e01cad43abcc63a192d53fe8269ecaf2d9ca3171c2172d85956fe44fcc1ac01efe4c610dd0280809aa6eaafe30101a92fccd2bcadfa42dcbd28d483d32abde14b377b72c4e6ef31a1f1e0ff6c2c9f452f0280a0b787e9050197d9420ce413f5321a785cd5bea4952e6c32acd0b733240a2ea2835747bb916a032da7028080d287e2bc2d01c48a01a3c4afcbbdac70524b27585c68ed1f8ea3858c1c392aeb7ada3432f3eed7cab10280f092cbdd0801abb130d362484a00ea63b2a250a8ae7cf8b904522638838a460653a3c37db1b76ff3de0280e08d84ddcb0101cc980fce5c2d8b34b3039e612adeb707f9ab397c75f76c1f0da8af92c64cd021976ff3028080dd9da4170198c217e4a7b63fd645bd27aa5afc6fae7db1e66896cece0d4b84ac76428268b5c213c30280a0b6cef7850201e3e20acab086a758c56372cf461e5339c09745ee510a785b540d68c7f62c35b8b6c5c10280a094a58d1d01ab842ac0ba57e87a3f204a56cbecca405419e4c896c817c5ced715d903a104a09735660280e0a596bb1101ade921f1ef2fe56c74320ceb1f6c52506d0b835808474b928ad6309398b42434d27f3d0280d0b8e1981a01dab515d684a324cff4b00753a9ef0868f308b8121cbc79077ede84d70cf6015ec989be0280c0ee8ed20b01f99c227da8d616ce3669517282902cdf1ef75e75a427385270d1a94197b93cf6620c15028080dd9da41701d2d0175c8494f151e585f01e80c303c84eea460b69874b773ba01d20f28a05916111a0028090dfc04a01a4f312c12a9f52a99f69e43979354446fd4e2ba5e2d5fb8aaa17cd25cdf591543149da0280d0dbc3f40201969c3510fbca0efa6d5b0c45dca32a5a91b10608594a58e5154d6a453493d4a0f10cf70280f8cce2840201ddcb0b76ca6a2df4544ea2d9634223becf72b6d6a176eae609d8a496ee7c0a45bec8240280e0bcefa7570180920e95d8d04f9f7a4b678497ded16da4caca0934fc019f582d8e1db1239920914d35028090cad2c60e01c2d43a30bbb2dcbb2b6c361dc49649a6cf733b29df5f6e7504b03a55ee707ed3db2c4e028080d287e2bc2d01c38801941b46cb00712de68cebc99945dc7b472b352c9a2e582a9217ea6d0b8c3f07590280e0a596bb1101b6eb219463e6e8aa6395eefc4e7d2d7d6484b5f684e7018fe56d3d6ddca82f4b89c5840280d0dbc3f40201db9535db1fb02f4a45c21eae26f5c40c01ab1bca304deac2fb08d2b3d9ac4f65fd10c60280a094a58d1d01948c2a413da2503eb92880f02f764c2133ed6f2951ae86e8c8c17d1e9e024ca4dc72320280c0ee8ed20b01869f22d3e106082527d6f0b052106a4850801fcd59d0b6ce61b237c2321111ed8bdf47028080d194b57401acd20b9c0e61b23698c4b4d47965a597284d409f71d7f16f4997bc04ba042d3cbe044d028090cad2c60e0194b83ac3b448f0bd45f069df6a80e49778c289edeb93b9f213039e53a828e685c270f90280a094a58d1d01bdfb2984b37167dce720d3972eaa50ba42ae1c73ce8e8bc15b5b420e55c9ae96e5ca8c028090dfc04a01abf3120595fbef2082079af5448c6d0d6491aa758576881c1839f4934fa5f6276b33810280e0a596bb1101f9ea2170a571f721540ec01ae22501138fa808045bb8d86b22b1be686b258b2cc999c5028088aca3cf02019cb60d1ffda55346c6612364a9f426a8b9942d9269bef1360f20b8f3ccf57e9996b5f70280e8eda1ba0101aff90c87588ff1bb510a30907357afbf6c3292892c2d9ff41e363889af32e70891cb9b028080d49ca7981201d65ee875df2a98544318a5f4e9aa70a799374b40cff820c132a388736b86ff6c7b7d0280c0caf384a30201dab52bbf532aa44298858b0a313d0f29953ea90efd3ac3421c674dbda79530e4a6b0060280f092cbdd0801c3ab30b0fc9f93dddc6c3e4d976e9c5e4cfee5bfd58415c96a3e7ec05a3172c29f223f0280a094a58d1d01a2812a3e0ec75af0330302c35c582d9a14c8e5f00a0bf84da22eec672c4926ca6fccb10280a094a58d1d01ca842a2b03a22e56f164bae94e43d1c353217c1a1048375731c0c47bb63216e1ef6c480280e08d84ddcb0101d68b0fb2d29505b3f25a8e36f17a2fde13bce41752ecec8c2042a7e1a7d65a0fd35cdf028090cad2c60e0199ce3afa0b62692f1b87324fd4904bf9ffd45ed169d1f5096634a3ba8602919681e5660280c0f9decfae010193ed081977c266b88f1c3cb7027c0216adb506f0e929ce650cd178b81645458c3af4c6028090cad2c60e01eec13a9cce0e6750904e5649907b0bdc6c6963f62fef41ef3932765418d933fc1cd97a0280c0ee8ed20b019ea8228d467474d1073d5c906acdec6ee799b71e16141930c9d66b7f181dbd7a6e924a028080bb8b939b4401c23d3cb4e840ad6766bb0fd6d2b81462f1e4828d2eae341ce3bd8d7ce38b036ac6fb028080e983b1de1601b9e601e3cf485fa441836d83d1f1be6d8611599eccc29f3af832b922e45ab1cd7f31d00280f092cbdd0801fc9e30fff408d7b0c5d88f662dddb5d06ff382baa06191102278e54a0030f7e3246e7c0280d88ee16f01bfcd0f96a24f27ac225278701c6b54df41c6aa511dd86ce682516fb1824ff104c572fb0280f092cbdd0801cbb430bd5f343e45c62efcd6e0f62e77ceb3c95ef945b0cff7002872ea350b5dfffef10280c0caf384a30201bfb22b14dccbba4582da488aef91b530395979f73fa83511e3b3bcb311221c6832b18d0280a0b6cef7850201c4dc0a31fb268316ab21229072833191b7a77b9832afea700a1b93f2e86fb31be9480f028090cad2c60e01cab63a313af96a15c06fcf1f1cf10b69eae50e2c1d005357df457768d384b7a35fa0bb0280d0dbc3f40201fe9835fffd89020879ec3ca02db3eadbb81b0c34a6d77f9d1031438d55fd9c33827db00280d0dbc3f40201d19b354a25ddf2641fc7e000f9306df1c6bf731bddfe9ab148713781bbfab4814ed87e0280e08d84ddcb0101ba960fec80e1dcda9fae2d63e14500354f191f287811f5503e269c9ec1ae56cef4cd110280a0b787e90501acde42b0bdfd00ab0518c8bd6938f0a6efab1b1762704e86c71a154f6d6378dd63ce840280e0bcefa75701b5900eedc2ce12618978351788e46fefe8b9b776510ec32add7423f649c613b9db853a028080e983b1de1601edeb01d68b226cd6b71903a812aa6a6f0799381cf6f70870810df560042cd732b26526028080f696a6b6880101ca18a91fd53d6370a95ca2e0700aabc3784e355afcafb25656c70d780de90e30be31028090cad2c60e0184c03adc307ee3753a20f8f687344aae487639ab12276b604b1f74789d47f1371cac6b0280c0fc82aa0201a2dc0b000aa40a7e3e44d0181aaa5cc64df6307cf119798797fbf82421e3b78a0aa2760280e8eda1ba0101daf20caa8a7c80b400f4dd189e4a00ef1074e26fcc186fed46f0d97814c464aa7561e20280c0f9decfae0101a18b092ee7d48a9fb88cefb22874e5a1ed7a1bf99cc06e93e55c7f75ca4bf38ad185a60280a094a58d1d01dff92904ef53b1415cdb435a1589c072a7e6bd8e69a31bf31153c3beb07ebf585aa838028080bfb59dd20d01916c9d21b580aed847f256b4f507f562858396a9d392adc92b7ed3585d78acf9b38b028080a2a9eae80101fab80b153098181e5fabf1056b4e88db7ce5ed875132e3b7d78ed3b6fc528edda921050280d88ee16f019fcf0fd5f4d68c9afe2e543c125963043024fe557e817c279dbd0602b158fe96ec4b6f0280e0bcefa75701d1910e44b59722c588c30a65b920fc72e0e58c5acc1535b4cad4fc889a89fccfa271510280d0dbc3f40201b78b358b066d485145ada1c39153caacf843fcd9c2f4681d226d210a9a9942109314d90280e0bcefa75701a88b0e5f100858379f9edbbfe92d8f3de825990af354e38edc3b0d246d8a62f01ab3220280d0dbc3f40201959135c6a904269f0bf29fbf8cef1f705dde8c7364ba4618ad9ee378b69a3d590af5680280e0a596bb1101edeb2140e07858aa36619a74b0944c52663b7d3818ab6bf9e66ee792cda1b6dd39842e0280c0a8ca9a3a01aae213a90a6708e741f688a32fb5f1b800800e64cfd341c0f82f8e1ca822336d70c78e0280c0fc82aa02018ddf0b5e03adc078c32952c9077fee655a65a933558c610f23245cd7416669da12611e0280f092cbdd0801aca0305b7157269b35d5068d64f8d43386e8463f2893695bc94f07b4a14f9f5c85e8c50280e0bcefa75701b18f0efd26a0ad840829429252c7e6db2ff0eb7980a8f4c4e400b3a68475f6831cc5f50280b09dc2df0101a6830c2b7555fd29e82d1f0cf6a00f2c671c94c3c683254853c045519d1c5d5dc314fb028080bb8b939b4401be3d76fcfea2c6216513382a75aedaba8932f339ed56f4aad33fb04565429c7f7fa50280c0ee8ed20b01b4a322218a5fd3a13ed0847e8165a28861fb3edc0e2c1603e95e042e2cbb0040e49ab50280c0caf384a30201ecb42b7c10020495d95b3c1ea45ce8709ff4a181771fc053911e5ec51d237d585f19610280f092cbdd0801eba3309533ea103add0540f9624cb24c5cecadf4b93ceb39aa2e541668a0dd23bf3f2f028090dfc04a01a6f3121520ad99387cf4dd779410542b3f5ed9991f0fadbb40f292be0057f4a1dfbf10028090cad2c60e019ac83a125492706ba043a4e3b927ab451c8dccb4b798f83312320dcf4d306bc45c3016028080a2a9eae80101b4ba0bd413da8f7f0aad9cd41d728b4fef20e31fbc61fc397a585c6755134406680b14028080d49ca798120192600ef342c8cf4c0e9ebf52986429add3de7f7757c3d5f7c951810b2fb5352aec620280a0b787e90501afe442797256544eb3515e6fa45b1785d65816dd179bd7f0372a561709f87fae7f95f10280a094a58d1d01dc882aacbd3e13a0b97c2a08b6b6deec5e9685b94409d30c774c85a373b252169d588f028090dfc04a0184f81225e7ded2e83d4f9f0ee64f60c9c8bce2dcb110fd2f3d66c17aafdff53fbf6bbe028080d287e2bc2d01d18901e2fd0eeb4fe9223b4610e05022fcc194240e8afe5472fceda8346cb5b66a0a5902808095e789c60401cf88036cf7317af6dc47cd0ce319a51aaaf936854287c07a24afad791a1431cbd2df5c0280c0f9decfae0101999909d038b9c30a2de009813e56ba2ba17964a24d5f195aaa5f7f2f5fefacd69893e80280a0e5b9c291010199b10cf336c49e2864d07ad3c7a0b9a19e0c17aaf0e72f9fcc980180272000fe5ba1260280a0b6cef7850201a2e20a7a870af412e8fff7eba50b2f8f3be318736996a347fa1222019be9971b6f9b81028090dfc04a01bae5127889a54246328815e9819a05eea4c93bdfffaa2a2cc9747c5d8e74a9a4a8bfe10280f8cce284020191da0b24ee29cd3f554bb618f336dd2841ba23168bf123ee88ebdb48bcbb033a67a02f0280f8cce2840201e6c30b2756e87b0b6ff35103c20c1ddb3b0502f712977fd7909a0b552f1c7dfc3e0c3c028080e983b1de16018fed01a3c245ee280ff115f7e92b16dc2c25831a2da6af5321ad76a1fbbcdd6afc780c0280e0bcefa7570183920ef957193122bb2624d28c0a3cbd4370a1cfff4e1c2e0c8bb22d4c4b47e7f0a5a60280f092cbdd0801ccab30f5440aceabe0c8c408dddf755f789fae2afbf21a64bc183f2d4218a8a792f2870280e08d84ddcb0101f8870f8e26eacca06623c8291d2b29d26ca7f476f09e89c21302d0b85e144267b2712a028080aace938c0901b0b1014c9b9fab49660c2067f4b60151427cf415aa0887447da450652f83a8027524170580b09dc2df01028c792dea94dab48160e067fb681edd6247ba375281fbcfedc03cb970f3b98e2d80b081daaf14021ab33e69737e157d23e33274c42793be06a8711670e73fa10ecebc604f87cc7180a0b6cef78502020752a6308df9466f0838c978216926cb69e113761e84446d5c8453863f06a05c808095e789c60402edc8db59ee3f13d361537cb65c6c89b218e5580a8fbaf9734e9dc71c26a996d780809ce5fd9ed40a024d3ae5019faae01f3e7ae5e978ae0f6a4344976c414b617146f7e76d9c6020c52101038c6d9ccd2f949909115d5321a26e98018b4679138a0a2c06378cf27c8fdbacfd82214a59c99d9251fa00126d353f9cf502a80d8993a6c223e3c802a40ab405555637f495903d3ba558312881e586d452e6e95826d8e128345f6c0a8f9f350e8c04ef50cf34afa3a9ec19c457143496f8cf7045ed869b581f9efa2f1d65e30f1cec5272b00e9c61a34bdd3c78cf82ae8ef4df3132f70861391069b9c255cd0875496ee376e033ee44f8a2d5605a19c88c07f354483a4144f1116143bb212f02fafb5ef7190ce928d2ab32601de56eb944b76935138ca496a345b89b54526a0265614d7932ac0b99289b75b2e18eb4f2918de8cb419bf19d916569b8f90e450bb5bc0da806b7081ecf36da21737ec52379a143632ef483633059741002ee520807713c344b38f710b904c806cf93d3f604111e6565de5f4a64e9d7ea5c24140965684e03cefcb9064ecefb46b82bb5589a6b9baac1800bed502bbc6636ad92026f57fdf2839f36726b0d69615a03b35bb182ec1ef1dcd790a259127a65208e08ea0dd55c8f8cd993c32458562638cf1fb09f77aa7f40e3fecc432f16b2396d0cb7239f7e9f5600bdface5d5f5c0285a9dca1096bd033c4ccf9ceebe063c01e0ec6e2d551189a3d70ae6214a22cd79322de7710ac834c98955d93a5aeed21f900792a98210a1a4a44a17901de0d93e20863a04903e2e77eb119b31c9971653f070ddec02bd08a577bf132323ccf763d6bfc615f1a35802877c6703b70ab7216089a3d5f9b9eacb55ba430484155cb195f736d6c094528b29d3e01032fe61c2c07da6618cf5edad594056db4f6db44adb47721616c4c70e770661634d436e6e90cbcdfdb44603948338401a6ba60c64ca6b51bbf493ecd99ccddd92e6cad20160b0b983744f90cdc4260f60b0776af7c9e664eeb5394ee1182fb6881026271db0a9aad0764782ba106074a0576239681ecae941a9ef56b7b6dda7dbf08ecafac08ab8302d52ee495e4403f2c8b9b18d53ac3863e22d4181688f2bda37943afbf04a436302498f2298b50761eb6e1f43f6354bdc79671b9e97fa239f77924683904e0cf6b1351d4535393a9352d27b007dfda7a8ae8b767e2b5241313d7b5daf20523a80dd6cc9c049da66a5d23f76c132a85d772c45b4c10f2032f58b90c862f09f625cbd18c91a37bb3fc3a413a2e081618da845910cf5b2e6bffea555e883b0bb9c5f9063380a1c33ebdb764d9ffefe9e3169de40b18eeb9bfca48296457bb0b4e29d7b2b5bc4e0021ba0a1d389f77a8e253d6db149d400f675a9330f3bcfd09c7169224a947b6b4e0745ae08cd7adea4151277a94f51f85292ba082cf28300cca233ff4966b093c9cb6abcef476026040fec2b435021aff717b8bb90a40950e010f70bb416a618dc3c5c03f590c5b7ec8e0c05b85ba94078de4817918f783022364b8aa228b5df43b38fba3060c30616f265022584ab6034ddbc832450f90047d0cf41a4af8a20fb1aa66406133a17c2e905ee28d8acd186c872859c196db0474dfaaaded2d63768143cf6b5e2e34662f7bae573a08cb15069ef881892e5a0c08b5c6c7b2e6376cd2080fb29e8d3d5aa5b853662b4f1784ba7f072130e4dc00cba3cc9278fc4213f2ce2fc82bd1ea9fa91bb17b4f7c36962c78d864eab9f30ef327039da6607962a156a05c384a4a58ddd8f51a0d4fe91f64ae7b0a5199110a66f1e676392ec8d31b20a65f7a7fcff90b37a8a3962bff0c83ee6033a70c5b0af663ca48a8f22ced255839444fc51f5b6a6c1237eda5804289aa25fc93f14d0d4a63cecfa30d213eb3b2497af4a22396cc8c0e7c8b8bb57be8878bfc7fb29c038d39cf9fe0c964ebac13354a580527b1dbaced58500a292eb5f7cdafc772860f8d5c324a7079de9e0c1382228effaf2ac0278ebedad1117c5edacf08105a3f0905bca6e59cdf9fd074e1fbb53628a3d9bf3b7be28b33747438a12ae4fed62d035aa49965912839e41d35206a87fff7f79c686584cc23f38277db146dc4bebd0e612edf8b031021e88d1134188cde11bb6ea30883e6a0b0cc38ababe1eb55bf06f26955f25c25c93f40c77f27423131a7769719b09225723dd5283192f74c8a050829fc6fdec46e708111c2bcb1f562a00e831c804fad7a1f74a9be75a7e1720a552f8bd135b6d2b8e8e2a7712b562c33ec9e1030224c0cfc7a6f3b5dc2e6bd02a98d25c73b3a168daa768b70b8aef5bd12f362a89725571c4a82a06d55b22e071a30e15b006a8ea03012d2bb9a7c6a90b7fbd012efbb1c4fa4f35b2a2a2e4f0d54c4e125084208e096cdee54b2763c0f6fbd1f4f341d8829a2d551bfb889e30ca3af81b2fbecc10d0f106845b73475ec5033baab1bad777c23fa55704ba14e01d228597339f3ba6b6caaaa53a8c701b513c4272ff617494277baf9cdea37870ce0d3c03203f93a4ce87b63c577a9d41a7ccbf1c9d0bcdecd8b72a71e9b911b014e172ff24bc63ba064f6fde212df25c40e88257c92f8bc35c4139f058748b00fa511755d9ae5a7b2b2bdf7cdca13b3171ca85a0a1f75c3cae1983c7da7c748076a1c0d2669e7b2e6b71913677af2bc1a21f1c7c436509514320e248015798a050b2cbb1b076cd5eb72cc336d2aad290f959dc6636a050b0811933b01ea25ec006688da1b7e8b4bb963fbe8bc06b5f716a96b15e22be7f8b99b8feba54ac74f080b799ea3a7599daa723067bf837ca32d8921b7584b17d708971fb21cbb8a2808c7da811cff4967363fe7748f0f8378b7c14dd7a5bd10055c78ccb8b8e8b88206317f35dcad0cb2951e5eb7697d484c63764483f7bbc0ad3ca41630fc76a44006e310249d8a73d7f9ca7ce648b5602b331afb584a3e1db1ec9f2a1fc1d030650557c7dbc62008235911677709dea7b60c8d400c9da16b4b0a988b25e5cf26c00c3ef02812def049bb149ea635280e5b339db1035b7275e154b587cc50464a4c0bfd15c79f54faa10fbe571b73cf1aa4a20746b11c80c8c95899521fe5f0bb3104b0a050c55a79511e202fee30c005339694b18f4e18ab5e36ea21952a01864a0e067d9f19362e009a21c6c1a798f7c1325edd95e98fd1f9cb544909fdf9d076070d1233e183fb6d46a46fbc6e10452ef4c45fa0b88a84962ad6e91cbcc52bc000b12a82e93ae5998b20ee9000a8ef68ec8a44862cc108869fd388142692be6b0657e3fe79eff0e8b72f63aeec5874acf5fb0bfc9fa22645ed6ecaaf186eca690ecdf8a71b8f4789ac41b1f4f7539e04c53dd05e67488ea5849bf069d4eefc040273f6018819fdcbaa170c2ce078062b7bbe951d2214b077c4c836db85e1b138059c382ab408a65a3b94132136945cc4a3974c0f96d88eaa1b07cce02dce04ea0126e6210a9543129bb8296839949f6c3867243d4b0e1ff32be58c188ba905d40e32c53f7871920967210de94f71709f73e826036b4e3fa3e42c23f2912f4ea50557dff78aeb34cb35444965614812cbe14068a62be075fce6bf3310b9e8b12e0dd8379104360f728d47a327c172257134e2c0e7c32e01321f4d636f9047bd750e7993eeda7d39fc16f29696b1becee4d8026e967f8149935b947fce8517b2ce02b7831a232f3a29010129c49494ed2b84c7f881b7e4b02a00ebabf5a36023c404002d6cb88cee76c8ce97b03143ca867359d7e118d54e053b02c94998e6fd8409f8d46fc1741a2e56aebb1e7dab7ca3296a2566263d9be2f4bbef4872a49ee1082cbaf86e21b0c232c4182fc660f0c0b6aaeb0393750e553bc406e2a27842bd033da45a562ed1998ef9bd83e35ed813bef00a3e6147cb363bee63c543ba5e770b043dbacc155214a2496f91879bbc9170a2a513d7b48fad40c8c2d96f951e3a0932f6d12956789198430b352803852aa9726163fbe839979b33f8dbf7f76cd50755c1ce0c40a072aeec35057d06abaf59e878000b1d796e51908bfbf23b13900dcb30f9bd52b52994e7245a7017653a404a70d1c444b8c613ff10a2b057c02d062c5faf13cdc4445809fb6e096923cdbbdca18f59318ff86c7e449f596050b404d3cde0338dfdf9b1389178b1d4c70eefa2bbd76fefc1ee1f1688ef507821e40ae31d8d8e673d183b54563e2cbd27e0e042f61b046877d37a68c1b5784830690f2dd4ebbbd2dbdb35800b9e0ba8ea985fa106dd2ce8493e845586716c538ee9008b88a7c482f3c00c14c08468230d40cdc040e145282c4d61985cb5800306e305146204f63e96ad194bcdf1338ab8480341b6fbccf18fc32145f84bece4069c09e41096e94c24fa4f0db988e860a3bff3604143f2b17e8c219f28189e4cd49a0e506fe62dc419299bcd78c6ccb107f63eb31b4bd8ea1e2fed10e3ac17341d3505019e2376b01f7a7fcea3db110fb090c681c866ac86f13e6f8d44a32861e0580def063736b5c771b2b3b9067045867b4393f3eb2a4610bd0216e29906aaac370986451c6bf78264dda7e7a5fcbcf7bd6e024ff6003c6db780d89b97765cee8d0ff3ff25d94d4b4b919f722b26a6903a017daa62af387843087680c57952de06064de05b662af87be49b6e34cf0991cec7be3396e2eec9678ba259bd8de1c192014d02928f9113488215658df4078ed661fa4e79e58decaeb0ee5a00488b094b0b77f083b2b7844f481e7788ffe8004b96ccdf853532bfd9632a8a652c2d97d10173c90864fbb6facf47fae415df4acc0b099140a657b35d083d74dbdfbf107303e74c64471bed4b2199f2babcb4e1fc593d6f309e21f85e68ffd9904731559d0f2b673b36d3984e5d66d897dfa17d601edef3ed78cb70dc5115d4ae240c203e031263f0cf1e98075bac0361fde24cbcb852b8055d53ae01d61a0a1e1ba423d00833747e7364df7ebfd1f84598d801c249e1805279dc37d39fc7f7e27b067e4e0287aec432ed49e4d701a0ff377e88179968430d110cb20476ed4c6bf1624d1907ef24406d3295fcacde2a102cc85f4f3d0cb87a8fae7535a06e442833e58cfc04242ff85fb654d05f9874c0a6756f542db4e9d8b0366191fbb8b09a1bbcb6af04c069978417ca80d92f442b7dbd092f74e1268aa73b54e4b64e84543449ecd30b5ea392a1669a5f441d7208925e91c75df611cd26042630c6b98f160b8c0156048108d5465b71bbc54d31a9f90e34428d97590a427e1ae618d4a35fc1022d4e007c6108dcb1672b88d43ae4d886a5adcc26faf56bc5e5a0b08342fb88263fd80940d1edf794c6ad6d339b974e164b38439e11b4fa87cc793b080b4f8bf0eb56043f79ed3911da21092475fcf8320b55b9f558f194c6c8121b2e696039340d97057be2583726d762b5ae4327e5286a2d8c14ddbe0027c75aacbf7e9de13037390df7d72e13b46bc06bad0363b070e0174d034120d7fa7b4550e7dc28f7f0241f059ae266fc13dccd1d07f744208a7d6a2e565b6613d46e4550f79ef3209c46a805b97284df558719e131f44e419e690f4fc28ee4862b9d1f8f7e1a164ac18141076087693e70ac76a10f7851530d4cbc65def90d5544671ad64249569c3abf0200d09be3c63efaa7cb723b39ccffc9b3a3ba0c8426847123d2a881efbd4937a40cb3e8011c70ba427f80b3dc91a608086f8b61f8bd86fcb482ea388299a7cfbd00a3ddfadb4b6d0e51c1369276c25889a9f3592900b6502d9af1c732e1fb7db307d71e45deb1553ba1568d0480ea9e132b52564da6ac5c56eff823e7f37976cd075ce8f7a78aaef1b87f7437a0b84035677f266f7d0596e493101fec3e14fcf80b22322454587b51fda0636231c07d4e63e007f1b89137d8a37b03bf00f3a7c10169f757d9a74b7bffba797c746e3845decc3d0559d7cf6f08f3bd67dac5f33109b212582dc7df5d561ad63dddc794f2aea4e493db1a73c702d258c9b922c35d04c47f88f87c54c821a29f04abd91a079ce8cef252a21dc72d409fd1618c9be709af029ba98b0140e74666fcb01bced4f88ab68e6b63b8ed6febc0905d22cb2200493c071ce136833697406f8a04e77b21747dda997046cf3c7080096fe481790d77cf5904e7f7128ed95a6e576d109fdf10eb0c888db35a4a685b62253987b70fb1538e6c0932889460fa31c60d123266b7bcb828f846a35b2851127679b05f05a75266529c343a6075e54c455e02b2c83e6f7bf1ae23326506a5f532472d780815c5af425f7d8b543a8f014966e0538f48ca84d181695381a09701eb65c9ae084bf2a4dc84f1b2071be32be25d5f4fcdc59668fd800496ef7eb6dddf867ab908e543cb51f0451706cce4be6b9f68a537a79ea88e17fcd78965b3da68c0d9d30623a2a9e275e1c320f59e118e09c02eee527167bc06f7693e7b584e3b25ecc1093d46b80a1cacced87c2b32e2f90c5bbb9cd1b701aae69a04b16d535fac6eab0d091790fc5fdfa8a8842bfcb62dbf963cbf62c4afb4c468be98c770e6078b8c0a8cfcbae43dcfff17d3c6d587c3e4309fd39c66acd14781fea66fc57278b02302c0fa386280e67acff19955b6a428b0e22ceb1e54e913a37cd19eb6e9d2268a039f2b5fdda7d5804db79385f0e50082b128c952f8dfdedc4411d0675d95127f0bfc01710a869b10d7a8b9e632dad944062567439e6d192fb09329d058e87ecd0aa8981328f541e87ed02cfe4031f2d3a046ff517a2a80486b04ade31a647aec0884fb96ed753ffc47892431c6e6f08fd1c633a1a44e882d3d8b92c567e0fb8305327a354851464ca0f18d89c6ee2a91a4afef0c55883acf8fcb68c2c3b7402e005d8affc19c13f1f26fee0698dff181ab22cb84a2b31e0a6a81dc5d02e60a3c07090397ae58a985526b2ad6ee5725e82328062b68566b4871705ce3b9856e550d068c20fd9aaeb27740c07aad53d79fc20e46e40e7103e2d69626ee64b6aa600f6f1a86f37948ff4990d88f43c34994e2fe586cb779997be323da53329c10480aeb08fe440e9e4b979171371c73b94da9f928a3f6c8f6792f782f3d6432b86d06f54557327fef31fd6ae0a3f6d2f16c9ad947d132e14def33fa24cb4565370e0832fa50f5f5f93c9f3d65776cc22608b68a4f3719e9be47a19432991e4a2c49089c0ea20e7f7c73feaa47970da424d8543d80d622e2f2be9f4c65cc39dc369009a9d41a52bdea7cc0e8e04da87a633fd4f814fda1b646121a469ba0b5b8006d0e9118761d97b5d1856e2d690f27a81c42b176df853d07cf4a66ee83c9eb24ac0a382f5143a10a33ec3ddf17dcd8a8303fac8f279d31b4d04d74bd8804cefbb400c86174ad444e43ed33ee1e1e73f660b9814d5ca3cb1d650f1978a825a617bb05f84eab3b9b8359b991e1084cf4e8179ecb67f92398638e31227ff63427b67f0f232b454a341d85d4b56e31135c9035e231f7d9318ca12b5ab524f87bb0ca9b04b80effed202897ab016d5acc054c4fe62a5f0192f136cf2cd714998a4b164b0c2cdbace52243fdc9ea879b0d247d4fe8bd80481fad6b325cb5f2cfa2534dec0e47d41b6b99352e6e5faccb5ee28ca2fe96e04f9c83a0461ba34cfb499d864f05dc734b6c8f51cc0c994290b2a868cb8312df39fb6d457a81e62d872c65d4f3007094be42663bca3d64ebbcc8401158fce4f5a38a49c20f029c338f126451820459866e77c6984a467aad571cc7452392a9cb9f8e65099fff2f2acd170a833e01ed3d22a683356ee42dcbe6bab6d05d3edda2d40e9ba53884d430c2e0cd87c0067dc8cb68c868bd9f29db1dd73703c139ffc15e4f7264e727c70560ae02da100871f30e8a7c1275805f621752d73aafecddc2a7808b6c2ecbb8d0134a644bb603f30f8d18b3fc5efaa7f206ce180bfb14f5dbd3b0115145a227113eeaf1c1ec04244227b931388e72960833a40baa4319c5cf74aa94f7e3233e1d09f0a4f74409999684ad1cc836ac74563c85b164664dfab08ea084b25e2cbd7e7b94a781a10fcd455ee38b65126dcc52016127fd195c80b05660ed931e128b0cb92868955c0d032ada9fb951210a5442d21c1718ebc4702ad4a57967e15a63ffb05e1e072a0c41ebdf1e7205373eeaf4587f695de887fa3a8c96b8deb99e040fa1fc4dc2a402a017891943d734ae2f3798b22b1269d3d9f6d65581b9c637a6896a4fb554810bbd3db5c5737391a74150b43413b2e3824490b7911cbeb845147f1a8521620b0dd31306f13a9754a01bcdbd18bfdeade06b0ec97f48df56c45d3670a1fe18d00ef13e613c8a77aeb40401a814b377137cf44f29cb2cb94186ad1161ecb05a7c07837a5ab3474e57990cff2ab16b4d99f62e646da28e8bb712a5b561cf0e25be039c3e08583c8ebc3dd2fdb8fdc6e135ecc7851c73218a70b75e697cc84ea50504b9c34a33ed52f87230b9d192a940f3b7bb6d45b58dbf52f0afeb8dac85c77b06bdf9b70a10cb81c50055c9d8cf7e3a5c4b7dfae55beabcb3e8a8a1cb822d8d0bf6c01e32056929f853021eae6c97fdb0c5031df6b2e7c57f1318866769a9cc09c38ed62d8bf4663334c0df67c47236ed73f6ce7f54e0ada9270398c1aa558d0f993b0d25d97aea77b1635ee4832362cd590bae5fc1549402ddcd42b15efc930111a01535c0242116078d6d2d53b8612d378c4370e90d0d01b01bd7da591bec07981652a98485d8ed5c8f3def2bdac7d992ee5fc6a1ec7bd36940e1bc58c7050451248fc3ee6069e6b1b0d3ef122c6ef2a9b99aa0f145fb43341c58dbb472130b51730c956273a3ef6df9e000f6a87c2bacdefcdb5daef28b6170f61bc3a9c101f439755c86e6b85ee06a7a60688b3843eb359cd4acd9221a2ee131e2fd2e190652e5c47c0b98c41010eb99a991ec48a5de99cc8f403d6d76f8307d6657c1e007ebd64eec7bbd0d4f1ba2db7bb0efe27c7828f053e00def775943ab01a7e33d0fffcfe6f9a7285237f2c381b638758e373f8ceac672190664bb25fb5d355c240bd1773d61bda7f7ef1f4261b80ff5058ec6f7e024ab9459b1103815624b81f80c39db2f6fecb72de452b11636b0f71b16cb55f883d93bebb94328f13ef1ab6d0df449e32d27884f5139af584035547dace65ee25ba05cc461e74760d4468af90dcaa982e52cb902e2b84b3324019da575601ca54e91655913892e703257deaa01d14fd8459ff780c724161ba4d4280b70a5039dcfb5d775560714009724cb0d0b7e178c71e777b896bcfcde7d4c9c3dc6ab819d74a1a1fda8486448b1ad79be02fb134ea93a8600f1bc2a42e68d0213ab461a07cef3ad3965bc130beb76bab409102f82bf6c4cd626f6df3388e17b87584310c50832cde3191f6557f0014bdc0a68d924119e43111043bc6f26d16a5f2612dae6ab24984e2d87a71d93d5f4670dba2176d4f16633407bf7c10b51b6842dfdbc6fe3eaa4b6a12f0550700ece070ca382dec3b587e0e1fc317a48a83754d15aaf9a6971b8cb641fd8b32846d89002e6301700a0e7056e8002d8f269d29ebaf64f4493b1f1e676fc78e673067fb00625df15dc0490235b386ee14e55b335f3bc6dcedd7d3a80fd3a6e9bc2ccf3af0d89be71b5ca92bd7a9b97b9ff8976f75702419aa5bf9be34600496ca1bfa8ad0400602a23579365574252434f2bcd7efb360b0e8a495e8f7e78923b6fbf2207049e9179f0d4d7d6b4a4a10ca10f0ef4dd6cb5a74f7574e832044d6120fbc1580a68eddfbc65ab300bed960a6f24a102dc36b72937a8be4385daf5946e81ccde0619251babbff17e5685217a134d22f6130d0322483b3475227ffd27adc73ca202a6debfa37e5731747f4449ac70a33684f460eede65918c6d89acf4b50fd28d040ffbd436a944d3be0210606bfc2301e7ac66d462dba29a0489eb55af714a760e5302592cccc726e535b945ceb6126eb84e31f0f140ff54df8be0fa3a22f418036ad996787a5616a97a42049ebce351dc11857cab3dc914ef26833b0e75653004a8cafea099fb0750135255c41ef43e2f29c75714e2f0be2545e7c109b70c43004a471daa85b47befc65907d033f133b2f3ac2ad568df630ee80506610b8dc9052d442668dc06b13ea76ab1ab7b34870341d660af5d3007c21bb72512e4f8a60d8916a037b93f9e15ac9e4a6a1246d73ebb40e5fdd5a0d6dc0cf175023b891301f69fe5a3ca6f12cb8312d16333de1cd3ebb99339ab18c0715bfcd35b8365b407ad759e2c591d8270ad335381573e27ec18af7ca157b4a2bbca921db083d9b0009dc332a79dde14354a8c18bce76a1bfc1a25a1e702ccaa0feb521ee9279b8a01ceab6e237bbe4128b23cb53b1e5185f3266e20670a307ea0cfb5377025e0bb0790d48f1636c8b836c1a1f69ad61265f19057197e86cd526da6ddb94fd1ece80b60852f27ef2ce56ccb5a32d8cab6d16be06f380dfde3602ea4c1ae927173b2001ff0d9e29bc66b2b2a20c3e3ac174fcba187aacab0876c1356d30d4021e6dd0048c3bdfbf254108bb09d3ca9f2be423a92408bca52fbcd68f972c46fc8d20e0350d12c2f2d6c7da85e96bcec3ce61119793d44a210f81ece859fef6360ae3b0e1af0634fc141a8b50b3b383fb264e8a4fb84ea06db6becbf5e140edf66ee190da8968da579eb349fedea45e4c252a79570501278bab5fa984d7b1179d7c2460faa7beafee153bbae0a591701632aa94839528d3ef50cf809c1f7209b9e5c99010eaff7f921c45b6546358ee7a90948e3c710cd3e1796860839a345516fdf4f07c415029627abe1273a1f510c36a662562d18169b23305b4efadfefbfbb41a400ab533e61c14cafa49bc5d2818058ee4f3e1aeb329e150820d1de1f1eaaad31051a6dfdd3a1d5cec7b16bc0ea2c649d409917faa42138b1f824b4d534a050be0a99ea6772daf0b2e58623cc7a250ef37599bd556508f08886e663ef0917ecd3077072c3268ea5b9b89cbb6b761ee9f9c4765d8b267d9eb19728a28ce67a42ed0cad142b5dc0fc5313853860ec3f0ee2bc3d47cbe12dbe9633db809967d5b8bf0e45574eac657059530c30aeeade1e4f858a4a6e79d6e441b4af0127a13340d908d48cfec849ee93d53b1564231f048d34885e791a9d40c61a7b00f12f6f72a5050bcaabcc98480170ea6e20bef6b5c6f504c808108454fe2f3c275bf8f89a5e0a3304a7c4787e6d4fbe569930f7cfd38ab7d1d2ebd599bbb411950cec3e53b90cefb82234990d353c71ce4c21ef674a1c4070f71c90e1ea7edf35f5a421118f01b49a92ea97720e2d4df6b5885c181002656629a90eaa1904fe1c379b8291480ca15d0dc2b65a20c22f1e01d612d21ecb5738e5ebfc578a4a65066ee6e913e3030d3fdfb0168fd75022492728ee82869deb9ff2827f4e10759ecddb20f67e9808e707257a74d3dc0a6068f264066f95c9f772a3dcec0b4f0a327e3745517ad60ccbc5392890d2479b724d068fcdb83607e02291c06e1a5a1dac7604889cce2500f418da2f7080a7e9a1bdf28b87028a2bbb0c14f059f10f46d46716eac2cdfc06676cbec91b8c2c0f7c9bea7e27fa5048662398b23a9b488a49e1d3330c04e60179a4492c8b836780899899d2af17e6119a94d54a890ce8c0b550e87fd54cba0821fa7c48f6e09a60dcbddf853f82b47195aa44a5ceae14a9257296acd711c8073ff3345befca5d3ebf64901b283df96395fe9785d7090176bfe5a9f13ceae701c6c93af0e13d949bc3c7e9b06674a73e7affc508302258a27fb34569c3742201c0721aef282a31c69a5d98a67ac5c3d920d50c089896f7f8c8c237a81f803f0444f417246d695e89a3a523b62a3cd2203d42607cac7c7782dec1f9edbb806c0a7a37d1a969082a126bc726151a50233456a07d374399e74aaa8cc66821511d092615950d302e815cfcc021e1250cdea20fd9e1e4b5e88280d6e4283b918e780d12cbba59ef2ed2ce86135a48fba6c0dc2bf2efee190d9a3f9aa22a622b1953058f2bb3a371637d13e045d54e7eb54c0d25851f49283d7d34e9785d2d5c3f70086c48a8325a2083bdf5b3531fcc697cc0c9f63892a866c84585d673a2a63fd60e77995bfb0c0a44a4b63c0ff67e813027d3e84cddd393a0f4e6bc95525c5ae20eed9d0cea4a12aa748eb5209cfd75990b055f1ad0472f9f7599f569a8743a720755aa11555df4bb2e725fa93bc5dea603a964e8dc9fb1742e81825022866fc50a6b2a19b6a234a38ee27a74f2f5832b294143ca7ff8d07fd7d4e01f479e9792058871d90ee3aaa3329e82cebe41dff5e6d00a36268a7965466b80c6510ac1350cee797e1d6737f6aaff155266d2a2d611b2124affed1ac73a6a06515627b2230ce0d7fed33ecbde511f4d472cbc556cc8d9c5640e67657035112976b626847a0a4ea5fdd14d4a3eed57f0dfbe153393d8bd28c8b4f9e62940e8379790393fa20c617050c780a7d870193b4611bd7a12d26947a3cf4605e225da8b1646a76984015a5e317016a4d8301eaeec0db3ae0daa719182e2f4479154dbcccfcce1f365099de6c91934c395ce82abba8062a51d7773b418330921766cd3d275c689098e06039698db6f09accb292e7eb79e7a022d4257bb2f9ed993c519860919bc229a06ad88954c9ebf7f5b9fe95cf56e8181cb9175dac06be0be70fd28df20cdb4600ef0869668c645c9ea01360fdae7c922cb3d2b3583ae1de5ae7d899a83ff2bb00d7365c782a0fccfcba7f87bb29416469bb051f9b0755123e0f2fa76dc7644b70e452f49a84bc372b384c843b8161b7f9b63699adcadd5cb2b33b36c7eb3e1b00f25218bc16447968b939016242fceaebd796c17a24d1b9870991a9c3ae90e380302b7bb320adacc08cfb9249d29cd9275c52476dac6a7e9870ee3776cbc3352036f9c8f681d44856c6c5f90b7cde0877472ddd48719c449f59dca1f49442f7505e4809c6d323b37530ecccf3e41e19822f53d64dc90efb113405ee88799c37f0a342293b5bfc019a9057138326de6107b5613554dffc737aed7237fb16cd77e09f581d12220ac930c6ca279efd1d07a92125fb2606ec3ec35351987a15fc72806cfb3cb66fce8dcfabee5c1e586bf0f802fa12ae5ad5a708e3a5d54e1926dbd0202bf1150f1bb612b9a4590b5b520b86a90860ec3d9c2184f9975ced15ae1300882d9918021b43a1184ba88ddd7091539fe5a7017b8708d0f5c916f9c42de5103f8116863864b508f5880ca60b7492385c16a02b6ceb64d257a4838873b85d2041517c5c7c4508e4d5a5faa72729d73af0361e11828eeca992b8f20d903a5ef065976a9f322e34bd4b3984bb09e18be40e77e833c8c1a2e80093227d3f40d4a067f5e3aee9fce9bd234bb6ff4d0c34fc060d23e86b1f5a6d8d052e53e913182052a2d9c5e97bb0e0a51bb2fafbe7346bacfcbadb00ce2ba129f29d41a11f7d105cf19bb60b5f5b0dfd6a894698ef7f56a02d69cc03eb62a56563d3a77e3ac2302"], + "untrusted": false +}"#; +} + +define_request_and_response! { + get_alt_blocks_hashes (other), + GET_ALT_BLOCKS_HASHES: &str, + Request = "{}"; + Response = +r#"{ + "blks_hashes": ["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109"], + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} + +define_request_and_response! { + is_key_image_spent (other), + IS_KEY_IMAGE_SPENT: &str, + Request = +r#"{ + "key_images": [ + "8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3", + "7319134bfc50668251f5b899c66b005805ee255c136f0e1cecbb0f3a912e09d4" + ] +}"#; + Response = +r#"{ + "credits": 0, + "spent_status": [1,1], + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} + +define_request_and_response! { + send_raw_transaction (other), + SEND_RAW_TRANSACTION: &str, + Request = +r#"{ + "tx_as_hex": "dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308", + "do_not_relay": false +}"#; + Response = +r#"{ + "credits": 0, + "double_spend": false, + "fee_too_low": false, + "invalid_input": false, + "invalid_output": false, + "low_mixin": false, + "not_relayed": false, + "overspend": false, + "reason": "", + "sanity_check_failed": false, + "status": "Failed", + "too_big": false, + "too_few_outputs": false, + "top_hash": "", + "tx_extra_too_big": false, + "untrusted": false +}"#; +} + +define_request_and_response! { + start_mining (other), + START_MINING: &str, + Request = +r#"{ + "do_background_mining": false, + "ignore_battery": true, + "miner_address": "47xu3gQpF569au9C2ajo5SSMrWji6xnoE5vhr94EzFRaKAGw6hEGFXYAwVADKuRpzsjiU1PtmaVgcjUJF89ghGPhUXkndHc", + "threads_count": 1 +}"#; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + stop_mining (other), + STOP_MINING: &str, + Request = "{}"; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + mining_status (other), + MINING_STATUS: &str, + Request = "{}"; + Response = +r#"{ + "active": false, + "address": "", + "bg_idle_threshold": 0, + "bg_ignore_battery": false, + "bg_min_idle_seconds": 0, + "bg_target": 0, + "block_reward": 0, + "block_target": 120, + "difficulty": 292022797663, + "difficulty_top64": 0, + "is_background_mining_enabled": false, + "pow_algorithm": "RandomX", + "speed": 0, + "status": "OK", + "threads_count": 0, + "untrusted": false, + "wide_difficulty": "0x43fdea455f" +}"#; +} + +define_request_and_response! { + save_bc (other), + SAVE_BC: &str, + Request = "{}"; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + get_peer_list (other), + GET_PEER_LIST: &str, + Request = "{}"; + Response = +r#"{ + "gray_list": [{ + "host": "161.97.193.0", + "id": 18269586253849566614, + "ip": 12673441, + "last_seen": 0, + "port": 18080 + },{ + "host": "193.142.4.2", + "id": 10865563782170056467, + "ip": 33853121, + "last_seen": 0, + "port": 18085, + "pruning_seed": 387, + "rpc_port": 19085 + }], + "status": "OK", + "untrusted": false, + "white_list": [{ + "host": "78.27.98.0", + "id": 11368279936682035606, + "ip": 6429518, + "last_seen": 1721246387, + "port": 18080, + "pruning_seed": 384 + },{ + "host": "67.4.163.2", + "id": 16545113262826842499, + "ip": 44237891, + "last_seen": 1721246387, + "port": 18080 + },{ + "host": "70.52.75.3", + "id": 3863337548778177169, + "ip": 55260230, + "last_seen": 1721246387, + "port": 18080, + "rpc_port": 18081 + }] +}"#; +} + +define_request_and_response! { + set_log_hash_rate (other), + SET_LOG_HASH_RATE: &str, + Request = +r#"{ + "visible": true +}"#; + Response = +r#" +{ + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + set_log_level (other), + SET_LOG_LEVEL: &str, + Request = +r#"{ + "level": 1 +}"#; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + set_log_categories (other), + SET_LOG_CATEGORIES: &str, + Request = +r#"{ + "categories": "*:INFO" +}"#; + Response = +r#" +{ + "categories": "*:INFO", + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + set_bootstrap_daemon (other), + SET_BOOTSTRAP_DAEMON: &str, + Request = +r#"{ + "address": "http://getmonero.org:18081" +}"#; + Response = +r#"{ + "status": "OK" +}"#; +} + +define_request_and_response! { + get_transaction_pool (other), + GET_TRANSACTION_POOL: &str, + Request = "{}"; + Response = +r#"{ + "credits": 0, + "spent_key_images": [{ + "id_hash": "563cd0f22a17177353e494beb070af0f53ed6d003ada32123c7ec3c23f681393", + "txs_hashes": ["63b7d903d41ab2605043be9df08eb45b752727bf7a02d0d686c823d5863d7d83"] + },{ + "id_hash": "913f889441c829e62c741c27614cdbb6278555b768fbd583424e1bb45c65e43b", + "txs_hashes": ["3fd963b931b1ac20e3709ba0249143fe8cff4856200055336ba9330970e6306a"] + },{ + "id_hash": "0007a41ed49aa2f094518d30db5442accaa7d3632381474d649644678b6d23c0", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "05138378dedfae3adbd844cf76c060226aaeddcd4450c67178e41085d0ae9e53", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "1cccfcece29fbd7a28052821fdd7aac6548212cab0d679dd779a37799111f9ec", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "1eda8e08b1024028064450019b924eca2e3b3e3446d1ac58d0b8e89dc4ba980d", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "38d739cfb68aba73f0f451c7d8d8e51ae8821e17b275d03214054cc1fe4f72d6", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "40e57cb9a9f313f864eef7bf70dea07c2636952f3cbff30385ac26ee244a4349", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "52418ac25be58fbfcc8bd35c9833532d0fa911c875fa34b53118df5be0b3ba48", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "65bb760c9a31da39911fa6d0e918e884538f0a218d479f84a1c9cca2f9a5f500", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "7d805459f05d89c92443f43863fa5a4d17241d936fc042cc9847a33a461090c5", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "88f7594b26dcbaff22f7e7569473462c49d8fb845aa916d7a7663be8b85b8553", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "a2b08a090f611ea1097622cc63a49256a2d94a90b8dbaaa5e53a85001c86d55a", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "a5ebf4914f887ecdfde8e7ef303a7f2cc20521a2a305ba9a618e63d95debfb22", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "c5b7d94e661c5eb09714b243f3854cc06531b1085442834c9e870501031b73da", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "987605d678e8bfb17e8d2651e8dd5c69c73c705d003c82e4e35d2b5b89c9ebe3", + "txs_hashes": ["7c32ac906393a55797b17efef623ca9577ba5e3d26c1cf54231dcf06459eff81"] + },{ + "id_hash": "ca559feaf79de4445ca4d2bcc05883b25ecff2f6dd8fd02a9a14adea4849f06f", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "d656ac13a64576e7af5ca416d99b899b0bafef5e71d50e349e467fa463b13600", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "dc006e92fc1e623298b3415ddccfc96a8cae64cb7c9199505a767a16ddd39bb9", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "fb3e7cc08761a6037ca29965f27d2a145f045da5a1018ca7e6a5a5a93dbbd33d", + "txs_hashes": ["c072513a1e96497ad7a99c2cc39182bcb4f820e42cce0f04718048424713d9b1"] + },{ + "id_hash": "4ffd1487bf46e5a1929ca0dd48077cb8ddbff923e74517f1aeb7c54317c0fd68", + "txs_hashes": ["88504bd6a72b26bccbc7563efe365baeedb295011a4022089bdc735f508a9412"] + },{ + "id_hash": "f64056280ede74b3b1fe275cf9b9aa1feda77b3b5fd5218d6a765384e3d180ff", + "txs_hashes": ["88504bd6a72b26bccbc7563efe365baeedb295011a4022089bdc735f508a9412"] + },{ + "id_hash": "d2ed8513f48724933df6229a9fb6ededdcf5d0963280ee44fa9216ceebe7941f", + "txs_hashes": ["a60834967cc6d22e61acbc298d5d2c725cbf5c8c492b999f3420da126f41b6c7"] + },{ + "id_hash": "428be79097b510e49fe5b25804029ac8bfa5e2a640a8b0e3e0a8199b1d26f22f", + "txs_hashes": ["d696e0a07d4a5315239fda1d2fec3fa94c7f87148e254a2e6ce8a648bed86bb3"] + },{ + "id_hash": "368fbc77179fb30bf07073783f6ef08bfb1a8c096e9bd60bb57aead3b0f3663d", + "txs_hashes": ["9d1bcbdb17d24a4e615a9af7100da671ab34bffc808da978004dcef86ddf831e"] + },{ + "id_hash": "45a88adb7fcac982f5f4d8367f84e0f205235f58ad997f5dfa4707192fd3d9e0", + "txs_hashes": ["9d1bcbdb17d24a4e615a9af7100da671ab34bffc808da978004dcef86ddf831e"] + },{ + "id_hash": "6d80d9c12f1439b0a994f767d71d98d2d2cde1a54c6a6134a00c2f07135d98cf", + "txs_hashes": ["dbabb82a5f97d9da58d587c179b5861576144ea0cd14b96bef62b83d8838f363"] + },{ + "id_hash": "2c479dbff819502441604a914af485db2f795b7f5bc0eab877d60a1419ee5498", + "txs_hashes": ["aef60754dc1b2cd788faf23dd3c62afd3a0ac14e088cd6c8d22f1597860e47cd"] + },{ + "id_hash": "a7f204f932169b1b056fc63be06db8ec91a436f7188a30545bcd6a8bae817ca4", + "txs_hashes": ["8a6ebea82ede84b743c256c050a40ae7999e67b443c06ccb2322db626d62d970"] + }], + "status": "OK", + "top_hash": "", + "transactions": [{ + "blob_size": 2221, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 44420000, + "id_hash": "88504bd6a72b26bccbc7563efe365baeedb295011a4022089bdc735f508a9412", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261656, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261656, + "relayed": true, + "tx_blob": "020002020010f0bcd533b7d71bdb8915caa004b3a214f99f0993a303fd9804d1f101aa6c870de32a932ab774f80fc92cf64056280ede74b3b1fe275cf9b9aa1feda77b3b5fd5218d6a765384e3d180ff020010bc85cd27e4bfb407c598a104bc5e8e8c2d9bfc40add114c0e501b09e04edb204d1a901d2f4019603d50d9f07c4354ffd1487bf46e5a1929ca0dd48077cb8ddbff923e74517f1aeb7c54317c0fd68020003bbb37ea2c935e3c7245150d154748b59bdf280d557779b6cf8063165a7d9b5b9210003ffc23770cf9e4536c0db95978dbc937d1de339cb8dedf909f5adf7ce6fb8c4ea5b2c01d3b65a92cbd04597cc4f3da6a003ca5309af343f2d0f190102fdd7ed13e5b9ee020901d979920cfa70c6ef06a0979715e6d4eb51a032a1511050389f0d8eafbe5f91af7e37907d37de11bae31af65af35e1fabe13cece1d503a9987d7f4813c8ad2b6ef2b5e77281113637ace74d9ac41f16f431a1a49e6d8de1b73fe877c5c301fcaac875ca31629041923a1bb014f86a232227c41e1611f5961a0e095c6201ea34a7b7367b0045c3a841d57ebac2a9b323ab21f6d954f4441fc79fd98414e5c4acdbff571108da31d0face012eb149b24bc16de858b4cb8d96fdc0bb614cc2895a6859cdeb086e647983308714da41be9ada21abfbec1ed4d224315017dacf01c5b2a59d18ac5c3b81c9bfd5c031b9929c1dc802a22593bdea39612039601c0e09f64702dde1507e3daef5655b0f1f32e19fcbcbaeea6b495fed05543cbb65010730de65cd66a314cfdbe7474a387045b3000dd43eedc021ed492075d314da6d8c6a3905275d41cdc8758e258c4a71a64d2ba1aec68b7ad68018aa8fdcf97538898c61392ded8e0715ddd471638be54eda62622f5787cafc577da4ff7dda01578982328c51f59ad3d9218eb0d3201d1136d54e7567e15c3f8bc956772bee20f5976b0f343096ab4a0c2b68099bca4d61eff7a078c91875483213f4cd226b587b5c12bf7a41abc9079e274e6229187f4c3cc1a8579f60f2a8112aafa78eaefa765ed7588be97d471720979fa5b907c5b83be30d62d5a2b0b9a59f1330dface4cddd07f591829caac227efef5fe5076e3fa93dc9a787be8f57c3d2ec216342784321c80b956f44dec2d484500371f9a4fdad1de571f16d2cccca13f2f3bb65718dc4a861276d08d11bc72536b787537aa0b26d68462500baa1b5b47a1ff669346481ac5c0d2199d6197dfc9c74cdd6adf13e06223af430e48bcafce9cd8765ae6411d5a3ff2c8827ca2fb9ec63cfd0c84c2e1cc76d2fb0a3f9619034adc3d0fa60b729fa3352433a1f4f2c7bbb51fc61673b61833f70d8700446442d57e0a6fde600fc1cd0d659f8b6b6ca8e320395b831d2b79b95d006c2fc5afb72635535ce1e953d9e70a0022ac9091cb5810450d72edf9bff63c2b64933e0d69881b6ae9c9bc402b11bcd2ca24cea5171ce4040398ba42f87dec9791ac7376adce1cb47be22bc0ba083395b214bf88c5e81357eb95b461c1ea4c814357b5fa7dda0b7083a3360089af604f25927d738dfb3806a559285f04435a9245356051d27cbb0f5c020f40612a4ed7e2d124a8d6dc7d2e39127f5d66bbcad0bd8af4cf173b89283d09c610ec53ffb5b2e0aaedad8700de5555decda90d9b1f022ec0f1cabe3627ea89bb80420d73a60f7f33886541626e5aa0cb758bc9775a80c2427bd9fd373ce1492f90e93d0cd063f1233975d5ae4d732970c686b21850cd4146e5775af2a3b48f6920f021bc91e59b79200bacd2ebef0e2045ae01d7287f14a3ef08de813492f92c6034a7ef9b7a74660125a9761379d3495eb8cbd5e0461a0ae90ac7bb4dd6ee57902158dab0fbe495663b5536ac17e444ea5d6a5ec67f2145d5ac6eb033fe3ed88023133d514073957b3ab9506b375b52ab4e25df97c81f210d2a5d0ac2fd625c00522ee9cecb7dc0c84a47eef9338472bfa766f0bf5919be6a5b56bf5b84f1be1058d570d7ea8f372c5c253c189f006b314d377610bbc9a41fc162b3df3d860d904c74d0451fccecdcb8c0fdb66f55f10a955f49406f16c6ce397b78af25dcbfa001a1cc63df1dfcd2918dba5e64532af7a24f3a95c722815ad2192f488fe8da9080f8c295fdf955dbfe98666d411605e11598745385d7b639d8aed5b5499ffd007143ff548f1f2956da85253ed716d16f7ed1ba3ed100426e2a81dfa2bdd952f06997389359aef4673cff1fcf634c4261c3f8a028c25712896381ef8e88b53ce0996cd93d9bfc6fa1a578554d1b0767962bbfb88f553d5bb129cf18ed93685b50d60d8e13ef8c06f14e7f4fb212ac28be059f83bd3c375220c4368d405ecc9f601a31d48f081ab49014d562c39b464f850af6679daffddb75935f4bdf2d8735a013df11848f92dca8088339595f99024bb766c19e863175c0234157738925e4c0f4b5a83686667e9711547b3a2a96946fc126a026cdfc477de41e6c85835dad80a1a370f59950b9c9759595425609d6371d41801098202cb87ff96fcfb0247730bb2178497eeb94f794d151fd5393082ec7a0359b409b7508303493f749723780159badacf201cb6bf41691ba3ed894dc4a3b22a3a829ff13a349256379e65b108b53c62247b27176ae5d22295393e1856372f1a89fa7d364173647ba296b76c04950e768eec5f38634e8cf3beff55bd7ce266b5bebe5b854a02cad0307b0b670433bdbf8bc2631430773465cb091c7a666709285816e6d503acd5e649045d3c07baad6c71df6b89a0481a1fe7f45f6aa8837625ccb43ed5f9e8e9f7daf2cdee0e7f1baa61b625d9f5d0c9ee49605d403afa625927e9bf41d7e5d48b454dc1520b19e1c35dd25fc5dff641ea05bc2b6b5697485f96bd3664f90077c567923d4f0404107251310935d78e2d06471962f23277b5207500917d528aeef43e2b670c03e614bd3ee3fd58b486a3d0a2494916785325e6546dec8fb880cc401319a7f90b4d3832853ea6e0ae698543e20975eaa9c6606068f2465bade18d110a6937e199229fc569e4dbb09f253fdc89279b76b70f3bf61d6808d7fa8ff438c795464101a97d68d18f240c9d7137f2db1d38013ba94cf478338fa0353b2a5cbf937f00bb", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 108355184, 453559, 345307, 69706, 332083, 151545, 53651, 68733, 30929, 13866, 1671, 5475, 5395, 14903, 2040, 5705\n ], \n \"k_image\": \"f64056280ede74b3b1fe275cf9b9aa1feda77b3b5fd5218d6a765384e3d180ff\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 83051196, 15540196, 8932421, 12092, 738830, 1064475, 338093, 29376, 69424, 72045, 21713, 31314, 406, 1749, 927, 6852\n ], \n \"k_image\": \"4ffd1487bf46e5a1929ca0dd48077cb8ddbff923e74517f1aeb7c54317c0fd68\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"bbb37ea2c935e3c7245150d154748b59bdf280d557779b6cf8063165a7d9b5b9\", \n \"view_tag\": \"21\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"ffc23770cf9e4536c0db95978dbc937d1de339cb8dedf909f5adf7ce6fb8c4ea\", \n \"view_tag\": \"5b\"\n }\n }\n }\n ], \n \"extra\": [ 1, 211, 182, 90, 146, 203, 208, 69, 151, 204, 79, 61, 166, 160, 3, 202, 83, 9, 175, 52, 63, 45, 15, 25, 1, 2, 253, 215, 237, 19, 229, 185, 238, 2, 9, 1, 217, 121, 146, 12, 250, 112, 198, 239\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 44420000, \n \"ecdhInfo\": [ {\n \"amount\": \"e6d4eb51a032a151\"\n }, {\n \"amount\": \"1050389f0d8eafbe\"\n }], \n \"outPk\": [ \"5f91af7e37907d37de11bae31af65af35e1fabe13cece1d503a9987d7f4813c8\", \"ad2b6ef2b5e77281113637ace74d9ac41f16f431a1a49e6d8de1b73fe877c5c3\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"fcaac875ca31629041923a1bb014f86a232227c41e1611f5961a0e095c6201ea\", \n \"A1\": \"34a7b7367b0045c3a841d57ebac2a9b323ab21f6d954f4441fc79fd98414e5c4\", \n \"B\": \"acdbff571108da31d0face012eb149b24bc16de858b4cb8d96fdc0bb614cc289\", \n \"r1\": \"5a6859cdeb086e647983308714da41be9ada21abfbec1ed4d224315017dacf01\", \n \"s1\": \"c5b2a59d18ac5c3b81c9bfd5c031b9929c1dc802a22593bdea39612039601c0e\", \n \"d1\": \"09f64702dde1507e3daef5655b0f1f32e19fcbcbaeea6b495fed05543cbb6501\", \n \"L\": [ \"30de65cd66a314cfdbe7474a387045b3000dd43eedc021ed492075d314da6d8c\", \"6a3905275d41cdc8758e258c4a71a64d2ba1aec68b7ad68018aa8fdcf9753889\", \"8c61392ded8e0715ddd471638be54eda62622f5787cafc577da4ff7dda015789\", \"82328c51f59ad3d9218eb0d3201d1136d54e7567e15c3f8bc956772bee20f597\", \"6b0f343096ab4a0c2b68099bca4d61eff7a078c91875483213f4cd226b587b5c\", \"12bf7a41abc9079e274e6229187f4c3cc1a8579f60f2a8112aafa78eaefa765e\", \"d7588be97d471720979fa5b907c5b83be30d62d5a2b0b9a59f1330dface4cddd\"\n ], \n \"R\": [ \"f591829caac227efef5fe5076e3fa93dc9a787be8f57c3d2ec216342784321c8\", \"0b956f44dec2d484500371f9a4fdad1de571f16d2cccca13f2f3bb65718dc4a8\", \"61276d08d11bc72536b787537aa0b26d68462500baa1b5b47a1ff669346481ac\", \"5c0d2199d6197dfc9c74cdd6adf13e06223af430e48bcafce9cd8765ae6411d5\", \"a3ff2c8827ca2fb9ec63cfd0c84c2e1cc76d2fb0a3f9619034adc3d0fa60b729\", \"fa3352433a1f4f2c7bbb51fc61673b61833f70d8700446442d57e0a6fde600fc\", \"1cd0d659f8b6b6ca8e320395b831d2b79b95d006c2fc5afb72635535ce1e953d\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"9e70a0022ac9091cb5810450d72edf9bff63c2b64933e0d69881b6ae9c9bc402\", \"b11bcd2ca24cea5171ce4040398ba42f87dec9791ac7376adce1cb47be22bc0b\", \"a083395b214bf88c5e81357eb95b461c1ea4c814357b5fa7dda0b7083a336008\", \"9af604f25927d738dfb3806a559285f04435a9245356051d27cbb0f5c020f406\", \"12a4ed7e2d124a8d6dc7d2e39127f5d66bbcad0bd8af4cf173b89283d09c610e\", \"c53ffb5b2e0aaedad8700de5555decda90d9b1f022ec0f1cabe3627ea89bb804\", \"20d73a60f7f33886541626e5aa0cb758bc9775a80c2427bd9fd373ce1492f90e\", \"93d0cd063f1233975d5ae4d732970c686b21850cd4146e5775af2a3b48f6920f\", \"021bc91e59b79200bacd2ebef0e2045ae01d7287f14a3ef08de813492f92c603\", \"4a7ef9b7a74660125a9761379d3495eb8cbd5e0461a0ae90ac7bb4dd6ee57902\", \"158dab0fbe495663b5536ac17e444ea5d6a5ec67f2145d5ac6eb033fe3ed8802\", \"3133d514073957b3ab9506b375b52ab4e25df97c81f210d2a5d0ac2fd625c005\", \"22ee9cecb7dc0c84a47eef9338472bfa766f0bf5919be6a5b56bf5b84f1be105\", \"8d570d7ea8f372c5c253c189f006b314d377610bbc9a41fc162b3df3d860d904\", \"c74d0451fccecdcb8c0fdb66f55f10a955f49406f16c6ce397b78af25dcbfa00\", \"1a1cc63df1dfcd2918dba5e64532af7a24f3a95c722815ad2192f488fe8da908\"], \n \"c1\": \"0f8c295fdf955dbfe98666d411605e11598745385d7b639d8aed5b5499ffd007\", \n \"D\": \"143ff548f1f2956da85253ed716d16f7ed1ba3ed100426e2a81dfa2bdd952f06\"\n }, {\n \"s\": [ \"997389359aef4673cff1fcf634c4261c3f8a028c25712896381ef8e88b53ce09\", \"96cd93d9bfc6fa1a578554d1b0767962bbfb88f553d5bb129cf18ed93685b50d\", \"60d8e13ef8c06f14e7f4fb212ac28be059f83bd3c375220c4368d405ecc9f601\", \"a31d48f081ab49014d562c39b464f850af6679daffddb75935f4bdf2d8735a01\", \"3df11848f92dca8088339595f99024bb766c19e863175c0234157738925e4c0f\", \"4b5a83686667e9711547b3a2a96946fc126a026cdfc477de41e6c85835dad80a\", \"1a370f59950b9c9759595425609d6371d41801098202cb87ff96fcfb0247730b\", \"b2178497eeb94f794d151fd5393082ec7a0359b409b7508303493f7497237801\", \"59badacf201cb6bf41691ba3ed894dc4a3b22a3a829ff13a349256379e65b108\", \"b53c62247b27176ae5d22295393e1856372f1a89fa7d364173647ba296b76c04\", \"950e768eec5f38634e8cf3beff55bd7ce266b5bebe5b854a02cad0307b0b6704\", \"33bdbf8bc2631430773465cb091c7a666709285816e6d503acd5e649045d3c07\", \"baad6c71df6b89a0481a1fe7f45f6aa8837625ccb43ed5f9e8e9f7daf2cdee0e\", \"7f1baa61b625d9f5d0c9ee49605d403afa625927e9bf41d7e5d48b454dc1520b\", \"19e1c35dd25fc5dff641ea05bc2b6b5697485f96bd3664f90077c567923d4f04\", \"04107251310935d78e2d06471962f23277b5207500917d528aeef43e2b670c03\"], \n \"c1\": \"e614bd3ee3fd58b486a3d0a2494916785325e6546dec8fb880cc401319a7f90b\", \n \"D\": \"4d3832853ea6e0ae698543e20975eaa9c6606068f2465bade18d110a6937e199\"\n }], \n \"pseudoOuts\": [ \"229fc569e4dbb09f253fdc89279b76b70f3bf61d6808d7fa8ff438c795464101\", \"a97d68d18f240c9d7137f2db1d38013ba94cf478338fa0353b2a5cbf937f00bb\"]\n }\n}", + "weight": 2221 + },{ + "blob_size": 2348, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 56160000, + "id_hash": "9d1bcbdb17d24a4e615a9af7100da671ab34bffc808da978004dcef86ddf831e", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261653, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "0200020200108df79b209386c61387df3decb508a9bb05acc3028d9902deaf01bf208d05ac06ac09b014fd05b501de0d45a88adb7fcac982f5f4d8367f84e0f205235f58ad997f5dfa4707192fd3d9e0020010ace4d528efdb8009c5f8ca01a6f859b4a204c6bc24e3c306d68a01b1e203d401cbc6038103cb61840ad204a40f368fbc77179fb30bf07073783f6ef08bfb1a8c096e9bd60bb57aead3b0f3663d03000300b4750401a83c37a01ad5e0a9404faacc1356e0b553d17f31e16e8b8570e86cea0003befd3d4f80d897085c68417d4af03a4c2520b5ff75c400d1d018ec0d4617e6770a00037b2e69e0187086e600aa2ee921c61ff6847ebf80512e5f86c0500a889da2e8205c210147aa7f30ddab6c818f008f074a4a20528d522fbed0be3f581a8f574e697876a00680dee31a0a24de88aa0fcb7e567bad3c08c3a7247b06d68e46efce9940d6036d4b2b7e03c383c26989ac5176cef29aa6592d688747a7bc1989d88311aaad63d60db25d64d04ed7d40ebbbd4b6adeff521ddc60c9b00594381c89b9d9e4afabcff8a906fe5120b70bd38328753d4cee997f92552087ba220aa427758f010c8c7c9eb2e43443be439372d50e3cad3141de924fa118bad635f8105a086eb741d4b609c9a7fca073984dd6b9561b8a5dd0bc1fefd32a839ba25fa34b2c3d5021cd4b2157a936ecd28cbd4ad243876c84a0c09447b0ecfcf216c7c9f7ae6ec8c58d694f185dc9c4d4115e5d8d58dbffec407c39cd455c5842410c92ff9c0107359836b8b462f81e20852f2b14ad81c2931a6bc41097d824d175310f15d7890a99ac8ab0cf3e9cf0048da193a3a7706c824dbc98d3be3c7aee69d296c519380d085c64adc25186867f568afbd0ab351aeaebda94ff0832ea71771c255ff165669495211b42b692e6f6f745365f45baa6af535b6bf4868eb9699d7fd7b5d8a00a4c35bb4a24827cfc8d6f77f56a907a20dc80437a3f9cd3a4a889ad30eae65bfe3481af3a7f398933896be7bb06bce96c64113c3da8732c4f28f0a83b12ebc7ec329627128e8f6332b0c89fc1f18850786f594a7ef566a2970a4c308d5a0a7649dd81890916da41de7d8f74349fdea603f529d6aa77747ef2bda70d02688b4f121399f25ddbed7f3d2fe2cb136daa069b3548c9f0736057b68e249165662f8782f6cdbfe3e6a9eee2623dcb25eb492ad6ad825ae9917453c2772643e8bcb44b8122085661c1ea7185fdabc273bd62dffd938e1bee1b95c6a5940900331254bdb7f0361d66af8aa68615444bc98631b9cbee7ad6fb2c64fa92e42b472b7098acf2564206e152560f919105aee65e4116fd730f6201e639b585a0886262aef0e65db50b3413e399527b6a031081a272c0bdc89516b55107d186038fd8b2641690dbed6148e3084de6599e25bda1c719290dc31742b6863d3fcc57980ccb808215dd17a4b949f725231f2c29eabf7487e7c93bdf92cc5786c5614e77b2d2d10d9b9ccd475a2bf8992c52219480706e5676b40c203ee6799bca4636aeb7011875eb13dc2da7e6a777c7ccf246a19195e7aa4d7a617f0e5b7797a13228abfa6d288799b17702e263aa90ab2883591877afd734279ccf3442e40437401e4a6875b6603bb10db2503ff62c27a6ee89ba54efff4a15c149aae234cbdd165b6acd87f8f68d0408c3357e22b6ac83ea40186091d41ee82a467e6eedfb81065e1a6261493572820bb3a02732c48afe03a04344e108a16c6ed9b57f8bc70c0373c14e02aa58c39d081b9d63df16411a24065419e149dd5cf0d16c361d52077e372668658217ae0008052f944bf07460dc2650b271abbd3470f9d9f1151915b47f4e843c2cb66e8104b954b9b11711f668d700a7e8e73bba0e1f3325c7ee168c2e52fbbc42a9a37c0a5acfbd247117d89d3dec678cd2f1e5a358adba45cd96eb83bdefa574fe7ee70462e723e878f558b4ff133353015d12039d88ff4e4a42542274a29eec9d48f606f819b255f796a383db4db031d966f0512c0be65bcb570be40c5afeb301ced60bbd96cb11101317b582c5a4938f4b42aa4e4764923917fe03a849783821052701b601b95768bf304145239a0dcad54850a49431706b58139f92ab1aff4083ce021da8fbb5e19ef43bdcb40a61b67d615fdcc7d1ef9937efe0f64c65a6fc7d3a0f656bc19201f87d279c7dec96160b8cd0001f5064609b3561259728e19b5da20e57181f2d5e36ab9edc5fe93b71fde30f262d4e60525df697dcd66d45f079da0e5d95974c8bff7e89d06a64c25714eaa1b65522218c996c7ddb6cf902519f340524e084e6b195f994bc9be4cbfd8e6e9c5a1e00551148e1f8b6c154b2115b1f088c5ab9747f5d6f49a819f0eb06a4277090292c3b14f219196d91a3d8333b11278b5ca04440eb42f46cca567d8a2c3e9fd2880078544e47a6e932a828b494a7056038560559de1bdefeda1e9da672b6e931f22d530a7dd7ff8586184b5d593d0ebed9c5263818225fb9db1e64cbfc0174f872a3ea5e92f314c87b96daa8e7a400742ea90a1c357194d610b998a0c07dbad2bd2541158439be096b8f59ad7dcc073345072ecdb181af27eecaa21642d23abca25c1cd6a45daec5d423c8c002f60a99e4218d23ae7bd8055f9d473a8de7a36d5c00c64eea4be99c751655ab904604992a602c49416d56179835e3c6c5d0e89aca53a372dc61cfd9d105c7c0bdeb02765e41608cec6275ba3b7c19f2dcecd8cf7c02eed764b953066535c9395ba50ea8e8223b185992e646d89d4ab71009b3244dd2c2949d98557b21ad8cd2f03d0f140613a8e1366fe9d73e2282e471274b7304b94dc5a99a84411c5504b86176042351e695642aa3608fb3c413e7221b153d2ba3c0f8adca0d9636b3f95922ed040c19b92c35505041a39b8c9492c08577c9689bebbf1955827ab7298779d9d50b861f800ae0baa6761569a92e7b74d2d9dde4d8c98ffb37737edba691f4c94a0c78ef2c281ffe9c1d7f290cc7c60efd992f2b436c73b04f920984a1e8b8256a053d938f9f385b823aeee4f760e7c4346a3be6921700c433664d6806deaa3a6c039a1dc227d0fb1d07b26156b4b998053b2d07e037c024eb305212852edea364010936723eed3db539fd792c5acfe56bf2444998a107cff83eb7e37504b05c990f78111416918e784305b77030f0e2a14124afce9bf4d285c09a77264b2cf85e7fe99253575d3a7dbca07223afc25c085bc2e45aea9b399d58339b4ac1edfcd5e9ef14d6f64ab94d4f94eca4765dfabac9c91a423283387f51301a31d35de5b61d", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 67566477, 40993555, 1011591, 137964, 89513, 41388, 35981, 22494, 4159, 653, 812, 1196, 2608, 765, 181, 1758\n ], \n \"k_image\": \"45a88adb7fcac982f5f4d8367f84e0f205235f58ad997f5dfa4707192fd3d9e0\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 85291564, 18886127, 3324997, 1473574, 69940, 597574, 106979, 17750, 61745, 212, 58187, 385, 12491, 1284, 594, 1956\n ], \n \"k_image\": \"368fbc77179fb30bf07073783f6ef08bfb1a8c096e9bd60bb57aead3b0f3663d\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"00b4750401a83c37a01ad5e0a9404faacc1356e0b553d17f31e16e8b8570e86c\", \n \"view_tag\": \"ea\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"befd3d4f80d897085c68417d4af03a4c2520b5ff75c400d1d018ec0d4617e677\", \n \"view_tag\": \"0a\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"7b2e69e0187086e600aa2ee921c61ff6847ebf80512e5f86c0500a889da2e820\", \n \"view_tag\": \"5c\"\n }\n }\n }\n ], \n \"extra\": [ 1, 71, 170, 127, 48, 221, 171, 108, 129, 143, 0, 143, 7, 74, 74, 32, 82, 141, 82, 47, 190, 208, 190, 63, 88, 26, 143, 87, 78, 105, 120, 118, 160\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 56160000, \n \"ecdhInfo\": [ {\n \"amount\": \"0a24de88aa0fcb7e\"\n }, {\n \"amount\": \"567bad3c08c3a724\"\n }, {\n \"amount\": \"7b06d68e46efce99\"\n }], \n \"outPk\": [ \"40d6036d4b2b7e03c383c26989ac5176cef29aa6592d688747a7bc1989d88311\", \"aaad63d60db25d64d04ed7d40ebbbd4b6adeff521ddc60c9b00594381c89b9d9\", \"e4afabcff8a906fe5120b70bd38328753d4cee997f92552087ba220aa427758f\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"0c8c7c9eb2e43443be439372d50e3cad3141de924fa118bad635f8105a086eb7\", \n \"A1\": \"41d4b609c9a7fca073984dd6b9561b8a5dd0bc1fefd32a839ba25fa34b2c3d50\", \n \"B\": \"21cd4b2157a936ecd28cbd4ad243876c84a0c09447b0ecfcf216c7c9f7ae6ec8\", \n \"r1\": \"c58d694f185dc9c4d4115e5d8d58dbffec407c39cd455c5842410c92ff9c0107\", \n \"s1\": \"359836b8b462f81e20852f2b14ad81c2931a6bc41097d824d175310f15d7890a\", \n \"d1\": \"99ac8ab0cf3e9cf0048da193a3a7706c824dbc98d3be3c7aee69d296c519380d\", \n \"L\": [ \"5c64adc25186867f568afbd0ab351aeaebda94ff0832ea71771c255ff1656694\", \"95211b42b692e6f6f745365f45baa6af535b6bf4868eb9699d7fd7b5d8a00a4c\", \"35bb4a24827cfc8d6f77f56a907a20dc80437a3f9cd3a4a889ad30eae65bfe34\", \"81af3a7f398933896be7bb06bce96c64113c3da8732c4f28f0a83b12ebc7ec32\", \"9627128e8f6332b0c89fc1f18850786f594a7ef566a2970a4c308d5a0a7649dd\", \"81890916da41de7d8f74349fdea603f529d6aa77747ef2bda70d02688b4f1213\", \"99f25ddbed7f3d2fe2cb136daa069b3548c9f0736057b68e249165662f8782f6\", \"cdbfe3e6a9eee2623dcb25eb492ad6ad825ae9917453c2772643e8bcb44b8122\"\n ], \n \"R\": [ \"5661c1ea7185fdabc273bd62dffd938e1bee1b95c6a5940900331254bdb7f036\", \"1d66af8aa68615444bc98631b9cbee7ad6fb2c64fa92e42b472b7098acf25642\", \"06e152560f919105aee65e4116fd730f6201e639b585a0886262aef0e65db50b\", \"3413e399527b6a031081a272c0bdc89516b55107d186038fd8b2641690dbed61\", \"48e3084de6599e25bda1c719290dc31742b6863d3fcc57980ccb808215dd17a4\", \"b949f725231f2c29eabf7487e7c93bdf92cc5786c5614e77b2d2d10d9b9ccd47\", \"5a2bf8992c52219480706e5676b40c203ee6799bca4636aeb7011875eb13dc2d\", \"a7e6a777c7ccf246a19195e7aa4d7a617f0e5b7797a13228abfa6d288799b177\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"02e263aa90ab2883591877afd734279ccf3442e40437401e4a6875b6603bb10d\", \"b2503ff62c27a6ee89ba54efff4a15c149aae234cbdd165b6acd87f8f68d0408\", \"c3357e22b6ac83ea40186091d41ee82a467e6eedfb81065e1a6261493572820b\", \"b3a02732c48afe03a04344e108a16c6ed9b57f8bc70c0373c14e02aa58c39d08\", \"1b9d63df16411a24065419e149dd5cf0d16c361d52077e372668658217ae0008\", \"052f944bf07460dc2650b271abbd3470f9d9f1151915b47f4e843c2cb66e8104\", \"b954b9b11711f668d700a7e8e73bba0e1f3325c7ee168c2e52fbbc42a9a37c0a\", \"5acfbd247117d89d3dec678cd2f1e5a358adba45cd96eb83bdefa574fe7ee704\", \"62e723e878f558b4ff133353015d12039d88ff4e4a42542274a29eec9d48f606\", \"f819b255f796a383db4db031d966f0512c0be65bcb570be40c5afeb301ced60b\", \"bd96cb11101317b582c5a4938f4b42aa4e4764923917fe03a849783821052701\", \"b601b95768bf304145239a0dcad54850a49431706b58139f92ab1aff4083ce02\", \"1da8fbb5e19ef43bdcb40a61b67d615fdcc7d1ef9937efe0f64c65a6fc7d3a0f\", \"656bc19201f87d279c7dec96160b8cd0001f5064609b3561259728e19b5da20e\", \"57181f2d5e36ab9edc5fe93b71fde30f262d4e60525df697dcd66d45f079da0e\", \"5d95974c8bff7e89d06a64c25714eaa1b65522218c996c7ddb6cf902519f3405\"], \n \"c1\": \"24e084e6b195f994bc9be4cbfd8e6e9c5a1e00551148e1f8b6c154b2115b1f08\", \n \"D\": \"8c5ab9747f5d6f49a819f0eb06a4277090292c3b14f219196d91a3d8333b1127\"\n }, {\n \"s\": [ \"8b5ca04440eb42f46cca567d8a2c3e9fd2880078544e47a6e932a828b494a705\", \"6038560559de1bdefeda1e9da672b6e931f22d530a7dd7ff8586184b5d593d0e\", \"bed9c5263818225fb9db1e64cbfc0174f872a3ea5e92f314c87b96daa8e7a400\", \"742ea90a1c357194d610b998a0c07dbad2bd2541158439be096b8f59ad7dcc07\", \"3345072ecdb181af27eecaa21642d23abca25c1cd6a45daec5d423c8c002f60a\", \"99e4218d23ae7bd8055f9d473a8de7a36d5c00c64eea4be99c751655ab904604\", \"992a602c49416d56179835e3c6c5d0e89aca53a372dc61cfd9d105c7c0bdeb02\", \"765e41608cec6275ba3b7c19f2dcecd8cf7c02eed764b953066535c9395ba50e\", \"a8e8223b185992e646d89d4ab71009b3244dd2c2949d98557b21ad8cd2f03d0f\", \"140613a8e1366fe9d73e2282e471274b7304b94dc5a99a84411c5504b8617604\", \"2351e695642aa3608fb3c413e7221b153d2ba3c0f8adca0d9636b3f95922ed04\", \"0c19b92c35505041a39b8c9492c08577c9689bebbf1955827ab7298779d9d50b\", \"861f800ae0baa6761569a92e7b74d2d9dde4d8c98ffb37737edba691f4c94a0c\", \"78ef2c281ffe9c1d7f290cc7c60efd992f2b436c73b04f920984a1e8b8256a05\", \"3d938f9f385b823aeee4f760e7c4346a3be6921700c433664d6806deaa3a6c03\", \"9a1dc227d0fb1d07b26156b4b998053b2d07e037c024eb305212852edea36401\"], \n \"c1\": \"0936723eed3db539fd792c5acfe56bf2444998a107cff83eb7e37504b05c990f\", \n \"D\": \"78111416918e784305b77030f0e2a14124afce9bf4d285c09a77264b2cf85e7f\"\n }], \n \"pseudoOuts\": [ \"e99253575d3a7dbca07223afc25c085bc2e45aea9b399d58339b4ac1edfcd5e9\", \"ef14d6f64ab94d4f94eca4765dfabac9c91a423283387f51301a31d35de5b61d\"]\n }\n}", + "weight": 2808 + },{ + "blob_size": 2387, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 116500000, + "id_hash": "dbabb82a5f97d9da58d587c179b5861576144ea0cd14b96bef62b83d8838f363", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261653, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "020001020010df97d533d1c51185e031f69e08adce05b0aa06fb8a03b137895afb14c08e02b0549c1af60da305cb1f6d80d9c12f1439b0a994f767d71d98d2d2cde1a54c6a6134a00c2f07135d98cf0b0003e205de62fdbf4ff1b6554e3180bfd09051e368d70a5739bc039d864eea9d355da60003dacd0606c42ee249c304393e3169de7baacc28a9ff94240b603c54b0238d542d11000324dffd0f9dfb98b459a463f1c62df0d5ec76ad3781c41d5a65bdce94dc43aa5e5b0003631aff9f6291c9890f02a667c976b7e13bce27d443d00bda47d72d32ca2eacff40000360ba12b274ebbfe8f74abfe132a02a4a91e96f2f5db3b5772e5bab2e1066015959000348e42fd446643b5ab32024d193050ad976173b0ce43cc33add718167b0d99b33ac000356a51fe30876666e834c9741ee4e4fcbd08372005319f58cdc1a46bd98db5dec4b0003421637f078091694b19f1d2d3691a2ba8fe38198a808d0bd38f4202270d5b47dbb0003b42ff611a9b780b8e4afc2958d059013c55512102b3f6d7b6f30f4da75c5bbbb340003c3253ebf1a9125a52b9a7d4fca7d29cc41c701aefd7b8d5caa6ca02a128a0433cf00030fbe7d06b657e84ffa29bcb937a649a647cc1e967fdeb3a1c7a00f2e72faddbbaf21016b391fa654bc1e4141eec1d8a55bfbe363d9632b56dd4c7393b7b1f8934932e506a0ccc6376dad44ee6f7e9686a70f600a18341bd60dcbe99a71bc8a05eb8f5ad01274c1590bb0e0848ccd0872e311b24115067be37fa3244c7785ce295d3eaff88e7bd50450bcfe9aebd902dbf3fd4d3d5f5deaa8f5517e89834007556c99f923978cb868d155d761085d03d11684d6d75b79e461da935fe8b8ccff02aaba98f24626e21abd6b7cee23ff35c800e99b7b8719b7adcf2987f61a51c7e81728cfab8ac5ec5fa22a10dbd58ae64976b188860a88ea9a7ea3ce49cc5460168c355de58788139c74011a95e49b0d91e6e033be4f212573cae4ae8b77c7f59e727f4ff9ef5045de7a733c53eb98c57bf4c643a1183da886f66d7984e4559192c1de69533b7777e5da4290431063f42b9f900f82564deffb967e214eb22c6835fda6a27569c7211b2ccf9602046049c5b72ed13a70193cec07dc1336b609c575b4dd58693376a102246f13508d44c88dec158b3a9b263bf8557818979f7a833f758602ffc62037d78994082ea998cd1b4944ebed5a3cba2f3e19dcc7f9bfc96901a60c2293b2949b0e9d3b84260d2f6085c628ba1a0fbfaab1611bdad0ec39ba599342f1de56d2beb0930aea3ef3f446ebf52bf5c0cc89566a68ca68ad05f9cb012acf619b5da2f005a99b98d6ab1244a4458435fdd4ba5aabab8884eede69ee25d063c5e054a0763bd8e6cad5859a1f54d5d280af664cd2b3ab6a8ba944e2858bc9979f63b12e75b3a647fcb31e8796502947ada7d65526c74f70e2ff4820e599a03200a8bc6475cde069c7e90a9fda27d204a8c01c10706f1fec6c791d6e6d05707f92d85119d8b54b5818e894ddc4e5ce45612e601094aca90fac825b8f13060b9cf42dc96310520835ddff845dd782508fd8b73d99613d425323b206d3ae060a6ae9c2bf3c383f11392df4b0cdb537c62e618cd97de259135abc4de9fc3fa1e9d64eaa642cdd7d6a6e3f2208ed76771b2be4a2416e53c15f1e3bd7eee3ec3e92f6ceb61873e03d5ac5f2bf583fb5f86453310e232a2620a6bfa4cfb4981b533b8625b08e0938291ba52a9850629e3097bfef1b3764c73643ade61a8d425d3f2da30df56d9fd7c736efe223b158ed49114e1eb8e616a2ef47d673c8ed52b2d94c5c25ebab35e7e0512aa8d9a48c01ece39a0dd29125cafa904e1a33cca56d64ea06551895fa1f1691674216e56eca2ac8ba84da58eac436746fe39e028815dd812f19d8236dadccbc23378bece2a78a8cf6ce42a40d7e97ac095f2fbe3c769eb5674359a42c69eba31b8952c54fcc90ef3f141acb32d9f4f7d88c0d416d541aa2300560059f8f8ed209d935b3e26185f387ebc5f0ac4a279b661a97f13da023010afcc6f7d3542ee9511acf4ff6782d5f516ebcb08f76ef089cfbca6012001893bb8ef584c963ec50d1249f063d3a138a836e98fd3c845b15d97e819e60fc8b7298bb52ac2e31690570e777e3a18b0d2d30e9219d4c53ad018c05f9150963045dfacd472e1d89929f4df50429794c4214bd02cf7ef7a1dbac9bd7d882748c36ade1c99a664e1ebf3b88c9f2af6b0facadce56c11ee6e7f9bb642d612952642a2e584bc487bae947cac5301fb28e8635b282d5aeb999ac21fe3060a351649c00ed98646e663edab73c1d545771f1e85919b2d89ba44149a086e188ecaec21f5ecebe04198c506cae6da01a2413e414d2aaa0309f16c8afb17b146577f41fc383957d8ab02506d67795f41c6709c5cba79a14fa32842c4e38e9d97b0c69500f9dffcf78e6aa8232f46e7f6b2e402d0a5ee548a0e503be9b789a08803e0970f2d745249fbc34dc464b3ae4d5ba8b98946e3201fb1d9185507aae2a77e129394c8b64071d658e5eff25510bd2becae400f2db0a865a4a6fd55fbe6ea016f1370eab2d0090eddd7545194e0b3fce7cab06c968a6bd17cc848f5d3d20a26f5dccd004e803839d366680832ed1290ed6469f0a75a5ef9a35c426381a9372d4f0cddd77370d506ec1d7573950192844f4f3fe1089077dc6faa84d6513eb75621f5ade8eff0bceba99fa0b988daa13fe0c7fd15d85f050a339d447581c8459db9833952e710ae6c97b50495765c8d034d821da26bb950c6d379b923ba2482ae700098579400f3337c5898f002ab7832e258e3ead08da9c71bcde6449a5cacd506b6977549b08ffcdd6d90ce4f0b06e36f0b1c61055211284292df52d88532ade8962e3679f0e313298b6a931e790e641d5176247e5dcfa715134624e4bca5dfd92259cacd907014bf9138373942890180ea291a80e2594d0c062d1826dce12d4167bb10cd30a90cd211a857b80f7c1887c1ce14aa8124ede4b989f3520cce7011978b955cf030c76902a5b22867f9c3d47e8523c9cfd8a4d97ad2592f1debea8c30a7856bf04a435b617fd6b6a78ee5bd65e354f1b6ca5a21fc66d399e4debb8c2a97acc07048967d4c0fa15746f102312f0fd1f8ea851ee133282cba0f1f439408f2742df0af040c7e3ec8e5a0deeb6e952316fa300d986c29986cfb6293b90f5dac8a9760e2c8178c818aa7a44292f3129578d6bead21638d57b020709f39379f3b579cc0ae6aa3044eb12eb2487b34119b5f3ed92fc020406eaa991cf0a51c60cb1b7789afa8610e6d02955b7063068ebc5b481d70f39e570e880cd8551eaa333670fb213", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 108350431, 287441, 815109, 135030, 91949, 103728, 50555, 7089, 11529, 2683, 34624, 10800, 3356, 1782, 675, 4043\n ], \n \"k_image\": \"6d80d9c12f1439b0a994f767d71d98d2d2cde1a54c6a6134a00c2f07135d98cf\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"e205de62fdbf4ff1b6554e3180bfd09051e368d70a5739bc039d864eea9d355d\", \n \"view_tag\": \"a6\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"dacd0606c42ee249c304393e3169de7baacc28a9ff94240b603c54b0238d542d\", \n \"view_tag\": \"11\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"24dffd0f9dfb98b459a463f1c62df0d5ec76ad3781c41d5a65bdce94dc43aa5e\", \n \"view_tag\": \"5b\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"631aff9f6291c9890f02a667c976b7e13bce27d443d00bda47d72d32ca2eacff\", \n \"view_tag\": \"40\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"60ba12b274ebbfe8f74abfe132a02a4a91e96f2f5db3b5772e5bab2e10660159\", \n \"view_tag\": \"59\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"48e42fd446643b5ab32024d193050ad976173b0ce43cc33add718167b0d99b33\", \n \"view_tag\": \"ac\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"56a51fe30876666e834c9741ee4e4fcbd08372005319f58cdc1a46bd98db5dec\", \n \"view_tag\": \"4b\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"421637f078091694b19f1d2d3691a2ba8fe38198a808d0bd38f4202270d5b47d\", \n \"view_tag\": \"bb\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"b42ff611a9b780b8e4afc2958d059013c55512102b3f6d7b6f30f4da75c5bbbb\", \n \"view_tag\": \"34\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"c3253ebf1a9125a52b9a7d4fca7d29cc41c701aefd7b8d5caa6ca02a128a0433\", \n \"view_tag\": \"cf\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"0fbe7d06b657e84ffa29bcb937a649a647cc1e967fdeb3a1c7a00f2e72faddbb\", \n \"view_tag\": \"af\"\n }\n }\n }\n ], \n \"extra\": [ 1, 107, 57, 31, 166, 84, 188, 30, 65, 65, 238, 193, 216, 165, 91, 251, 227, 99, 217, 99, 43, 86, 221, 76, 115, 147, 183, 177, 248, 147, 73, 50, 229\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 116500000, \n \"ecdhInfo\": [ {\n \"amount\": \"6dad44ee6f7e9686\"\n }, {\n \"amount\": \"a70f600a18341bd6\"\n }, {\n \"amount\": \"0dcbe99a71bc8a05\"\n }, {\n \"amount\": \"eb8f5ad01274c159\"\n }, {\n \"amount\": \"0bb0e0848ccd0872\"\n }, {\n \"amount\": \"e311b24115067be3\"\n }, {\n \"amount\": \"7fa3244c7785ce29\"\n }, {\n \"amount\": \"5d3eaff88e7bd504\"\n }, {\n \"amount\": \"50bcfe9aebd902db\"\n }, {\n \"amount\": \"f3fd4d3d5f5deaa8\"\n }, {\n \"amount\": \"f5517e8983400755\"\n }], \n \"outPk\": [ \"6c99f923978cb868d155d761085d03d11684d6d75b79e461da935fe8b8ccff02\", \"aaba98f24626e21abd6b7cee23ff35c800e99b7b8719b7adcf2987f61a51c7e8\", \"1728cfab8ac5ec5fa22a10dbd58ae64976b188860a88ea9a7ea3ce49cc546016\", \"8c355de58788139c74011a95e49b0d91e6e033be4f212573cae4ae8b77c7f59e\", \"727f4ff9ef5045de7a733c53eb98c57bf4c643a1183da886f66d7984e4559192\", \"c1de69533b7777e5da4290431063f42b9f900f82564deffb967e214eb22c6835\", \"fda6a27569c7211b2ccf9602046049c5b72ed13a70193cec07dc1336b609c575\", \"b4dd58693376a102246f13508d44c88dec158b3a9b263bf8557818979f7a833f\", \"758602ffc62037d78994082ea998cd1b4944ebed5a3cba2f3e19dcc7f9bfc969\", \"01a60c2293b2949b0e9d3b84260d2f6085c628ba1a0fbfaab1611bdad0ec39ba\", \"599342f1de56d2beb0930aea3ef3f446ebf52bf5c0cc89566a68ca68ad05f9cb\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"2acf619b5da2f005a99b98d6ab1244a4458435fdd4ba5aabab8884eede69ee25\", \n \"A1\": \"d063c5e054a0763bd8e6cad5859a1f54d5d280af664cd2b3ab6a8ba944e2858b\", \n \"B\": \"c9979f63b12e75b3a647fcb31e8796502947ada7d65526c74f70e2ff4820e599\", \n \"r1\": \"a03200a8bc6475cde069c7e90a9fda27d204a8c01c10706f1fec6c791d6e6d05\", \n \"s1\": \"707f92d85119d8b54b5818e894ddc4e5ce45612e601094aca90fac825b8f1306\", \n \"d1\": \"0b9cf42dc96310520835ddff845dd782508fd8b73d99613d425323b206d3ae06\", \n \"L\": [ \"6ae9c2bf3c383f11392df4b0cdb537c62e618cd97de259135abc4de9fc3fa1e9\", \"d64eaa642cdd7d6a6e3f2208ed76771b2be4a2416e53c15f1e3bd7eee3ec3e92\", \"f6ceb61873e03d5ac5f2bf583fb5f86453310e232a2620a6bfa4cfb4981b533b\", \"8625b08e0938291ba52a9850629e3097bfef1b3764c73643ade61a8d425d3f2d\", \"a30df56d9fd7c736efe223b158ed49114e1eb8e616a2ef47d673c8ed52b2d94c\", \"5c25ebab35e7e0512aa8d9a48c01ece39a0dd29125cafa904e1a33cca56d64ea\", \"06551895fa1f1691674216e56eca2ac8ba84da58eac436746fe39e028815dd81\", \"2f19d8236dadccbc23378bece2a78a8cf6ce42a40d7e97ac095f2fbe3c769eb5\", \"674359a42c69eba31b8952c54fcc90ef3f141acb32d9f4f7d88c0d416d541aa2\", \"300560059f8f8ed209d935b3e26185f387ebc5f0ac4a279b661a97f13da02301\"\n ], \n \"R\": [ \"fcc6f7d3542ee9511acf4ff6782d5f516ebcb08f76ef089cfbca6012001893bb\", \"8ef584c963ec50d1249f063d3a138a836e98fd3c845b15d97e819e60fc8b7298\", \"bb52ac2e31690570e777e3a18b0d2d30e9219d4c53ad018c05f9150963045dfa\", \"cd472e1d89929f4df50429794c4214bd02cf7ef7a1dbac9bd7d882748c36ade1\", \"c99a664e1ebf3b88c9f2af6b0facadce56c11ee6e7f9bb642d612952642a2e58\", \"4bc487bae947cac5301fb28e8635b282d5aeb999ac21fe3060a351649c00ed98\", \"646e663edab73c1d545771f1e85919b2d89ba44149a086e188ecaec21f5ecebe\", \"04198c506cae6da01a2413e414d2aaa0309f16c8afb17b146577f41fc383957d\", \"8ab02506d67795f41c6709c5cba79a14fa32842c4e38e9d97b0c69500f9dffcf\", \"78e6aa8232f46e7f6b2e402d0a5ee548a0e503be9b789a08803e0970f2d74524\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"9fbc34dc464b3ae4d5ba8b98946e3201fb1d9185507aae2a77e129394c8b6407\", \"1d658e5eff25510bd2becae400f2db0a865a4a6fd55fbe6ea016f1370eab2d00\", \"90eddd7545194e0b3fce7cab06c968a6bd17cc848f5d3d20a26f5dccd004e803\", \"839d366680832ed1290ed6469f0a75a5ef9a35c426381a9372d4f0cddd77370d\", \"506ec1d7573950192844f4f3fe1089077dc6faa84d6513eb75621f5ade8eff0b\", \"ceba99fa0b988daa13fe0c7fd15d85f050a339d447581c8459db9833952e710a\", \"e6c97b50495765c8d034d821da26bb950c6d379b923ba2482ae700098579400f\", \"3337c5898f002ab7832e258e3ead08da9c71bcde6449a5cacd506b6977549b08\", \"ffcdd6d90ce4f0b06e36f0b1c61055211284292df52d88532ade8962e3679f0e\", \"313298b6a931e790e641d5176247e5dcfa715134624e4bca5dfd92259cacd907\", \"014bf9138373942890180ea291a80e2594d0c062d1826dce12d4167bb10cd30a\", \"90cd211a857b80f7c1887c1ce14aa8124ede4b989f3520cce7011978b955cf03\", \"0c76902a5b22867f9c3d47e8523c9cfd8a4d97ad2592f1debea8c30a7856bf04\", \"a435b617fd6b6a78ee5bd65e354f1b6ca5a21fc66d399e4debb8c2a97acc0704\", \"8967d4c0fa15746f102312f0fd1f8ea851ee133282cba0f1f439408f2742df0a\", \"f040c7e3ec8e5a0deeb6e952316fa300d986c29986cfb6293b90f5dac8a9760e\"], \n \"c1\": \"2c8178c818aa7a44292f3129578d6bead21638d57b020709f39379f3b579cc0a\", \n \"D\": \"e6aa3044eb12eb2487b34119b5f3ed92fc020406eaa991cf0a51c60cb1b7789a\"\n }], \n \"pseudoOuts\": [ \"fa8610e6d02955b7063068ebc5b481d70f39e570e880cd8551eaa333670fb213\"]\n }\n}", + "weight": 5817 + },{ + "blob_size": 1664, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 42480000, + "id_hash": "3fd963b931b1ac20e3709ba0249143fe8cff4856200055336ba9330970e6306a", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261660, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261660, + "relayed": true, + "tx_blob": "020001020010cfc19c31d9c65acfdb1bcfb91dd6e00aa7c2b601e2b51489e01ab3cd038e99039aa705c0f806ba2ffc1da40ba002913f889441c829e62c741c27614cdbb6278555b768fbd583424e1bb45c65e43b030003ad696cce55ac392f061b7e0f1acccbd17cbdfc11d20805df2ee8efb087476ed59c000357483f9fa7379f9b72e6149fd2b319c4e60653a627d0107d73f5604159d597ebf000034360e4f9cfb1109148e0b8428c36fa62270e4aa68e6aabd768270363b53a195acc210173c64bf5cd3e9f401a3800c9624c45f8e9408163348c4e2c473b88f610874eff0680e3a01479bce24f3573dbafaae47cc85d8723551119c984e226cab7daa8193188e5b32952ea0a464812c5f1189db475fe78ad4a6351e7d335a79163d9bd1c7b680e62a0ef36e83b122965bf6042bef9cf535da9286ea586681c4bef225c49f8f54b6b1a92a4a2d53c699fd021e1d2cf9ae75729ba7cf244a0264cf901fd2a5b8b2a2016880735d531b55fa6e206cc0c0d5177c93d10e2f60fadde3bebebf7ff3558e9949869a8401f4174a6e2e6092074d44b2665008391bcef3be3b38dcbb6f45f57379ba40acdffb81ffc8a3d312c6b728eff17a8c8f15d49f1517aca615e2f8bc5257f7b7a27cd455c633a400b6ff63f58d7c66c1172e1e56ef30e4edf1f17a43bd06754bcc657391acef09cbc15d6b827e61cfa8f6e33a4f7ce0837c0edf5a192c739ca6c8da976af9a1fa2ca1649024639f6c26e09dd01489b0a08e64e75e0dd89bd999cdaf72f1334263b2daa2b27b593951c8ea7781d7f4479fa8b24c4e3e7d02de85eb0144aa0e26be30c4a283db27c5b488ee4895345f81a51b54741caf457eef595c9753aa58d82d45182325aea6d77198090e8e14fd413620fcb3a7750dcb87178431ed8b4b23f39fd893c1e500dce31de0bf1861540e212e1263d567951d7c73e3f082a9fd7086aba9b58fbbbf61783fda9d037afa96250fe234734c186422495f5d5db92690958e43e5fd5ec5e302537d3353950f5a8e2a65a13ea58137481ebc02768f12ba06d4c3a58cc3f2725567a544434acc7934c87eff281268d9495670dc73032cab96056aeeda57fb16e153074dbe5b490ee2608d4ee6684886fec29e4f03741b6cfeb7bf468eca760fdf20085df50309f6942585ded84a963d18448029d3990a43ad3ba730b70193b7195961375390863ef0e721f26495ea684c129a7bfe0b58837269c2f3b9ed5697b4e5f50205e1699086db0d89e441c287e7aff567135d73a6724f206a15909cf4505c99a1ece8d2d44197ec9c2d28ca55008ba1819f7092e694e0048502dd937674b825e8325ccf06d2257e50dccf6d450f07a040def2b0af67d7a9ca8d54179bf474b11f3e0342b92e6f0d5c2c5b01dd7769f3651c5a5b7e4af34428cfd9429ecf373cc409db223a3f81075b8d10b0e409336f9922b0acc60780ed8c7e6916eafa813e486f678133c7c287040847e31fae2ed636fc2830394ecfef0c93ae4a380394907018dfdcf9814083efcaee92919e11f36ec098917436902e5752d62d43e054178b9cb6a66e7d4087b234f9f3fff46cdec0fd531ab50b82ffbbcf22171250fd8ce8015e6ccb940022ee8c5d7a0d10186b416c51c3370b0300f54651accfbebd3ac3e4a22ebbf2409b5e7ebc40dae1be188c5208668d6216d1cdb0552541c27fc6b9f770aa6fa2e0f50eedef2110162cd67b027348beb3865d40b71dfcd4bb02caa5d85454868f8071d6fda35f3729b3fdf7041ae02302733ed00dafdbb91e81d304daa7a99e0350ed8967cea2637fddda63bb273ed406be7ee694bff1fb5a0cdbf792fe25da78d0be68bff0add3f8868ece66332a446415da39994ed247606a7951ccbe639006008a2796c33bb8d53f64de030b7618deac7e15fc6b53bea8f04b30d244007e6b805a5c6a79cfa5e75c89eb96b298fe5f3722c9ab5219b839a6a88999d288913d9025d07125c47cc210f19bf42dc7edd84d62fd4d6ebfba9d865c8909477d5aa98047b12e6430d55c9f19d9ee5fe6020cb0fd4604ff5999ef3e8904d6016f99334035c1a93efd452b75925209e0eabbefade7d92f8ed581135d87f791170b3dd7b024eb818dba8ccfb23e897c6edc986ac47cd95292156e50c06797f9d9e552e3e01ac75a709909f7650bb41125bda22a768d3673bb7a28cf418514fbde82c3cc7035c483ea223a5c017b42873aca5381b7d4926c47d6ac62e8289a4bfe1f72751049b04cbacc98c9796300170897711eab28348eb4c73fc31ba3fc82d0043a67d628e9f3dc440a410d0dbe2c8c3c46caf95165cc82f3716f56fd0a6c3b187e1f892", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 103227599, 1483609, 454095, 482511, 176214, 2990375, 334562, 438281, 59059, 52366, 86938, 113728, 6074, 3836, 1444, 288\n ], \n \"k_image\": \"913f889441c829e62c741c27614cdbb6278555b768fbd583424e1bb45c65e43b\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"ad696cce55ac392f061b7e0f1acccbd17cbdfc11d20805df2ee8efb087476ed5\", \n \"view_tag\": \"9c\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"57483f9fa7379f9b72e6149fd2b319c4e60653a627d0107d73f5604159d597eb\", \n \"view_tag\": \"f0\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"4360e4f9cfb1109148e0b8428c36fa62270e4aa68e6aabd768270363b53a195a\", \n \"view_tag\": \"cc\"\n }\n }\n }\n ], \n \"extra\": [ 1, 115, 198, 75, 245, 205, 62, 159, 64, 26, 56, 0, 201, 98, 76, 69, 248, 233, 64, 129, 99, 52, 140, 78, 44, 71, 59, 136, 246, 16, 135, 78, 255\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 42480000, \n \"ecdhInfo\": [ {\n \"amount\": \"79bce24f3573dbaf\"\n }, {\n \"amount\": \"aae47cc85d872355\"\n }, {\n \"amount\": \"1119c984e226cab7\"\n }], \n \"outPk\": [ \"daa8193188e5b32952ea0a464812c5f1189db475fe78ad4a6351e7d335a79163\", \"d9bd1c7b680e62a0ef36e83b122965bf6042bef9cf535da9286ea586681c4bef\", \"225c49f8f54b6b1a92a4a2d53c699fd021e1d2cf9ae75729ba7cf244a0264cf9\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"fd2a5b8b2a2016880735d531b55fa6e206cc0c0d5177c93d10e2f60fadde3beb\", \n \"A1\": \"ebf7ff3558e9949869a8401f4174a6e2e6092074d44b2665008391bcef3be3b3\", \n \"B\": \"8dcbb6f45f57379ba40acdffb81ffc8a3d312c6b728eff17a8c8f15d49f1517a\", \n \"r1\": \"ca615e2f8bc5257f7b7a27cd455c633a400b6ff63f58d7c66c1172e1e56ef30e\", \n \"s1\": \"4edf1f17a43bd06754bcc657391acef09cbc15d6b827e61cfa8f6e33a4f7ce08\", \n \"d1\": \"37c0edf5a192c739ca6c8da976af9a1fa2ca1649024639f6c26e09dd01489b0a\", \n \"L\": [ \"e64e75e0dd89bd999cdaf72f1334263b2daa2b27b593951c8ea7781d7f4479fa\", \"8b24c4e3e7d02de85eb0144aa0e26be30c4a283db27c5b488ee4895345f81a51\", \"b54741caf457eef595c9753aa58d82d45182325aea6d77198090e8e14fd41362\", \"0fcb3a7750dcb87178431ed8b4b23f39fd893c1e500dce31de0bf1861540e212\", \"e1263d567951d7c73e3f082a9fd7086aba9b58fbbbf61783fda9d037afa96250\", \"fe234734c186422495f5d5db92690958e43e5fd5ec5e302537d3353950f5a8e2\", \"a65a13ea58137481ebc02768f12ba06d4c3a58cc3f2725567a544434acc7934c\", \"87eff281268d9495670dc73032cab96056aeeda57fb16e153074dbe5b490ee26\"\n ], \n \"R\": [ \"d4ee6684886fec29e4f03741b6cfeb7bf468eca760fdf20085df50309f694258\", \"5ded84a963d18448029d3990a43ad3ba730b70193b7195961375390863ef0e72\", \"1f26495ea684c129a7bfe0b58837269c2f3b9ed5697b4e5f50205e1699086db0\", \"d89e441c287e7aff567135d73a6724f206a15909cf4505c99a1ece8d2d44197e\", \"c9c2d28ca55008ba1819f7092e694e0048502dd937674b825e8325ccf06d2257\", \"e50dccf6d450f07a040def2b0af67d7a9ca8d54179bf474b11f3e0342b92e6f0\", \"d5c2c5b01dd7769f3651c5a5b7e4af34428cfd9429ecf373cc409db223a3f810\", \"75b8d10b0e409336f9922b0acc60780ed8c7e6916eafa813e486f678133c7c28\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"7040847e31fae2ed636fc2830394ecfef0c93ae4a380394907018dfdcf981408\", \"3efcaee92919e11f36ec098917436902e5752d62d43e054178b9cb6a66e7d408\", \"7b234f9f3fff46cdec0fd531ab50b82ffbbcf22171250fd8ce8015e6ccb94002\", \"2ee8c5d7a0d10186b416c51c3370b0300f54651accfbebd3ac3e4a22ebbf2409\", \"b5e7ebc40dae1be188c5208668d6216d1cdb0552541c27fc6b9f770aa6fa2e0f\", \"50eedef2110162cd67b027348beb3865d40b71dfcd4bb02caa5d85454868f807\", \"1d6fda35f3729b3fdf7041ae02302733ed00dafdbb91e81d304daa7a99e0350e\", \"d8967cea2637fddda63bb273ed406be7ee694bff1fb5a0cdbf792fe25da78d0b\", \"e68bff0add3f8868ece66332a446415da39994ed247606a7951ccbe639006008\", \"a2796c33bb8d53f64de030b7618deac7e15fc6b53bea8f04b30d244007e6b805\", \"a5c6a79cfa5e75c89eb96b298fe5f3722c9ab5219b839a6a88999d288913d902\", \"5d07125c47cc210f19bf42dc7edd84d62fd4d6ebfba9d865c8909477d5aa9804\", \"7b12e6430d55c9f19d9ee5fe6020cb0fd4604ff5999ef3e8904d6016f9933403\", \"5c1a93efd452b75925209e0eabbefade7d92f8ed581135d87f791170b3dd7b02\", \"4eb818dba8ccfb23e897c6edc986ac47cd95292156e50c06797f9d9e552e3e01\", \"ac75a709909f7650bb41125bda22a768d3673bb7a28cf418514fbde82c3cc703\"], \n \"c1\": \"5c483ea223a5c017b42873aca5381b7d4926c47d6ac62e8289a4bfe1f7275104\", \n \"D\": \"9b04cbacc98c9796300170897711eab28348eb4c73fc31ba3fc82d0043a67d62\"\n }], \n \"pseudoOuts\": [ \"8e9f3dc440a410d0dbe2c8c3c46caf95165cc82f3716f56fd0a6c3b187e1f892\"]\n }\n}", + "weight": 2124 + },{ + "blob_size": 1537, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 491840000, + "id_hash": "8a6ebea82ede84b743c256c050a40ae7999e67b443c06ccb2322db626d62d970", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261651, + "max_used_block_height": 3195153, + "max_used_block_id_hash": "8d15c2bf99e9a1c5a0513dca2106c5bcd81d94aa07c1c2d17d7ea96883cd1158", + "receive_time": 1721261651, + "relayed": true, + "tx_blob": "020001020010f783dd2ee9ec9804c7ca2ed7dd3aea990fd3fa0ae8c00edcfe1bee950493de03ba8302b494048114ab6ac7b70110a7f204f932169b1b056fc63be06db8ec91a436f7188a30545bcd6a8bae817ca4020003aa62b55146dd60777113d2c62c8bc41c9339b63e758513d57d5ada453786995ab00003b71531f0476ff381570af6bff1ca3bc8bf8286a098a939e32eaee36450895bef412c019ee82c35546584a78c1713407439e107e98c4da5e6735d77f5d61a369bbf4e2b020901c8c9a284894d6ce90680c4c3ea011d42ea4108a9eb4c47d3233193a71c74ed6c841d99c6eaad2a605daac8f0cdcb6b75c30f2a4aec0e955b2fd93b2cf65bb62d9b9ae29de54a88a5157484d03bd04b300ad7516fb0dc7c647b3ce7f2b2c301e106566731515046dbcf50b5e8f6edeea6bbd2e945b4eb49bc121da137e276481e0265fb162e11730623ead0848e5abfb5007e24282d4a5dc95ad1332e383bfd3d403de9cc283ef16eac799ea9ecfffd24b0a0faed955a5ce82f2a717f5b30bba3cc22335eb0edcfc3b2af623db3309981e8f39722accfc7fd215bd3f1f6a60f476ca5eb56798e464d5791a1cd5d57fbe2d2fbf1c6824a3e80e48d3fa807240fa78029fdf2795b8bc4f0b5d5336ebaa65c179534ec8038dec26c8b5cc103230607d277a8b482900c60791c03c6a072eb576cdf8532b2da3bb493872c264559a7832f46f076e6b541c665df3baaf4486d9466655a26e3aea8f9d6e4d3f5613f4dbcb5c1cc78c9698551ed28ebaccebba9ffe10b38c4791afa73c620cf433342e45733b26f192642f6498a0d482df12e605b91957614d4a6707d0c103883104210508812038852ef55bf0ceed35382c508d19b82bacb5891bf9070c4634bbdb229c22c4e1296d55ccad33aac94342e8f75acee853fa05e69fe1f545c3b4ef839b401f7dd53846c9a9d8b1b0b04282f802fa451475b86d39edc01368a86cf9382a037075bba459244e0dc6488730bc628330b776bf8c6ababb3046ee3b103fcc2d9d544cd92ea335770c68c0e6f385105299bffd1ae17a12fe4b40e6880a9c6f5038daed1464b0fee68ecdfcdc712405936422918197dfec8f4351b82e7da04f6bcb9c765924afbd077c168ee0306cf456e1f4606086e2155260b3f5e9d8443533de0bb2fa410fc5465603b16c0137a236f761fb254e7b53c8667d8ba931e41bf6ed5cd131a4171a265158843415ca2f5c1690c9ec431091f3312b7b308420878e7e9118bed3c3f7df4e5acc3bd7d209d3539ce182e551af5f5e6369d29367dfa97c8951cfa0ba11a23b5c5e1bf6cd59db4b658b1437652f6e3f25717c9f4f522b37a0767c193fdbbdb9dada9d5d46e38a7d8a42460c0bf37c7adc0a317fd3e4a29390de895b526f0368b4737af0746647e2025a10cae52d452400710c9709e2b375c09c73b2529c97d87b8e62f15d0c776dbb0f5564cde6fb5872c124193d47d92ac007bfab088ebd9a44f194597802213475438f21112a50cb4f02c7f650ecfd3290f1c9bffdd70877dbc77cd5485cfbec143687cf605938787c1426ed42646a1f0080757ba370ba2b36b492a71708e36736db7857405cae3b128879d8e9c7dff030b3fb734e6faf49c85ed5d7940b400375b12674b3858d31b7ab56222e82ab03e00ce265d7d694571d3db08a7f23baf68007c4aa67f31185aeaba276eb3688b26018d8c904b9deea0fd68ceeebdf1aac9d967aec1ebaa4d3c6df2c3ce7c874ace05a3cb5af380be76fd67a7fc64d7da5a3ad94350b295a86cf7943c05c12f8267017e2beada8abf3e0fbb104492c222bbd8a6058dbdb05042213ed077c0d36cbf0cc6f1333a49e977ef452cfba812ba428b19cca4ea461681abce4f707e8f691b06fa6c56667fae356bcfa47f8f74f1db6bb470ce8fb96815e3fa2aaf2d22d1b00f4487e8e83e98b7d879c1699fd6f1092b1a9b5827effc22f14e3c7bf59ef2fc0b9eaf15677934da8c39016b3f771dd9227e76f541818cd4ca03d3bc78bd18cc0ee9dea19ceb194f06dc893c192932468ef107461f70a03b4d9137b72958ef68031ae7ae8bfa3fcc4d0f335959b7ac162dd86aae3da1580817d5fa6f932fe3c5511d1585d0f1383e6c1035c5ace104f8b03f27958c9160375a80498f8b73f861c4", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 97993207, 8795753, 763207, 962263, 249066, 179539, 237672, 458588, 68334, 61203, 33210, 68148, 2561, 13611, 23495, 16\n ], \n \"k_image\": \"a7f204f932169b1b056fc63be06db8ec91a436f7188a30545bcd6a8bae817ca4\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"aa62b55146dd60777113d2c62c8bc41c9339b63e758513d57d5ada453786995a\", \n \"view_tag\": \"b0\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"b71531f0476ff381570af6bff1ca3bc8bf8286a098a939e32eaee36450895bef\", \n \"view_tag\": \"41\"\n }\n }\n }\n ], \n \"extra\": [ 1, 158, 232, 44, 53, 84, 101, 132, 167, 140, 23, 19, 64, 116, 57, 225, 7, 233, 140, 77, 165, 230, 115, 93, 119, 245, 214, 26, 54, 155, 191, 78, 43, 2, 9, 1, 200, 201, 162, 132, 137, 77, 108, 233\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 491840000, \n \"ecdhInfo\": [ {\n \"amount\": \"1d42ea4108a9eb4c\"\n }, {\n \"amount\": \"47d3233193a71c74\"\n }], \n \"outPk\": [ \"ed6c841d99c6eaad2a605daac8f0cdcb6b75c30f2a4aec0e955b2fd93b2cf65b\", \"b62d9b9ae29de54a88a5157484d03bd04b300ad7516fb0dc7c647b3ce7f2b2c3\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"e106566731515046dbcf50b5e8f6edeea6bbd2e945b4eb49bc121da137e27648\", \n \"A1\": \"1e0265fb162e11730623ead0848e5abfb5007e24282d4a5dc95ad1332e383bfd\", \n \"B\": \"3d403de9cc283ef16eac799ea9ecfffd24b0a0faed955a5ce82f2a717f5b30bb\", \n \"r1\": \"a3cc22335eb0edcfc3b2af623db3309981e8f39722accfc7fd215bd3f1f6a60f\", \n \"s1\": \"476ca5eb56798e464d5791a1cd5d57fbe2d2fbf1c6824a3e80e48d3fa807240f\", \n \"d1\": \"a78029fdf2795b8bc4f0b5d5336ebaa65c179534ec8038dec26c8b5cc1032306\", \n \"L\": [ \"d277a8b482900c60791c03c6a072eb576cdf8532b2da3bb493872c264559a783\", \"2f46f076e6b541c665df3baaf4486d9466655a26e3aea8f9d6e4d3f5613f4dbc\", \"b5c1cc78c9698551ed28ebaccebba9ffe10b38c4791afa73c620cf433342e457\", \"33b26f192642f6498a0d482df12e605b91957614d4a6707d0c10388310421050\", \"8812038852ef55bf0ceed35382c508d19b82bacb5891bf9070c4634bbdb229c2\", \"2c4e1296d55ccad33aac94342e8f75acee853fa05e69fe1f545c3b4ef839b401\", \"f7dd53846c9a9d8b1b0b04282f802fa451475b86d39edc01368a86cf9382a037\"\n ], \n \"R\": [ \"5bba459244e0dc6488730bc628330b776bf8c6ababb3046ee3b103fcc2d9d544\", \"cd92ea335770c68c0e6f385105299bffd1ae17a12fe4b40e6880a9c6f5038dae\", \"d1464b0fee68ecdfcdc712405936422918197dfec8f4351b82e7da04f6bcb9c7\", \"65924afbd077c168ee0306cf456e1f4606086e2155260b3f5e9d8443533de0bb\", \"2fa410fc5465603b16c0137a236f761fb254e7b53c8667d8ba931e41bf6ed5cd\", \"131a4171a265158843415ca2f5c1690c9ec431091f3312b7b308420878e7e911\", \"8bed3c3f7df4e5acc3bd7d209d3539ce182e551af5f5e6369d29367dfa97c895\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"1cfa0ba11a23b5c5e1bf6cd59db4b658b1437652f6e3f25717c9f4f522b37a07\", \"67c193fdbbdb9dada9d5d46e38a7d8a42460c0bf37c7adc0a317fd3e4a29390d\", \"e895b526f0368b4737af0746647e2025a10cae52d452400710c9709e2b375c09\", \"c73b2529c97d87b8e62f15d0c776dbb0f5564cde6fb5872c124193d47d92ac00\", \"7bfab088ebd9a44f194597802213475438f21112a50cb4f02c7f650ecfd3290f\", \"1c9bffdd70877dbc77cd5485cfbec143687cf605938787c1426ed42646a1f008\", \"0757ba370ba2b36b492a71708e36736db7857405cae3b128879d8e9c7dff030b\", \"3fb734e6faf49c85ed5d7940b400375b12674b3858d31b7ab56222e82ab03e00\", \"ce265d7d694571d3db08a7f23baf68007c4aa67f31185aeaba276eb3688b2601\", \"8d8c904b9deea0fd68ceeebdf1aac9d967aec1ebaa4d3c6df2c3ce7c874ace05\", \"a3cb5af380be76fd67a7fc64d7da5a3ad94350b295a86cf7943c05c12f826701\", \"7e2beada8abf3e0fbb104492c222bbd8a6058dbdb05042213ed077c0d36cbf0c\", \"c6f1333a49e977ef452cfba812ba428b19cca4ea461681abce4f707e8f691b06\", \"fa6c56667fae356bcfa47f8f74f1db6bb470ce8fb96815e3fa2aaf2d22d1b00f\", \"4487e8e83e98b7d879c1699fd6f1092b1a9b5827effc22f14e3c7bf59ef2fc0b\", \"9eaf15677934da8c39016b3f771dd9227e76f541818cd4ca03d3bc78bd18cc0e\"], \n \"c1\": \"e9dea19ceb194f06dc893c192932468ef107461f70a03b4d9137b72958ef6803\", \n \"D\": \"1ae7ae8bfa3fcc4d0f335959b7ac162dd86aae3da1580817d5fa6f932fe3c551\"\n }], \n \"pseudoOuts\": [ \"1d1585d0f1383e6c1035c5ace104f8b03f27958c9160375a80498f8b73f861c4\"]\n }\n}", + "weight": 1537 + },{ + "blob_size": 1534, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 122720000, + "id_hash": "7c32ac906393a55797b17efef623ca9577ba5e3d26c1cf54231dcf06459eff81", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261673, + "max_used_block_height": 3195144, + "max_used_block_id_hash": "464cb0e47663a64ee8eaf483c46d6584e9a7945a0c792b19cdbde426ec3a5034", + "receive_time": 1721261673, + "relayed": true, + "tx_blob": "0200010200108088a024a3deeb0ef5939001e7eb0fc19401cab201aeaf01e08001cd16f4a601f52a8c8d01a106d811c83e5e987605d678e8bfb17e8d2651e8dd5c69c73c705d003c82e4e35d2b5b89c9ebe3020003507533a540f57548b44e011305bd39d6a6a64e5ccc82bac1833b6ededb9121f3690003b32a2175124a8e0a0fd95a37d51a290a397dc74637a8cdd73ba4dfdec91b2c0ec42c0159ef5288bf4ea3ba0f6780416feedec329416b7fd78af51f494f3eb89db900a7020901810cdc8e9ce7315906809ec23ab7e164d1d01bfc65b2f30e77c390a5fd68d73b4565628faee8a1f9bd7cffe31a3bdb37ef7fc670e18f6190cc20459a32ac6e15f56e257ed3471f4bbf5394501db831fa2d05b784f3e5091f322c1df20601326379bcc24fdcb0e68dbca4cbfac5fc5238e31b4ea8619c5eff8776a42e9c9a88287c0c5fc24c5a1872237e971762f9ec12853ca27eb3b1e611d3713bdd4778bc8585ef0adb3e8f144518a22d2eb4e77c526335c5ad9f5c4754740eaba4623f59cce4ea9eab890ec0976f3e03bc08f3218192c56d82c36ddc4692f5a96f8b056f11c7f15635ba12a22274a0a171999444f21079496b016867b7dac9f05f5f0141b181816c004f0f3033d7e0a9819f5a8623a637dc7d949b34c0da6eba6a7d07073c7219d079608c6ff8d04b0d4bb425b73142e84750e7e5548ef86cc02ac0b91132c3e4df24b11a32f8c30a483bcd5b903b2cd87197d8172bd4bd190c4034322dabd82600f852c346716518c93c439a799a2763ff9d2457c47f96e22371440cc1db354184fddabde2d51512556b7d05ad6be9f44fbf1671834f6fd45a6f8f1f09edf96551160fe83c207fb8eabab30ed294aca1287ea196f1640af3a183cc7891a680264c41b34d56ab4219e8175b1d847de3174d4c298cb5ac40e1c84169b0a5fbee401b237ad92093cb34b6752229e03cc7ceb2546102360ca5fe823192024b0775b50b22fe9e7cfb29fec3400ea8a03b047ca1c92af76d14da40589415116b922b080f61446f1e5a11aaaa84acd4bee2e67f25c8a0c77db1e3cf8d9a2f57a1f6a622a4c76475ecb20fb1b6221caa4be32876414e6d6b0375582fbf9c1d50403981d303b53d877af580443431499ed7a030d01a618c37139ac4fec11b3a2afa1aee51a3605abd36c3cc05c02348430bdec52a481e04249a7adde0a4b718d459ee1aa67e4ce05980a451753b0e9b7dd543b373a137cd900f81929699bfa4fd5bd51d096c93673bc035031bf18d1e153a5b62e5965f4865827d7c871403fcda46a7e38ec6b2c7c7de6f6e88e7e32514fdbcf04bfdc851a1a052ad32052ca4a74f039ca030a488b3f3160043add8d6b6f7f5275c49ca6c5f3e6556641b5d08c80f053adcb2928105c91712723592a72f37c0da5ec12f62325cf8ce9a9d98244c830715d805c79b3e0d09ff094563869a28864beaf3d2e4a257f2ac7e05b253f84801fa53fc02c27fb618a582eeb68261458ff5a24f19db62368984e35c13a9b3b10de92a20b1f757574390e8a9e6cf58a33e6f2d7425dd5cff36f15b5992cca1690cedb97159b128e4e509bb7735bbfab47d0e925103bd69585fd60a772b9fbf300bde8d80256510c267a7fba688969b4e89e06d8faaa7b1369cdbba78b19520fd0a8e3e0858e4ca6457e9444053ef08c62efdf1eb2176e81261c5f2b1febf72f4047ef2981ea380eea8a4429fc107fa6f96bb9a60feb70db7cda063be92d3f650098f85e14b1a45dab1687f1e2d4e3b11099286945c997c1cc2f9f908367ce5af0d7fc4f2f770cce3184f98be164dfcd15b896c5290e46dbc2e1458683022d9980d87afe5ff98453b33f1ce50617b33ebe0565bfe0166c1af28cee2ff788e623c0614b3db3ca8b04ae0ca9fc6a036ba40269a3da9cbfd2cd1ce2c8690a6df13ed00cc5527e60edd183603ffc5fb3fec14ee3e6c24bd708192756a2f10d6c3d54102ba4b38a06f9ca37fe3e10c68a4a6bd3858015f2b18f82e7e446a347c488890046b48a02d406ce2f4ae39ab70fa019f4c5086aa861f73f8b343eb433873c52e0901d1794138c64ea8943d6763d334920c5d57014a4ff9b2e163abcd9af5da269cb8dd672e123f737a896c99551b4c610bc68bb68b30ba5206881e18fd2288a42f", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 76022784, 31125283, 2361845, 259559, 19009, 22858, 22446, 16480, 2893, 21364, 5493, 18060, 801, 2264, 8008, 94\n ], \n \"k_image\": \"987605d678e8bfb17e8d2651e8dd5c69c73c705d003c82e4e35d2b5b89c9ebe3\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"507533a540f57548b44e011305bd39d6a6a64e5ccc82bac1833b6ededb9121f3\", \n \"view_tag\": \"69\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"b32a2175124a8e0a0fd95a37d51a290a397dc74637a8cdd73ba4dfdec91b2c0e\", \n \"view_tag\": \"c4\"\n }\n }\n }\n ], \n \"extra\": [ 1, 89, 239, 82, 136, 191, 78, 163, 186, 15, 103, 128, 65, 111, 238, 222, 195, 41, 65, 107, 127, 215, 138, 245, 31, 73, 79, 62, 184, 157, 185, 0, 167, 2, 9, 1, 129, 12, 220, 142, 156, 231, 49, 89\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 122720000, \n \"ecdhInfo\": [ {\n \"amount\": \"b7e164d1d01bfc65\"\n }, {\n \"amount\": \"b2f30e77c390a5fd\"\n }], \n \"outPk\": [ \"68d73b4565628faee8a1f9bd7cffe31a3bdb37ef7fc670e18f6190cc20459a32\", \"ac6e15f56e257ed3471f4bbf5394501db831fa2d05b784f3e5091f322c1df206\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"326379bcc24fdcb0e68dbca4cbfac5fc5238e31b4ea8619c5eff8776a42e9c9a\", \n \"A1\": \"88287c0c5fc24c5a1872237e971762f9ec12853ca27eb3b1e611d3713bdd4778\", \n \"B\": \"bc8585ef0adb3e8f144518a22d2eb4e77c526335c5ad9f5c4754740eaba4623f\", \n \"r1\": \"59cce4ea9eab890ec0976f3e03bc08f3218192c56d82c36ddc4692f5a96f8b05\", \n \"s1\": \"6f11c7f15635ba12a22274a0a171999444f21079496b016867b7dac9f05f5f01\", \n \"d1\": \"41b181816c004f0f3033d7e0a9819f5a8623a637dc7d949b34c0da6eba6a7d07\", \n \"L\": [ \"3c7219d079608c6ff8d04b0d4bb425b73142e84750e7e5548ef86cc02ac0b911\", \"32c3e4df24b11a32f8c30a483bcd5b903b2cd87197d8172bd4bd190c4034322d\", \"abd82600f852c346716518c93c439a799a2763ff9d2457c47f96e22371440cc1\", \"db354184fddabde2d51512556b7d05ad6be9f44fbf1671834f6fd45a6f8f1f09\", \"edf96551160fe83c207fb8eabab30ed294aca1287ea196f1640af3a183cc7891\", \"a680264c41b34d56ab4219e8175b1d847de3174d4c298cb5ac40e1c84169b0a5\", \"fbee401b237ad92093cb34b6752229e03cc7ceb2546102360ca5fe823192024b\"\n ], \n \"R\": [ \"75b50b22fe9e7cfb29fec3400ea8a03b047ca1c92af76d14da40589415116b92\", \"2b080f61446f1e5a11aaaa84acd4bee2e67f25c8a0c77db1e3cf8d9a2f57a1f6\", \"a622a4c76475ecb20fb1b6221caa4be32876414e6d6b0375582fbf9c1d504039\", \"81d303b53d877af580443431499ed7a030d01a618c37139ac4fec11b3a2afa1a\", \"ee51a3605abd36c3cc05c02348430bdec52a481e04249a7adde0a4b718d459ee\", \"1aa67e4ce05980a451753b0e9b7dd543b373a137cd900f81929699bfa4fd5bd5\", \"1d096c93673bc035031bf18d1e153a5b62e5965f4865827d7c871403fcda46a7\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"e38ec6b2c7c7de6f6e88e7e32514fdbcf04bfdc851a1a052ad32052ca4a74f03\", \"9ca030a488b3f3160043add8d6b6f7f5275c49ca6c5f3e6556641b5d08c80f05\", \"3adcb2928105c91712723592a72f37c0da5ec12f62325cf8ce9a9d98244c8307\", \"15d805c79b3e0d09ff094563869a28864beaf3d2e4a257f2ac7e05b253f84801\", \"fa53fc02c27fb618a582eeb68261458ff5a24f19db62368984e35c13a9b3b10d\", \"e92a20b1f757574390e8a9e6cf58a33e6f2d7425dd5cff36f15b5992cca1690c\", \"edb97159b128e4e509bb7735bbfab47d0e925103bd69585fd60a772b9fbf300b\", \"de8d80256510c267a7fba688969b4e89e06d8faaa7b1369cdbba78b19520fd0a\", \"8e3e0858e4ca6457e9444053ef08c62efdf1eb2176e81261c5f2b1febf72f404\", \"7ef2981ea380eea8a4429fc107fa6f96bb9a60feb70db7cda063be92d3f65009\", \"8f85e14b1a45dab1687f1e2d4e3b11099286945c997c1cc2f9f908367ce5af0d\", \"7fc4f2f770cce3184f98be164dfcd15b896c5290e46dbc2e1458683022d9980d\", \"87afe5ff98453b33f1ce50617b33ebe0565bfe0166c1af28cee2ff788e623c06\", \"14b3db3ca8b04ae0ca9fc6a036ba40269a3da9cbfd2cd1ce2c8690a6df13ed00\", \"cc5527e60edd183603ffc5fb3fec14ee3e6c24bd708192756a2f10d6c3d54102\", \"ba4b38a06f9ca37fe3e10c68a4a6bd3858015f2b18f82e7e446a347c48889004\"], \n \"c1\": \"6b48a02d406ce2f4ae39ab70fa019f4c5086aa861f73f8b343eb433873c52e09\", \n \"D\": \"01d1794138c64ea8943d6763d334920c5d57014a4ff9b2e163abcd9af5da269c\"\n }], \n \"pseudoOuts\": [ \"b8dd672e123f737a896c99551b4c610bc68bb68b30ba5206881e18fd2288a42f\"]\n }\n}", + "weight": 1534 + },{ + "blob_size": 1535, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 491200000, + "id_hash": "63b7d903d41ab2605043be9df08eb45b752727bf7a02d0d686c823d5863d7d83", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261665, + "max_used_block_height": 3195155, + "max_used_block_id_hash": "c8ad671ebe68cc5244ea7aed3a70f13682c3e93fc21321abcb20609d42a5b6e7", + "receive_time": 1721261665, + "relayed": true, + "tx_blob": "020001020010d6e18c2ffcc1e40483f90be9b30b9af70af8ac0dc3bc099313ccbb01c79f04aa25946bc7a102ac37c30a8d1d563cd0f22a17177353e494beb070af0f53ed6d003ada32123c7ec3c23f681393020003416ee9d85c13be6a1ad2a0b9a5c3fad790bc3c266cd0eb55f1d38959a2eef8a49b0003d6292302f486945eb9bab6beaa2564a4dbe09cf8e92a107eb8734f0f8a09a1c9052c017ce27d3675d2db5af9968b93733350b10749e9fa4a0c1bfcfdc86c550088fdd1020901c6821af937903f5f0680bc9cea01e240eebed50fd3fd162b7b3b54185d5ecb8253de6123d50449a2746c6a82023347509df7efd5f8106b4e3d60dd0d7f86958d05ee5bc8a6963af900849d2a4118e031bcaeb2788aa2a1e56b036ebadab201d92af1585cb9cb3db78d3da41c66221180e1c8128a35b304ae96de328ce538d160dd66e00a9b57988589eb48c4e131b24bb266de41540a164b34008c591871cd93fe7c3fa4aacd67f35315c0927e7b4add9c63e94a95732b7f12cf1346b7b26e1d1d893d582b2b0787f00f331e787749b14a3e0cb5363537cb4654d90a4c90001e08066b76e5b7cb3d4a9a58b88c613f5b4ca2fb0a875becee6a26f287d14906f3be11a310ff9a3bdcb579818bb958229c1b0ab3d4ce9b935723bdf8c888190d071089e5724f75ce14fe58360ba419f7e7bda58a0175b9ce9b12627b599ae896d060e7a21f3b9d65cf4d386aa6ac044e2283c64b92f4b3f4234ad7e1036fa96f53f187ed53d0c753805df8de748339f72e901ca157aeabff55f14c451f0084e725a55ffc85d7ba5d26a7f678bbbcd1e40e8a3a5400620ea4eee86796b7ec2266b20f8fd361a94759654d7d39ba8cdd6b3a002f140dcefe0f498a6b33655bfb2981ac38db4dd0cc6b7c25d408bbe69fb7cb86114fd7aea4136aacfe15475f20f3d4f7cd51eedec4e4a1890fcc0d29f18d6270a84d9c4aa6ba6bf9ec3900016057240713ad161ed5d9dd3a852ad64f7183a57b83015ab461f36e5ac1ed0bb0d2f7e1ff6c37cb4ef43c6e65f11858490d29b6fff50e91acfb2799fa5b8c0c9e9f913f2941ffa2414ca459db6de293f9e8a231152f6fbdb1dcb2a79bebb5687870f69ec96254f56d963bc8e283a7fdf1fbdf4f60d5d97d4224d9f1a9d4c52c14ba4ffabf5dd9955c0e075da49a8f8d84686d4213331b64a5f770fa35260ff1e1e13ec9da2b7aa35b728810febb734410cb117e37040d2a3c3198b816272e8e76fca9199064b01511b86f7578718590b6ffedb78a5ae175e4efeb3ed71028913deb2e52e5f17d3792990cec53ff4b834616d77cd50e32c84b95422b8436645c304b22c2018c6308ab20f4f7e06c5f67bd59c6f106fabe2b14dc1713a4b13f6522aa74f40410fd14ef905febb4e95ccd80265803c37e285ed3939d43e368b20b9e49b955041dd3d895f4885e8264d99f574d4da3e94b785e5679e300b817e15f1c60228f0745d6faf106d0d36d000be6d5779d6d83165dec7ae167ed7fe6ef7a688f391b0e93c15448214aa121b110d4573e9f386b432451f5689b29a81cec1497ba9c17022f3c310407533b549210dc47186bf117beb821a3ac0d255a31bc57c795b4bd02982a42b4ce5d5a86dade14e37ef9a9a4870687800d2d8ae637cad458e3c7a80ed3e47cc7f6af7523f1ed6bdbbd3f4eaeb6fb9cc9ae13a905618821ca41f13902430b77134bc29f5319b96b17c9fda24cc72af61a890b422bbcd10fd323001605dd25ae17c9c1492fb180e3ac206e780db485c3f8940e1d80301e962fb3384d05d187fdf65cf5b1e2ad3e379f5a765773a3f196bc0b835ad305946328ace26502dc43c5790a5ca6c3076206b56bb1c4bc53bfb4b0fba11dfa24809087e6a6760d7475a3c2465b02f2068d356289d4f17ee35fc0956deb061a737fb4bde998320cfd3e85d3e5113062b0b49f860318d909c9cb714758e6203a2af2ee69e5bf58032014aafdf920276f5a50ca4c3a1b4b7c7f7099ea414e0982bc806beeb68bd303b51723220ce63175d0ba8577400c59109d97357692b9ace8ec0a57992585ed07bea256456a8600ec85716f9bf7dae9cd0cf88ccc2c542600e12c41c7403cf4056db57e4fe2422adcc28c3ec0a02dbb6b1b5926d4012ec3f27ad757faeae60cad", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 98775254, 10035452, 195715, 186857, 179098, 218744, 155203, 2451, 24012, 69575, 4778, 13716, 37063, 7084, 1347, 3725\n ], \n \"k_image\": \"563cd0f22a17177353e494beb070af0f53ed6d003ada32123c7ec3c23f681393\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"416ee9d85c13be6a1ad2a0b9a5c3fad790bc3c266cd0eb55f1d38959a2eef8a4\", \n \"view_tag\": \"9b\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d6292302f486945eb9bab6beaa2564a4dbe09cf8e92a107eb8734f0f8a09a1c9\", \n \"view_tag\": \"05\"\n }\n }\n }\n ], \n \"extra\": [ 1, 124, 226, 125, 54, 117, 210, 219, 90, 249, 150, 139, 147, 115, 51, 80, 177, 7, 73, 233, 250, 74, 12, 27, 252, 253, 200, 108, 85, 0, 136, 253, 209, 2, 9, 1, 198, 130, 26, 249, 55, 144, 63, 95\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 491200000, \n \"ecdhInfo\": [ {\n \"amount\": \"e240eebed50fd3fd\"\n }, {\n \"amount\": \"162b7b3b54185d5e\"\n }], \n \"outPk\": [ \"cb8253de6123d50449a2746c6a82023347509df7efd5f8106b4e3d60dd0d7f86\", \"958d05ee5bc8a6963af900849d2a4118e031bcaeb2788aa2a1e56b036ebadab2\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"d92af1585cb9cb3db78d3da41c66221180e1c8128a35b304ae96de328ce538d1\", \n \"A1\": \"60dd66e00a9b57988589eb48c4e131b24bb266de41540a164b34008c591871cd\", \n \"B\": \"93fe7c3fa4aacd67f35315c0927e7b4add9c63e94a95732b7f12cf1346b7b26e\", \n \"r1\": \"1d1d893d582b2b0787f00f331e787749b14a3e0cb5363537cb4654d90a4c9000\", \n \"s1\": \"1e08066b76e5b7cb3d4a9a58b88c613f5b4ca2fb0a875becee6a26f287d14906\", \n \"d1\": \"f3be11a310ff9a3bdcb579818bb958229c1b0ab3d4ce9b935723bdf8c888190d\", \n \"L\": [ \"1089e5724f75ce14fe58360ba419f7e7bda58a0175b9ce9b12627b599ae896d0\", \"60e7a21f3b9d65cf4d386aa6ac044e2283c64b92f4b3f4234ad7e1036fa96f53\", \"f187ed53d0c753805df8de748339f72e901ca157aeabff55f14c451f0084e725\", \"a55ffc85d7ba5d26a7f678bbbcd1e40e8a3a5400620ea4eee86796b7ec2266b2\", \"0f8fd361a94759654d7d39ba8cdd6b3a002f140dcefe0f498a6b33655bfb2981\", \"ac38db4dd0cc6b7c25d408bbe69fb7cb86114fd7aea4136aacfe15475f20f3d4\", \"f7cd51eedec4e4a1890fcc0d29f18d6270a84d9c4aa6ba6bf9ec390001605724\"\n ], \n \"R\": [ \"13ad161ed5d9dd3a852ad64f7183a57b83015ab461f36e5ac1ed0bb0d2f7e1ff\", \"6c37cb4ef43c6e65f11858490d29b6fff50e91acfb2799fa5b8c0c9e9f913f29\", \"41ffa2414ca459db6de293f9e8a231152f6fbdb1dcb2a79bebb5687870f69ec9\", \"6254f56d963bc8e283a7fdf1fbdf4f60d5d97d4224d9f1a9d4c52c14ba4ffabf\", \"5dd9955c0e075da49a8f8d84686d4213331b64a5f770fa35260ff1e1e13ec9da\", \"2b7aa35b728810febb734410cb117e37040d2a3c3198b816272e8e76fca91990\", \"64b01511b86f7578718590b6ffedb78a5ae175e4efeb3ed71028913deb2e52e5\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"f17d3792990cec53ff4b834616d77cd50e32c84b95422b8436645c304b22c201\", \"8c6308ab20f4f7e06c5f67bd59c6f106fabe2b14dc1713a4b13f6522aa74f404\", \"10fd14ef905febb4e95ccd80265803c37e285ed3939d43e368b20b9e49b95504\", \"1dd3d895f4885e8264d99f574d4da3e94b785e5679e300b817e15f1c60228f07\", \"45d6faf106d0d36d000be6d5779d6d83165dec7ae167ed7fe6ef7a688f391b0e\", \"93c15448214aa121b110d4573e9f386b432451f5689b29a81cec1497ba9c1702\", \"2f3c310407533b549210dc47186bf117beb821a3ac0d255a31bc57c795b4bd02\", \"982a42b4ce5d5a86dade14e37ef9a9a4870687800d2d8ae637cad458e3c7a80e\", \"d3e47cc7f6af7523f1ed6bdbbd3f4eaeb6fb9cc9ae13a905618821ca41f13902\", \"430b77134bc29f5319b96b17c9fda24cc72af61a890b422bbcd10fd323001605\", \"dd25ae17c9c1492fb180e3ac206e780db485c3f8940e1d80301e962fb3384d05\", \"d187fdf65cf5b1e2ad3e379f5a765773a3f196bc0b835ad305946328ace26502\", \"dc43c5790a5ca6c3076206b56bb1c4bc53bfb4b0fba11dfa24809087e6a6760d\", \"7475a3c2465b02f2068d356289d4f17ee35fc0956deb061a737fb4bde998320c\", \"fd3e85d3e5113062b0b49f860318d909c9cb714758e6203a2af2ee69e5bf5803\", \"2014aafdf920276f5a50ca4c3a1b4b7c7f7099ea414e0982bc806beeb68bd303\"], \n \"c1\": \"b51723220ce63175d0ba8577400c59109d97357692b9ace8ec0a57992585ed07\", \n \"D\": \"bea256456a8600ec85716f9bf7dae9cd0cf88ccc2c542600e12c41c7403cf405\"\n }], \n \"pseudoOuts\": [ \"6db57e4fe2422adcc28c3ec0a02dbb6b1b5926d4012ec3f27ad757faeae60cad\"]\n }\n}", + "weight": 1535 + },{ + "blob_size": 11843, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 236860000, + "id_hash": "b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261659, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261659, + "relayed": true, + "tx_blob": "020010020010f081a521c5a1d41186eb5698f516b5e818bbba1bedb705a5d00df89f039357c73b890bf54f9217b344cb19dc006e92fc1e623298b3415ddccfc96a8cae64cb7c9199505a767a16ddd39bb9020010f3eab531b38b98029ff116d1ec1faad00187d521d51bc7e80487c304913cbaac01629510e861df07da0fd656ac13a64576e7af5ca416d99b899b0bafef5e71d50e349e467fa463b13600020010e6cf81308cc3e702cbd661ecd64b8650a7bd029cdb05b7e5019ae20cd8f801e87ca479ba3c8a24d08c01b667ca559feaf79de4445ca4d2bcc05883b25ecff2f6dd8fd02a9a14adea4849f06f020010d5989d32d582698beb2499c931dcb2379ae705fcad02ac9e0e85a902f2f30193ed019c0796d302a85ec50e872bc5b7d94e661c5eb09714b243f3854cc06531b1085442834c9e870501031b73da02001084f7ef27b2c2e20acbd767c49a16a092298fe60a8a880286d903aa8d02b2bb1ecb8d02d4d306a814d612f25ba605a5ebf4914f887ecdfde8e7ef303a7f2cc20521a2a305ba9a618e63d95debfb22020010ea98ad1ad2e88618ddf413bbbc9b01feba1d908508938e15eca804fbd803f4f50bc154c661fd60a706be1dae03a2b08a090f611ea1097622cc63a49256a2d94a90b8dbaaa5e53a85001c86d55a0200108acfff31e1c28e02bfd8139bd603e09604e26387af0586f501c79401a059800a9e24e10d9629fb010288f7594b26dcbaff22f7e7569473462c49d8fb845aa916d7a7663be8b85b85530200109c83ed30a19a25d3a9bf01a08c1cbccd14fbc90cccb59701b953c0e403df41e69608bc1dae33c40edc2482037d805459f05d89c92443f43863fa5a4d17241d936fc042cc9847a33a461090c5020010d4a2f828fda6f308b19f8101eacab90199cc02ffae01eff701a4c005d320b32cc10bca9001b101ff59a906cd0365bb760c9a31da39911fa6d0e918e884538f0a218d479f84a1c9cca2f9a5f500020010b8b18c2f80a3c704dfa34e808b02d0a704daa501ecb2039a29eef503a19102ef07669343c20ed525a10452418ac25be58fbfcc8bd35c9833532d0fa911c875fa34b53118df5be0b3ba480200108cf3cc3296f472e2e75184ee0aa3cf0483bb01fec70a96c602c562cb59f6cf01da07d861c14ac411ea0c40e57cb9a9f313f864eef7bf70dea07c2636952f3cbff30385ac26ee244a4349020010b9a4e12cfa958d02b4e18705e6831da49807c8a007b7c202d3ba04b40ba7ca02edb402b4b803d38c01b431ecdb01e50438d739cfb68aba73f0f451c7d8d8e51ae8821e17b275d03214054cc1fe4f72d60200109ccba132acadc401c98731a0ef0ce461b9ad0895f702a24ec955c68401f975da32f616bf07e530c81c1eda8e08b1024028064450019b924eca2e3b3e3446d1ac58d0b8e89dc4ba980d020010cf888128c1f8fb0ac0f221efe24fd7bb14e3a40fddca0bfdcc0fb78f02d7ed02b5a0018e09be10cd1eb10d89051cccfcece29fbd7a28052821fdd7aac6548212cab0d679dd779a37799111f9ec020010db90af22e0eaf40bcbd19b04b6d0b401aec905f48402e5bc0f94cb08bf9808b9db13809701b817d49902fe629903db1b05138378dedfae3adbd844cf76c060226aaeddcd4450c67178e41085d0ae9e53020010a7bbf4299ff7fa05ff9bd201b1fee801e1d529b38341e8900f81a908c78b03b0e101b89201fe6faa5aa701d501ce050007a41ed49aa2f094518d30db5442accaa7d3632381474d649644678b6d23c00200038c2ba531d06e4ac990213d765751e89981303d4714d81229ac09e385c8ac1cd3c900030c9d3c41171e04e42f6ed61a932c2ecdb5c0103d7cdd909fa0ab15881469c66af52c01b848c2cbf8e0ee4a984bf645c0e6118450971072a18d23b6cf716f0bf681fd40020901d0fecbabcf50dc5706e0e4f870dc9c59cdde692b10e76b3405d76aec5c479df6fa3357cbddcb22e20d2b458011dd31dfc1c78316bad7ed9ffd39ba12b86a8ad571f7f5aa9668806d10d2b03c2c84c90690e4ddeb3dc6f870fbd5df5b0901c55abb99bdda211773d14dcf7cd68ca2c2e1df64896976c36da0b72b9b0b5abbf68499752558d3b7b342a554f91ea3171cba93075d35968565e37020bd60f6bf0086548c6fd600cc04f704607fd008bdd1ef83ed341109114d9301c30a86d3d87b9a9a3a6cf62d4aedea2cbe4f48af3a830c33486b3e3c7942ad7b4dfbc322076cbaf3b5e0afffc36549482dc4e5220ad7be9284470405e2a889dc4b0eda950b9eacce92f4b6fe9bbb9f853cd5a68b1ea882a66fc1f86df6b7f21ed49e21fb07070c2330673beaf7961285d721e00c55e6d97b77efb12cfcdac429cbe92fe7ed641ae9878d713edc784573be96c47e80dec59c487590f721dda777066a66a388a8cbd007967d90e323d91cae997f9449518ca71dd5e0473cd88d52a1b63f4994cb355fc68976cdff244295ea25e9d579eeee92211f52ed942307827480bf639e9b945e1f12d5a0369f990945dc143dcbb3e7bb7eada7b4e10ccf65128ab3a203d27441035465a96cac073e12db24bb696f38587021576582596bd0351ea8dcd30123af3e518560a843188e19aa666d9b70d7c55b2072c3813f05084de714988a9a07e21b543d2d50a2b7949e9c6ee3638731aa1f19909ac0978ef10ebe83e52558f6e96814d9b64fb0344d8a37e43e9933a9bcb087173ad895400a680f3f7919a585572b4c0639855b4a146492b00c82eb9d7bd9566aa94387e79f80561676a045e4be44d3a42061a710a8c5be6ac2af7531504911a4e17d27249f7395c40ca90ac64e8a0eaee615e0a23be18c2a2b74e5afea867b60acf8ddf7301055d2e9b6556e2b8750caaddea43764d08b5dbe1497cc7db2be2ccee30629fb313078fdb9fc4f9f01b9d7e2e9cb90fdf2654411be9914ac32fe797c427f2a3f5e0ab974226f243363e401c6214a87d6d33bd5d66a6f74f273c9eaeba1210b64089c7ca7418d0ae9ea96a6e3608b004341be48b3fb658836e80e9c3c2786764db9e239d115c206e2c5d846b20c33614aa533f068a9363f9e78b728faedd5fd5ac6c2b427dd0b00278d9000c2872ff0d2506b842847a65eaf93d1f5ed3267d63cf9128158e1d307aa6a0c69a73c3a1b9914c285981e40de53329e666a98e065354dae1c92765d0af2a1b19b7a3f102fe8a7ba53487879d77c8df704ee4785bd27a6083ab60ec00681a849c4a7064e6265c60e06924fe32cec542a40b1c89d81356cdb200e8429033f75ee3e3749acce3d130cca4e7892f68cd7cdfa8e122bc78530b2e8d0176d07ebae1b0281338c2b2ec14d71d211f1e5500ea3bf298e88b2bb39edc1a4614d0d5048bbe60650d351ec2ff4cafb7f29ed646885572bab7ca50503bd7cb1a6e40775276f4e3914a473acc0a6520ea22947173052c525ff7f69b4161bab3f4e55097adc053156eda0d8d255298da99b7612756be610b5d4c2d4c5622c2e3d45a00a2847e2e122a38f797072440f3b2860dc4a184d42f64b1a3db4c26d43cc3c6a040a1df4fe0cb81346469ee48b4c64ca3a163a234dd107cfa155d610e859d04405d90e1d6a6c4f5b5cd93fa65fc80abff6607d8dc1dd932abb1a760e5a05b8d90003f7429d83df3df24cad2012bd01134cf6ac445145c54434b6e12aea99ed030e66902fd5bbbefe4ec9d59d9e38da2e3e182da804b9cd69fc6516e5c42d12e409807e7e4661a8300cc35c9a5aaf05c74f315d301d3b58af8423e8b3a9dab2a5f47cb7bac961689da9c8480fdcd7699abfc7b1846b5406095930e305225408a80fff283d2ff7b05766873ea25e37a51af6e80fe42c040c09e1254fec0c561d7005078faf699880bad1bff0adcaf0fbbed6a6b708e993738fcf3f51307f49427b0dca0a31c36ab9453b909cc184d4c63908b94427deeaac5521a838ce0e7c6547082dac768455bb669ab5297535a143999b247d8999b9c8d041a2eb3bc3d152c50ae586639c731aa945cc0ee851bcef18a27d942354cb8d66ea356d8c2c04eb490e54f4fd74727b0237fee689ea8802a8d8c885884c216702e32c8e8769430e8709954ea828466f98d09e756e5ed6c12ef5eff3a4ee36c990542f935a460da469037bb52fb8e07f50801ab6a9b81b8eab75b73a7fe93b9d65b75679b3103d5e880aa1d7c47088f50c690def8612384bfd2f01dc17e184a9d0529735d34bba8e0704c1d20036078d9394a593de43e28afeed8e9f9e37a283de8bfa383f333281b502baab61d08ee55dc83a7a7bf7b9be015aa90cfaa3cf2c7976eb1616b6a53277079c48c0cd37af84fd9804839055052758c47dcbf861d6f481edd26426abec9b0f23396a7e14bd9f6ce9168c88eb5cc00c16e92c7133ef6cf66c16ab51d6209401f49e4a023ecaac0ec7ffac5f168d4e7d4ccf098bf7c59a769430b9131bad6500fe9c562cafea11ce248b40a51faf4a9a380b9abf6407f1184c05ab6f1c2caf0d83d399b9afb3f50c7d1b305bb321c14df0787e226ecd2def7bfc6b7c312dca0b255e606d60022c07faf74422223d45772072dc8a7452802ec6e0a208deee8d7da374add905f27f162b82bd779d88d0f533b9cb550de4b7a51679ba3469bf5b0d003038393e91408af3ab39972995190a27c377fd568695b06cf0a2c0f374680c7e6b2a71d81eb28f336edf96840978b03a9c42bc72c9004dea1ed37d1208670da75ed7d6209b278e9362c880ad85e32253589c0ba3fb6a613df14128040e4d0b37dcdb26edc3ed3d88b1c2608439d2b930cfff4eea3c5386d27928ad0f8af70b6e81677aa7c2ceb9280628cd810e5b7e175393342d5cd560ca14ead9574bc20cbf0d7268a249e56526af3029493eb31fd3d20160aa32d30a0f53c44f9d82970105a30031d96eb6efa0bca4d61a6739c37cde501a33ed7de3b8f1fc3ea696830c52fa1350698901ad198fa276f73406da4a2d4cac670860fd0da77c42d9c67305e4044076d2fb8bf2765ecb825e468e698130674102dc58f3c7fcb8194a6ad60546555d48c63f1354f34db8b6c6a381a4052cf4fd6a30ee2274af2da5684e3d0d32f2ad376e21aed1ffd8b3c0e48032a16283c3ce6f12ac2cc90b389e81f59c053fc9310d8c947f78ea37f66d2df64ec8c4e04b8bc58801a6a362802ad8c26106743aa699d067999feb437d92b438ca45bf1a8d1ca5106f6c8609f277a360f70b9237b6b9a393af4c7af6da7647c4bcb7b50da483f82478af3d99bcdbe4ef5e00464e5e541e187854dc98a69aa48fc444019a803cb71719f44b175e968330850f4cc9c96f6c2c24075edcb03b79a8d82d83b49db4a33b7eb993d48e4ece3d2a0f524c847d7f15c03e03708b96b6ea8eaca6a4657c3af0244e6401779a0ee21221fa92f13efd7721c5cc4500e7cdb8254997d8b73a7b2243f580a5e91cc246910e7fa7d13567b4164e6911f6722ed13aadd2a76f127a3435a4bd919836c363cb09ebf110b68ed56b28b4e8bd5d3e4568adfa509e914d98eb21aeaafb6734e42f0710aaa746fe4ea17c423f38ac951dd1007dc17486d2b823ecebf9fb9dcb62dd0de523f315c266de82c74f0378fcbfd2b030c0e1e30b034ec8f7d27946b8c86403863350520c89e34975e27b05df44cbcb017e0abbb6a756faf0b34f43f27529079fa6b98c8b7a26493a215c954448e1958f6f1aa558cee11352fceac48273440c199f152d34e7d3c805b35e61a2ce914b5e03dc5d7858ec15c44413e5b2569c00f31567df8324d01565d4e4e9e04d86f75f24dea9d9d24568cc0ff02740c50c06e0c1712ec5b711aa78a21e7433da47430ed76a9e5a20748ff5df632a9cfdce0d1e54ab98f2bf59db008b5c6a03c2d47fa12540d1c927fde4da851325e28afc0efc1fdd8a0cb2544e57306a0b5f486299a713a8cd4aad16b38f3c1b789937690b35287cdb337f33ce203d6303892c64e8be17016436717ff0c5c1af6ad7082e0b0bb3ca7bdf4a6369598e25d28c42189fc879daef22144424854ba28fef20680b5d32e9ae7872d9c134d6d9fe5b0ffe9352ceabd3805a6af5ac2b7b6234af960a77523d6e80b27849bd7f784a98d2422ff79c902f9dcbe4609c429e5b4a9fc90b15bac05d97ba07fe3734d3f7491c6a962ae942f382a7d28ea1fa5302c51bb40ccfdbd190f3286cf798bb345b51723f6e8ea8515534773a340613d8612db8f9adfff3278d190f7894a6ae534e2ef7a0a8b7b2fae577aaf438b62ff0e4d79b8a0856a76827f3e29042f3cb444e67dc16633c30d8db17c541ab4db7d81fc5a32f00b3609aa0b8d2544843206117a826e456a41aa87d80d320c09f1ac0f93ed8e30a22b70ab8b6be5dd1288e09f9c20906d6bca5f0eff1e2ed57da9465ce60ecc00faeebd450c3c3ad2453b75c18bdb725cca06d8cee657c77564ef03cda16d06f0d8e5ac1cce961abddbb5879738d04997263622263d9f27ee8d5868814afc7a2024e8807bfbf6943fd0792d13b9bef4ff83ed82f0c34df5f9c42aa472cc2c1ac023e152321855eb77e8f762d5b6937d9e7cb3a9f3c2419b806203aa2deb57ae90680a3c83cdcee63d0bf296027ef22cfac0938f9b115359e6c6d91306dadb2fb0441107ab8a88b18f0bde29fd68432767c7ef193cb6815c4456eb0d917b9a9e80f4cf02dee189d4b8accb839002f8b6eba365c9f3f693e156932c09941bfc64600fa9a1ed117e99068c3220c9f0e94b9d7c122c6c391e8f6dc0ddd0ec8097f4d0da7e3987fc86c5523fc788c68b25f86ff5e347849e71171ff0f5c6b15a0b501076b1dcd3e4ba22639beba069d6c813548c99db963077c1f2bcdd173f38e79bb05b450a4748c5959889f22d53f62476626e22587a023dfc4517d49396b0c2efc05817d8187aee15d20ef0f623615bf4da108d2429e596dce7d8e9fe6e8596c06009824b7bc0d7dc58a097658babc3701f19a7210f2c197eea886c5d00dfc50c108570002d437713df562fa1e3d67f70450db016570e1119532ed0e3e1bdcfd3155792e33a1110e87d24c8eedbd1b5d731c4fdea67a7d31168432e7d7cfd6564706d6a5c45ffc307daa0cbc600a5e6e3c9c82d493388ada75deffd3c83906dba60d6de0af97e952ddad74e59b92013a6e55d63a0289e09feb33641ae2bc1b2fd60e33ebb741b522f6c0d47a331846edd4f4d82223d193e0e5aae4b88750280b5702afb63b883a67e6a442ebaf9c7c60be8037a040c0a6e1814696ca2238717ecc0ef670db23a3063db45d74557d715d643255f5cc2b02ce024a42a2c4f48d0a020c4fe82d187b3b6edb7e2e9444cb2cb3b8862523febcd7bd5e7549b5031eb8c90c27907eadeef4328d325dfa862735dc9ba8ae26df2fbf9712ac4fc813122ea10c0ffd43cf70c217fbfed3edd012d66d959d03aba43873f87634a3ec91efdc920986321b3ae6152c4e1688b81775c069fd5a41e71e2dae304bd5c479e3247ee20a94266393a0761debae592b659f32e71c39343636ac1ab2d3732af1fa9778d701fceb0b71bad10c860e51e637430e6520d5639c6a1c430aaad3802c30a92b500fe55616cfb92ae1ce65f3eba6285da428c7849d24918f6d3bf5bcd8b37db6a403a3220064e82e94bc8a3df93e39332b133bda28c866f30e45c824039e448ef70459086810004d7613201583e9497b5c17e878238bc1280c87ed2381b8fd30860656df5be83e02f18e2308f2546a553299ca285b53c7f66ad0297c907e01dccb021d0e21631768d826068eeb457b877f4009977de0fda3d981dc3887515060c50b6cbeb0704498abea125f623107f97b33daf3ba02d619b6edb8223167fcc1df4c1909cb506805d1f464b1ebeaa69a1b1a0cf02387504296c7ce63c61e0385f00036dde496366c9988b48d904c4111379548fa829d55787ed9c629ca873862a40648f6f8e24387826471f8e6047baf0babff8f21cdfb81d4930749638856f2340d306f636d277ae1792f84a293e4f66350c75023a1b5967eb40a43c440a246c60342d8e7ebaa4ee68b8551b9807d7b308574a90a7c54134112d6fdb3151af2c90a2463bad24e336dd34bffcb34713b6802e711a168e4afd30bfc3dba82550fba036a8d9f5070875b622865ce43fa773e77ebcca52dd7fb846aca9c133eb93bfd08030bf7d2f95f541cee1f8ede2d0099177bc447d8c67a6d3b05ee3183498b1b0a9e8c271f40e36941df4924502e31d8122d28fef01063e9692152371ffbd44d0d54b527ba4a550b6191a40192e1735f9e9634df5647c3580179ed7f11d893890e968845f97aa642bccfb6b9e84a4d68189d59fc729f23059dead12b5bce438403aa7a1ef89da4ac0e4617a978acd99ce57213509702e31941dcab7530c902690a7da1e1c0ae121c31fa19cf8889ecd8b61cf086be7cfe695b172a36cdc326d504e55918e88a21efc120a54ffb6f852d5b97ccd7cba3ed1e44b6fda91be7b5de0589ee42794cc797c25249a9a6d40f873e52b491389065a950c8aed5b6675f5c066d1dce8ae2cee39a8f7b1c4adf5fba44fbb41a81b06a5ca9b597ff6081d89a0edf9d832bc866de4095119e7fb0eaeb8930b705d0712aa17e11754ee27da80105befbd6dfc2d3e6e8a040c080a7d4508621474e11a3ba7fee3a2d6ed428324c22d723f61c46b0dedf57ed8a1f5b4588f9922fa5bcbfe9cb9d97d558a4030e7809c1057838edc60be81f357bf466bc5b9fe05d58ce8009fa509db7d24e0404f609b7ade4df5611ef00761069d18aec275e33941db96feb81abcf87171b7d54f0013d88f8d39dbcb57df1b0a3a3463bc75995e8193afb08815de576c1de957b700aba945788360754f9575a3d9c3909e56b171e193da4dec2789cc41ce933c82904e5c4c01c218680c44c46ff1d501ffef20d6a5b067c523ea161e115e7b0179007835c82d226c6d3f7615d13ee729f7c926d429a33302cc61febb9ec53d24dcc00322740613d4714cdf380dec0673929b91f0c173455c1d5f605b3c2a2998e970309c59130a49a6dfe6b9f56d4ae04e21e9b14034e54e0b8745ba2c918f39d2f0f95e48323f664da395a8e2d6e294c202a51e5cb513a664f947deb2ce6c270000632cf0c9decc4857d801b939cb2e97498832d469e9487978a447e40b25904f80ce5d6e7d6a32ea5fdbaa470d134b318b41c8ab7e8cdffec513ad4cc2abe280406081827c389a2c247d0ee40235dd62f1ffecbf6aa3389abda43298ab752f9480f898f59005b4d8a2fdc4631bf7f2cb10a6cb789dd028e5ce71f0cb3512b3ed30a497bedd241b6bbef8bc099340e859f20d47d928fd1bd804eed2b41e266dcb00e3ba7ab8b1d442ef9200458654d613fa08ced2b8d86cbc27260e099bea23e4904e5ce096899cee734b4a524e714c1b98e9acf571563328ef71d45e3bf6fca2d0bafe7b32ad8919db80210eaef8c2c89bcbbafabcd6d5292cdfe439c1a9f1fae66090411640c6ccf905547102b49f7a025a3c7e095e9f7eadd457c147d96210208962f737018ec057b427e2d31ca7eb58b25e9fc4cf7cd9550129035d6b98da60ba7919d4fe9b99e4da7598fe85015c43be4eca45d1913458e9db9bb8a155c1a0eb64774939673b8914c6bfe98348182ec3a222da702175ebd2b8c970bb878d807858ebb7d35f6675bd2f4ab35af156defc38ca3fe0ec5b305f2d13481bdfaf40b09d0076aa6d6e2de0a284fc8027f7ef58df0eba11a524764b4e939371566a50a2f1c2171207626099ea70cbb62112001ce82353e7640b91370b2a277047b9903e6aac7bdc67db5d1e316b7ecd9d26d9efa52a1427aef62ffe91d19e5549f9106059a55d8085e937920beceb69b6233a950913aedb6c9913853257d80a7f2df09240a0b0e2265f861f25f3b3f020295e4c3a557d8f210a19713fbea955e2fbc00aee13bb44e9d935d547cf7b071b05d94292a4d753018e4b2058fe919e98fca0209f9e3815440b6b34990f49af2210cb6074f74dc7bbf5804a6a2986c2a1fe60c70493fb69d8a0a256d3ad34313bb2d88cd9e3dbee169bd656369c2ad0b598e03e2d43843317dcd53af92eaf7de73f89d0aae108da35e9639a951cc4e6bb6bc08478d9108aa862add6731184656515718c3bbe4c666bd3730ecbb150447fb2a0f6a1cd0c4c26f836163f5bd045d30243b49e10bedad9c3a974faed2469b128d02025202ee0e8e262e50081c08bf278b9de30afe9fa80ed99f0b50915bb83752051ce7b914a5b6c8eb0e4a2de2f412a377ebfbe8e3f409ef8580b4878a380c11e476588b8804b369130947a7b805aaf563125b08d79fc2a7a6dd59edbfcac7aa08de4ec98ad49fa4c88de7702238c883b2790c56b5752326843113fc7a3e860e058ac662acfecfe2f803960414b6c5a3f0e16ba8bc45d2e9442b74d13f7774f30af7f4cbb7eb1a2545914dd2562cd1cc69ca12a46b76e4b3d0fdaf520e4a4a7f0dd8ce412d8b0567f994a8c1684e5549402078b59200b8b697c776e6e58d34cd0b797938f4eba967963055852af7194e1d776f455e2e84402598e1ce415f8720000531b2935021bad2348c4b5976d3cf2d89626138a5f204102559c592a33be507dd596db1cbc03ab91f1f5a80729619bfb1bc830d88188e3f9593fb430d4050092f9064ce03ebf328aa662c7adf612240056e0418f531fe086f19509d44cd8e0c83393059bff678437c3d61acafab248c7b41119bb777354b836e9752849c80054fcad749c5660627ac7a24adce564b62426563b0caeb16769ffd5afae42643071d211d06f860445b0a384ea4275a1f6f2b0caf11ec2fead1b8a8abd71215fb0ba65cee1f28aeaf4fb54685c4b9c1d4583d05be800fb3b392c9c9b16d45b4740589c45a1d7adc9fbc4a8a91a6d95a256954408f1e1794b9b73c7fa92d9d1d3d0d62e035bd6a3c91f354608ac7d002f1ec1481b92fe1efb47dd717c58388432e0821b4642894dc1d95bbcb4bea812edd2be509e3723eb27dab02f0aa5174bfda011bac757ac05198bfae729198b21ad83ed77d7099933ca8d438223eb5172e29005f8d4bb439e9f1fd7d9319bc235a800ad8cea46cf72aa161403411fcd239c2ccc1caf86b559d263ec57ba80793cb6045195b2306dc2d3d1545b4f9f628733f0150a751cc8468d64e4fd87d8f6808c387112ea6cbd2747347a2702a1c53d036061c256f19897dced055dedc2f6000444b891b2f0f103ec52169bef5620ecc8802b9c40573c76e1a18907d9aaa82cfde4f69fbe29a031ae39e9d47161b52561104b9c4272fd23a758596329701d313cf2a2a5a0783b9f39b0d9d376b1a53d9aa02008e31c110ea76d7f3dd3b6119d18436d7d87638aaa30c633eef1fcd5923ef0f702f504ae3e3157382c50c022725f50e0de515d1bef117ca08d832e098b0240e6c2be19c7e25a8f32f876f4ea74809a659e02b8f2ec908dbd119ccf0edcd790994de8034f11691ac2cd6500c32e20287e968196dfd93de98d31a5733b19e7a0735c541ce2da123a622e10b79abd68ed242218fc198d3fd9bbe3dced2aabf070750593ef0ddbcf3da82d63d30faf24289e054434fe9b2547de07056c2ae927f00ddf5820c7620d2b9cc0f5047fe4a74d163cc3813f5465bcad682ea0be2d0070d34b1b382dc98e343b261a1a605ce916498f476b2131ff2353e1f9a3ce3e71902f2820f6f514b72f32b2a3e2d50fad8c47129849e2643ffd978f3d6a8893a6a0c1dfb540d9405f41c1b62bba4b0893273535b223deb4bfbf64c2a8b6ac040f00610d5d9beda68f687ebd4035c39c45cd80cef2c15ab5f35f24af9c0560ae08f07b50751c330e15f3ee3a8e07783dc00ca6b8f78d465d7dea2c8c83de74d5f000e240a2046c4d4346fde8802305e568074e94ff89cf0a3d5346eb77564c35b15b6b5746f3412f0a78c40323c98a985c255d63656bf1115c4c826622d462b26f608130852be14f740b8570f5f4a5511b86a784c51a5e4fb8e945e31c8040628da04fcadb8cc0c23e4b24bf2222932c7893ca6b9904dca2ff8a4968d35a0d368ac017496afccec6a815cb767f0b779ae95e3ba5260724a34f18cbc9204840414e60344dedaec24d73ccdbe4807b8f1555c074c94f53d94177fd1698997c431bd100d9a501dbae938247f5b77eb42505ef9cefefd4e7f42287903b316f036dacf80089174621ca177d5a71c9aed771de721522b0f72c815e97b130f1cbcb83877010f56c9926851bccc1f48abeb9972a7cd765435ed59666c643e8bf9f6f0dbf91e0c32da0d9416c4c27b002fe1547ad2406532ecf44c89e6245ac041ac0ce4e4eb0d7ca53b3bab439481930d4e214b3b8460354614c9fc4a2c5035671c4fbc522d0b0ea983f506dcead626b4f7932bc9b7f497f8233a1556478c0786218bac666e0ea9e7ac6a29eba7c27edc4f25dbf6906883bbf4ec47d1ebf38a3fb70c0df46f0b0541a8b5e21a9ebfa0a08a660daf10a2dac0d7c8bba437f3b931c96455b58805b0d522d58f81354805dc073ae2dfd82bc827ae72807fd533687f78a450eba70ef15be34213fc5ca3b3762944f1d018c46e7bb0b9829132633e54528c7178b7000c3852eff49d00338308b216cf77fa36f67130ca7424138560ff9adbbec1ac0f3c300aa9725662614ee790040e3668b7b512e4953b5a68a3df04f881978dac0e77be03f07de3ea414ab5de8b4148df7b2c7548de2392fed2d26dcb61deeb329a434e34961ba8fc0a8533731fbf13923c60ecaece5ac0633100fd18b1a47ab40a9c3d10e14fdd5f1c448479b933db813f16c87d74e925e91a370be9d5cc5f550f3bec745edc71c836a48d90e51777f2c480f5338917fa9ab9ce258bcf6eae6708542e1d2ac11c524ee41c307b7deac63d13dcea39ca2a3fe38f4fe0ba59a6f4052485619c52efba1b68fb0f0ef5aa921cc57fd1d341f3556a1d6b73d17f6db60b496621f542826cddad8de2e517258ab046842f696b9c4f2835506522f9d2b50093ed535605c4815979bac5627c103f51e4ad001e6a600f0d0b4ca4b81f06390b14ad02ea0c3b0349824c1e0573549bd853d20cad9fa1edcca9bc566014f0af06528ffe2ecbaa066240c9bff71fd5ddeff863b27cb00a74fccfb1db5499a8bc06eb01cdeb3b01b261e61f3fdf0f3973a4e1104fb997c8b6e3ae436f1ce0353c028c292daef3187740a653da65cf9610bc22e0fdd7dd5ae16293b057f31e8db002edb9e8b76d70aeb7d1aafa772a757c790590260f51c7671871f0df38f6cb230d84ab59ad81a924c2c8e28a7537ada86ea383b9476311c1ba4fc3ab2cdfec0205637c21cdb13444bc4b3a74e18f399b62fdabea074a3fbf866486b4ed86a6a4068b85c656cca6ed3e68b35175e1924973e82d462e4c2704e06609d552f5e81203d04fbbdf96911fd3fef07f359a8ea935d5a3657bb773783cdc060395d0081102f50063354fae3f6e43ab851dc5c29fc36d48b1e654b09260aa5e62c743cc6b0861f9bfa62752561e2a80f8275dc0e2abaf8f65572fe4fe8e8ccc2d91bdd7c6468783932798cf483e2b6793f0fbc08495d135ae16abd9d334a58bc97de071d10bf13f1dd379ef24f8e41f63787166417e18e5960f38163fb0b92a7127fa22ee03db3562c139a0887a8820e0f85f1714fd29a59d500474f92e1187bf9729c7210cf85a5b1f13d581dd7b8b25a709888f147420a493cc6e036ab9c6366c0e4ebb07026b0e3fd5b34eee445669452cf92bbab5fad18b9b164179b2bd489155c017091a448388d6ca40cea2523618c4a992c37d3c20a7139b940417cdda9c7852bd082bc30446d38f51d4d3ef9b4a8d2482dc05a1efe359d65e9fac6d673fc3a9570b123f5a609851be3b90461ccdd602d85a251164e1c757ed256c4d92b10bec9302a5e9f89069c73bbd2266ab755a4575254a43604b69965de8d131cb812e6a6e0c56a983915850fe148d329c29d15eec67464427da9d763b89c52995b32c1c500ff81d35fa348ec90c2f4680c1e073fb259df6a4dd145b8951e017948c2976050784ed0e7207997ffc619ef2ee4e53fa88af9aea63cd2f1a761151bcab58025a07e3a2b2ce40f9c25e0a88d82efd67c0fefa4cab7cfaa290865508e2e17edee80898d4eed281cd7a8d0e4090a97ff3cbc0ac62712528b367a32bea105840701e0c6588cd1a118bc1b8c4625a3e48f69b779f0f575cd2e505b362051021df8c1b04fb273a52cfe42e7fb16164d3218777622f89580bea14ed586073df23c6c9da01574ae621f62af3728281a4c3cffcfd6f192324ea9eece0755366bde98b5daf0e2b9c58587c15f7b76023125dfbd5fadb374d71481f4427e8a15eb189ee2bc4cbe3bb3b0fdcac5fce8f46a31e9a33a1ce34b8dcf22990f9e15218f67d8ce5470ae819fbd3781424adda93c5a443cbb6c209b9ca424c0eee28ab168f05331b610606f7a9f33ed3ea477451373c0f3578017beacc7b15fae66c0b11abace6d3860bc48977b4d2a9ecaca3b0e3f43b4583f86d560ae1ff0e2cb77edb5419deea3f0cacba39780fc58f3ce0c29c66a3591892c176e1a7d3bb54fbcec8f79fa2bbc004229afd6fdc18c4cb89edf1459ee314763cafe1e75287615c3dbb7f5b05fbab0a9adf444c01a3227b51509dc5bcbf4e6a2eec548d68d1fc6d1bb52673c7efe303c80b9b1cacaac0cfd73dcca1618f30116410c8e6a1475fc191bd6a080b0166087aa6531e59f9d96dc30101580f2cac5efda8fcb7996e085fac2ae03f7b0a460d5cab710600c77c4d3f7f6af775a422b9af2b687d6cab4ddff8e6190e0d9985035f6529e1a277a089ad1b5894cd8f1261918587b89e66dd9bf5891943a319b607ebf127991ac2145696f4b8489d14fe076983381b0ceb983014bea17aed90d90a79d2c9c6e58453483488bf5c892cce23273e98ba4847f39250b126885643ed00540fcb83defd5588e0a342a4d5f73b815e8b18c7d0c7a9aa32b86e65783d2b07e2465870e9a8e1364e6baa88dea32a47f215f661a692d52c0ddada551bbc8d0db7a4e1e987f9f330b0abfcab7318c30fcb4d801f008bbadad93172b8d8f0ce085648a5b033e7f5921900756f12d59d70334cfe24a848e1b85d83e316d303a100add604567b2f5703238410cb63177a22dc934864af6763498346f6039ff360352519cca87dbad1d3f8003bb46a9072b8b396114731d0e1f0df03fcdfee3a0401435fcd82fb448d96064ca438928ab3f2dbc705ef16e9b9dfd65ae8d800a49c0162c35f8ab595be1da87c6c3a8e10886b16519589f22a260d1ef3c2ab11ee9204bda92882ad266b29b1eb64f959c3cf828b27afe849a61195870c65d34ea70706c0944f8091ea483dbc06745a8658dcf5749cbb4887a079399af13cfa69a306088e5bbc7389fd33bbedc0f8b2bcd730b8fa0fb7d5c957a4551e9dce29c4b15f04e057cd99771aab75a7887b82cb12453cea07f785b4ef560e654eac26a079e90a9d3a60c39c1a3a301b9b3a2a0bb7c7071c96ce37bf59b1b847632ceba30b5d044c532e896c29fb2b3769aa0f7495f97a3475a718ce17479f805ede3c893c8d0c0e362207bf9a4c5a48ca988550185e3d010a52b4ad65bce64419142d7238c80020e43b99cb80ceb18bc6a2b045333f8ec169211dd26dc8dfa398691f1d76d30f1b4204a9113a1e353594033e50c908cae266ccd449bae6e030a0acb6619b360f92833140fe243c5be4bd1fda19455a96818f3cbee2232383fbb909c615d1050b687f4451524d507cc9130149899e040927a553b90dd324d57db73c08a5fd0e013f08be740ff23243f87b2c0f4fab5d65edf4e7297ed774852e423e9412e7be0f99a677719928d879cd56bcabfd4dce3ff96a7c5ce34f23471268f42d4b73990295b03026c77645eccf963218535168fd08ab0969ba97b812b6bf3a598e710f09450dd5123579658951b21eb143e4b76ee660a32654a0723e48d2069dcb52edde0690dbbb2bd0c2de2536eb40c89238127c48085de1eb89c8f5bd3208e2c2292422bf268ade80cb5331cbf74cb7338f5d8c99b106b837d0b7679b40907ce4fc4a054bf6418951192254108b6765270f70cecbc01f3e145ca05f1123ad79f8f09bbf7320af0e1802202b1fccb190fa8750d845bd8ea324bcbf558233ae7b65835aa84a144c11883a3a19c084b9c9a9e4a4fbc4a722294a0bd35a8374fa6910fe4b635c8ab7bc1a7951f0f16cfea09a52ab31e5c85848273df135cfacf630e2e8db9728ef733e2adde7dc65b29d6816743ee4b57cdbcc507ae8156edc3f5548e616439cc02daa04a7e0edfc5755b0463072cd9f1480c33e7730e098e6201dd68c35574847b37652d8dfc7ab8eb88788fcc3a54304f69287396bd6783c33dd2f9d92e6bf19bd48cd062b5a5b222e8cc9a84883593261ee7308701453e7250749a7b0f26a28be17dbcbe096e717476b12c1093da5718771a43de20d9b51b76c3a206946f097408973c8441e5a94cd160192ef7e6aac57b8b82ca809f7e504fb6a48dc58a18f5fa5952de6df1f3af19d3041b908946d480ad2328876bc22fc174762e475a5502d85e878e536a78f06574df2119e5c514f8e78910659bf079b189725d9ee0918829b2772828ea6691664868283974656827c44687c47b6164aca68817a888859e80063da37ac442386181fa238fb2db123b49d3f158c2b8a444c80c9c9", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 69812464, 37032133, 1422726, 375448, 406581, 449851, 89069, 223269, 53240, 11155, 7623, 1417, 10229, 2962, 8755, 3275\n ], \n \"k_image\": \"dc006e92fc1e623298b3415ddccfc96a8cae64cb7c9199505a767a16ddd39bb9\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 103642483, 4588979, 374943, 521809, 26666, 551559, 3541, 78919, 74119, 7697, 22074, 98, 2069, 12520, 991, 2010\n ], \n \"k_image\": \"d656ac13a64576e7af5ca416d99b899b0bafef5e71d50e349e467fa463b13600\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 100689894, 5890444, 1600331, 1239916, 10246, 40615, 93596, 29367, 209178, 31832, 15976, 15524, 7738, 4618, 18000, 13238\n ], \n \"k_image\": \"ca559feaf79de4445ca4d2bcc05883b25ecff2f6dd8fd02a9a14adea4849f06f\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 105335893, 1720661, 603531, 812185, 907612, 95130, 38652, 233260, 38021, 31218, 30355, 924, 43414, 12072, 1861, 5511\n ], \n \"k_image\": \"c5b7d94e661c5eb09714b243f3854cc06531b1085442834c9e870501031b73da\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 83622788, 22585650, 1698763, 363844, 674080, 176911, 33802, 60550, 34474, 499122, 34507, 109012, 2600, 2390, 11762, 678\n ], \n \"k_image\": \"a5ebf4914f887ecdfde8e7ef303a7f2cc20521a2a305ba9a618e63d95debfb22\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 55266410, 50443346, 326237, 2547259, 482686, 131728, 345875, 70764, 60539, 195316, 10817, 12486, 12413, 807, 3774, 430\n ], \n \"k_image\": \"a2b08a090f611ea1097622cc63a49256a2d94a90b8dbaaa5e53a85001c86d55a\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 104851338, 4432225, 322623, 60187, 68448, 12770, 87943, 31366, 19015, 11424, 1280, 4638, 1761, 5270, 251, 2\n ], \n \"k_image\": \"88f7594b26dcbaff22f7e7569473462c49d8fb845aa916d7a7663be8b85b8553\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 102449564, 609569, 3134675, 460320, 337596, 206075, 2480844, 10681, 62016, 8415, 133990, 3772, 6574, 1860, 4700, 386\n ], \n \"k_image\": \"7d805459f05d89c92443f43863fa5a4d17241d936fc042cc9847a33a461090c5\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 85856596, 18666365, 2117553, 3040618, 42521, 22399, 31727, 90148, 4179, 5683, 1473, 18506, 177, 11519, 809, 461\n ], \n \"k_image\": \"65bb760c9a31da39911fa6d0e918e884538f0a218d479f84a1c9cca2f9a5f500\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 98769080, 9556352, 1282527, 34176, 70608, 21210, 55660, 5274, 64238, 34977, 1007, 102, 8595, 1858, 4821, 545\n ], \n \"k_image\": \"52418ac25be58fbfcc8bd35c9833532d0fa911c875fa34b53118df5be0b3ba48\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 106117516, 1882646, 1340386, 177924, 75683, 23939, 173054, 41750, 12613, 11467, 26614, 986, 12504, 9537, 2244, 1642\n ], \n \"k_image\": \"40e57cb9a9f313f864eef7bf70dea07c2636952f3cbff30385ac26ee244a4349\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 93868601, 4410106, 10612916, 475622, 117796, 118856, 41271, 73043, 1460, 42279, 39533, 56372, 18003, 6324, 28140, 613\n ], \n \"k_image\": \"38d739cfb68aba73f0f451c7d8d8e51ae8821e17b275d03214054cc1fe4f72d6\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 105407900, 3217068, 803785, 210848, 12516, 136889, 48021, 10018, 10953, 16966, 15097, 6490, 2934, 959, 6245, 3656\n ], \n \"k_image\": \"1eda8e08b1024028064450019b924eca2e3b3e3446d1ac58d0b8e89dc4ba980d\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 83903567, 23002177, 555328, 1306991, 335319, 250467, 189789, 255613, 34743, 46807, 20533, 1166, 2110, 3917, 1713, 649\n ], \n \"k_image\": \"1cccfcece29fbd7a28052821fdd7aac6548212cab0d679dd779a37799111f9ec\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 72075355, 24982880, 8841419, 2959414, 91310, 33396, 253541, 140692, 134207, 323001, 19328, 3000, 36052, 12670, 409, 3547\n ], \n \"k_image\": \"05138378dedfae3adbd844cf76c060226aaeddcd4450c67178e41085d0ae9e53\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 87891367, 12499871, 3444223, 3817265, 682721, 1065395, 247912, 136321, 50631, 28848, 18744, 14334, 11562, 167, 213, 718\n ], \n \"k_image\": \"0007a41ed49aa2f094518d30db5442accaa7d3632381474d649644678b6d23c0\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"8c2ba531d06e4ac990213d765751e89981303d4714d81229ac09e385c8ac1cd3\", \n \"view_tag\": \"c9\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"0c9d3c41171e04e42f6ed61a932c2ecdb5c0103d7cdd909fa0ab15881469c66a\", \n \"view_tag\": \"f5\"\n }\n }\n }\n ], \n \"extra\": [ 1, 184, 72, 194, 203, 248, 224, 238, 74, 152, 75, 246, 69, 192, 230, 17, 132, 80, 151, 16, 114, 161, 141, 35, 182, 207, 113, 111, 11, 246, 129, 253, 64, 2, 9, 1, 208, 254, 203, 171, 207, 80, 220, 87\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 236860000, \n \"ecdhInfo\": [ {\n \"amount\": \"dc9c59cdde692b10\"\n }, {\n \"amount\": \"e76b3405d76aec5c\"\n }], \n \"outPk\": [ \"479df6fa3357cbddcb22e20d2b458011dd31dfc1c78316bad7ed9ffd39ba12b8\", \"6a8ad571f7f5aa9668806d10d2b03c2c84c90690e4ddeb3dc6f870fbd5df5b09\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"c55abb99bdda211773d14dcf7cd68ca2c2e1df64896976c36da0b72b9b0b5abb\", \n \"A1\": \"f68499752558d3b7b342a554f91ea3171cba93075d35968565e37020bd60f6bf\", \n \"B\": \"0086548c6fd600cc04f704607fd008bdd1ef83ed341109114d9301c30a86d3d8\", \n \"r1\": \"7b9a9a3a6cf62d4aedea2cbe4f48af3a830c33486b3e3c7942ad7b4dfbc32207\", \n \"s1\": \"6cbaf3b5e0afffc36549482dc4e5220ad7be9284470405e2a889dc4b0eda950b\", \n \"d1\": \"9eacce92f4b6fe9bbb9f853cd5a68b1ea882a66fc1f86df6b7f21ed49e21fb07\", \n \"L\": [ \"0c2330673beaf7961285d721e00c55e6d97b77efb12cfcdac429cbe92fe7ed64\", \"1ae9878d713edc784573be96c47e80dec59c487590f721dda777066a66a388a8\", \"cbd007967d90e323d91cae997f9449518ca71dd5e0473cd88d52a1b63f4994cb\", \"355fc68976cdff244295ea25e9d579eeee92211f52ed942307827480bf639e9b\", \"945e1f12d5a0369f990945dc143dcbb3e7bb7eada7b4e10ccf65128ab3a203d2\", \"7441035465a96cac073e12db24bb696f38587021576582596bd0351ea8dcd301\", \"23af3e518560a843188e19aa666d9b70d7c55b2072c3813f05084de714988a9a\"\n ], \n \"R\": [ \"e21b543d2d50a2b7949e9c6ee3638731aa1f19909ac0978ef10ebe83e52558f6\", \"e96814d9b64fb0344d8a37e43e9933a9bcb087173ad895400a680f3f7919a585\", \"572b4c0639855b4a146492b00c82eb9d7bd9566aa94387e79f80561676a045e4\", \"be44d3a42061a710a8c5be6ac2af7531504911a4e17d27249f7395c40ca90ac6\", \"4e8a0eaee615e0a23be18c2a2b74e5afea867b60acf8ddf7301055d2e9b6556e\", \"2b8750caaddea43764d08b5dbe1497cc7db2be2ccee30629fb313078fdb9fc4f\", \"9f01b9d7e2e9cb90fdf2654411be9914ac32fe797c427f2a3f5e0ab974226f24\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"3363e401c6214a87d6d33bd5d66a6f74f273c9eaeba1210b64089c7ca7418d0a\", \"e9ea96a6e3608b004341be48b3fb658836e80e9c3c2786764db9e239d115c206\", \"e2c5d846b20c33614aa533f068a9363f9e78b728faedd5fd5ac6c2b427dd0b00\", \"278d9000c2872ff0d2506b842847a65eaf93d1f5ed3267d63cf9128158e1d307\", \"aa6a0c69a73c3a1b9914c285981e40de53329e666a98e065354dae1c92765d0a\", \"f2a1b19b7a3f102fe8a7ba53487879d77c8df704ee4785bd27a6083ab60ec006\", \"81a849c4a7064e6265c60e06924fe32cec542a40b1c89d81356cdb200e842903\", \"3f75ee3e3749acce3d130cca4e7892f68cd7cdfa8e122bc78530b2e8d0176d07\", \"ebae1b0281338c2b2ec14d71d211f1e5500ea3bf298e88b2bb39edc1a4614d0d\", \"5048bbe60650d351ec2ff4cafb7f29ed646885572bab7ca50503bd7cb1a6e407\", \"75276f4e3914a473acc0a6520ea22947173052c525ff7f69b4161bab3f4e5509\", \"7adc053156eda0d8d255298da99b7612756be610b5d4c2d4c5622c2e3d45a00a\", \"2847e2e122a38f797072440f3b2860dc4a184d42f64b1a3db4c26d43cc3c6a04\", \"0a1df4fe0cb81346469ee48b4c64ca3a163a234dd107cfa155d610e859d04405\", \"d90e1d6a6c4f5b5cd93fa65fc80abff6607d8dc1dd932abb1a760e5a05b8d900\", \"03f7429d83df3df24cad2012bd01134cf6ac445145c54434b6e12aea99ed030e\"], \n \"c1\": \"66902fd5bbbefe4ec9d59d9e38da2e3e182da804b9cd69fc6516e5c42d12e409\", \n \"D\": \"807e7e4661a8300cc35c9a5aaf05c74f315d301d3b58af8423e8b3a9dab2a5f4\"\n }, {\n \"s\": [ \"7cb7bac961689da9c8480fdcd7699abfc7b1846b5406095930e305225408a80f\", \"ff283d2ff7b05766873ea25e37a51af6e80fe42c040c09e1254fec0c561d7005\", \"078faf699880bad1bff0adcaf0fbbed6a6b708e993738fcf3f51307f49427b0d\", \"ca0a31c36ab9453b909cc184d4c63908b94427deeaac5521a838ce0e7c654708\", \"2dac768455bb669ab5297535a143999b247d8999b9c8d041a2eb3bc3d152c50a\", \"e586639c731aa945cc0ee851bcef18a27d942354cb8d66ea356d8c2c04eb490e\", \"54f4fd74727b0237fee689ea8802a8d8c885884c216702e32c8e8769430e8709\", \"954ea828466f98d09e756e5ed6c12ef5eff3a4ee36c990542f935a460da46903\", \"7bb52fb8e07f50801ab6a9b81b8eab75b73a7fe93b9d65b75679b3103d5e880a\", \"a1d7c47088f50c690def8612384bfd2f01dc17e184a9d0529735d34bba8e0704\", \"c1d20036078d9394a593de43e28afeed8e9f9e37a283de8bfa383f333281b502\", \"baab61d08ee55dc83a7a7bf7b9be015aa90cfaa3cf2c7976eb1616b6a5327707\", \"9c48c0cd37af84fd9804839055052758c47dcbf861d6f481edd26426abec9b0f\", \"23396a7e14bd9f6ce9168c88eb5cc00c16e92c7133ef6cf66c16ab51d6209401\", \"f49e4a023ecaac0ec7ffac5f168d4e7d4ccf098bf7c59a769430b9131bad6500\", \"fe9c562cafea11ce248b40a51faf4a9a380b9abf6407f1184c05ab6f1c2caf0d\"], \n \"c1\": \"83d399b9afb3f50c7d1b305bb321c14df0787e226ecd2def7bfc6b7c312dca0b\", \n \"D\": \"255e606d60022c07faf74422223d45772072dc8a7452802ec6e0a208deee8d7d\"\n }, {\n \"s\": [ \"a374add905f27f162b82bd779d88d0f533b9cb550de4b7a51679ba3469bf5b0d\", \"003038393e91408af3ab39972995190a27c377fd568695b06cf0a2c0f374680c\", \"7e6b2a71d81eb28f336edf96840978b03a9c42bc72c9004dea1ed37d1208670d\", \"a75ed7d6209b278e9362c880ad85e32253589c0ba3fb6a613df14128040e4d0b\", \"37dcdb26edc3ed3d88b1c2608439d2b930cfff4eea3c5386d27928ad0f8af70b\", \"6e81677aa7c2ceb9280628cd810e5b7e175393342d5cd560ca14ead9574bc20c\", \"bf0d7268a249e56526af3029493eb31fd3d20160aa32d30a0f53c44f9d829701\", \"05a30031d96eb6efa0bca4d61a6739c37cde501a33ed7de3b8f1fc3ea696830c\", \"52fa1350698901ad198fa276f73406da4a2d4cac670860fd0da77c42d9c67305\", \"e4044076d2fb8bf2765ecb825e468e698130674102dc58f3c7fcb8194a6ad605\", \"46555d48c63f1354f34db8b6c6a381a4052cf4fd6a30ee2274af2da5684e3d0d\", \"32f2ad376e21aed1ffd8b3c0e48032a16283c3ce6f12ac2cc90b389e81f59c05\", \"3fc9310d8c947f78ea37f66d2df64ec8c4e04b8bc58801a6a362802ad8c26106\", \"743aa699d067999feb437d92b438ca45bf1a8d1ca5106f6c8609f277a360f70b\", \"9237b6b9a393af4c7af6da7647c4bcb7b50da483f82478af3d99bcdbe4ef5e00\", \"464e5e541e187854dc98a69aa48fc444019a803cb71719f44b175e968330850f\"], \n \"c1\": \"4cc9c96f6c2c24075edcb03b79a8d82d83b49db4a33b7eb993d48e4ece3d2a0f\", \n \"D\": \"524c847d7f15c03e03708b96b6ea8eaca6a4657c3af0244e6401779a0ee21221\"\n }, {\n \"s\": [ \"fa92f13efd7721c5cc4500e7cdb8254997d8b73a7b2243f580a5e91cc246910e\", \"7fa7d13567b4164e6911f6722ed13aadd2a76f127a3435a4bd919836c363cb09\", \"ebf110b68ed56b28b4e8bd5d3e4568adfa509e914d98eb21aeaafb6734e42f07\", \"10aaa746fe4ea17c423f38ac951dd1007dc17486d2b823ecebf9fb9dcb62dd0d\", \"e523f315c266de82c74f0378fcbfd2b030c0e1e30b034ec8f7d27946b8c86403\", \"863350520c89e34975e27b05df44cbcb017e0abbb6a756faf0b34f43f2752907\", \"9fa6b98c8b7a26493a215c954448e1958f6f1aa558cee11352fceac48273440c\", \"199f152d34e7d3c805b35e61a2ce914b5e03dc5d7858ec15c44413e5b2569c00\", \"f31567df8324d01565d4e4e9e04d86f75f24dea9d9d24568cc0ff02740c50c06\", \"e0c1712ec5b711aa78a21e7433da47430ed76a9e5a20748ff5df632a9cfdce0d\", \"1e54ab98f2bf59db008b5c6a03c2d47fa12540d1c927fde4da851325e28afc0e\", \"fc1fdd8a0cb2544e57306a0b5f486299a713a8cd4aad16b38f3c1b789937690b\", \"35287cdb337f33ce203d6303892c64e8be17016436717ff0c5c1af6ad7082e0b\", \"0bb3ca7bdf4a6369598e25d28c42189fc879daef22144424854ba28fef20680b\", \"5d32e9ae7872d9c134d6d9fe5b0ffe9352ceabd3805a6af5ac2b7b6234af960a\", \"77523d6e80b27849bd7f784a98d2422ff79c902f9dcbe4609c429e5b4a9fc90b\"], \n \"c1\": \"15bac05d97ba07fe3734d3f7491c6a962ae942f382a7d28ea1fa5302c51bb40c\", \n \"D\": \"cfdbd190f3286cf798bb345b51723f6e8ea8515534773a340613d8612db8f9ad\"\n }, {\n \"s\": [ \"fff3278d190f7894a6ae534e2ef7a0a8b7b2fae577aaf438b62ff0e4d79b8a08\", \"56a76827f3e29042f3cb444e67dc16633c30d8db17c541ab4db7d81fc5a32f00\", \"b3609aa0b8d2544843206117a826e456a41aa87d80d320c09f1ac0f93ed8e30a\", \"22b70ab8b6be5dd1288e09f9c20906d6bca5f0eff1e2ed57da9465ce60ecc00f\", \"aeebd450c3c3ad2453b75c18bdb725cca06d8cee657c77564ef03cda16d06f0d\", \"8e5ac1cce961abddbb5879738d04997263622263d9f27ee8d5868814afc7a202\", \"4e8807bfbf6943fd0792d13b9bef4ff83ed82f0c34df5f9c42aa472cc2c1ac02\", \"3e152321855eb77e8f762d5b6937d9e7cb3a9f3c2419b806203aa2deb57ae906\", \"80a3c83cdcee63d0bf296027ef22cfac0938f9b115359e6c6d91306dadb2fb04\", \"41107ab8a88b18f0bde29fd68432767c7ef193cb6815c4456eb0d917b9a9e80f\", \"4cf02dee189d4b8accb839002f8b6eba365c9f3f693e156932c09941bfc64600\", \"fa9a1ed117e99068c3220c9f0e94b9d7c122c6c391e8f6dc0ddd0ec8097f4d0d\", \"a7e3987fc86c5523fc788c68b25f86ff5e347849e71171ff0f5c6b15a0b50107\", \"6b1dcd3e4ba22639beba069d6c813548c99db963077c1f2bcdd173f38e79bb05\", \"b450a4748c5959889f22d53f62476626e22587a023dfc4517d49396b0c2efc05\", \"817d8187aee15d20ef0f623615bf4da108d2429e596dce7d8e9fe6e8596c0600\"], \n \"c1\": \"9824b7bc0d7dc58a097658babc3701f19a7210f2c197eea886c5d00dfc50c108\", \n \"D\": \"570002d437713df562fa1e3d67f70450db016570e1119532ed0e3e1bdcfd3155\"\n }, {\n \"s\": [ \"792e33a1110e87d24c8eedbd1b5d731c4fdea67a7d31168432e7d7cfd6564706\", \"d6a5c45ffc307daa0cbc600a5e6e3c9c82d493388ada75deffd3c83906dba60d\", \"6de0af97e952ddad74e59b92013a6e55d63a0289e09feb33641ae2bc1b2fd60e\", \"33ebb741b522f6c0d47a331846edd4f4d82223d193e0e5aae4b88750280b5702\", \"afb63b883a67e6a442ebaf9c7c60be8037a040c0a6e1814696ca2238717ecc0e\", \"f670db23a3063db45d74557d715d643255f5cc2b02ce024a42a2c4f48d0a020c\", \"4fe82d187b3b6edb7e2e9444cb2cb3b8862523febcd7bd5e7549b5031eb8c90c\", \"27907eadeef4328d325dfa862735dc9ba8ae26df2fbf9712ac4fc813122ea10c\", \"0ffd43cf70c217fbfed3edd012d66d959d03aba43873f87634a3ec91efdc9209\", \"86321b3ae6152c4e1688b81775c069fd5a41e71e2dae304bd5c479e3247ee20a\", \"94266393a0761debae592b659f32e71c39343636ac1ab2d3732af1fa9778d701\", \"fceb0b71bad10c860e51e637430e6520d5639c6a1c430aaad3802c30a92b500f\", \"e55616cfb92ae1ce65f3eba6285da428c7849d24918f6d3bf5bcd8b37db6a403\", \"a3220064e82e94bc8a3df93e39332b133bda28c866f30e45c824039e448ef704\", \"59086810004d7613201583e9497b5c17e878238bc1280c87ed2381b8fd308606\", \"56df5be83e02f18e2308f2546a553299ca285b53c7f66ad0297c907e01dccb02\"], \n \"c1\": \"1d0e21631768d826068eeb457b877f4009977de0fda3d981dc3887515060c50b\", \n \"D\": \"6cbeb0704498abea125f623107f97b33daf3ba02d619b6edb8223167fcc1df4c\"\n }, {\n \"s\": [ \"1909cb506805d1f464b1ebeaa69a1b1a0cf02387504296c7ce63c61e0385f000\", \"36dde496366c9988b48d904c4111379548fa829d55787ed9c629ca873862a406\", \"48f6f8e24387826471f8e6047baf0babff8f21cdfb81d4930749638856f2340d\", \"306f636d277ae1792f84a293e4f66350c75023a1b5967eb40a43c440a246c603\", \"42d8e7ebaa4ee68b8551b9807d7b308574a90a7c54134112d6fdb3151af2c90a\", \"2463bad24e336dd34bffcb34713b6802e711a168e4afd30bfc3dba82550fba03\", \"6a8d9f5070875b622865ce43fa773e77ebcca52dd7fb846aca9c133eb93bfd08\", \"030bf7d2f95f541cee1f8ede2d0099177bc447d8c67a6d3b05ee3183498b1b0a\", \"9e8c271f40e36941df4924502e31d8122d28fef01063e9692152371ffbd44d0d\", \"54b527ba4a550b6191a40192e1735f9e9634df5647c3580179ed7f11d893890e\", \"968845f97aa642bccfb6b9e84a4d68189d59fc729f23059dead12b5bce438403\", \"aa7a1ef89da4ac0e4617a978acd99ce57213509702e31941dcab7530c902690a\", \"7da1e1c0ae121c31fa19cf8889ecd8b61cf086be7cfe695b172a36cdc326d504\", \"e55918e88a21efc120a54ffb6f852d5b97ccd7cba3ed1e44b6fda91be7b5de05\", \"89ee42794cc797c25249a9a6d40f873e52b491389065a950c8aed5b6675f5c06\", \"6d1dce8ae2cee39a8f7b1c4adf5fba44fbb41a81b06a5ca9b597ff6081d89a0e\"], \n \"c1\": \"df9d832bc866de4095119e7fb0eaeb8930b705d0712aa17e11754ee27da80105\", \n \"D\": \"befbd6dfc2d3e6e8a040c080a7d4508621474e11a3ba7fee3a2d6ed428324c22\"\n }, {\n \"s\": [ \"d723f61c46b0dedf57ed8a1f5b4588f9922fa5bcbfe9cb9d97d558a4030e7809\", \"c1057838edc60be81f357bf466bc5b9fe05d58ce8009fa509db7d24e0404f609\", \"b7ade4df5611ef00761069d18aec275e33941db96feb81abcf87171b7d54f001\", \"3d88f8d39dbcb57df1b0a3a3463bc75995e8193afb08815de576c1de957b700a\", \"ba945788360754f9575a3d9c3909e56b171e193da4dec2789cc41ce933c82904\", \"e5c4c01c218680c44c46ff1d501ffef20d6a5b067c523ea161e115e7b0179007\", \"835c82d226c6d3f7615d13ee729f7c926d429a33302cc61febb9ec53d24dcc00\", \"322740613d4714cdf380dec0673929b91f0c173455c1d5f605b3c2a2998e9703\", \"09c59130a49a6dfe6b9f56d4ae04e21e9b14034e54e0b8745ba2c918f39d2f0f\", \"95e48323f664da395a8e2d6e294c202a51e5cb513a664f947deb2ce6c2700006\", \"32cf0c9decc4857d801b939cb2e97498832d469e9487978a447e40b25904f80c\", \"e5d6e7d6a32ea5fdbaa470d134b318b41c8ab7e8cdffec513ad4cc2abe280406\", \"081827c389a2c247d0ee40235dd62f1ffecbf6aa3389abda43298ab752f9480f\", \"898f59005b4d8a2fdc4631bf7f2cb10a6cb789dd028e5ce71f0cb3512b3ed30a\", \"497bedd241b6bbef8bc099340e859f20d47d928fd1bd804eed2b41e266dcb00e\", \"3ba7ab8b1d442ef9200458654d613fa08ced2b8d86cbc27260e099bea23e4904\"], \n \"c1\": \"e5ce096899cee734b4a524e714c1b98e9acf571563328ef71d45e3bf6fca2d0b\", \n \"D\": \"afe7b32ad8919db80210eaef8c2c89bcbbafabcd6d5292cdfe439c1a9f1fae66\"\n }, {\n \"s\": [ \"090411640c6ccf905547102b49f7a025a3c7e095e9f7eadd457c147d96210208\", \"962f737018ec057b427e2d31ca7eb58b25e9fc4cf7cd9550129035d6b98da60b\", \"a7919d4fe9b99e4da7598fe85015c43be4eca45d1913458e9db9bb8a155c1a0e\", \"b64774939673b8914c6bfe98348182ec3a222da702175ebd2b8c970bb878d807\", \"858ebb7d35f6675bd2f4ab35af156defc38ca3fe0ec5b305f2d13481bdfaf40b\", \"09d0076aa6d6e2de0a284fc8027f7ef58df0eba11a524764b4e939371566a50a\", \"2f1c2171207626099ea70cbb62112001ce82353e7640b91370b2a277047b9903\", \"e6aac7bdc67db5d1e316b7ecd9d26d9efa52a1427aef62ffe91d19e5549f9106\", \"059a55d8085e937920beceb69b6233a950913aedb6c9913853257d80a7f2df09\", \"240a0b0e2265f861f25f3b3f020295e4c3a557d8f210a19713fbea955e2fbc00\", \"aee13bb44e9d935d547cf7b071b05d94292a4d753018e4b2058fe919e98fca02\", \"09f9e3815440b6b34990f49af2210cb6074f74dc7bbf5804a6a2986c2a1fe60c\", \"70493fb69d8a0a256d3ad34313bb2d88cd9e3dbee169bd656369c2ad0b598e03\", \"e2d43843317dcd53af92eaf7de73f89d0aae108da35e9639a951cc4e6bb6bc08\", \"478d9108aa862add6731184656515718c3bbe4c666bd3730ecbb150447fb2a0f\", \"6a1cd0c4c26f836163f5bd045d30243b49e10bedad9c3a974faed2469b128d02\"], \n \"c1\": \"025202ee0e8e262e50081c08bf278b9de30afe9fa80ed99f0b50915bb8375205\", \n \"D\": \"1ce7b914a5b6c8eb0e4a2de2f412a377ebfbe8e3f409ef8580b4878a380c11e4\"\n }, {\n \"s\": [ \"76588b8804b369130947a7b805aaf563125b08d79fc2a7a6dd59edbfcac7aa08\", \"de4ec98ad49fa4c88de7702238c883b2790c56b5752326843113fc7a3e860e05\", \"8ac662acfecfe2f803960414b6c5a3f0e16ba8bc45d2e9442b74d13f7774f30a\", \"f7f4cbb7eb1a2545914dd2562cd1cc69ca12a46b76e4b3d0fdaf520e4a4a7f0d\", \"d8ce412d8b0567f994a8c1684e5549402078b59200b8b697c776e6e58d34cd0b\", \"797938f4eba967963055852af7194e1d776f455e2e84402598e1ce415f872000\", \"0531b2935021bad2348c4b5976d3cf2d89626138a5f204102559c592a33be507\", \"dd596db1cbc03ab91f1f5a80729619bfb1bc830d88188e3f9593fb430d405009\", \"2f9064ce03ebf328aa662c7adf612240056e0418f531fe086f19509d44cd8e0c\", \"83393059bff678437c3d61acafab248c7b41119bb777354b836e9752849c8005\", \"4fcad749c5660627ac7a24adce564b62426563b0caeb16769ffd5afae4264307\", \"1d211d06f860445b0a384ea4275a1f6f2b0caf11ec2fead1b8a8abd71215fb0b\", \"a65cee1f28aeaf4fb54685c4b9c1d4583d05be800fb3b392c9c9b16d45b47405\", \"89c45a1d7adc9fbc4a8a91a6d95a256954408f1e1794b9b73c7fa92d9d1d3d0d\", \"62e035bd6a3c91f354608ac7d002f1ec1481b92fe1efb47dd717c58388432e08\", \"21b4642894dc1d95bbcb4bea812edd2be509e3723eb27dab02f0aa5174bfda01\"], \n \"c1\": \"1bac757ac05198bfae729198b21ad83ed77d7099933ca8d438223eb5172e2900\", \n \"D\": \"5f8d4bb439e9f1fd7d9319bc235a800ad8cea46cf72aa161403411fcd239c2cc\"\n }, {\n \"s\": [ \"c1caf86b559d263ec57ba80793cb6045195b2306dc2d3d1545b4f9f628733f01\", \"50a751cc8468d64e4fd87d8f6808c387112ea6cbd2747347a2702a1c53d03606\", \"1c256f19897dced055dedc2f6000444b891b2f0f103ec52169bef5620ecc8802\", \"b9c40573c76e1a18907d9aaa82cfde4f69fbe29a031ae39e9d47161b52561104\", \"b9c4272fd23a758596329701d313cf2a2a5a0783b9f39b0d9d376b1a53d9aa02\", \"008e31c110ea76d7f3dd3b6119d18436d7d87638aaa30c633eef1fcd5923ef0f\", \"702f504ae3e3157382c50c022725f50e0de515d1bef117ca08d832e098b0240e\", \"6c2be19c7e25a8f32f876f4ea74809a659e02b8f2ec908dbd119ccf0edcd7909\", \"94de8034f11691ac2cd6500c32e20287e968196dfd93de98d31a5733b19e7a07\", \"35c541ce2da123a622e10b79abd68ed242218fc198d3fd9bbe3dced2aabf0707\", \"50593ef0ddbcf3da82d63d30faf24289e054434fe9b2547de07056c2ae927f00\", \"ddf5820c7620d2b9cc0f5047fe4a74d163cc3813f5465bcad682ea0be2d0070d\", \"34b1b382dc98e343b261a1a605ce916498f476b2131ff2353e1f9a3ce3e71902\", \"f2820f6f514b72f32b2a3e2d50fad8c47129849e2643ffd978f3d6a8893a6a0c\", \"1dfb540d9405f41c1b62bba4b0893273535b223deb4bfbf64c2a8b6ac040f006\", \"10d5d9beda68f687ebd4035c39c45cd80cef2c15ab5f35f24af9c0560ae08f07\"], \n \"c1\": \"b50751c330e15f3ee3a8e07783dc00ca6b8f78d465d7dea2c8c83de74d5f000e\", \n \"D\": \"240a2046c4d4346fde8802305e568074e94ff89cf0a3d5346eb77564c35b15b6\"\n }, {\n \"s\": [ \"b5746f3412f0a78c40323c98a985c255d63656bf1115c4c826622d462b26f608\", \"130852be14f740b8570f5f4a5511b86a784c51a5e4fb8e945e31c8040628da04\", \"fcadb8cc0c23e4b24bf2222932c7893ca6b9904dca2ff8a4968d35a0d368ac01\", \"7496afccec6a815cb767f0b779ae95e3ba5260724a34f18cbc9204840414e603\", \"44dedaec24d73ccdbe4807b8f1555c074c94f53d94177fd1698997c431bd100d\", \"9a501dbae938247f5b77eb42505ef9cefefd4e7f42287903b316f036dacf8008\", \"9174621ca177d5a71c9aed771de721522b0f72c815e97b130f1cbcb83877010f\", \"56c9926851bccc1f48abeb9972a7cd765435ed59666c643e8bf9f6f0dbf91e0c\", \"32da0d9416c4c27b002fe1547ad2406532ecf44c89e6245ac041ac0ce4e4eb0d\", \"7ca53b3bab439481930d4e214b3b8460354614c9fc4a2c5035671c4fbc522d0b\", \"0ea983f506dcead626b4f7932bc9b7f497f8233a1556478c0786218bac666e0e\", \"a9e7ac6a29eba7c27edc4f25dbf6906883bbf4ec47d1ebf38a3fb70c0df46f0b\", \"0541a8b5e21a9ebfa0a08a660daf10a2dac0d7c8bba437f3b931c96455b58805\", \"b0d522d58f81354805dc073ae2dfd82bc827ae72807fd533687f78a450eba70e\", \"f15be34213fc5ca3b3762944f1d018c46e7bb0b9829132633e54528c7178b700\", \"0c3852eff49d00338308b216cf77fa36f67130ca7424138560ff9adbbec1ac0f\"], \n \"c1\": \"3c300aa9725662614ee790040e3668b7b512e4953b5a68a3df04f881978dac0e\", \n \"D\": \"77be03f07de3ea414ab5de8b4148df7b2c7548de2392fed2d26dcb61deeb329a\"\n }, {\n \"s\": [ \"434e34961ba8fc0a8533731fbf13923c60ecaece5ac0633100fd18b1a47ab40a\", \"9c3d10e14fdd5f1c448479b933db813f16c87d74e925e91a370be9d5cc5f550f\", \"3bec745edc71c836a48d90e51777f2c480f5338917fa9ab9ce258bcf6eae6708\", \"542e1d2ac11c524ee41c307b7deac63d13dcea39ca2a3fe38f4fe0ba59a6f405\", \"2485619c52efba1b68fb0f0ef5aa921cc57fd1d341f3556a1d6b73d17f6db60b\", \"496621f542826cddad8de2e517258ab046842f696b9c4f2835506522f9d2b500\", \"93ed535605c4815979bac5627c103f51e4ad001e6a600f0d0b4ca4b81f06390b\", \"14ad02ea0c3b0349824c1e0573549bd853d20cad9fa1edcca9bc566014f0af06\", \"528ffe2ecbaa066240c9bff71fd5ddeff863b27cb00a74fccfb1db5499a8bc06\", \"eb01cdeb3b01b261e61f3fdf0f3973a4e1104fb997c8b6e3ae436f1ce0353c02\", \"8c292daef3187740a653da65cf9610bc22e0fdd7dd5ae16293b057f31e8db002\", \"edb9e8b76d70aeb7d1aafa772a757c790590260f51c7671871f0df38f6cb230d\", \"84ab59ad81a924c2c8e28a7537ada86ea383b9476311c1ba4fc3ab2cdfec0205\", \"637c21cdb13444bc4b3a74e18f399b62fdabea074a3fbf866486b4ed86a6a406\", \"8b85c656cca6ed3e68b35175e1924973e82d462e4c2704e06609d552f5e81203\", \"d04fbbdf96911fd3fef07f359a8ea935d5a3657bb773783cdc060395d0081102\"], \n \"c1\": \"f50063354fae3f6e43ab851dc5c29fc36d48b1e654b09260aa5e62c743cc6b08\", \n \"D\": \"61f9bfa62752561e2a80f8275dc0e2abaf8f65572fe4fe8e8ccc2d91bdd7c646\"\n }, {\n \"s\": [ \"8783932798cf483e2b6793f0fbc08495d135ae16abd9d334a58bc97de071d10b\", \"f13f1dd379ef24f8e41f63787166417e18e5960f38163fb0b92a7127fa22ee03\", \"db3562c139a0887a8820e0f85f1714fd29a59d500474f92e1187bf9729c7210c\", \"f85a5b1f13d581dd7b8b25a709888f147420a493cc6e036ab9c6366c0e4ebb07\", \"026b0e3fd5b34eee445669452cf92bbab5fad18b9b164179b2bd489155c01709\", \"1a448388d6ca40cea2523618c4a992c37d3c20a7139b940417cdda9c7852bd08\", \"2bc30446d38f51d4d3ef9b4a8d2482dc05a1efe359d65e9fac6d673fc3a9570b\", \"123f5a609851be3b90461ccdd602d85a251164e1c757ed256c4d92b10bec9302\", \"a5e9f89069c73bbd2266ab755a4575254a43604b69965de8d131cb812e6a6e0c\", \"56a983915850fe148d329c29d15eec67464427da9d763b89c52995b32c1c500f\", \"f81d35fa348ec90c2f4680c1e073fb259df6a4dd145b8951e017948c29760507\", \"84ed0e7207997ffc619ef2ee4e53fa88af9aea63cd2f1a761151bcab58025a07\", \"e3a2b2ce40f9c25e0a88d82efd67c0fefa4cab7cfaa290865508e2e17edee808\", \"98d4eed281cd7a8d0e4090a97ff3cbc0ac62712528b367a32bea105840701e0c\", \"6588cd1a118bc1b8c4625a3e48f69b779f0f575cd2e505b362051021df8c1b04\", \"fb273a52cfe42e7fb16164d3218777622f89580bea14ed586073df23c6c9da01\"], \n \"c1\": \"574ae621f62af3728281a4c3cffcfd6f192324ea9eece0755366bde98b5daf0e\", \n \"D\": \"2b9c58587c15f7b76023125dfbd5fadb374d71481f4427e8a15eb189ee2bc4cb\"\n }, {\n \"s\": [ \"e3bb3b0fdcac5fce8f46a31e9a33a1ce34b8dcf22990f9e15218f67d8ce5470a\", \"e819fbd3781424adda93c5a443cbb6c209b9ca424c0eee28ab168f05331b6106\", \"06f7a9f33ed3ea477451373c0f3578017beacc7b15fae66c0b11abace6d3860b\", \"c48977b4d2a9ecaca3b0e3f43b4583f86d560ae1ff0e2cb77edb5419deea3f0c\", \"acba39780fc58f3ce0c29c66a3591892c176e1a7d3bb54fbcec8f79fa2bbc004\", \"229afd6fdc18c4cb89edf1459ee314763cafe1e75287615c3dbb7f5b05fbab0a\", \"9adf444c01a3227b51509dc5bcbf4e6a2eec548d68d1fc6d1bb52673c7efe303\", \"c80b9b1cacaac0cfd73dcca1618f30116410c8e6a1475fc191bd6a080b016608\", \"7aa6531e59f9d96dc30101580f2cac5efda8fcb7996e085fac2ae03f7b0a460d\", \"5cab710600c77c4d3f7f6af775a422b9af2b687d6cab4ddff8e6190e0d998503\", \"5f6529e1a277a089ad1b5894cd8f1261918587b89e66dd9bf5891943a319b607\", \"ebf127991ac2145696f4b8489d14fe076983381b0ceb983014bea17aed90d90a\", \"79d2c9c6e58453483488bf5c892cce23273e98ba4847f39250b126885643ed00\", \"540fcb83defd5588e0a342a4d5f73b815e8b18c7d0c7a9aa32b86e65783d2b07\", \"e2465870e9a8e1364e6baa88dea32a47f215f661a692d52c0ddada551bbc8d0d\", \"b7a4e1e987f9f330b0abfcab7318c30fcb4d801f008bbadad93172b8d8f0ce08\"], \n \"c1\": \"5648a5b033e7f5921900756f12d59d70334cfe24a848e1b85d83e316d303a100\", \n \"D\": \"add604567b2f5703238410cb63177a22dc934864af6763498346f6039ff36035\"\n }, {\n \"s\": [ \"2519cca87dbad1d3f8003bb46a9072b8b396114731d0e1f0df03fcdfee3a0401\", \"435fcd82fb448d96064ca438928ab3f2dbc705ef16e9b9dfd65ae8d800a49c01\", \"62c35f8ab595be1da87c6c3a8e10886b16519589f22a260d1ef3c2ab11ee9204\", \"bda92882ad266b29b1eb64f959c3cf828b27afe849a61195870c65d34ea70706\", \"c0944f8091ea483dbc06745a8658dcf5749cbb4887a079399af13cfa69a30608\", \"8e5bbc7389fd33bbedc0f8b2bcd730b8fa0fb7d5c957a4551e9dce29c4b15f04\", \"e057cd99771aab75a7887b82cb12453cea07f785b4ef560e654eac26a079e90a\", \"9d3a60c39c1a3a301b9b3a2a0bb7c7071c96ce37bf59b1b847632ceba30b5d04\", \"4c532e896c29fb2b3769aa0f7495f97a3475a718ce17479f805ede3c893c8d0c\", \"0e362207bf9a4c5a48ca988550185e3d010a52b4ad65bce64419142d7238c800\", \"20e43b99cb80ceb18bc6a2b045333f8ec169211dd26dc8dfa398691f1d76d30f\", \"1b4204a9113a1e353594033e50c908cae266ccd449bae6e030a0acb6619b360f\", \"92833140fe243c5be4bd1fda19455a96818f3cbee2232383fbb909c615d1050b\", \"687f4451524d507cc9130149899e040927a553b90dd324d57db73c08a5fd0e01\", \"3f08be740ff23243f87b2c0f4fab5d65edf4e7297ed774852e423e9412e7be0f\", \"99a677719928d879cd56bcabfd4dce3ff96a7c5ce34f23471268f42d4b739902\"], \n \"c1\": \"95b03026c77645eccf963218535168fd08ab0969ba97b812b6bf3a598e710f09\", \n \"D\": \"450dd5123579658951b21eb143e4b76ee660a32654a0723e48d2069dcb52edde\"\n }], \n \"pseudoOuts\": [ \"0690dbbb2bd0c2de2536eb40c89238127c48085de1eb89c8f5bd3208e2c22924\", \"22bf268ade80cb5331cbf74cb7338f5d8c99b106b837d0b7679b40907ce4fc4a\", \"054bf6418951192254108b6765270f70cecbc01f3e145ca05f1123ad79f8f09b\", \"bf7320af0e1802202b1fccb190fa8750d845bd8ea324bcbf558233ae7b65835a\", \"a84a144c11883a3a19c084b9c9a9e4a4fbc4a722294a0bd35a8374fa6910fe4b\", \"635c8ab7bc1a7951f0f16cfea09a52ab31e5c85848273df135cfacf630e2e8db\", \"9728ef733e2adde7dc65b29d6816743ee4b57cdbcc507ae8156edc3f5548e616\", \"439cc02daa04a7e0edfc5755b0463072cd9f1480c33e7730e098e6201dd68c35\", \"574847b37652d8dfc7ab8eb88788fcc3a54304f69287396bd6783c33dd2f9d92\", \"e6bf19bd48cd062b5a5b222e8cc9a84883593261ee7308701453e7250749a7b0\", \"f26a28be17dbcbe096e717476b12c1093da5718771a43de20d9b51b76c3a2069\", \"46f097408973c8441e5a94cd160192ef7e6aac57b8b82ca809f7e504fb6a48dc\", \"58a18f5fa5952de6df1f3af19d3041b908946d480ad2328876bc22fc174762e4\", \"75a5502d85e878e536a78f06574df2119e5c514f8e78910659bf079b189725d9\", \"ee0918829b2772828ea6691664868283974656827c44687c47b6164aca68817a\", \"888859e80063da37ac442386181fa238fb2db123b49d3f158c2b8a444c80c9c9\"]\n }\n}", + "weight": 11843 + },{ + "blob_size": 2320, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 115000000, + "id_hash": "c072513a1e96497ad7a99c2cc39182bcb4f820e42cce0f04718048424713d9b1", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261657, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261657, + "relayed": true, + "tx_blob": "0200010200108fb0ad269cfd890bb8a5980195e69e0184fc06c7f705d0d21b8df305b7c601e5da08dc0197ed03e6cd06a632cc7680bf01fb3e7cc08761a6037ca29965f27d2a145f045da5a1018ca7e6a5a5a93dbbd33d0a0003dcfa3a2800cdabeea3c3206f05408adb96d2d6fdbbf704a0372bfee0535ca036f00003992edd42ab4ef6f21ac1639c8515b5930f39788788671122993164515bedfe4c8a0003e255c00974b24fbfd3cd45fd4004d6dc76201813ac827295b1eb04e7d14d10dd9500037d42e7770c696359631ae7e566fedd1417077d2f3bdee93240f544703b299ef7e40003755335d298ae54a80e4f3fca0c1066fe744d5a2719d188c0ab10e45a94ada4d3850003e5ccb6fc793c664acfd057d7f6fcc77850032b96702ce4d7112bda338c7aa576d10003f7e699af9f0b9ff887845f0b0600816a58faf680ad32c61ee4465c643e30a340d200031de678a8a50613c5db143e7b3d5aef14be9f1f5ce2805b3a3f2fabd0af950acc380003d1af1d1d909b9d07e4cbd8d4792ce3c719280a2c4a7618d8b2be89d3e2472dab56000365159da725872bbe45cd5b5a8eb8b2082c52f161c66130ecd74aa1690e31e1cf2e2101669fe64b9088044833435038ee59827b0b3f93b042e2ea7a2de803e8889a473a06c085eb36a2d1d7927d22c62713d484cfcc32ce08d57d1588f967ef3dce69f0b186e9417afe28372e3011410c15d178d85cb7992d5489eeaa56ab735167278d301a060edacb9944a0d91f4d1890403da176509a5a8fe8b16032ba1abf24aa58fcf1f37233985203599a5196678b96873b000d7e7138e1a39ec14709f7e44ed3d5a8d9be17401d2d4d362ce2798383fc2954c8f79876471eb52ad4e7cbbd0a1647771d11aef73612f15ca15c13ac18ed477bef4492b0b229ae04b00ac4a144329a0ea2f86e4decd8ab76e8f76e604a39aa8350777c113b99f7c2808a50c8556079f6b4b1e857d75ccf4fa43429e27d1bb205c9bd8dd2ccd6151c8106eaeda4ac3f28771864870d2327e129acd4a88a16e143901bdf17e2bbba7e8934c8b05bd95a35a04a020beb1298b8ae94c2b2bf7911ad173f20a9dd9a2dfd3929a3300e8522e0db027f391f8b03ae58064b76218608c02ef5771f3058852719367c5c7cef36169357e3b9ee637eecba7f28df2ac097dad28fc995a01f42f19181d1d691f92e8e2737cac55974f4d3e0f86f674c3e84cee205f60143108ec2a3072162e9b3b82aed46df599bd192c8f923293a398281f0c67e8aae7a1a1878999827176bcfc32df00414cd6be57d8bbad907564d071e77c625005d6e497cd58d30d420d5cde92a7ef8ddcdc5e953d55eb223e95cc1e9edba4049e7728542ceacaa8bceb6dc82ce3b0294eb57476103f4669b1b446fc8b3ff970409ad02e606b08b273e39f50344351bbe3e7f1f09437982f3c9817698394df12004635da6726be57bc727d6cc2488e2d1f1a7f64cd9fffcad5281176cb43cfc36040ae73b14501d732dda57d80108176de3a885cd7ca322e7368c5d1ef63b74d38d4f94a7ea1436feead534e9bfdbe5fd186199a172462f2165026673c883f6093633257c2b940a6a5c7dc3561e7685017a7a612d59facd45ad3aedee4433d66d7f6194c0ca2b30c86c26c07ff69a8fc5c568980c3662598a19f5c63259258dacf50ee6b83395d52fba08cb0305746c9ff74c8e0431ac10b65fada1ab841921b5a70538a2a1792830106d045c51f95043ca1ee492169208ec52dd8ce307025f5297d7d40cd94668079ea06fb9b65b2ca7d8edf58062cde615fc324b921db1611f24228ff52d986087dc7c56247aff907f7434b094e2d1f6c69be514196acbf758a3ab3db25b0a80c0fc3e9076e0a934f4cf5d2bdddbd7d9af0c47fe3dfe5d8de4dc203dd4a862cde72b72f138109137de2481ea146e6758877e7bdf4d98db15e97b610acaca6ad74c33497b2ca9a3e4445dd211d89156337d0b2e6bbc5ad5f6833f36b239e79526a48fc7b86cf9c7882af011404cab69de4b9c4f2b7929e7d23763eaf5fc673c2c2755231bf1e41bf1dcd4ea549e0b610d7a449337c499817921ef2fce52795d3ac0410faea86d480fe37dfe169ec177d53a83532d27b3ccfa7663c6d727a093c62d3f7988584f579bd146fde10992155bcb47ffec0d5390e4190aaf24a8709c9954e65d370ec79a47eb973fdcee672afa283c986be21f59db0f1b914438ae83c19c43c6a0113400e6883c84aa64a788086d2e6827bf125db4f23612d7e92d8fe260b2e1e76a235ef3ab824a000713ab23f34661ccc596e2198239f29bfcc05d99a86c42fe802eed8da93490cffadfc7adc848568af250835d9e926a71c446422faa9b57cb1ee4c4b9251d8e99c43635e7ce796d0dec1918393562bb70463c67a254e58ae723dd8c2f4534ced8e8fd68c2b655a046756cd42c93b4660418b166ac1ca569cc5642462664b87d02a15cd8fcc81c335d4a131b34e880ee01241d90a51e88fbeaaeb3952f8e2c32376cb883b28445d345ccd3991632732f0b315388f9b8fc18599ac2d74bc0654e8e6f684c87a274889384dc7393b3301f04e94dc23c3c41d08ad7cebe96c4221b9c1b9f02d24ee707a28a91e7e9f9fa84073be8794c7c34beaa52086b541f9904607eb115f2111dc96b5bce5b75454214098279c030920e6d1bfbfd083421eba2365d671467b2a2a3fcba832c5a6d86a60173f228a07d33596edc8aa922a0c9b26996502b33eeec0038f74fe87485f7b6075a484e14e6c961d8417dd2cbc48029b272dd42b935f27fe7ca7afed71dceed0b5f3a4688c6ea8b0a444b442ac0396a20784f4f9f94d025e2098c482717cf4e05a1862e45b8d12b90b19773857752fe4721a49a97eedf189d25c8b0d659883d0d3e2ce49e18a2f85ecf736191b9058f16cb65d96bf12be8300642c31400e3df05a8d748a1c84d932aa94c1337ed2c6303e4e4e1e4360cb23e4879760e60501007e2d2e18200688a0d71e5fb79211614cdf88bd2cd96252cd8742a2d8ea5914f0946752e3c0ffca147d0b962337b4e8674966b3ef8ed0f7698ea50e6dfc43a8105989c046ed697d5317e81ab30add72eadaf7d5cc74e68fd4b23e1d70a3ba43d0bd64720bb8524bf732f9f5ea45fbaf38470c693834b48658c4961e4d4987e30068b15e598afbade29176f45c702a78f0af5576519e177d355d8c17e2f2a859261369cc31747f01d8cb57916b59a11ff4aba68e8ea5ead7fa431f5389f8620f759", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 80435215, 23232156, 2495160, 2601749, 114180, 97223, 452944, 96653, 25399, 142693, 220, 63127, 108262, 6438, 15180, 24448\n ], \n \"k_image\": \"fb3e7cc08761a6037ca29965f27d2a145f045da5a1018ca7e6a5a5a93dbbd33d\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"dcfa3a2800cdabeea3c3206f05408adb96d2d6fdbbf704a0372bfee0535ca036\", \n \"view_tag\": \"f0\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"992edd42ab4ef6f21ac1639c8515b5930f39788788671122993164515bedfe4c\", \n \"view_tag\": \"8a\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"e255c00974b24fbfd3cd45fd4004d6dc76201813ac827295b1eb04e7d14d10dd\", \n \"view_tag\": \"95\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"7d42e7770c696359631ae7e566fedd1417077d2f3bdee93240f544703b299ef7\", \n \"view_tag\": \"e4\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"755335d298ae54a80e4f3fca0c1066fe744d5a2719d188c0ab10e45a94ada4d3\", \n \"view_tag\": \"85\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"e5ccb6fc793c664acfd057d7f6fcc77850032b96702ce4d7112bda338c7aa576\", \n \"view_tag\": \"d1\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"f7e699af9f0b9ff887845f0b0600816a58faf680ad32c61ee4465c643e30a340\", \n \"view_tag\": \"d2\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"1de678a8a50613c5db143e7b3d5aef14be9f1f5ce2805b3a3f2fabd0af950acc\", \n \"view_tag\": \"38\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d1af1d1d909b9d07e4cbd8d4792ce3c719280a2c4a7618d8b2be89d3e2472dab\", \n \"view_tag\": \"56\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"65159da725872bbe45cd5b5a8eb8b2082c52f161c66130ecd74aa1690e31e1cf\", \n \"view_tag\": \"2e\"\n }\n }\n }\n ], \n \"extra\": [ 1, 102, 159, 230, 75, 144, 136, 4, 72, 51, 67, 80, 56, 238, 89, 130, 123, 11, 63, 147, 176, 66, 226, 234, 122, 45, 232, 3, 232, 136, 154, 71, 58\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 115000000, \n \"ecdhInfo\": [ {\n \"amount\": \"a2d1d7927d22c627\"\n }, {\n \"amount\": \"13d484cfcc32ce08\"\n }, {\n \"amount\": \"d57d1588f967ef3d\"\n }, {\n \"amount\": \"ce69f0b186e9417a\"\n }, {\n \"amount\": \"fe28372e3011410c\"\n }, {\n \"amount\": \"15d178d85cb7992d\"\n }, {\n \"amount\": \"5489eeaa56ab7351\"\n }, {\n \"amount\": \"67278d301a060eda\"\n }, {\n \"amount\": \"cb9944a0d91f4d18\"\n }, {\n \"amount\": \"90403da176509a5a\"\n }], \n \"outPk\": [ \"8fe8b16032ba1abf24aa58fcf1f37233985203599a5196678b96873b000d7e71\", \"38e1a39ec14709f7e44ed3d5a8d9be17401d2d4d362ce2798383fc2954c8f798\", \"76471eb52ad4e7cbbd0a1647771d11aef73612f15ca15c13ac18ed477bef4492\", \"b0b229ae04b00ac4a144329a0ea2f86e4decd8ab76e8f76e604a39aa8350777c\", \"113b99f7c2808a50c8556079f6b4b1e857d75ccf4fa43429e27d1bb205c9bd8d\", \"d2ccd6151c8106eaeda4ac3f28771864870d2327e129acd4a88a16e143901bdf\", \"17e2bbba7e8934c8b05bd95a35a04a020beb1298b8ae94c2b2bf7911ad173f20\", \"a9dd9a2dfd3929a3300e8522e0db027f391f8b03ae58064b76218608c02ef577\", \"1f3058852719367c5c7cef36169357e3b9ee637eecba7f28df2ac097dad28fc9\", \"95a01f42f19181d1d691f92e8e2737cac55974f4d3e0f86f674c3e84cee205f6\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"43108ec2a3072162e9b3b82aed46df599bd192c8f923293a398281f0c67e8aae\", \n \"A1\": \"7a1a1878999827176bcfc32df00414cd6be57d8bbad907564d071e77c625005d\", \n \"B\": \"6e497cd58d30d420d5cde92a7ef8ddcdc5e953d55eb223e95cc1e9edba4049e7\", \n \"r1\": \"728542ceacaa8bceb6dc82ce3b0294eb57476103f4669b1b446fc8b3ff970409\", \n \"s1\": \"ad02e606b08b273e39f50344351bbe3e7f1f09437982f3c9817698394df12004\", \n \"d1\": \"635da6726be57bc727d6cc2488e2d1f1a7f64cd9fffcad5281176cb43cfc3604\", \n \"L\": [ \"e73b14501d732dda57d80108176de3a885cd7ca322e7368c5d1ef63b74d38d4f\", \"94a7ea1436feead534e9bfdbe5fd186199a172462f2165026673c883f6093633\", \"257c2b940a6a5c7dc3561e7685017a7a612d59facd45ad3aedee4433d66d7f61\", \"94c0ca2b30c86c26c07ff69a8fc5c568980c3662598a19f5c63259258dacf50e\", \"e6b83395d52fba08cb0305746c9ff74c8e0431ac10b65fada1ab841921b5a705\", \"38a2a1792830106d045c51f95043ca1ee492169208ec52dd8ce307025f5297d7\", \"d40cd94668079ea06fb9b65b2ca7d8edf58062cde615fc324b921db1611f2422\", \"8ff52d986087dc7c56247aff907f7434b094e2d1f6c69be514196acbf758a3ab\", \"3db25b0a80c0fc3e9076e0a934f4cf5d2bdddbd7d9af0c47fe3dfe5d8de4dc20\", \"3dd4a862cde72b72f138109137de2481ea146e6758877e7bdf4d98db15e97b61\"\n ], \n \"R\": [ \"caca6ad74c33497b2ca9a3e4445dd211d89156337d0b2e6bbc5ad5f6833f36b2\", \"39e79526a48fc7b86cf9c7882af011404cab69de4b9c4f2b7929e7d23763eaf5\", \"fc673c2c2755231bf1e41bf1dcd4ea549e0b610d7a449337c499817921ef2fce\", \"52795d3ac0410faea86d480fe37dfe169ec177d53a83532d27b3ccfa7663c6d7\", \"27a093c62d3f7988584f579bd146fde10992155bcb47ffec0d5390e4190aaf24\", \"a8709c9954e65d370ec79a47eb973fdcee672afa283c986be21f59db0f1b9144\", \"38ae83c19c43c6a0113400e6883c84aa64a788086d2e6827bf125db4f23612d7\", \"e92d8fe260b2e1e76a235ef3ab824a000713ab23f34661ccc596e2198239f29b\", \"fcc05d99a86c42fe802eed8da93490cffadfc7adc848568af250835d9e926a71\", \"c446422faa9b57cb1ee4c4b9251d8e99c43635e7ce796d0dec1918393562bb70\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"463c67a254e58ae723dd8c2f4534ced8e8fd68c2b655a046756cd42c93b46604\", \"18b166ac1ca569cc5642462664b87d02a15cd8fcc81c335d4a131b34e880ee01\", \"241d90a51e88fbeaaeb3952f8e2c32376cb883b28445d345ccd3991632732f0b\", \"315388f9b8fc18599ac2d74bc0654e8e6f684c87a274889384dc7393b3301f04\", \"e94dc23c3c41d08ad7cebe96c4221b9c1b9f02d24ee707a28a91e7e9f9fa8407\", \"3be8794c7c34beaa52086b541f9904607eb115f2111dc96b5bce5b7545421409\", \"8279c030920e6d1bfbfd083421eba2365d671467b2a2a3fcba832c5a6d86a601\", \"73f228a07d33596edc8aa922a0c9b26996502b33eeec0038f74fe87485f7b607\", \"5a484e14e6c961d8417dd2cbc48029b272dd42b935f27fe7ca7afed71dceed0b\", \"5f3a4688c6ea8b0a444b442ac0396a20784f4f9f94d025e2098c482717cf4e05\", \"a1862e45b8d12b90b19773857752fe4721a49a97eedf189d25c8b0d659883d0d\", \"3e2ce49e18a2f85ecf736191b9058f16cb65d96bf12be8300642c31400e3df05\", \"a8d748a1c84d932aa94c1337ed2c6303e4e4e1e4360cb23e4879760e60501007\", \"e2d2e18200688a0d71e5fb79211614cdf88bd2cd96252cd8742a2d8ea5914f09\", \"46752e3c0ffca147d0b962337b4e8674966b3ef8ed0f7698ea50e6dfc43a8105\", \"989c046ed697d5317e81ab30add72eadaf7d5cc74e68fd4b23e1d70a3ba43d0b\"], \n \"c1\": \"d64720bb8524bf732f9f5ea45fbaf38470c693834b48658c4961e4d4987e3006\", \n \"D\": \"8b15e598afbade29176f45c702a78f0af5576519e177d355d8c17e2f2a859261\"\n }], \n \"pseudoOuts\": [ \"369cc31747f01d8cb57916b59a11ff4aba68e8ea5ead7fa431f5389f8620f759\"]\n }\n}", + "weight": 5750 + },{ + "blob_size": 1879, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 66240000, + "id_hash": "d696e0a07d4a5315239fda1d2fec3fa94c7f87148e254a2e6ce8a648bed86bb3", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261654, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "020001020010d4cbc025f888800aa9b3da02d483d801b4c3188ad105b38a0ee7c3089831b542af37b3f901e68006885282d2028701428be79097b510e49fe5b25804029ac8bfa5e2a640a8b0e3e0a8199b1d26f22f050003f4c8ce3812f84c4dae9fab0e120faf7e9583c5bb024674a4ceb36550a58da368690003edcedc3b21577e2cb73380c7a829cc6707109e672cee030aa007618ff033ed9e8c00037d3b66c65d73e2c172c52ef1336987487da2507fc7c6ca0579ff7e8d34c47f4d170003d423aefc2536b8839995f569dc5e61385f8a537067f5c00aac957cccfb5a4790b5000397fcebd0877bdd71d2a90aed5a58180a0deecaed068f9e43ce44df011aecae0e382101430ad497db87046e33184d9617daa64add82a53dc16982a056b682b2f9a2da980680fcca1f6a94c40de9d6fb8935d837180bad6fc5bb224d8ed00140afb3123c36a4ff194f108db26877967b024bbd0cb51d34a7f5cb26262573b9a66cd70635204820f80d01f04297474b9ca965cdc9302126eebe75aa86a0a837f9dcface5fa38ad81bdcaa40e1fe942f2fc2768f267feece915df41d9864291b57885c1e0f8e4c93a7a4610aef477cf53b805dad5a1006071fde67ca8c47f545c2fdf3202120ef652e0173b4257b91811f7f39df6d0c9c1d2e8f00074c619e9e4322dd006c41c10b67c4ed21aae62ce8748701865c7cb3df6f168e9564b1c7f0f7ac8429f2ecc311a10557feec8395cd8e03be8f38908c2fee85f557cdb534d9775ae9583f7a73024d9a7efa1d75b74f4e62b0a40954b6ca4c0c2091beb3021f21cce994f5a679c0f45126a83d9cb431ab6babb1d3272ed9456ea6af5c35037b4a89a23640c6edf1a4576d592bbc5dd0c4f40aa3e918712d4812f5bc8a01e38e6dc1d4b5dc39ccb5e26eeaf5c88b85d968c800e10bfca42e320f6b03c3065856b58908555a266b7308321ba94e80aab0956b0c09e2f1c8c5da991672dea0c43de4c27d232f50c35e3fc836ac8eec7d6466d8af2ce9277a92aca9f4d4d4230d1dfbb042871dc92498339a6225d6ac016e0e9dde852364c31878a19a2820bd196c1f837b398a56152ae01928f9f2fb80aae7301795da93006123b6cea54f34f1232a9f1ddafa340764a5b0b305af112c72c76a6ed5d5513739fbbc56bc027b4251b23a8c7727a2abf5b3e9a803aa3d144022d129fede2abec4f787d574df639bd48d0de0d1e981d88de680a7d40033b0f93b51ed6e60b8199c2adbc7de0b8f76802d8e74d9e46d2145a61232920571bc1d23fce134c02c268b523f5d5d31876d2259b2292597c15937c3057e5c4350391327429428ac6069bdd5db4515113d046f8058506b6e04399016157964b7c7dd41b929e9fb09997f51fa6db7903465b3cab1093ad38a783b67542e75bde42bbf5c07e415c24241017f2365a3e05b1bb8eab7b4ef681d3208a98715fd84e60be86931800276ce667812e9602afa5baacb0caa1ef9fc37cbac120918564ca04d73ce130ca42c28577d504516e5f44b843a76b2c1558d142e64ce4a55c34bf197a0251022a94e973f59a4352fe5e086e79fc90319e29834ab75bb24c4438c383f19ff77463d0f322566c1b5c64f1dd5ab8472ae4e8b4ac20ab110f5281a848ceca73330fc45d3366b7bd1accb139fec1ed99f15d11ef3ec2e7207a5e7bdafa136a85ccb0a55c3e77c86a70c2496fbcd0373c8aac6067731183cf211bbefbc7d71269d6ba62c98aa791613cbd267b4424763141075bf53d6c4472d3f7685f73f726b16caff0b90183a71c42ca6a27bf6d818abea8c67eb90ce4aca1f9efcb43215214336da99b4074faba4a99c9cfb429e70759024f3d5ce150e9e97e77c4f70501efc575c485e0aea5ed0f246930d73be9d8662f41a408323beacd53135df2eeea93722a00bfc026f5827dccdb05f8e9fedbfc7632b36324d891eb8220978da3ad6e4ad1f3e3507f4b7b8f4914326b28fda56141b2be6350c9c8499afbe66da9417d03598c40f05ec043be6b64b2aa2a0a5da6ab9825d6ebc03578ef5e7370ea63d051cb9f87e01933cfe48a5b22c49d6aca2cf869f6e98bb89c51c6fc9a5cea61a03920214aa0a014a19d0f3fe97670e3990021dae0fdbb5ed4979b1e2144ec3d1ba4b68c39205a86c3350cf2315a25c4ddb21d24ff2a3bdd5e0c53556e2c917920059781700081bdc3fd3966568487382cdb556e8fce96995dc2fb2d598edd55abb423e1ab50b74fdd8568356d62c742394e7bd5e1869a8f28a8557525664b8b28ab14c3d1108e4bae38d05447408f71bdb233aab9cd9a740e242794b9c75760064fdc90c2e0b902b407f311b34d9a172731fe7aa10c9bd189071bdf6f24be900fd8711a08407052db276f4ec535ffb2341e486de7e4ef4b0bbd10de4409c80221a653455930b82edf3a40421c3c665bba621821945a05a7c6c7ca3946f0b673a7e8fa2023d0e2b05b2fc21ffbdb68b0ccb9e0aacb2623f1a843896e68eb7157f8d115a3d7e0e02fd894c154ea56eb1b0d8f296325d3a71f581b868cbade1bbb50b73bf52fa0883cf71eb20aae786f9b27637e3d19ae75ae5206c0a6f789241741bfee9ce05259fe40f2628445d7d741b152930c35ab1c40aba265817242be986971e83665677", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 78652884, 20972664, 5675433, 3539412, 401844, 92298, 230707, 139751, 6296, 8501, 7087, 31923, 98406, 10504, 43266, 135\n ], \n \"k_image\": \"428be79097b510e49fe5b25804029ac8bfa5e2a640a8b0e3e0a8199b1d26f22f\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"f4c8ce3812f84c4dae9fab0e120faf7e9583c5bb024674a4ceb36550a58da368\", \n \"view_tag\": \"69\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"edcedc3b21577e2cb73380c7a829cc6707109e672cee030aa007618ff033ed9e\", \n \"view_tag\": \"8c\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"7d3b66c65d73e2c172c52ef1336987487da2507fc7c6ca0579ff7e8d34c47f4d\", \n \"view_tag\": \"17\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d423aefc2536b8839995f569dc5e61385f8a537067f5c00aac957cccfb5a4790\", \n \"view_tag\": \"b5\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"97fcebd0877bdd71d2a90aed5a58180a0deecaed068f9e43ce44df011aecae0e\", \n \"view_tag\": \"38\"\n }\n }\n }\n ], \n \"extra\": [ 1, 67, 10, 212, 151, 219, 135, 4, 110, 51, 24, 77, 150, 23, 218, 166, 74, 221, 130, 165, 61, 193, 105, 130, 160, 86, 182, 130, 178, 249, 162, 218, 152\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 66240000, \n \"ecdhInfo\": [ {\n \"amount\": \"6a94c40de9d6fb89\"\n }, {\n \"amount\": \"35d837180bad6fc5\"\n }, {\n \"amount\": \"bb224d8ed00140af\"\n }, {\n \"amount\": \"b3123c36a4ff194f\"\n }, {\n \"amount\": \"108db26877967b02\"\n }], \n \"outPk\": [ \"4bbd0cb51d34a7f5cb26262573b9a66cd70635204820f80d01f04297474b9ca9\", \"65cdc9302126eebe75aa86a0a837f9dcface5fa38ad81bdcaa40e1fe942f2fc2\", \"768f267feece915df41d9864291b57885c1e0f8e4c93a7a4610aef477cf53b80\", \"5dad5a1006071fde67ca8c47f545c2fdf3202120ef652e0173b4257b91811f7f\", \"39df6d0c9c1d2e8f00074c619e9e4322dd006c41c10b67c4ed21aae62ce87487\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"865c7cb3df6f168e9564b1c7f0f7ac8429f2ecc311a10557feec8395cd8e03be\", \n \"A1\": \"8f38908c2fee85f557cdb534d9775ae9583f7a73024d9a7efa1d75b74f4e62b0\", \n \"B\": \"a40954b6ca4c0c2091beb3021f21cce994f5a679c0f45126a83d9cb431ab6bab\", \n \"r1\": \"b1d3272ed9456ea6af5c35037b4a89a23640c6edf1a4576d592bbc5dd0c4f40a\", \n \"s1\": \"a3e918712d4812f5bc8a01e38e6dc1d4b5dc39ccb5e26eeaf5c88b85d968c800\", \n \"d1\": \"e10bfca42e320f6b03c3065856b58908555a266b7308321ba94e80aab0956b0c\", \n \"L\": [ \"e2f1c8c5da991672dea0c43de4c27d232f50c35e3fc836ac8eec7d6466d8af2c\", \"e9277a92aca9f4d4d4230d1dfbb042871dc92498339a6225d6ac016e0e9dde85\", \"2364c31878a19a2820bd196c1f837b398a56152ae01928f9f2fb80aae7301795\", \"da93006123b6cea54f34f1232a9f1ddafa340764a5b0b305af112c72c76a6ed5\", \"d5513739fbbc56bc027b4251b23a8c7727a2abf5b3e9a803aa3d144022d129fe\", \"de2abec4f787d574df639bd48d0de0d1e981d88de680a7d40033b0f93b51ed6e\", \"60b8199c2adbc7de0b8f76802d8e74d9e46d2145a61232920571bc1d23fce134\", \"c02c268b523f5d5d31876d2259b2292597c15937c3057e5c4350391327429428\", \"ac6069bdd5db4515113d046f8058506b6e04399016157964b7c7dd41b929e9fb\"\n ], \n \"R\": [ \"997f51fa6db7903465b3cab1093ad38a783b67542e75bde42bbf5c07e415c242\", \"41017f2365a3e05b1bb8eab7b4ef681d3208a98715fd84e60be86931800276ce\", \"667812e9602afa5baacb0caa1ef9fc37cbac120918564ca04d73ce130ca42c28\", \"577d504516e5f44b843a76b2c1558d142e64ce4a55c34bf197a0251022a94e97\", \"3f59a4352fe5e086e79fc90319e29834ab75bb24c4438c383f19ff77463d0f32\", \"2566c1b5c64f1dd5ab8472ae4e8b4ac20ab110f5281a848ceca73330fc45d336\", \"6b7bd1accb139fec1ed99f15d11ef3ec2e7207a5e7bdafa136a85ccb0a55c3e7\", \"7c86a70c2496fbcd0373c8aac6067731183cf211bbefbc7d71269d6ba62c98aa\", \"791613cbd267b4424763141075bf53d6c4472d3f7685f73f726b16caff0b9018\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"3a71c42ca6a27bf6d818abea8c67eb90ce4aca1f9efcb43215214336da99b407\", \"4faba4a99c9cfb429e70759024f3d5ce150e9e97e77c4f70501efc575c485e0a\", \"ea5ed0f246930d73be9d8662f41a408323beacd53135df2eeea93722a00bfc02\", \"6f5827dccdb05f8e9fedbfc7632b36324d891eb8220978da3ad6e4ad1f3e3507\", \"f4b7b8f4914326b28fda56141b2be6350c9c8499afbe66da9417d03598c40f05\", \"ec043be6b64b2aa2a0a5da6ab9825d6ebc03578ef5e7370ea63d051cb9f87e01\", \"933cfe48a5b22c49d6aca2cf869f6e98bb89c51c6fc9a5cea61a03920214aa0a\", \"014a19d0f3fe97670e3990021dae0fdbb5ed4979b1e2144ec3d1ba4b68c39205\", \"a86c3350cf2315a25c4ddb21d24ff2a3bdd5e0c53556e2c91792005978170008\", \"1bdc3fd3966568487382cdb556e8fce96995dc2fb2d598edd55abb423e1ab50b\", \"74fdd8568356d62c742394e7bd5e1869a8f28a8557525664b8b28ab14c3d1108\", \"e4bae38d05447408f71bdb233aab9cd9a740e242794b9c75760064fdc90c2e0b\", \"902b407f311b34d9a172731fe7aa10c9bd189071bdf6f24be900fd8711a08407\", \"052db276f4ec535ffb2341e486de7e4ef4b0bbd10de4409c80221a653455930b\", \"82edf3a40421c3c665bba621821945a05a7c6c7ca3946f0b673a7e8fa2023d0e\", \"2b05b2fc21ffbdb68b0ccb9e0aacb2623f1a843896e68eb7157f8d115a3d7e0e\"], \n \"c1\": \"02fd894c154ea56eb1b0d8f296325d3a71f581b868cbade1bbb50b73bf52fa08\", \n \"D\": \"83cf71eb20aae786f9b27637e3d19ae75ae5206c0a6f789241741bfee9ce0525\"\n }], \n \"pseudoOuts\": [ \"9fe40f2628445d7d741b152930c35ab1c40aba265817242be986971e83665677\"]\n }\n}", + "weight": 3312 + },{ + "blob_size": 1539, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 492480000, + "id_hash": "a60834967cc6d22e61acbc298d5d2c725cbf5c8c492b999f3420da126f41b6c7", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261654, + "max_used_block_height": 3195144, + "max_used_block_id_hash": "464cb0e47663a64ee8eaf483c46d6584e9a7945a0c792b19cdbde426ec3a5034", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "020001020010dab5ae1feefaf606fea6c609aca1ec018fa0e70186de3df48307c5b72ab9a001dea302de3c9c17c2b701a923a801d302d2ed8513f48724933df6229a9fb6ededdcf5d0963280ee44fa9216ceebe7941f020003c9558a2daba528058e11b62fb334f0c1324096575db177d3676f2f3a7287f154640003cfc87315c4bbbfc8053d6f4921465da881c2675a531141d272c7b4e085829909c82c01af5114379f90bb68c84743bedf90bd63aa394ea61191a0c315c9816d8e0b9efd020901d1c905878ab04b850680cceaea016f7857e7ade8bd0ee83ef9696bc9b3823e5b43d81c8f49c1f4c6e6ccdb93f7d3d500bcc38801eba594e5377f8339c5e2baed73a25fdcb6162c8cac13ab20adf8fa0652db145499fff83ca114f5b5c39b01d0531831fce743ab9dcdd3571f8a93c433dfa17e9885741b50e34ed92e5d3b41b43bd7a99784d3e95385b94d578222b3bd7e8f0d981a318c424c98eaeffb9cab22da9b9ff6d666a72f2e56751796015b36634f455d2b1f99874b6b30707a20961c12819b752f60ec321436fe0d3bfd681b14048578b14eef8bbedad3f672fc010518a1c63af4ed08d8e7b875641eff37acf9fe46ca0a1b428ea685131eab8003a3cbe82bb87769ab6c5763eb468d04c985a650dde6db581ed3926f44f68bd9040796c94d7d0655ace603e046b12e0267718d575ac32b6fbfe0b911158198d2c5bc08c54b7f62ed326d8965c90e9bb29ad63fcb6d1d4ca5f849aba2eccb5a51ed1d28f2eceda481bd1f3a0229a9cce6b8816831ff5ac7d129f8f3088cdcfe356d73cd4b07a4453a016b3cd65bcb3ac7f85bed185044ffced69b4e111af67582c85551a1fee0e370eb1360611653b571f6dd35f1ab90b1af509dd630b88e6851322e65f1189e46bde8175c4add525e5975c8de17b718155ba857dcba3695543ef6e6a71d548c4940850707eeaf3769839ec636f3e0088d92bbdd5eb2e508529cfb4c078cfa9204c35863256579aefad74fbc7e411174375840bc3070c74939a0de834131dabafa47de92c03d38384d870f3b65f73e7e411ca4130de1f0a706399b64f63dc809524dcdcc76b3a1c0eb963384c39779125ec10c7eeccf9190200a76c4df88dedc7a6b7661ef39be599844c88f5320a1288db2f6395f73907851f966fe67c584ec77c89b15e597254b6550c460f4607c9aa7287577c6c1fc3c3b0b01f0f30818dce251455e18138c4509100cf7c1c79e9b0d332a57a0e3734b8bdaddfa0a3233560a441f2bff47b98de49b8c87e5401e7dfe844b7ad4e3b669394e44816e4cb057b6afce2a9fe3915084deae4d047e698b6753ec6da7e4b3a34babe73206a5990f57933939c242e27891786882631d5fd264e26d2853aba16336ae15070f00602edfbfd9abf9702c7cf9607b52ad0a04d08518054c7f4cada448853092090164292412d8c4196fb4ddb842669ea9de50421b6f219cb05a0befe63f618f043ececbc47f1e3547e4e7abe935c972ed8f60258e1bb7b7b97a74109bfb5b6a03308939028b18f56c3128641e4bf7c7c5884fc05ee7ebdb76b85ad30cb9a1d90680dfdfecbfee9ea2e98bc50e3021f165dcf3890713572b0847f4767a244dc908c8931d54a79dbc34f35888485b977aa7d809b3513aec10d4a522175edfcc9e044a15f68a6e1f5b2095f9c7791984e794b75d3f9940b602b8984773443e4c760bf28dca4e93a036aa4be7771073d23aca7732aae7a364c628437064dc5e8dbf0dfb1fe2333a90b6950dc764e90840ebb74fb98803afe6ccd792ad637183747402c3218537f5b9416b9559b6cadd148c5a8bb52b4f92be7cf130ef729227acac092c13e6c8a9b5046688b81ded00f5ccfbd2f70bfb5aeec37de7d7ea4097feb5005590fab38475f3e37dcc9df3fa3a176d8f45e732acfd12e3f680bc4b511aa90e8c853f0d294f351f8a16a486dbd7bdbe582d48ff956a7c17d719220293d9b3088b24cbaa956ee96d662fe1f30b3d477c96dd3fab9902fcecf8692725440a380f5ffe1c8872ada9fcba8ad026cbb5e9d8324943cb5ebde77eefdcd5bb175c750a577032337113d6d1f45d488bfedbc3a25427633bb1873eb45a9a4ea726c33c0b297af95c01880e1f3bcb0ac86f4d7105e5c6cee032282fc50aa2d34c06b4c4bb", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 65772250, 14531950, 20026238, 3870892, 3788815, 1011462, 115188, 695237, 20537, 37342, 7774, 2972, 23490, 4521, 168, 339\n ], \n \"k_image\": \"d2ed8513f48724933df6229a9fb6ededdcf5d0963280ee44fa9216ceebe7941f\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"c9558a2daba528058e11b62fb334f0c1324096575db177d3676f2f3a7287f154\", \n \"view_tag\": \"64\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"cfc87315c4bbbfc8053d6f4921465da881c2675a531141d272c7b4e085829909\", \n \"view_tag\": \"c8\"\n }\n }\n }\n ], \n \"extra\": [ 1, 175, 81, 20, 55, 159, 144, 187, 104, 200, 71, 67, 190, 223, 144, 189, 99, 170, 57, 78, 166, 17, 145, 160, 195, 21, 201, 129, 109, 142, 11, 158, 253, 2, 9, 1, 209, 201, 5, 135, 138, 176, 75, 133\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 492480000, \n \"ecdhInfo\": [ {\n \"amount\": \"6f7857e7ade8bd0e\"\n }, {\n \"amount\": \"e83ef9696bc9b382\"\n }], \n \"outPk\": [ \"3e5b43d81c8f49c1f4c6e6ccdb93f7d3d500bcc38801eba594e5377f8339c5e2\", \"baed73a25fdcb6162c8cac13ab20adf8fa0652db145499fff83ca114f5b5c39b\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"d0531831fce743ab9dcdd3571f8a93c433dfa17e9885741b50e34ed92e5d3b41\", \n \"A1\": \"b43bd7a99784d3e95385b94d578222b3bd7e8f0d981a318c424c98eaeffb9cab\", \n \"B\": \"22da9b9ff6d666a72f2e56751796015b36634f455d2b1f99874b6b30707a2096\", \n \"r1\": \"1c12819b752f60ec321436fe0d3bfd681b14048578b14eef8bbedad3f672fc01\", \n \"s1\": \"0518a1c63af4ed08d8e7b875641eff37acf9fe46ca0a1b428ea685131eab8003\", \n \"d1\": \"a3cbe82bb87769ab6c5763eb468d04c985a650dde6db581ed3926f44f68bd904\", \n \"L\": [ \"96c94d7d0655ace603e046b12e0267718d575ac32b6fbfe0b911158198d2c5bc\", \"08c54b7f62ed326d8965c90e9bb29ad63fcb6d1d4ca5f849aba2eccb5a51ed1d\", \"28f2eceda481bd1f3a0229a9cce6b8816831ff5ac7d129f8f3088cdcfe356d73\", \"cd4b07a4453a016b3cd65bcb3ac7f85bed185044ffced69b4e111af67582c855\", \"51a1fee0e370eb1360611653b571f6dd35f1ab90b1af509dd630b88e6851322e\", \"65f1189e46bde8175c4add525e5975c8de17b718155ba857dcba3695543ef6e6\", \"a71d548c4940850707eeaf3769839ec636f3e0088d92bbdd5eb2e508529cfb4c\"\n ], \n \"R\": [ \"8cfa9204c35863256579aefad74fbc7e411174375840bc3070c74939a0de8341\", \"31dabafa47de92c03d38384d870f3b65f73e7e411ca4130de1f0a706399b64f6\", \"3dc809524dcdcc76b3a1c0eb963384c39779125ec10c7eeccf9190200a76c4df\", \"88dedc7a6b7661ef39be599844c88f5320a1288db2f6395f73907851f966fe67\", \"c584ec77c89b15e597254b6550c460f4607c9aa7287577c6c1fc3c3b0b01f0f3\", \"0818dce251455e18138c4509100cf7c1c79e9b0d332a57a0e3734b8bdaddfa0a\", \"3233560a441f2bff47b98de49b8c87e5401e7dfe844b7ad4e3b669394e44816e\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"4cb057b6afce2a9fe3915084deae4d047e698b6753ec6da7e4b3a34babe73206\", \"a5990f57933939c242e27891786882631d5fd264e26d2853aba16336ae15070f\", \"00602edfbfd9abf9702c7cf9607b52ad0a04d08518054c7f4cada44885309209\", \"0164292412d8c4196fb4ddb842669ea9de50421b6f219cb05a0befe63f618f04\", \"3ececbc47f1e3547e4e7abe935c972ed8f60258e1bb7b7b97a74109bfb5b6a03\", \"308939028b18f56c3128641e4bf7c7c5884fc05ee7ebdb76b85ad30cb9a1d906\", \"80dfdfecbfee9ea2e98bc50e3021f165dcf3890713572b0847f4767a244dc908\", \"c8931d54a79dbc34f35888485b977aa7d809b3513aec10d4a522175edfcc9e04\", \"4a15f68a6e1f5b2095f9c7791984e794b75d3f9940b602b8984773443e4c760b\", \"f28dca4e93a036aa4be7771073d23aca7732aae7a364c628437064dc5e8dbf0d\", \"fb1fe2333a90b6950dc764e90840ebb74fb98803afe6ccd792ad637183747402\", \"c3218537f5b9416b9559b6cadd148c5a8bb52b4f92be7cf130ef729227acac09\", \"2c13e6c8a9b5046688b81ded00f5ccfbd2f70bfb5aeec37de7d7ea4097feb500\", \"5590fab38475f3e37dcc9df3fa3a176d8f45e732acfd12e3f680bc4b511aa90e\", \"8c853f0d294f351f8a16a486dbd7bdbe582d48ff956a7c17d719220293d9b308\", \"8b24cbaa956ee96d662fe1f30b3d477c96dd3fab9902fcecf8692725440a380f\"], \n \"c1\": \"5ffe1c8872ada9fcba8ad026cbb5e9d8324943cb5ebde77eefdcd5bb175c750a\", \n \"D\": \"577032337113d6d1f45d488bfedbc3a25427633bb1873eb45a9a4ea726c33c0b\"\n }], \n \"pseudoOuts\": [ \"297af95c01880e1f3bcb0ac86f4d7105e5c6cee032282fc50aa2d34c06b4c4bb\"]\n }\n}", + "weight": 1539 + },{ + "blob_size": 1536, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 491520000, + "id_hash": "aef60754dc1b2cd788faf23dd3c62afd3a0ac14e088cd6c8d22f1597860e47cd", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261653, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "02000102001081d9e02d98cd8606d18f0cbc9919ce9704e78b04bdeb03e28806af8601cec110c54dafc502a6118b1c9743c7052c479dbff819502441604a914af485db2f795b7f5bc0eab877d60a1419ee54980200031085022c023e67ccef3004d93b200be5df0cca89c5a17c51d76c3ee3c107d39190000332dd926cf0eac221cabeb5beee0cc9ef68a07a79748a69c3142377b5a8dbd6c7be2c019eb1fa0d8e9c4853c7db214bf6547ddd8b23bec77445b208e296fb26d0861a5b0209016ae500293b5f1167068080b0ea0113648ac5774104fe28d4ea6e7e28511a1c5c4189735a8c2e98bb12572283f3797ff1273d5dd44312e10cbb4cfdf34c3f7664dfc643db30f011129a53843d647e7cab9e03c2df68ef71b2040d59005f4501fb8885aaa4fc7f5fcae273f7a4612c629423864cb4d12c06044dad6629f40edfb2d6cb3c9dd78407302087c767c8bf17d507471a9cb9af54dc6528de99c8dd62ca69f07a623f4ca679eccc0fa03568e346cc061ff1624c4b73ee72a7a414c68164bc2abdf7f7798e6a0f13b2c3852371c2db35ebb00c7ba018a42b0c2c31fc06a748b257eafb46efc220a57a7767664f7744420bc3ee1c7c227aa711680dd301accf87381433838debd8e517ba86d06cb7475f3b8dfa301ee879112ab2e7a00807200f21ba8eeb86935deb899b3babb7a3ebb21b2f1dfd1bb08015ae4b8ff4f16356c7e807b6bf00f6178c752d92f9630508eeccf307f8efdc7dfae7592555a0824706000a2f329413ffe6ecfe68345053f441db1815286b951cbf83b4e7c327e27019fe81f3c534b297b9388438ffb1d5acd17514678a87719b843d64df591d1e478fdac7f3940088cfe0ea54a896fbcad933fb85c9e3f060db515cc8495f4a8939102799616733953f6bb959916ebdb8cfb8c958f62e787aa7b4a3bd45c311cd83be8617ee592413d8c76bf05922147cb08447e7b899344e9c7069cf710fe22a07744704b4b118e85e16e5dc3abc02e9e6fd45e95c73ec2412355907bc4a5d3d17849043695696eb46dbeb1389bc544c5ec11d5e35a9d9be1e5aa2884377c2f4410773a62507f87e7fe992e335377585bf63a7554e352066574e4e35fdf5d1c90a7ffa6b791e317f2c7c76a85532a7ae18a74c293e3ce9c853080fe23abc42d97fb8a8320d3586343706a56e8f293322aba82e9c159a41a3ceff344177dddc3df947cf9159c194d6b9c392dd76a5ddca3f1110ca3043f7b2e82ef1b55fbc87e8b3063bd71e497c90ae647f0c21719149341298ab1402e9bbee4b674bc9509070df1e5de5f3c400ef5230773a8c5b6fb1dc556bb25a8d0110b4c6f411785927930bcbc5b5a9057c4c6b55397a8000f4d8b6cc6159a4e642aa449512e56b405beb0ee74de267b38c7135eaba1f4171e7fbe7fa79807eba65b20a78e896c94e47ae0519d587d550fbf22015c99d604c7f55019901ac5fcbd674d0d2fd6d752087ca056f89bd649fc33ddb75888ecc0327e66d0642683825f262c5cec006599bdf1f07b0f9af7553379e798af1e9644d988e703c96ae415e22d053d4c42fbb19c44d062eec4bf6ce7888a7438c2f1d5b94a415ee8ff3bd95045429378b8c2b9fb7c600656b5a6f94b1149793517bce19d0ddf2f5b30fe3a16bbebed91ee0a03c47a003778322b5d214b960acc4b9b2cdd1e55c4aed04199aa9a0b5132b2d3553c3fe087b9ffbbd7409e47528c075ee1edc51e6cedd5fe05998fddab5a3f25edb0ff2083fcf87e8e261df81a9cf178f5facd906821a543560d3f5d0bb07cf0402d69302463bc90bf04cce521b1b115ea4c57c6512695ade926f3be142001ee73e7f130bc9e506619c93bdae690697a0a642fb15921a1426de845c949799218995a96207ebd6b2904f2a881b82a065eac32ade92b8cf9c73939b858cad212976836a8108f15230cfc5131e6a379c236531ea611b1e8d5277add4f4d211cb6f9f6364cf0e8c56ab32609f43aae59b20ada5e35e217d64e44d122665073837ed50ef23c90d8132b7a8e3a732f424f3166075ca4082ba690961a894a7ed2e6f42d2e772010133381b48d999bbd4cb548c381550626e9e5b8e91c7ea1e78d01e390b5a9de6ad42feb5d5d5f1408202abac88b8c70056af4bde7b2b1a412d8feb4b0b85b88c61", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 95956097, 12691096, 198609, 412860, 68558, 67047, 62909, 99426, 17199, 270542, 9925, 41647, 2214, 3595, 8599, 711\n ], \n \"k_image\": \"2c479dbff819502441604a914af485db2f795b7f5bc0eab877d60a1419ee5498\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"1085022c023e67ccef3004d93b200be5df0cca89c5a17c51d76c3ee3c107d391\", \n \"view_tag\": \"90\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"32dd926cf0eac221cabeb5beee0cc9ef68a07a79748a69c3142377b5a8dbd6c7\", \n \"view_tag\": \"be\"\n }\n }\n }\n ], \n \"extra\": [ 1, 158, 177, 250, 13, 142, 156, 72, 83, 199, 219, 33, 75, 246, 84, 125, 221, 139, 35, 190, 199, 116, 69, 178, 8, 226, 150, 251, 38, 208, 134, 26, 91, 2, 9, 1, 106, 229, 0, 41, 59, 95, 17, 103\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 491520000, \n \"ecdhInfo\": [ {\n \"amount\": \"13648ac5774104fe\"\n }, {\n \"amount\": \"28d4ea6e7e28511a\"\n }], \n \"outPk\": [ \"1c5c4189735a8c2e98bb12572283f3797ff1273d5dd44312e10cbb4cfdf34c3f\", \"7664dfc643db30f011129a53843d647e7cab9e03c2df68ef71b2040d59005f45\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"fb8885aaa4fc7f5fcae273f7a4612c629423864cb4d12c06044dad6629f40edf\", \n \"A1\": \"b2d6cb3c9dd78407302087c767c8bf17d507471a9cb9af54dc6528de99c8dd62\", \n \"B\": \"ca69f07a623f4ca679eccc0fa03568e346cc061ff1624c4b73ee72a7a414c681\", \n \"r1\": \"64bc2abdf7f7798e6a0f13b2c3852371c2db35ebb00c7ba018a42b0c2c31fc06\", \n \"s1\": \"a748b257eafb46efc220a57a7767664f7744420bc3ee1c7c227aa711680dd301\", \n \"d1\": \"accf87381433838debd8e517ba86d06cb7475f3b8dfa301ee879112ab2e7a008\", \n \"L\": [ \"200f21ba8eeb86935deb899b3babb7a3ebb21b2f1dfd1bb08015ae4b8ff4f163\", \"56c7e807b6bf00f6178c752d92f9630508eeccf307f8efdc7dfae7592555a082\", \"4706000a2f329413ffe6ecfe68345053f441db1815286b951cbf83b4e7c327e2\", \"7019fe81f3c534b297b9388438ffb1d5acd17514678a87719b843d64df591d1e\", \"478fdac7f3940088cfe0ea54a896fbcad933fb85c9e3f060db515cc8495f4a89\", \"39102799616733953f6bb959916ebdb8cfb8c958f62e787aa7b4a3bd45c311cd\", \"83be8617ee592413d8c76bf05922147cb08447e7b899344e9c7069cf710fe22a\"\n ], \n \"R\": [ \"744704b4b118e85e16e5dc3abc02e9e6fd45e95c73ec2412355907bc4a5d3d17\", \"849043695696eb46dbeb1389bc544c5ec11d5e35a9d9be1e5aa2884377c2f441\", \"0773a62507f87e7fe992e335377585bf63a7554e352066574e4e35fdf5d1c90a\", \"7ffa6b791e317f2c7c76a85532a7ae18a74c293e3ce9c853080fe23abc42d97f\", \"b8a8320d3586343706a56e8f293322aba82e9c159a41a3ceff344177dddc3df9\", \"47cf9159c194d6b9c392dd76a5ddca3f1110ca3043f7b2e82ef1b55fbc87e8b3\", \"063bd71e497c90ae647f0c21719149341298ab1402e9bbee4b674bc9509070df\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"1e5de5f3c400ef5230773a8c5b6fb1dc556bb25a8d0110b4c6f411785927930b\", \"cbc5b5a9057c4c6b55397a8000f4d8b6cc6159a4e642aa449512e56b405beb0e\", \"e74de267b38c7135eaba1f4171e7fbe7fa79807eba65b20a78e896c94e47ae05\", \"19d587d550fbf22015c99d604c7f55019901ac5fcbd674d0d2fd6d752087ca05\", \"6f89bd649fc33ddb75888ecc0327e66d0642683825f262c5cec006599bdf1f07\", \"b0f9af7553379e798af1e9644d988e703c96ae415e22d053d4c42fbb19c44d06\", \"2eec4bf6ce7888a7438c2f1d5b94a415ee8ff3bd95045429378b8c2b9fb7c600\", \"656b5a6f94b1149793517bce19d0ddf2f5b30fe3a16bbebed91ee0a03c47a003\", \"778322b5d214b960acc4b9b2cdd1e55c4aed04199aa9a0b5132b2d3553c3fe08\", \"7b9ffbbd7409e47528c075ee1edc51e6cedd5fe05998fddab5a3f25edb0ff208\", \"3fcf87e8e261df81a9cf178f5facd906821a543560d3f5d0bb07cf0402d69302\", \"463bc90bf04cce521b1b115ea4c57c6512695ade926f3be142001ee73e7f130b\", \"c9e506619c93bdae690697a0a642fb15921a1426de845c949799218995a96207\", \"ebd6b2904f2a881b82a065eac32ade92b8cf9c73939b858cad212976836a8108\", \"f15230cfc5131e6a379c236531ea611b1e8d5277add4f4d211cb6f9f6364cf0e\", \"8c56ab32609f43aae59b20ada5e35e217d64e44d122665073837ed50ef23c90d\"], \n \"c1\": \"8132b7a8e3a732f424f3166075ca4082ba690961a894a7ed2e6f42d2e7720101\", \n \"D\": \"33381b48d999bbd4cb548c381550626e9e5b8e91c7ea1e78d01e390b5a9de6ad\"\n }], \n \"pseudoOuts\": [ \"42feb5d5d5f1408202abac88b8c70056af4bde7b2b1a412d8feb4b0b85b88c61\"]\n }\n}", + "weight": 1536 + }], + "untrusted": false +}"#; +} + +define_request_and_response! { + get_transaction_pool_stats (other), + GET_TRANSACTION_POOL_STATS: &str, + Request = "{}"; + Response = +r#"{ + "credits": 0, + "pool_stats": { + "bytes_max": 11843, + "bytes_med": 2219, + "bytes_min": 1528, + "bytes_total": 144192, + "fee_total": 7018100000, + "histo": [{ + "bytes": 11219, + "txs": 4 + },{ + "bytes": 9737, + "txs": 5 + },{ + "bytes": 8757, + "txs": 4 + },{ + "bytes": 14763, + "txs": 4 + },{ + "bytes": 15007, + "txs": 6 + },{ + "bytes": 15924, + "txs": 6 + },{ + "bytes": 17869, + "txs": 8 + },{ + "bytes": 10894, + "txs": 5 + },{ + "bytes": 38485, + "txs": 10 + },{ + "bytes": 1537, + "txs": 1 + }], + "histo_98pc": 186, + "num_10m": 0, + "num_double_spends": 0, + "num_failing": 0, + "num_not_relayed": 0, + "oldest": 1721261651, + "txs_total": 53 + }, + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} + +define_request_and_response! { + stop_daemon (other), + STOP_DAEMON: &str, + Request = "{}"; + Response = +r#"{ + "status": "OK" +}"#; +} + +define_request_and_response! { + get_limit (other), + GET_LIMIT: &str, + Request = "{}"; + Response = +r#"{ + "limit_down": 1280000, + "limit_up": 1280000, + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + set_limit (other), + SET_LIMIT: &str, + Request = +r#"{ + "limit_down": 1024 +}"#; + Response = +r#"{ + "limit_down": 1024, + "limit_up": 128, + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + out_peers (other), + OUT_PEERS: &str, + Request = +r#"{ + "out_peers": 3232235535 +}"#; + Response = +r#"{ + "out_peers": 3232235535, + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + get_net_stats (other), + GET_NET_STATS: &str, + Request = "{}"; + Response = +r#"{ + "start_time": 1721251858, + "status": "OK", + "total_bytes_in": 16283817214, + "total_bytes_out": 34225244079, + "total_packets_in": 5981922, + "total_packets_out": 3627107, + "untrusted": false +}"#; +} + +define_request_and_response! { + get_outs (other), + GET_OUTS: &str, + Request = +r#"{ + "outputs": [{ + "amount": 1, + "index": 0 + },{ + "amount": 1, + "index": 1 + }], + "get_txid": true +}"#; + Response = +r#"{ + "credits": 0, + "outs": [{ + "height": 51941, + "key": "08980d939ec297dd597119f498ad69fed9ca55e3a68f29f2782aae887ef0cf8e", + "mask": "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522", + "txid": "9d651903b80fb70b9935b72081cd967f543662149aed3839222511acd9100601", + "unlocked": true + },{ + "height": 51945, + "key": "454fe46c405be77625fa7e3389a04d3be392346983f27603561ac3a3a74f4a75", + "mask": "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522", + "txid": "230bff732dc5f225df14fff82aadd1bf11b3fb7ad3a03413c396a617e843f7d0", + "unlocked": true + }], + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} + +define_request_and_response! { + update (other), + UPDATE: &str, + Request = +r#"{ + "command": "check" +}"#; + Response = +r#"{ + "auto_uri": "", + "hash": "", + "path": "", + "status": "OK", + "untrusted": false, + "update": false, + "user_uri": "", + "version": "" +}"#; +} + +define_request_and_response! { + pop_blocks (other), + POP_BLOCKS: &str, + Request = +r#"{ + "nblocks": 6 +}"#; + Response = +r#"{ + "height": 76482, + "status": "OK", + "untrusted": false +}"#; +} + +define_request_and_response! { + UNDOCUMENTED_ENDPOINT (other), + GET_TRANSACTION_POOL_HASHES: &str, + Request = "{}"; + Response = +r#"{ + "credits": 0, + "status": "OK", + "top_hash": "", + "tx_hashes": [ + "aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03", + "794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11", + "1e9d2ae11f2168a228942077483e70940d34e8658c972bbc3e7f7693b90edf17", + "7375c928f261d00f07197775eb0bfa756e5f23319819152faa0b3c670fe54c1b", + "2e4d5f8c5a45498f37fb8b6ca4ebc1efa0c371c38c901c77e66b08c072287329", + "eee6d596cf855adfb10e1597d2018e3a61897ac467ef1d4a5406b8d20bfbd52f", + "59c574d7ba9bb4558470f74503c7518946a85ea22c60fccfbdec108ce7d8f236", + "0d57bec1e1075a9e1ac45cf3b3ced1ad95ccdf2a50ce360190111282a0178655", + "60d627b2369714a40009c07d6185ebe7fa4af324fdfa8d95a37a936eb878d062", + "661d7e728a901a8cb4cf851447d9cd5752462687ed0b776b605ba706f06bdc7d", + "b80e1f09442b00b3fffe6db5d263be6267c7586620afff8112d5a8775a6fc58e", + "974063906d1ddfa914baf85176b0f689d616d23f3d71ed4798458c8b4f9b9d8f", + "d2575ae152a180be4981a9d2fc009afcd073adaa5c6d8b022c540a62d6c905bb", + "3d78aa80ee50f506683bab9f02855eb10257a08adceda7cbfbdfc26b10f6b1bb", + "8b5bc125bdb73b708500f734501d55088c5ac381a0879e1141634eaa72b6a4da", + "11c06f4d2f00c912ca07313ed2ea5366f3cae914a762bed258731d3d9e3706df", + "b3644dc7c9a3a53465fe80ad3769e516edaaeb7835e16fdd493aac110d472ae1", + "ed2478ad793b923dbf652c8612c40799d764e5468897021234a14a37346bc6ee" + ], + "untrusted": false +}"#; +} + +define_request_and_response! { + UNDOCUMENTED_ENDPOINT (other), + GET_PUBLIC_NODES: &str, + Request = "{}"; + Response = +r#"{ + "status": "OK", + "untrusted": false, + "white": [{ + "host": "70.52.75.3", + "last_seen": 1721246387, + "rpc_credits_per_hash": 0, + "rpc_port": 18081 + },{ + "host": "zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083", + "last_seen": 1720186288, + "rpc_credits_per_hash": 0, + "rpc_port": 18089 + }] +}"#; +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/test-utils/src/rpc/mod.rs b/test-utils/src/rpc/mod.rs index 14c963a..0da6b48 100644 --- a/test-utils/src/rpc/mod.rs +++ b/test-utils/src/rpc/mod.rs @@ -1,25 +1,6 @@ -//! Monero RPC client. +//! Monero RPC data & client. //! -//! This module is a client for Monero RPC that maps the types -//! into the native types used by Cuprate found in `cuprate_types`. -//! -//! # Usage -//! ```rust,ignore -//! #[tokio::main] -//! async fn main() { -//! // Create RPC client. -//! let rpc = HttpRpcClient::new(None).await; -//! -//! // Collect 20 blocks. -//! let mut vec: Vec = vec![]; -//! for height in (3130269 - 20)..3130269 { -//! vec.push(rpc.get_verified_block_information(height).await); -//! } -//! } -//! ``` +//! This module has a `monerod` RPC [`client`] and some real request/response [`data`]. -mod client; -pub use client::HttpRpcClient; - -mod constants; -pub use constants::LOCALHOST_RPC_URL; +pub mod client; +pub mod data; diff --git a/test-utils/src/test_netzone.rs b/test-utils/src/test_netzone.rs index f1f7582..0136890 100644 --- a/test-utils/src/test_netzone.rs +++ b/test-utils/src/test_netzone.rs @@ -69,16 +69,11 @@ impl TryFrom for TestNetZoneAddr { /// TODO #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct TestNetZone; +pub struct TestNetZone; #[async_trait::async_trait] -impl NetworkZone - for TestNetZone -{ +impl NetworkZone for TestNetZone { const NAME: &'static str = "Testing"; - const SEEDS: &'static [Self::Addr] = &[]; - const ALLOW_SYNC: bool = ALLOW_SYNC; - const DANDELION_PP: bool = DANDELION_PP; const CHECK_NODE_ID: bool = CHECK_NODE_ID; type Addr = TestNetZoneAddr; @@ -86,9 +81,8 @@ impl, MoneroWireCodec>; type Listener = Pin< Box< - dyn Stream< - Item = Result<(Option, Self::Stream, Self::Sink), std::io::Error>, - > + Send + dyn Stream, Self::Stream, Self::Sink), Error>> + + Send + 'static, >, >; diff --git a/types/Cargo.toml b/types/Cargo.toml index 7f6b8f8..e1ffb19 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -9,11 +9,36 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types" keywords = ["cuprate", "types"] [features] -default = ["blockchain"] +default = ["blockchain", "epee", "serde", "json", "hex"] blockchain = [] +epee = ["dep:cuprate-epee-encoding"] +serde = ["dep:serde", "hex"] +proptest = ["dep:proptest", "dep:proptest-derive"] +json = ["hex", "dep:cuprate-helper"] +# We sadly have no choice but to enable serde here as otherwise we will get warnings from the `hex` dep being unused. +# This isn't too bad as `HexBytes` only makes sense with serde anyway. +hex = ["serde", "dep:hex"] [dependencies] +cuprate-epee-encoding = { workspace = true, optional = true, features = ["std"] } +cuprate-helper = { workspace = true, optional = true, features = ["cast"] } +cuprate-fixed-bytes = { workspace = true, features = ["std", "serde"] } + +bytes = { workspace = true } curve25519-dalek = { workspace = true } monero-serai = { workspace = true } +hex = { workspace = true, features = ["serde", "alloc"], optional = true } +serde = { workspace = true, features = ["std", "derive"], optional = true } +strum = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } -[dev-dependencies] \ No newline at end of file +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } + +[dev-dependencies] +hex-literal = { workspace = true } +pretty_assertions = { workspace = true } +serde_json = { workspace = true, features = ["std"] } + +[lints] +workspace = true diff --git a/types/README.md b/types/README.md index 6a2015a..a864441 100644 --- a/types/README.md +++ b/types/README.md @@ -1,20 +1,14 @@ # `cuprate-types` -Various data types shared by Cuprate. +Shared data types within Cuprate. -- [1. File Structure](#1-file-structure) - - [1.1 `src/`](#11-src) +This crate is a kitchen-sink for data types that are shared across Cuprate. ---- - -## 1. File Structure -A quick reference of the structure of the folders & files in `cuprate-types`. - -Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`. - -### 1.1 `src/` -The top-level `src/` files. - -| File | Purpose | -|---------------------|---------| -| `service.rs` | Types used in database requests; `enum {Request,Response}` -| `types.rs` | Various general types used by Cuprate \ No newline at end of file +# Features flags +| Feature flag | Does what | +|--------------|-----------| +| `blockchain` | Enables the `blockchain` module, containing the blockchain database request/response types +| `serde` | Enables `serde` on types where applicable +| `epee` | Enables `cuprate-epee-encoding` on types where applicable +| `proptest` | Enables `proptest::arbitrary::Arbitrary` on some types +| `json` | Enables the `json` module, containing JSON representations of common Monero types +| `hex` | Enables the `hex` module, containing the `HexBytes` type \ No newline at end of file diff --git a/types/src/address_type.rs b/types/src/address_type.rs new file mode 100644 index 0000000..743902d --- /dev/null +++ b/types/src/address_type.rs @@ -0,0 +1,147 @@ +//! Types of network addresses; used in P2P. + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + error, + macros::bytes::{Buf, BufMut}, + EpeeValue, Marker, +}; + +use strum::{ + AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray, +}; + +/// An enumeration of address types. +/// +/// Used in `cuprate_p2p` and `cuprate_types` +/// +/// Original definition: +/// - +/// +/// # Serde +/// This type's `serde` implementation (de)serializes from a [`u8`]. +/// +/// ```rust +/// use cuprate_types::AddressType as A; +/// use serde_json::{to_string, from_str}; +/// +/// assert_eq!(from_str::(&"0").unwrap(), A::Invalid); +/// assert_eq!(from_str::(&"1").unwrap(), A::Ipv4); +/// assert_eq!(from_str::(&"2").unwrap(), A::Ipv6); +/// assert_eq!(from_str::(&"3").unwrap(), A::I2p); +/// assert_eq!(from_str::(&"4").unwrap(), A::Tor); +/// +/// assert_eq!(to_string(&A::Invalid).unwrap(), "0"); +/// assert_eq!(to_string(&A::Ipv4).unwrap(), "1"); +/// assert_eq!(to_string(&A::Ipv6).unwrap(), "2"); +/// assert_eq!(to_string(&A::I2p).unwrap(), "3"); +/// assert_eq!(to_string(&A::Tor).unwrap(), "4"); +/// ``` +#[derive( + Copy, + Clone, + Default, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + AsRefStr, + Display, + EnumCount, + EnumIs, + EnumString, + FromRepr, + IntoStaticStr, + VariantArray, +)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged, try_from = "u8", into = "u8"))] +#[repr(u8)] +pub enum AddressType { + #[default] + Invalid, + Ipv4, + Ipv6, + I2p, + Tor, +} + +impl AddressType { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_types::AddressType as A; + /// + /// assert_eq!(A::Invalid.to_u8(), 0); + /// assert_eq!(A::Ipv4.to_u8(), 1); + /// assert_eq!(A::Ipv6.to_u8(), 2); + /// assert_eq!(A::I2p.to_u8(), 3); + /// assert_eq!(A::Tor.to_u8(), 4); + /// ``` + pub const fn to_u8(self) -> u8 { + self as u8 + } + + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 4`. + /// + /// ```rust + /// use cuprate_types::AddressType as A; + /// + /// assert_eq!(A::from_u8(0), Some(A::Invalid)); + /// assert_eq!(A::from_u8(1), Some(A::Ipv4)); + /// assert_eq!(A::from_u8(2), Some(A::Ipv6)); + /// assert_eq!(A::from_u8(3), Some(A::I2p)); + /// assert_eq!(A::from_u8(4), Some(A::Tor)); + /// assert_eq!(A::from_u8(5), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::Invalid, + 1 => Self::Ipv4, + 2 => Self::Ipv6, + 3 => Self::I2p, + 4 => Self::Tor, + _ => return None, + }) + } +} + +impl From for u8 { + fn from(value: AddressType) -> Self { + value.to_u8() + } +} + +impl TryFrom for AddressType { + type Error = u8; + fn try_from(value: u8) -> Result { + match Self::from_u8(value) { + Some(s) => Ok(s), + None => Err(value), + } + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for AddressType { + const MARKER: Marker = u8::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 4")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) + } +} diff --git a/types/src/block_complete_entry.rs b/types/src/block_complete_entry.rs new file mode 100644 index 0000000..af5fa88 --- /dev/null +++ b/types/src/block_complete_entry.rs @@ -0,0 +1,153 @@ +//! Contains [`BlockCompleteEntry`] and the related types. + +//---------------------------------------------------------------------------------------------------- Import +use bytes::Bytes; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use cuprate_fixed_bytes::ByteArray; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + epee_object, + macros::bytes::{Buf, BufMut}, + EpeeValue, InnerMarker, +}; + +//---------------------------------------------------------------------------------------------------- BlockCompleteEntry +/// A block that can contain transactions. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BlockCompleteEntry { + /// `true` if transaction data is pruned. + pub pruned: bool, + /// The block. + pub block: Bytes, + /// The block weight/size. + pub block_weight: u64, + /// The block's transactions. + pub txs: TransactionBlobs, +} + +#[cfg(feature = "epee")] +epee_object!( + BlockCompleteEntry, + pruned: bool = false, + block: Bytes, + block_weight: u64 = 0_u64, + txs: TransactionBlobs = TransactionBlobs::None => + TransactionBlobs::tx_blob_read, + TransactionBlobs::tx_blob_write, + TransactionBlobs::should_write_tx_blobs, +); + +//---------------------------------------------------------------------------------------------------- TransactionBlobs +/// Transaction blobs within [`BlockCompleteEntry`]. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TransactionBlobs { + /// Pruned transaction blobs. + Pruned(Vec), + /// Normal transaction blobs. + Normal(Vec), + #[default] + /// No transactions. + None, +} + +impl TransactionBlobs { + /// Returns [`Some`] if `self` is [`Self::Pruned`]. + pub fn take_pruned(self) -> Option> { + match self { + Self::Normal(_) => None, + Self::Pruned(txs) => Some(txs), + Self::None => Some(vec![]), + } + } + + /// Returns [`Some`] if `self` is [`Self::Normal`]. + pub fn take_normal(self) -> Option> { + match self { + Self::Normal(txs) => Some(txs), + Self::Pruned(_) => None, + Self::None => Some(vec![]), + } + } + + /// Returns the byte length of the blob. + pub fn len(&self) -> usize { + match self { + Self::Normal(txs) => txs.len(), + Self::Pruned(txs) => txs.len(), + Self::None => 0, + } + } + + /// Returns `true` if the byte length of the blob is `0`. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Epee read function. + #[cfg(feature = "epee")] + fn tx_blob_read(b: &mut B) -> cuprate_epee_encoding::Result { + let marker = cuprate_epee_encoding::read_marker(b)?; + match marker.inner_marker { + InnerMarker::Object => Ok(Self::Pruned(Vec::read(b, &marker)?)), + InnerMarker::String => Ok(Self::Normal(Vec::read(b, &marker)?)), + _ => Err(cuprate_epee_encoding::Error::Value( + "Invalid marker for tx blobs".to_string(), + )), + } + } + + /// Epee write function. + #[cfg(feature = "epee")] + fn tx_blob_write( + self, + field_name: &str, + w: &mut B, + ) -> cuprate_epee_encoding::Result<()> { + if self.should_write_tx_blobs() { + match self { + Self::Normal(bytes) => { + cuprate_epee_encoding::write_field(bytes, field_name, w)?; + } + Self::Pruned(obj) => { + cuprate_epee_encoding::write_field(obj, field_name, w)?; + } + Self::None => (), + } + } + Ok(()) + } + + /// Epee should write function. + #[cfg(feature = "epee")] + fn should_write_tx_blobs(&self) -> bool { + !self.is_empty() + } +} + +//---------------------------------------------------------------------------------------------------- PrunedTxBlobEntry +/// A pruned transaction with the hash of the missing prunable data +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PrunedTxBlobEntry { + /// The transaction. + pub blob: Bytes, + /// The prunable transaction hash. + pub prunable_hash: ByteArray<32>, +} + +#[cfg(feature = "epee")] +epee_object!( + PrunedTxBlobEntry, + blob: Bytes, + prunable_hash: ByteArray<32>, +); + +//---------------------------------------------------------------------------------------------------- Import +#[cfg(test)] +mod tests {} diff --git a/types/src/blockchain.rs b/types/src/blockchain.rs index 42390f9..7518935 100644 --- a/types/src/blockchain.rs +++ b/types/src/blockchain.rs @@ -1,41 +1,51 @@ -//! Database [`BCReadRequest`]s, [`BCWriteRequest`]s, and [`BCResponse`]s. +//! Database [`BlockchainReadRequest`]s, [`BlockchainWriteRequest`]s, and [`BlockchainResponse`]s. //! //! Tests that assert particular requests lead to particular //! responses are also tested in Cuprate's blockchain database crate. - //---------------------------------------------------------------------------------------------------- Import use std::{ collections::{HashMap, HashSet}, ops::Range, }; -#[cfg(feature = "borsh")] -use borsh::{BorshDeserialize, BorshSerialize}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; +use monero_serai::block::Block; -use crate::types::{ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation}; +use crate::{ + types::{Chain, ExtendedBlockHeader, OutputOnChain, TxsInBlock, VerifiedBlockInformation}, + AltBlockInformation, BlockCompleteEntry, ChainId, ChainInfo, CoinbaseTxSum, + OutputHistogramEntry, OutputHistogramInput, +}; //---------------------------------------------------------------------------------------------------- ReadRequest /// A read request to the blockchain database. /// -/// This pairs with [`BCResponse`], where each variant here -/// matches in name with a [`BCResponse`] variant. For example, -/// the proper response for a [`BCReadRequest::BlockHash`] -/// would be a [`BCResponse::BlockHash`]. +/// This pairs with [`BlockchainResponse`], where each variant here +/// matches in name with a [`BlockchainResponse`] variant. For example, +/// the proper response for a [`BlockchainReadRequest::BlockHash`] +/// would be a [`BlockchainResponse::BlockHash`]. /// /// See `Response` for the expected responses per `Request`. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum BCReadRequest { +pub enum BlockchainReadRequest { + /// Request [`BlockCompleteEntry`]s. + /// + /// The input is the block hashes. + BlockCompleteEntries(Vec<[u8; 32]>), + /// Request a block's extended header. /// /// The input is the block's height. - BlockExtendedHeader(u64), + BlockExtendedHeader(usize), /// Request a block's hash. /// - /// The input is the block's height. - BlockHash(u64), + /// The input is the block's height and the chain it is on. + BlockHash(usize, Chain), + + /// Request to check if we have a block and which [`Chain`] it is on. + /// + /// The input is the block's hash. + FindBlock([u8; 32]), /// Removes the block hashes that are not in the _main_ chain. /// @@ -45,15 +55,15 @@ pub enum BCReadRequest { /// Request a range of block extended headers. /// /// The input is a range of block heights. - BlockExtendedHeaderInRange(Range), + BlockExtendedHeaderInRange(Range, Chain), /// Request the current chain height. /// /// Note that this is not the top-block height. ChainHeight, - /// Request the total amount of generated coins (atomic units) so far. - GeneratedCoins, + /// Request the total amount of generated coins (atomic units) at this height. + GeneratedCoins(usize), /// Request data for multiple outputs. /// @@ -83,24 +93,107 @@ pub enum BCReadRequest { /// The input is a list of output amounts. NumberOutputsWithAmount(Vec), - /// Check that all key images within a set arer not spent. + /// Check that all key images within a set are not spent. /// /// Input is a set of key images. KeyImagesSpent(HashSet<[u8; 32]>), + + /// A request for the compact chain history. + CompactChainHistory, + + /// A request for the next chain entry. + /// + /// Input is a list of block hashes and the amount of block hashes to return in the next chain entry. + /// + /// # Invariant + /// The [`Vec`] containing the block IDs must be sorted in reverse chronological block + /// order, or else the returned response is unspecified and meaningless, + /// as this request performs a binary search + NextChainEntry(Vec<[u8; 32]>, usize), + + /// A request to find the first unknown block ID in a list of block IDs. + /// + /// # Invariant + /// The [`Vec`] containing the block IDs must be sorted in chronological block + /// order, or else the returned response is unspecified and meaningless, + /// as this request performs a binary search. + FindFirstUnknown(Vec<[u8; 32]>), + + /// A request for transactions from a specific block. + TxsInBlock { + /// The block to get transactions from. + block_hash: [u8; 32], + /// The indexes of the transactions from the block. + /// This is not the global index of the txs, instead it is the local index as they appear in + /// the block. + tx_indexes: Vec, + }, + + /// A request for all alt blocks in the chain with the given [`ChainId`]. + AltBlocksInChain(ChainId), + + /// Get a [`Block`] by its height. + Block { height: usize }, + + /// Get a [`Block`] by its hash. + BlockByHash([u8; 32]), + + /// Get the total amount of non-coinbase transactions in the chain. + TotalTxCount, + + /// Get the current size of the database. + DatabaseSize, + + /// Get an output histogram. + /// + /// TODO: document fields after impl. + OutputHistogram(OutputHistogramInput), + + /// Get the coinbase amount and the fees amount for + /// `N` last blocks starting at particular height. + /// + /// TODO: document fields after impl. + CoinbaseTxSum { height: usize, count: u64 }, + + /// Get information on all alternative chains. + AltChains, + + /// Get the amount of alternative chains that exist. + AltChainCount, } //---------------------------------------------------------------------------------------------------- WriteRequest /// A write request to the blockchain database. -/// -/// There is currently only 1 write request to the database, -/// as such, the only valid [`BCResponse`] to this request is -/// the proper response for a [`BCResponse::WriteBlockOk`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum BCWriteRequest { +pub enum BlockchainWriteRequest { /// Request that a block be written to the database. /// /// Input is an already verified block. WriteBlock(VerifiedBlockInformation), + + /// Write an alternative block to the database, + /// + /// Input is the alternative block. + WriteAltBlock(AltBlockInformation), + + /// A request to pop some blocks from the top of the main chain + /// + /// Input is the amount of blocks to pop. + /// + /// This request flushes all alt-chains from the cache before adding the popped blocks to the + /// alt cache. + PopBlocks(usize), + + /// A request to reverse the re-org process. + /// + /// The inner value is the [`ChainId`] of the old main chain. + /// + /// # Invariant + /// It is invalid to call this with a [`ChainId`] that was not returned from [`BlockchainWriteRequest::PopBlocks`]. + ReverseReorg(ChainId), + + /// A request to flush all alternative blocks. + FlushAltBlocks, } //---------------------------------------------------------------------------------------------------- Response @@ -108,55 +201,71 @@ pub enum BCWriteRequest { /// /// These are the data types returned when using sending a `Request`. /// -/// This pairs with [`BCReadRequest`] and [`BCWriteRequest`], +/// This pairs with [`BlockchainReadRequest`] and [`BlockchainWriteRequest`], /// see those two for more info. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum BCResponse { +#[expect(clippy::large_enum_variant)] +pub enum BlockchainResponse { //------------------------------------------------------ Reads - /// Response to [`BCReadRequest::BlockExtendedHeader`]. + /// Response to [`BlockchainReadRequest::BlockCompleteEntries`]. + BlockCompleteEntries { + /// The [`BlockCompleteEntry`]s that we had. + blocks: Vec, + /// The hashes of blocks that were requested, but we don't have. + missing_hashes: Vec<[u8; 32]>, + /// Our blockchain height. + blockchain_height: usize, + }, + + /// Response to [`BlockchainReadRequest::BlockExtendedHeader`]. /// /// Inner value is the extended headed of the requested block. BlockExtendedHeader(ExtendedBlockHeader), - /// Response to [`BCReadRequest::BlockHash`]. + /// Response to [`BlockchainReadRequest::BlockHash`]. /// /// Inner value is the hash of the requested block. BlockHash([u8; 32]), - /// Response to [`BCReadRequest::FilterUnknownHashes`]. + /// Response to [`BlockchainReadRequest::FindBlock`]. + /// + /// Inner value is the chain and height of the block if found. + FindBlock(Option<(Chain, usize)>), + + /// Response to [`BlockchainReadRequest::FilterUnknownHashes`]. /// /// Inner value is the list of hashes that were in the main chain. FilterUnknownHashes(HashSet<[u8; 32]>), - /// Response to [`BCReadRequest::BlockExtendedHeaderInRange`]. + /// Response to [`BlockchainReadRequest::BlockExtendedHeaderInRange`]. /// /// Inner value is the list of extended header(s) of the requested block(s). BlockExtendedHeaderInRange(Vec), - /// Response to [`BCReadRequest::ChainHeight`]. + /// Response to [`BlockchainReadRequest::ChainHeight`]. /// /// Inner value is the chain height, and the top block's hash. - ChainHeight(u64, [u8; 32]), + ChainHeight(usize, [u8; 32]), - /// Response to [`BCReadRequest::GeneratedCoins`]. + /// Response to [`BlockchainReadRequest::GeneratedCoins`]. /// - /// Inner value is the total amount of generated coins so far, in atomic units. + /// Inner value is the total amount of generated coins up to and including the chosen height, in atomic units. GeneratedCoins(u64), - /// Response to [`BCReadRequest::Outputs`]. + /// Response to [`BlockchainReadRequest::Outputs`]. /// /// Inner value is all the outputs requested, /// associated with their amount and amount index. Outputs(HashMap>), - /// Response to [`BCReadRequest::NumberOutputsWithAmount`]. + /// Response to [`BlockchainReadRequest::NumberOutputsWithAmount`]. /// /// Inner value is a `HashMap` of all the outputs requested where: /// - Key = output amount /// - Value = count of outputs with the same amount NumberOutputsWithAmount(HashMap), - /// Response to [`BCReadRequest::KeyImagesSpent`]. + /// Response to [`BlockchainReadRequest::KeyImagesSpent`]. /// /// The inner value is `true` if _any_ of the key images /// were spent (existed in the database already). @@ -164,12 +273,94 @@ pub enum BCResponse { /// The inner value is `false` if _none_ of the key images were spent. KeyImagesSpent(bool), - //------------------------------------------------------ Writes - /// Response to [`BCWriteRequest::WriteBlock`]. + /// Response to [`BlockchainReadRequest::CompactChainHistory`]. + CompactChainHistory { + /// A list of blocks IDs in our chain, starting with the most recent block, all the way to the genesis block. + /// + /// These blocks should be in reverse chronological order, not every block is needed. + block_ids: Vec<[u8; 32]>, + /// The current cumulative difficulty of the chain. + cumulative_difficulty: u128, + }, + + /// Response to [`BlockchainReadRequest::NextChainEntry`]. /// - /// This response indicates that the requested block has - /// successfully been written to the database without error. - WriteBlockOk, + /// If all blocks were unknown `start_height` will be [`None`], the other fields will be meaningless. + NextChainEntry { + /// The start height of this entry, [`None`] if we could not find the split point. + start_height: Option>, + /// The current chain height. + chain_height: usize, + /// The next block hashes in the entry. + block_ids: Vec<[u8; 32]>, + /// The block weights of the next blocks. + block_weights: Vec, + /// The current cumulative difficulty of our chain. + cumulative_difficulty: u128, + /// The block blob of the 2nd block in `block_ids`, if there is one. + first_block_blob: Option>, + }, + + /// Response to [`BlockchainReadRequest::FindFirstUnknown`]. + /// + /// Contains the index of the first unknown block and its expected height. + /// + /// This will be [`None`] if all blocks were known. + FindFirstUnknown(Option<(usize, usize)>), + + /// The response for [`BlockchainReadRequest::TxsInBlock`]. + /// + /// Will return [`None`] if the request contained an index out of range. + TxsInBlock(Option), + + /// The response for [`BlockchainReadRequest::AltBlocksInChain`]. + /// + /// Contains all the alt blocks in the alt-chain in chronological order. + AltBlocksInChain(Vec), + + /// Response to: + /// - [`BlockchainReadRequest::Block`]. + /// - [`BlockchainReadRequest::BlockByHash`]. + Block(Block), + + /// Response to [`BlockchainReadRequest::TotalTxCount`]. + TotalTxCount(usize), + + /// Response to [`BlockchainReadRequest::DatabaseSize`]. + DatabaseSize { + /// The size of the database file in bytes. + database_size: u64, + /// The amount of free bytes there are + /// the disk where the database is located. + free_space: u64, + }, + + /// Response to [`BlockchainReadRequest::OutputHistogram`]. + OutputHistogram(Vec), + + /// Response to [`BlockchainReadRequest::CoinbaseTxSum`]. + CoinbaseTxSum(CoinbaseTxSum), + + /// Response to [`BlockchainReadRequest::AltChains`]. + AltChains(Vec), + + /// Response to [`BlockchainReadRequest::AltChainCount`]. + AltChainCount(usize), + + //------------------------------------------------------ Writes + /// A generic Ok response to indicate a request was successfully handled. + /// + /// currently the response for: + /// - [`BlockchainWriteRequest::WriteBlock`] + /// - [`BlockchainWriteRequest::WriteAltBlock`] + /// - [`BlockchainWriteRequest::ReverseReorg`] + /// - [`BlockchainWriteRequest::FlushAltBlocks`] + Ok, + + /// Response to [`BlockchainWriteRequest::PopBlocks`]. + /// + /// The inner value is the alt-chain ID for the old main chain blocks. + PopBlocks(ChainId), } //---------------------------------------------------------------------------------------------------- Tests diff --git a/types/src/connection_state.rs b/types/src/connection_state.rs new file mode 100644 index 0000000..69b8ed6 --- /dev/null +++ b/types/src/connection_state.rs @@ -0,0 +1,148 @@ +//! [`ConnectionState`]. + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + error, + macros::bytes::{Buf, BufMut}, + EpeeValue, Marker, +}; + +use strum::{ + AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray, +}; + +/// An enumeration of P2P connection states. +/// +/// Used in `cuprate_p2p` and `cuprate_rpc_types`. +/// +/// Original definition: +/// - +/// +/// # Serde +/// This type's `serde` implementation depends on `snake_case`. +/// +/// ```rust +/// use cuprate_types::ConnectionState as C; +/// use serde_json::to_string; +/// +/// assert_eq!(to_string(&C::BeforeHandshake).unwrap(), r#""before_handshake""#); +/// assert_eq!(to_string(&C::Synchronizing).unwrap(), r#""synchronizing""#); +/// assert_eq!(to_string(&C::Standby).unwrap(), r#""standby""#); +/// assert_eq!(to_string(&C::Idle).unwrap(), r#""idle""#); +/// assert_eq!(to_string(&C::Normal).unwrap(), r#""normal""#); +/// +/// assert_eq!(C::BeforeHandshake.to_string(), "before_handshake"); +/// assert_eq!(C::Synchronizing.to_string(), "synchronizing"); +/// assert_eq!(C::Standby.to_string(), "standby"); +/// assert_eq!(C::Idle.to_string(), "idle"); +/// assert_eq!(C::Normal.to_string(), "normal"); +/// ``` +#[derive( + Copy, + Clone, + Default, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + AsRefStr, + Display, + EnumCount, + EnumIs, + EnumString, + FromRepr, + IntoStaticStr, + VariantArray, +)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] // cuprate-rpc-types depends on snake_case +#[strum(serialize_all = "snake_case")] +#[repr(u8)] +pub enum ConnectionState { + BeforeHandshake, + Synchronizing, + Standby, + Idle, + #[default] + Normal, +} + +impl ConnectionState { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_types::ConnectionState as C; + /// + /// assert_eq!(C::BeforeHandshake.to_u8(), 0); + /// assert_eq!(C::Synchronizing.to_u8(), 1); + /// assert_eq!(C::Standby.to_u8(), 2); + /// assert_eq!(C::Idle.to_u8(), 3); + /// assert_eq!(C::Normal.to_u8(), 4); + /// ``` + pub const fn to_u8(self) -> u8 { + self as u8 + } + + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 4`. + /// + /// ```rust + /// use cuprate_types::ConnectionState as C; + /// + /// assert_eq!(C::from_u8(0), Some(C::BeforeHandshake)); + /// assert_eq!(C::from_u8(1), Some(C::Synchronizing)); + /// assert_eq!(C::from_u8(2), Some(C::Standby)); + /// assert_eq!(C::from_u8(3), Some(C::Idle)); + /// assert_eq!(C::from_u8(4), Some(C::Normal)); + /// assert_eq!(C::from_u8(5), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::BeforeHandshake, + 1 => Self::Synchronizing, + 2 => Self::Standby, + 3 => Self::Idle, + 4 => Self::Normal, + _ => return None, + }) + } +} + +impl From for u8 { + fn from(value: ConnectionState) -> Self { + value.to_u8() + } +} + +impl TryFrom for ConnectionState { + type Error = u8; + fn try_from(value: u8) -> Result { + match Self::from_u8(value) { + Some(s) => Ok(s), + None => Err(value), + } + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for ConnectionState { + const MARKER: Marker = u8::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 4")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) + } +} diff --git a/types/src/hard_fork.rs b/types/src/hard_fork.rs new file mode 100644 index 0000000..d16032f --- /dev/null +++ b/types/src/hard_fork.rs @@ -0,0 +1,196 @@ +//! The [`HardFork`] type. +use std::time::Duration; + +use strum::{ + AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray, +}; + +use monero_serai::block::BlockHeader; + +/// Target block time for hf 1. +/// +/// ref: +const BLOCK_TIME_V1: Duration = Duration::from_secs(60); +/// Target block time from v2. +/// +/// ref: +const BLOCK_TIME_V2: Duration = Duration::from_secs(120); + +/// An error working with a [`HardFork`]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] +pub enum HardForkError { + /// The raw-HF value is not a valid [`HardFork`]. + #[error("The hard-fork is unknown")] + HardForkUnknown, + /// The [`HardFork`] version is incorrect. + #[error("The block is on an incorrect hard-fork")] + VersionIncorrect, + /// The block's [`HardFork`] vote was below the current [`HardFork`]. + #[error("The block's vote is for a previous hard-fork")] + VoteTooLow, +} + +/// An identifier for every hard-fork Monero has had. +#[derive( + Default, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Copy, + Clone, + Hash, + EnumCount, + Display, + AsRefStr, + EnumIs, + EnumString, + FromRepr, + IntoStaticStr, + VariantArray, +)] +#[cfg_attr(any(feature = "proptest"), derive(proptest_derive::Arbitrary))] +#[repr(u8)] +pub enum HardFork { + #[default] + V1 = 1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + V12, + V13, + V14, + V15, + V16, +} + +impl HardFork { + /// The latest [`HardFork`]. + /// + /// ```rust + /// # use cuprate_types::HardFork; + /// assert_eq!(HardFork::LATEST, HardFork::V16); + /// ``` + pub const LATEST: Self = Self::VARIANTS[Self::COUNT - 1]; + + /// Returns the hard-fork for a blocks [`BlockHeader::hardfork_version`] field. + /// + /// ref: + /// + /// # Errors + /// Will return [`Err`] if the version is not a valid [`HardFork`]. + /// + /// ```rust + /// # use cuprate_types::{HardFork, HardForkError}; + /// # use strum::VariantArray; + /// assert_eq!(HardFork::from_version(0), Err(HardForkError::HardForkUnknown)); + /// assert_eq!(HardFork::from_version(17), Err(HardForkError::HardForkUnknown)); + /// + /// for (version, hf) in HardFork::VARIANTS.iter().enumerate() { + /// // +1 because enumerate starts at 0, hf starts at 1. + /// assert_eq!(*hf, HardFork::from_version(version as u8 + 1).unwrap()); + /// } + /// ``` + #[inline] + pub const fn from_version(version: u8) -> Result { + match Self::from_repr(version) { + Some(this) => Ok(this), + None => Err(HardForkError::HardForkUnknown), + } + } + + /// Returns the hard-fork for a blocks [`BlockHeader::hardfork_signal`] (vote) field. + /// + /// + /// + /// ```rust + /// # use cuprate_types::{HardFork, HardForkError}; + /// # use strum::VariantArray; + /// // 0 is interpreted as 1. + /// assert_eq!(HardFork::from_vote(0), HardFork::V1); + /// // Unknown defaults to `LATEST`. + /// assert_eq!(HardFork::from_vote(17), HardFork::V16); + /// + /// for (vote, hf) in HardFork::VARIANTS.iter().enumerate() { + /// // +1 because enumerate starts at 0, hf starts at 1. + /// assert_eq!(*hf, HardFork::from_vote(vote as u8 + 1)); + /// } + /// ``` + #[inline] + pub fn from_vote(vote: u8) -> Self { + if vote == 0 { + // A vote of 0 is interpreted as 1 as that's what Monero used to default to. + Self::V1 + } else { + // This must default to the latest hard-fork! + Self::from_version(vote).unwrap_or(Self::LATEST) + } + } + + /// Returns the [`HardFork`] version and vote from this block header. + /// + /// # Errors + /// Will return [`Err`] if the [`BlockHeader::hardfork_version`] is not a valid [`HardFork`]. + #[inline] + pub fn from_block_header(header: &BlockHeader) -> Result<(Self, Self), HardForkError> { + Ok(( + Self::from_version(header.hardfork_version)?, + Self::from_vote(header.hardfork_signal), + )) + } + + /// Returns the raw hard-fork value, as it would appear in [`BlockHeader::hardfork_version`]. + /// + /// ```rust + /// # use cuprate_types::{HardFork, HardForkError}; + /// # use strum::VariantArray; + /// for (i, hf) in HardFork::VARIANTS.iter().enumerate() { + /// // +1 because enumerate starts at 0, hf starts at 1. + /// assert_eq!(hf.as_u8(), i as u8 + 1); + /// } + /// ``` + pub const fn as_u8(self) -> u8 { + self as u8 + } + + /// Returns the next hard-fork. + pub fn next_fork(self) -> Option { + Self::from_version(self as u8 + 1).ok() + } + + /// Returns the target block time for this hardfork. + /// + /// ref: + pub const fn block_time(self) -> Duration { + match self { + Self::V1 => BLOCK_TIME_V1, + _ => BLOCK_TIME_V2, + } + } + + /// Returns `true` if `self` is [`Self::LATEST`]. + /// + /// ```rust + /// # use cuprate_types::HardFork; + /// # use strum::VariantArray; + /// + /// for hf in HardFork::VARIANTS.iter() { + /// if *hf == HardFork::LATEST { + /// assert!(hf.is_latest()); + /// } else { + /// assert!(!hf.is_latest()); + /// } + /// } + /// ``` + pub const fn is_latest(self) -> bool { + matches!(self, Self::LATEST) + } +} diff --git a/types/src/hex.rs b/types/src/hex.rs new file mode 100644 index 0000000..de4fc81 --- /dev/null +++ b/types/src/hex.rs @@ -0,0 +1,75 @@ +//! Hexadecimal serde wrappers for arrays. +//! +//! This module provides transparent wrapper types for +//! arrays that (de)serialize from hexadecimal input/output. + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{error, macros::bytes, EpeeValue, Marker}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Wrapper type for a byte array that (de)serializes from/to hexadecimal strings. +/// +/// # Deserialization +/// This struct has a custom deserialization that only applies to certain +/// `N` lengths because [`hex::FromHex`] does not implement for a generic `N`: +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[repr(transparent)] +pub struct HexBytes( + #[cfg_attr(feature = "serde", serde(with = "hex::serde"))] pub [u8; N], +); + +#[cfg(feature = "serde")] +impl<'de, const N: usize> Deserialize<'de> for HexBytes +where + [u8; N]: hex::FromHex, + <[u8; N] as hex::FromHex>::Error: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self(hex::serde::deserialize(deserializer)?)) + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for HexBytes { + const MARKER: Marker = <[u8; N] as EpeeValue>::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + Ok(Self(<[u8; N] as EpeeValue>::read(r, marker)?)) + } + + fn write(self, w: &mut B) -> error::Result<()> { + <[u8; N] as EpeeValue>::write(self.0, w) + } +} + +// Default is not implemented for arrays >32, so we must do it manually. +impl Default for HexBytes { + fn default() -> Self { + Self([0; N]) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn hex_bytes_32() { + let hash = [1; 32]; + let hex_bytes = HexBytes::<32>(hash); + let expected_json = r#""0101010101010101010101010101010101010101010101010101010101010101""#; + + let to_string = serde_json::to_string(&hex_bytes).unwrap(); + assert_eq!(to_string, expected_json); + + let from_str = serde_json::from_str::>(expected_json).unwrap(); + assert_eq!(hex_bytes, from_str); + } +} diff --git a/types/src/json/block.rs b/types/src/json/block.rs new file mode 100644 index 0000000..88f134d --- /dev/null +++ b/types/src/json/block.rs @@ -0,0 +1,359 @@ +//! JSON block types. + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use monero_serai::{block, transaction}; + +use cuprate_helper::cast::usize_to_u64; + +use crate::{ + hex::HexBytes, + json::output::{Output, TaggedKey, Target}, +}; + +/// JSON representation of a block. +/// +/// Used in: +/// - [`/get_block` -> `json`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block) +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Block { + pub major_version: u8, + pub minor_version: u8, + pub timestamp: u64, + pub prev_id: HexBytes<32>, + pub nonce: u32, + pub miner_tx: MinerTransaction, + pub tx_hashes: Vec>, +} + +impl From for Block { + fn from(b: block::Block) -> Self { + let Ok(miner_tx) = MinerTransaction::try_from(b.miner_transaction) else { + unreachable!("input is a miner tx, this should never fail"); + }; + + let tx_hashes = b.transactions.into_iter().map(HexBytes::<32>).collect(); + + Self { + major_version: b.header.hardfork_version, + minor_version: b.header.hardfork_signal, + timestamp: b.header.timestamp, + prev_id: HexBytes::<32>(b.header.previous), + nonce: b.header.nonce, + miner_tx, + tx_hashes, + } + } +} + +/// [`Block::miner_tx`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum MinerTransaction { + V1 { + /// This field is [flattened](https://serde.rs/field-attrs.html#flatten). + #[cfg_attr(feature = "serde", serde(flatten))] + prefix: MinerTransactionPrefix, + signatures: [(); 0], + }, + V2 { + /// This field is [flattened](https://serde.rs/field-attrs.html#flatten). + #[cfg_attr(feature = "serde", serde(flatten))] + prefix: MinerTransactionPrefix, + rct_signatures: MinerTransactionRctSignatures, + }, +} + +impl TryFrom for MinerTransaction { + type Error = transaction::Transaction; + + /// # Errors + /// This function errors if the input is not a miner transaction. + fn try_from(tx: transaction::Transaction) -> Result { + fn map_prefix( + prefix: transaction::TransactionPrefix, + version: u8, + ) -> Result { + let Some(input) = prefix.inputs.first() else { + return Err(prefix); + }; + + let height = match input { + transaction::Input::Gen(height) => usize_to_u64(*height), + transaction::Input::ToKey { .. } => return Err(prefix), + }; + + let vin = { + let r#gen = Gen { height }; + let input = Input { r#gen }; + [input] + }; + + let vout = prefix + .outputs + .into_iter() + .map(|o| { + let amount = o.amount.unwrap_or(0); + + let target = match o.view_tag { + Some(view_tag) => { + let tagged_key = TaggedKey { + key: HexBytes::<32>(o.key.0), + view_tag: HexBytes::<1>([view_tag]), + }; + + Target::TaggedKey { tagged_key } + } + None => Target::Key { + key: HexBytes::<32>(o.key.0), + }, + }; + + Output { amount, target } + }) + .collect(); + + let unlock_time = match prefix.additional_timelock { + transaction::Timelock::None => 0, + transaction::Timelock::Block(x) => usize_to_u64(x), + transaction::Timelock::Time(x) => x, + }; + + Ok(MinerTransactionPrefix { + version, + unlock_time, + vin, + vout, + extra: prefix.extra, + }) + } + + Ok(match tx { + transaction::Transaction::V1 { prefix, signatures } => { + let prefix = match map_prefix(prefix, 1) { + Ok(p) => p, + Err(prefix) => return Err(transaction::Transaction::V1 { prefix, signatures }), + }; + + Self::V1 { + prefix, + signatures: [(); 0], + } + } + transaction::Transaction::V2 { prefix, proofs } => { + let prefix = match map_prefix(prefix, 2) { + Ok(p) => p, + Err(prefix) => return Err(transaction::Transaction::V2 { prefix, proofs }), + }; + + Self::V2 { + prefix, + rct_signatures: MinerTransactionRctSignatures { r#type: 0 }, + } + } + }) + } +} + +impl Default for MinerTransaction { + fn default() -> Self { + Self::V1 { + prefix: Default::default(), + signatures: Default::default(), + } + } +} + +/// [`MinerTransaction::V1::prefix`] & [`MinerTransaction::V2::prefix`]. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct MinerTransactionPrefix { + pub version: u8, + pub unlock_time: u64, + pub vin: [Input; 1], + pub vout: Vec, + pub extra: Vec, +} + +/// [`MinerTransaction::V2::rct_signatures`]. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct MinerTransactionRctSignatures { + pub r#type: u8, +} + +/// [`MinerTransactionPrefix::vin`]. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Input { + pub r#gen: Gen, +} + +/// [`Input::gen`]. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Gen { + pub height: u64, +} + +#[cfg(test)] +mod test { + use hex_literal::hex; + use pretty_assertions::assert_eq; + + use super::*; + + #[expect(clippy::needless_pass_by_value)] + fn test(block: Block, block_json: &'static str) { + let json = serde_json::from_str::(block_json).unwrap(); + assert_eq!(block, json); + let string = serde_json::to_string(&json).unwrap(); + assert_eq!(block_json, &string); + } + + #[test] + fn block_300000() { + const JSON: &str = r#"{"major_version":1,"minor_version":0,"timestamp":1415690591,"prev_id":"e97a0ab6307de9b9f9a9872263ef3e957976fb227eb9422c6854e989e5d5d34c","nonce":2147484616,"miner_tx":{"version":1,"unlock_time":300060,"vin":[{"gen":{"height":300000}}],"vout":[{"amount":47019296802,"target":{"key":"3c1dcbf5b485987ecef4596bb700e32cbc7bd05964e3888ffc05f8a46bf5fc33"}},{"amount":200000000000,"target":{"key":"5810afc7a1b01a1c913eb6aab15d4a851cbc4a8cf0adf90bb80ac1a7ca9928aa"}},{"amount":3000000000000,"target":{"key":"520f49c5f2ce8456dc1a565f35ed3a5ccfff3a1210b340870a57d2749a81a2df"}},{"amount":10000000000000,"target":{"key":"44d7705e62c76c2e349a474df6724aa1d9932092002b03a94f9c19d9d12b9427"}}],"extra":[1,251,8,189,254,12,213,173,108,61,156,198,144,151,31,130,141,211,120,55,81,98,32,247,111,127,254,170,170,240,124,190,223,2,8,0,0,0,64,184,115,46,246],"signatures":[]},"tx_hashes":[]}"#; + + let block = Block { + major_version: 1, + minor_version: 0, + timestamp: 1415690591, + prev_id: HexBytes::<32>(hex!( + "e97a0ab6307de9b9f9a9872263ef3e957976fb227eb9422c6854e989e5d5d34c" + )), + nonce: 2147484616, + miner_tx: MinerTransaction::V1 { + prefix: MinerTransactionPrefix { + version: 1, + unlock_time: 300060, + vin: [Input { + r#gen: Gen { height: 300000 }, + }], + vout: vec![ + Output { + amount: 47019296802, + target: Target::Key { + key: HexBytes::<32>(hex!("3c1dcbf5b485987ecef4596bb700e32cbc7bd05964e3888ffc05f8a46bf5fc33")), + } + }, + Output { + amount: 200000000000, + target: Target::Key { + key: HexBytes::<32>(hex!("5810afc7a1b01a1c913eb6aab15d4a851cbc4a8cf0adf90bb80ac1a7ca9928aa")), + } + }, + Output { + amount: 3000000000000, + target: Target::Key { + key: HexBytes::<32>(hex!("520f49c5f2ce8456dc1a565f35ed3a5ccfff3a1210b340870a57d2749a81a2df")), + } + }, + Output { + amount: 10000000000000, + target: Target::Key { + key: HexBytes::<32>(hex!("44d7705e62c76c2e349a474df6724aa1d9932092002b03a94f9c19d9d12b9427")), + } + } + ], + extra: vec![ + 1, 251, 8, 189, 254, 12, 213, 173, 108, 61, 156, 198, 144, 151, 31, 130, + 141, 211, 120, 55, 81, 98, 32, 247, 111, 127, 254, 170, 170, 240, 124, 190, + 223, 2, 8, 0, 0, 0, 64, 184, 115, 46, 246, + ], + }, + signatures: [], + }, + tx_hashes: vec![], + }; + + test(block, JSON); + } + + #[test] + fn block_3245409() { + const JSON: &str = r#"{"major_version":16,"minor_version":16,"timestamp":1727293028,"prev_id":"41b56c273d69def3294e56179de71c61808042d54c1e085078d21dbe99e81b6f","nonce":311,"miner_tx":{"version":2,"unlock_time":3245469,"vin":[{"gen":{"height":3245409}}],"vout":[{"amount":601012280000,"target":{"tagged_key":{"key":"8c0b16c6df02b9944b49f375d96a958a0fc5431c048879bb5bf25f64a1163b9e","view_tag":"88"}}}],"extra":[1,39,23,182,203,58,48,15,217,9,13,147,104,133,206,176,185,56,237,179,136,72,84,129,113,98,206,4,18,50,130,162,94,2,17,73,18,21,33,32,112,5,0,0,0,0,0,0,0,0,0,0],"rct_signatures":{"type":0}},"tx_hashes":["eab76986a0cbcae690d8499f0f616f783fd2c89c6f611417f18011950dbdab2e","57b19aa8c2cdbb6836cf13dd1e321a67860965c12e4418f3c30f58c8899a851e","5340185432ab6b74fb21379f7e8d8f0e37f0882b2a7121fd7c08736f079e2edc","01dc6d31db56d68116f5294c1b4f80b33b048b5cdfefcd904f23e6c0de3daff5","c9fb6a2730678203948fef2a49fa155b63f35a3649f3d32ed405a6806f3bbd56","af965cdd2a2315baf1d4a3d242f44fe07b1fd606d5f4853c9ff546ca6c12a5af","97bc9e047d25fae8c14ce6ec882224e7b722f5e79b62a2602a6bacebdac8547b","28c46992eaf10dc0cceb313c30572d023432b7bd26e85e679bc8fe419533a7bf","c32e3acde2ff2885c9cc87253b40d6827d167dfcc3022c72f27084fd98788062","19e66a47f075c7cccde8a7b52803119e089e33e3a4847cace0bd1d17b0d22bab","8e8ac560e77a1ee72e82a5eb6887adbe5979a10cd29cb2c2a3720ce87db43a70","b7ff5141524b5cca24de6780a5dbfdf71e7de1e062fd85f557fb3b43b8e285dc","f09df0f113763ef9b9a2752ac293b478102f7cab03ef803a3d9db7585aea8912"]}"#; + + let block = Block { + major_version: 16, + minor_version: 16, + timestamp: 1727293028, + prev_id: HexBytes::<32>(hex!( + "41b56c273d69def3294e56179de71c61808042d54c1e085078d21dbe99e81b6f" + )), + nonce: 311, + miner_tx: MinerTransaction::V2 { + prefix: MinerTransactionPrefix { + version: 2, + unlock_time: 3245469, + vin: [Input { + r#gen: Gen { height: 3245409 }, + }], + vout: vec![Output { + amount: 601012280000, + target: Target::TaggedKey { + tagged_key: TaggedKey { + key: HexBytes::<32>(hex!( + "8c0b16c6df02b9944b49f375d96a958a0fc5431c048879bb5bf25f64a1163b9e" + )), + view_tag: HexBytes::<1>(hex!("88")), + }, + }, + }], + extra: vec![ + 1, 39, 23, 182, 203, 58, 48, 15, 217, 9, 13, 147, 104, 133, 206, 176, 185, + 56, 237, 179, 136, 72, 84, 129, 113, 98, 206, 4, 18, 50, 130, 162, 94, 2, + 17, 73, 18, 21, 33, 32, 112, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + }, + rct_signatures: MinerTransactionRctSignatures { r#type: 0 }, + }, + tx_hashes: vec![ + HexBytes::<32>(hex!( + "eab76986a0cbcae690d8499f0f616f783fd2c89c6f611417f18011950dbdab2e" + )), + HexBytes::<32>(hex!( + "57b19aa8c2cdbb6836cf13dd1e321a67860965c12e4418f3c30f58c8899a851e" + )), + HexBytes::<32>(hex!( + "5340185432ab6b74fb21379f7e8d8f0e37f0882b2a7121fd7c08736f079e2edc" + )), + HexBytes::<32>(hex!( + "01dc6d31db56d68116f5294c1b4f80b33b048b5cdfefcd904f23e6c0de3daff5" + )), + HexBytes::<32>(hex!( + "c9fb6a2730678203948fef2a49fa155b63f35a3649f3d32ed405a6806f3bbd56" + )), + HexBytes::<32>(hex!( + "af965cdd2a2315baf1d4a3d242f44fe07b1fd606d5f4853c9ff546ca6c12a5af" + )), + HexBytes::<32>(hex!( + "97bc9e047d25fae8c14ce6ec882224e7b722f5e79b62a2602a6bacebdac8547b" + )), + HexBytes::<32>(hex!( + "28c46992eaf10dc0cceb313c30572d023432b7bd26e85e679bc8fe419533a7bf" + )), + HexBytes::<32>(hex!( + "c32e3acde2ff2885c9cc87253b40d6827d167dfcc3022c72f27084fd98788062" + )), + HexBytes::<32>(hex!( + "19e66a47f075c7cccde8a7b52803119e089e33e3a4847cace0bd1d17b0d22bab" + )), + HexBytes::<32>(hex!( + "8e8ac560e77a1ee72e82a5eb6887adbe5979a10cd29cb2c2a3720ce87db43a70" + )), + HexBytes::<32>(hex!( + "b7ff5141524b5cca24de6780a5dbfdf71e7de1e062fd85f557fb3b43b8e285dc" + )), + HexBytes::<32>(hex!( + "f09df0f113763ef9b9a2752ac293b478102f7cab03ef803a3d9db7585aea8912" + )), + ], + }; + + test(block, JSON); + } +} diff --git a/types/src/json/mod.rs b/types/src/json/mod.rs new file mode 100644 index 0000000..abd8779 --- /dev/null +++ b/types/src/json/mod.rs @@ -0,0 +1,15 @@ +//! This module contains types mappings for other common types +//! to allow for easier JSON (de)serialization. +//! +//! The main types are: +//! - [`block::Block`] +//! - [`tx::Transaction`] +//! +//! Modules exist within this module as the JSON representation +//! of types sometimes differs, thus, the modules hold the types +//! that match the specific schema, for example [`block::Input`] +//! is different than [`tx::Input`]. + +pub mod block; +pub mod output; +pub mod tx; diff --git a/types/src/json/output.rs b/types/src/json/output.rs new file mode 100644 index 0000000..182618c --- /dev/null +++ b/types/src/json/output.rs @@ -0,0 +1,43 @@ +//! JSON output types. +//! +//! The same [`Output`] is used in both +//! [`crate::json::block::MinerTransactionPrefix::vout`] +//! and [`crate::json::tx::TransactionPrefix::vout`]. + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::hex::HexBytes; + +/// JSON representation of an output. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Output { + pub amount: u64, + pub target: Target, +} + +/// [`Output::target`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum Target { + Key { key: HexBytes<32> }, + TaggedKey { tagged_key: TaggedKey }, +} + +impl Default for Target { + fn default() -> Self { + Self::Key { + key: Default::default(), + } + } +} + +/// [`Target::TaggedKey::tagged_key`]. +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TaggedKey { + pub key: HexBytes<32>, + pub view_tag: HexBytes<1>, +} diff --git a/types/src/json/tx.rs b/types/src/json/tx.rs new file mode 100644 index 0000000..a18dc89 --- /dev/null +++ b/types/src/json/tx.rs @@ -0,0 +1,1455 @@ +//! JSON transaction types. + +#![expect( + non_snake_case, + reason = "JSON serialization requires non snake-case casing" +)] + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use cuprate_helper::cast::usize_to_u64; + +use monero_serai::{ringct, transaction}; + +use crate::{ + hex::HexBytes, + json::output::{Output, TaggedKey, Target}, +}; + +/// JSON representation of a non-miner transaction. +/// +/// Used in: +/// - [`/get_transactions` -> `txs.as_json`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_transactions) +/// - [`/get_transaction_pool` -> `tx_json`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_transaction_pool) +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum Transaction { + V1 { + /// This field is [flattened](https://serde.rs/field-attrs.html#flatten). + #[cfg_attr(feature = "serde", serde(flatten))] + prefix: TransactionPrefix, + signatures: Vec>, + }, + V2 { + /// This field is [flattened](https://serde.rs/field-attrs.html#flatten). + #[cfg_attr(feature = "serde", serde(flatten))] + prefix: TransactionPrefix, + rct_signatures: RctSignatures, + /// This field is [`Some`] if [`Self::V2::rct_signatures`] + /// is [`RctSignatures::NonCoinbase`], else [`None`]. + rctsig_prunable: Option, + }, +} + +/// [`Transaction::V1::prefix`] & [`Transaction::V2::prefix`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TransactionPrefix { + pub version: u8, + pub unlock_time: u64, + pub vin: Vec, + pub vout: Vec, + pub extra: Vec, +} + +impl From for Transaction { + fn from(tx: transaction::Transaction) -> Self { + fn map_prefix(prefix: transaction::TransactionPrefix, version: u8) -> TransactionPrefix { + let mut height = 0; + + let vin = prefix + .inputs + .into_iter() + .filter_map(|input| match input { + transaction::Input::ToKey { + amount, + key_offsets, + key_image, + } => { + let key = Key { + amount: amount.unwrap_or(0), + key_offsets, + k_image: HexBytes::<32>(key_image.compress().0), + }; + + Some(Input { key }) + } + transaction::Input::Gen(h) => { + height = usize_to_u64(h); + None + } + }) + .collect(); + + let vout = prefix + .outputs + .into_iter() + .map(|o| { + let amount = o.amount.unwrap_or(0); + + let target = match o.view_tag { + Some(view_tag) => { + let tagged_key = TaggedKey { + key: HexBytes::<32>(o.key.0), + view_tag: HexBytes::<1>([view_tag]), + }; + + Target::TaggedKey { tagged_key } + } + None => Target::Key { + key: HexBytes::<32>(o.key.0), + }, + }; + + Output { amount, target } + }) + .collect(); + + let unlock_time = match prefix.additional_timelock { + transaction::Timelock::None => 0, + transaction::Timelock::Block(x) => usize_to_u64(x), + transaction::Timelock::Time(x) => x, + }; + + TransactionPrefix { + version, + unlock_time, + vin, + vout, + extra: prefix.extra, + } + } + + #[expect(unused_variables, reason = "TODO: finish impl")] + match tx { + transaction::Transaction::V1 { prefix, signatures } => Self::V1 { + prefix: map_prefix(prefix, 1), + signatures: signatures + .into_iter() + .map(|sig| { + // TODO: `RingSignature` needs to expose the + // inner `Signature` struct as a byte array. + let sig_to_64_bytes = |sig| -> HexBytes<64> { todo!() }; + sig_to_64_bytes(sig) + }) + .collect(), + }, + transaction::Transaction::V2 { prefix, proofs } => { + let prefix = map_prefix(prefix, 2); + + let Some(proofs) = proofs else { + return Self::V2 { + prefix, + rct_signatures: RctSignatures::Coinbase { r#type: 0 }, + rctsig_prunable: None, + }; + }; + + let r#type = match proofs.rct_type() { + ringct::RctType::AggregateMlsagBorromean => 1, + ringct::RctType::MlsagBorromean => 2, + ringct::RctType::MlsagBulletproofs => 3, + ringct::RctType::MlsagBulletproofsCompactAmount => 4, + ringct::RctType::ClsagBulletproof => 5, + ringct::RctType::ClsagBulletproofPlus => 6, + }; + + let txnFee = proofs.base.fee; + + let ecdhInfo = proofs + .base + .encrypted_amounts + .into_iter() + .map(EcdhInfo::from) + .collect(); + + let outPk = proofs + .base + .commitments + .into_iter() + .map(|point| HexBytes::<32>(point.compress().0)) + .collect(); + + let rct_signatures = RctSignatures::NonCoinbase { + r#type, + txnFee, + ecdhInfo, + outPk, + }; + + let rctsig_prunable = Some(RctSigPrunable::from(proofs.prunable)); + + Self::V2 { + prefix, + rct_signatures, + rctsig_prunable, + } + } + } + } +} + +/// [`Transaction::V2::rct_signatures`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum RctSignatures { + NonCoinbase { + r#type: u8, + txnFee: u64, + ecdhInfo: Vec, + outPk: Vec>, + }, + Coinbase { + r#type: u8, + }, +} + +/// [`Transaction::V2::rctsig_prunable`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum RctSigPrunable { + /// - [`ringct::RctPrunable::AggregateMlsagBorromean`] + /// - [`ringct::RctPrunable::MlsagBorromean`] + MlsagBorromean { + rangeSigs: Vec, + MGs: Vec, + }, + + /// - [`ringct::RctPrunable::MlsagBulletproofs`] + MlsagBulletproofs { + nbp: u64, + bp: Vec, + MGs: Vec, + pseudoOuts: Vec>, + }, + + /// - [`ringct::RctPrunable::Clsag`] with [`ringct::bulletproofs::Bulletproof::Original`] + ClsagBulletproofs { + nbp: u64, + bp: Vec, + CLSAGs: Vec, + pseudoOuts: Vec>, + }, + + /// - [`ringct::RctPrunable::Clsag`] with [`ringct::bulletproofs::Bulletproof::Plus`] + ClsagBulletproofsPlus { + nbp: u64, + bpp: Vec, + CLSAGs: Vec, + pseudoOuts: Vec>, + }, +} + +#[expect(unused_variables, reason = "TODO: finish impl")] +impl From for RctSigPrunable { + fn from(r: ringct::RctPrunable) -> Self { + use ringct::RctPrunable as R; + + match r { + R::AggregateMlsagBorromean { mlsag, borromean } => { + todo!() + } + R::MlsagBorromean { mlsags, borromean } => { + todo!() + } + R::MlsagBulletproofs { + mlsags, + pseudo_outs, + bulletproof, + } => { + todo!() + } + R::MlsagBulletproofsCompactAmount { + mlsags, + pseudo_outs, + bulletproof, + } => { + todo!() + } + R::Clsag { + clsags, + pseudo_outs, + bulletproof, + } => { + todo!() + } + } + } +} + +/// [`RctSigPrunable::MlsagBorromean::rangeSigs`] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct RangeSignature { + // These fields are hex but way too big to be + // using stack arrays to represent them. + pub asig: String, + pub Ci: String, +} + +/// - [`RctSigPrunable::MlsagBorromean::MGs`] +/// - [`RctSigPrunable::MlsagBulletproofs::MGs`] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Mg { + pub ss: Vec<[HexBytes<32>; 2]>, + pub cc: HexBytes<32>, +} + +/// - [`RctSigPrunable::MlsagBulletproofs::bp`] +/// - [`RctSigPrunable::ClsagBulletproofs::bp`] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Bulletproof { + pub A: HexBytes<32>, + pub S: HexBytes<32>, + pub T1: HexBytes<32>, + pub T2: HexBytes<32>, + pub taux: HexBytes<32>, + pub mu: HexBytes<32>, + pub L: Vec>, + pub R: Vec>, + pub a: HexBytes<32>, + pub b: HexBytes<32>, + pub t: HexBytes<32>, +} + +/// - [`RctSigPrunable::ClsagBulletproofsPlus::bpp`] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BulletproofPlus { + pub A: HexBytes<32>, + pub A1: HexBytes<32>, + pub B: HexBytes<32>, + pub r1: HexBytes<32>, + pub s1: HexBytes<32>, + pub d1: HexBytes<32>, + pub L: Vec>, + pub R: Vec>, +} + +/// - [`RctSigPrunable::ClsagBulletproofs`] +/// - [`RctSigPrunable::ClsagBulletproofsPlus`] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Clsag { + pub s: Vec>, + pub c1: HexBytes<32>, + pub D: HexBytes<32>, +} + +/// [`RctSignatures::NonCoinbase::ecdhInfo`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[expect(variant_size_differences)] +pub enum EcdhInfo { + Original { + mask: HexBytes<32>, + amount: HexBytes<32>, + }, + Compact { + amount: HexBytes<8>, + }, +} + +impl From for EcdhInfo { + fn from(ea: ringct::EncryptedAmount) -> Self { + match ea { + ringct::EncryptedAmount::Original { amount, mask } => Self::Original { + amount: HexBytes::<32>(amount), + mask: HexBytes::<32>(mask), + }, + ringct::EncryptedAmount::Compact { amount } => Self::Compact { + amount: HexBytes::<8>(amount), + }, + } + } +} + +/// [`TransactionPrefix::vin`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Input { + pub key: Key, +} + +/// [`Input::key`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Key { + pub amount: u64, + pub key_offsets: Vec, + pub k_image: HexBytes<32>, +} + +#[cfg(test)] +mod test { + use hex_literal::hex; + use pretty_assertions::assert_eq; + + use super::*; + + #[expect(clippy::needless_pass_by_value)] + fn test(tx: Transaction, tx_json: &'static str) { + let json = serde_json::from_str::(tx_json).unwrap(); + assert_eq!(tx, json); + let string = serde_json::to_string(&json).unwrap(); + assert_eq!(tx_json, &string); + } + + #[test] + fn tx_v1() { + const JSON: &str = r#"{"version":1,"unlock_time":0,"vin":[{"key":{"amount":2865950000,"key_offsets":[0],"k_image":"f1b0eeff62493ea78b2b7e843c278d6d5a7b09adf0cbc83560380d1fe397d6f3"}},{"key":{"amount":6000000000000,"key_offsets":[75146],"k_image":"3d289ab83c06e0948a460e836699a33fe9c300b2448c0f2de0e3b40c13d9bd78"}},{"key":{"amount":3000000000000,"key_offsets":[49742],"k_image":"52a32e6ecadcce76c11262eda8f7265c098b3da1f6e27ae8c9656636faf51ae4"}}],"vout":[{"amount":29220020000,"target":{"key":"f9da453f7cd5248e109de3216208eb9ec8617b0739450405de582f09b7e3fc47"}},{"amount":400000000000,"target":{"key":"c31ce6d52fae900ffab9f30b036bbdea0b9442b589cbe24c2e071ddb8291da14"}},{"amount":400000000000,"target":{"key":"bd570e3805c0198c92f9a24d8f12e9dbe88570196efd176b7f186ade904803f4"}},{"amount":1000000000000,"target":{"key":"84d1ba528dfc2e2ff29b3840fc3ae1c87ae5f750e582b78c4161a6bdb6a4717a"}},{"amount":7000000000000,"target":{"key":"993fd478527fd3e790fd3f5a0d9a3a39bebe72598cc81cb9936e08dea7e5fb0f"}}],"extra":[2,33,0,236,254,1,219,138,20,181,240,174,155,149,49,142,23,185,3,251,47,59,239,236,73,246,142,19,181,27,254,76,248,75,191,1,180,204,225,45,175,103,127,119,53,211,168,192,138,14,121,64,19,218,222,27,66,129,115,185,5,113,142,40,157,70,87,62],"signatures":["318755c67c5d3379b0958a047f5439cf43dd251f64b6314c84b2edbf240d950abbeaad13233700e6b6c59bea178c6fbaa246b8fd84b5caf94d1affd520e6770b","a47e6a65e907e49442828db46475ecdf27f3c472f24688423ac97f0efbd8b90b164ed52c070f7a2a95b95398814b19c0befd14a4aab5520963daf3482604df01","fa6981c969c2a1b9d330a8901d2ef7def7f3ade8d9fba444e18e7e349e286a035ae1729a76e01bbbb3ccd010502af6c77049e3167cf108be69706a8674b0c508"]}"#; + + let tx = Transaction::V1 { + prefix: TransactionPrefix { + version: 1, + unlock_time: 0, + vin: vec![ + Input { + key: Key { + amount: 2865950000, + key_offsets: vec![0], + k_image: HexBytes::<32>(hex!( + "f1b0eeff62493ea78b2b7e843c278d6d5a7b09adf0cbc83560380d1fe397d6f3" + )), + }, + }, + Input { + key: Key { + amount: 6000000000000, + key_offsets: vec![75146], + k_image: HexBytes::<32>(hex!( + "3d289ab83c06e0948a460e836699a33fe9c300b2448c0f2de0e3b40c13d9bd78" + )), + }, + }, + Input { + key: Key { + amount: 3000000000000, + key_offsets: vec![49742], + k_image: HexBytes::<32>(hex!( + "52a32e6ecadcce76c11262eda8f7265c098b3da1f6e27ae8c9656636faf51ae4" + )), + }, + }, + ], + vout: vec![ + Output { + amount: 29220020000, + target: Target::Key { + key: HexBytes::<32>(hex!( + "f9da453f7cd5248e109de3216208eb9ec8617b0739450405de582f09b7e3fc47" + )), + }, + }, + Output { + amount: 400000000000, + target: Target::Key { + key: HexBytes::<32>(hex!( + "c31ce6d52fae900ffab9f30b036bbdea0b9442b589cbe24c2e071ddb8291da14" + )), + }, + }, + Output { + amount: 400000000000, + target: Target::Key { + key: HexBytes::<32>(hex!( + "bd570e3805c0198c92f9a24d8f12e9dbe88570196efd176b7f186ade904803f4" + )), + }, + }, + Output { + amount: 1000000000000, + target: Target::Key { + key: HexBytes::<32>(hex!( + "84d1ba528dfc2e2ff29b3840fc3ae1c87ae5f750e582b78c4161a6bdb6a4717a" + )), + }, + }, + Output { + amount: 7000000000000, + target: Target::Key { + key: HexBytes::<32>(hex!( + "993fd478527fd3e790fd3f5a0d9a3a39bebe72598cc81cb9936e08dea7e5fb0f" + )), + }, + }, + ], + extra: vec![ + 2, 33, 0, 236, 254, 1, 219, 138, 20, 181, 240, 174, 155, 149, 49, 142, 23, 185, + 3, 251, 47, 59, 239, 236, 73, 246, 142, 19, 181, 27, 254, 76, 248, 75, 191, 1, + 180, 204, 225, 45, 175, 103, 127, 119, 53, 211, 168, 192, 138, 14, 121, 64, 19, + 218, 222, 27, 66, 129, 115, 185, 5, 113, 142, 40, 157, 70, 87, 62, + ], + }, + signatures: vec![ + HexBytes::<64>(hex!("318755c67c5d3379b0958a047f5439cf43dd251f64b6314c84b2edbf240d950abbeaad13233700e6b6c59bea178c6fbaa246b8fd84b5caf94d1affd520e6770b")), + HexBytes::<64>(hex!("a47e6a65e907e49442828db46475ecdf27f3c472f24688423ac97f0efbd8b90b164ed52c070f7a2a95b95398814b19c0befd14a4aab5520963daf3482604df01")), + HexBytes::<64>(hex!("fa6981c969c2a1b9d330a8901d2ef7def7f3ade8d9fba444e18e7e349e286a035ae1729a76e01bbbb3ccd010502af6c77049e3167cf108be69706a8674b0c508")) + ], + }; + + test(tx, JSON); + } + + #[test] + fn tx_rct_3() { + const JSON: &str = r#"{"version":2,"unlock_time":0,"vin":[{"key":{"amount":0,"key_offsets":[8608351,301575,15985,56460,28593,9238,1709,170,369,1874,681],"k_image":"86e1cc68d3970757c4a265a7c28c3a39fe230851f2d8a14c5916a6aa60dbc892"}}],"vout":[{"amount":0,"target":{"key":"f21fd68e011df2e544a3d33221172baf921a121c85d1a2190c42e81d5dd1830e"}},{"amount":0,"target":{"key":"64a3e54d80a729f69ae04f85db06dd26a96f3b05674f6927337a755a9cdaefeb"}},{"amount":0,"target":{"key":"ad2ccf74d2c99946af10cedc922a87c30a4b1c0d7a13143e71d31cd788b0c171"}},{"amount":0,"target":{"key":"e03d9b552a50734487ed0da40ba977f718c91a782fe85899debfd2c56ea3e551"}},{"amount":0,"target":{"key":"b312d502c1b71a10d9483fb816e17d27d0508b5b74c462e14dca52395a14a155"}}],"extra":[1,224,97,197,75,148,190,193,227,206,86,37,3,184,209,129,160,202,192,210,43,32,138,51,151,70,119,47,146,57,223,154,50,4,5,0,191,65,91,3,171,12,85,167,171,16,104,155,253,200,141,80,208,150,162,213,53,57,197,121,68,106,70,96,188,175,119,160,72,148,223,225,199,34,97,143,5,107,219,86,93,114,31,160,6,17,195,221,157,186,50,144,147,117,99,25,160,173,15,167,20,15,91,127,217,28,255,151,248,119,197,199,201,4,248,90,115,44,13,172,116,229,191,216,187,111,255,104,43,62,207,138,134,126,114,34,99,248,241,243,25,208,2,7,247,134,6,9,213,173,242,95,39,187,214,105,81,94,111,53,212,160,183,216,152,137,123,32,101,253,223,108,59,6,176,24,161,45,42,98,92,200,74,34,7,116,231,53,86,94,40,84,151,129,250],"rct_signatures":{"type":3,"txnFee":86000000,"ecdhInfo":[{"mask":"95f1dcd5076d92d9592f1ad5d2e60e8b353c0048da1833db3d11634a9deff50f","amount":"9386f5401e2577e66dea290aae981a85f75ab81d21cd8060b6a2070c0c3d4209"},{"mask":"9a3015d73ee53f40c4a194c725aa5cea4822b99442ddb94223a52e365e02f70b","amount":"40b471293514f4399782abfe2968f5bb297a77b16b58261df7cffc60b68a5b04"},{"mask":"64b2b70d2e61fd4ac5c6d92f61d85dda1daf948853cc303a3a39baeeece41e08","amount":"b388bdce5bd31493dae245af4dbfc8486d959ef28af4ad1c1338f43dd3bd5a01"},{"mask":"e8d8b9380c446cace527ea1864d69f524b2c6b8eaf08f0f6c36621e73de49d0a","amount":"c74b47b823b7e5f2744e9643e4490f236eb9de006acd7bb8a32cca2f56223b06"},{"mask":"1ec895cc03e6831311a3ab6f86745312feec53de5aef1e1f204a984f082bff0c","amount":"d16c02a92488cd7d5fdf3c461ff8f4f7e75a18644e810ddd55a153e79464af0a"}],"outPk":["ff1a463fcb285d747df43612cc3bc925d4d27bebb08537b177a0dba960f90131","6b62f6ed7338cbf9b2639620400690676fa9d43aca93da4a9dc56d665a72b506","9363af049e5b0530fd77f884e84838efcabebf5fff57e2f00b49148c6415eafc","2fc11a581752a6465d59217e6a5841170de3ba96e27d2744ad280b5242efa9e7","56b6c2ca082d95600584ca429e6723816d4496cbf6f586cf1cfe2e0319224451"]},"rctsig_prunable":{"nbp":1,"bp":[{"A":"4e3433b32bd6d349774eac4ad467e698125d9e0402d216609ff0f4cfc216c50c","S":"7f6d8c127e4138c6153954836b127694786b890a138bae684eb951fb0fbf9be4","T1":"40ee0b2925d7555c17dd38bb69b80e1cfc05aa8b9dc2bd088c1361321a09d4f4","T2":"1488d918c2acdd6ff9e8d5bf82a08599733b9084cdfb0f891c9254742f2ea258","taux":"9b26002cff6e74e3da8ce59cadea4c8a0d93b9d4d94e6764031c21ecbac5f600","mu":"a414b36b00a290c62a443282295f50f381a44197af5db74f518a1b77dd8c120a","L":["d4c3360932332dd3cc407d857c6e759d7c29d10deede96a0649bba89fbdb0e04","33d7311748c6ee1fa92311513a3f32acf0bbcbd1c507e4378db19f108914f6c1","aeedddc3feaa374880a388a992e38027d97c8e0e3728fd536fb2f0a560f5f481","662e94760e3d51cf89a39db06c8f95e08b923ed97e883e9144d15f21e1639011","c07d35cb78309eec01052227df1967c0e0d553f6ca5d592f34bbeebcecdc78a8","9954f3a6c818fd5aed6fd7c94fdaf4f49d2159c47e31b953c3e44e11aa4c9943","a22d2b47f1a051daece177803252b976c33ac5e2a8c487afd89d61f3a08180f0","3ce357034185a6f540d00b2ab8229e64d2d6cad27a2b141d6f6e202307d959ae","5906da535fbd816292692880fe4630e9ed1dd2dc94495a4f7db080e18fd4a8e0"],"R":["0b40204226678fee886140e7c20e809165a58e1355101a2c5bdf7c797811ac21","94a1da201d9e85ad6ac564fe2e6a1fa62873d78e33a5931fd143ed165b360eba","fc458a6c42264f6c8890a386b7a68543a952ecc2b6239138b548c25d6bfa6c68","052da59d062001df5d95d3117deecb9b3175ed59a44aba9b92f84add748c1698","5aa7cf7545d4859a62903b29500449813a231a0c25cdb133a4780a9b0a411cd0","5366ad21b6b33b8f43aecfda087f0aee9cfdc2836e59f7721856058685965b39","960c4764aea3c0dff74c88728514da160bd79712cd50a948bd8b52d9569e69b1","6db5c54be77c08460e4581ee7709c0179108b46a55c3858e0897bd4f12e3e913","ffb4d75cab91763dc3e556fce046413382c84abe24615ada0605a43f8de88309"],"a":"43bf84ef0f596d1d76990c5202261f0963dade1affc1eee92a0508f5ce8d2900","b":"747be0d98f642649d2925a459238ed13f65bd6f12019683d4ede505394341604","t":"8592adba69d884c48e52135909a9738eafae80e590ae245b1a9ca65eea3a8b0f"}],"MGs":[{"ss":[["8a8838d965aa1bb49448c12ea1aabb680b393f5bf02e3b73874aa545cde6dc04","e16bf1d0c4c2639af6bed0c0205181b2a03bc5cdc22207906aac710acdd5170e"],["208d25cad34bcc9c49a5516102990814c75e0bbe2335b601880d9c6ce4fb400a","279a89826548b8b15ea342d892ca6f8bf9e6a5a14077a57edaa4fd676b0b9f0f"],["9edbd1d2082bad9dd9ca98baf82b4d70014dee720c758ed0944a9fb82ae55206","3314001eeec40a2e0ca83f48af1ade8b4139418da49e2c6d95aa3a1d4427de07"],["1837f42c1a4bd0747ed86c1e99bfe058031858c47ff4f066cfcdaf107499bf0f","963bd0ed98a01be7c847b393ad0c2c25c3052148d67126c12b25ec2239373005"],["e41e7dd0430ccbc17f717db7fa1720241ab4de24249c607b9f882143d266ff0e","95c4a4ec2756ec57caacb64f17a7e5306103f030dfb12dd53b42c72e68b6e60b"],["8ecfab987a8697c58f4b183620b2fa0e11972fa666b71c138e067621ab5d1703","2e070ae83ab7f01f91766c2fd6de425dc0f18ae4e34fdcb3ac18db4dfec77a0c"],["187cd1a318666e9f7a9f2f9d4eaf7c662c6162c5bc2be94219992f261f46b90b","97ca174ff4bcf1e5d139bf0ad85577b9c6247f9e4782cd69100e683bf2e3f80b"],["28eb6f60cfa35b52cbf74b7e68ce795ebfa0d3db6f00e69677fc98aef963bf05","6662186aa949465b7b2174d6da077ab8ffdddb710bdab42386e7d8ae20f1890d"],["577c9cf99480b0633121737756bcc7f4887fc7fdf3a9344c84578886e60d1404","2d241b48e63acc39c8c899f7c009fcbc09025ea1211930a338e193d17aed890a"],["7a3f489532743f117999a1b375789cd0863541cae0b8633e8cd4c7dedc740305","500c1033ca2b4b47c39e70a1c563553571e0e25a2e1fa984cb5ba08546bc4907"],["82efb453a98454e07e8f4b367ee0db2f957e6222e720a69354fdf910fe5fe803","1c3204cf63c8ba3ebd817d603a4e5cadfa6a9af5999648eabff7605b5de8b306"]],"cc":"8b579f973b9395a175fb2fc1df7d66511166c606903a3c082b63fa831e833b00"}],"pseudoOuts":["bd6260cafa1afbe44d24cf7c42ac9e2b451424472eb1334b3c042e82196be0d7"]}}"#; + + let tx = Transaction::V2 { + prefix: TransactionPrefix { + version: 2, + unlock_time: 0, + vin: vec![Input { + key: Key { + amount: 0, + key_offsets: vec![ + 8608351, 301575, 15985, 56460, 28593, 9238, 1709, 170, 369, 1874, 681, + ], + k_image: HexBytes::<32>(hex!( + "86e1cc68d3970757c4a265a7c28c3a39fe230851f2d8a14c5916a6aa60dbc892" + )), + }, + }], + vout: vec![ + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "f21fd68e011df2e544a3d33221172baf921a121c85d1a2190c42e81d5dd1830e" + )), + }, + }, + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "64a3e54d80a729f69ae04f85db06dd26a96f3b05674f6927337a755a9cdaefeb" + )), + }, + }, + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "ad2ccf74d2c99946af10cedc922a87c30a4b1c0d7a13143e71d31cd788b0c171" + )), + }, + }, + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "e03d9b552a50734487ed0da40ba977f718c91a782fe85899debfd2c56ea3e551" + )), + }, + }, + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "b312d502c1b71a10d9483fb816e17d27d0508b5b74c462e14dca52395a14a155" + )), + }, + }, + ], + extra: vec![ + 1, 224, 97, 197, 75, 148, 190, 193, 227, 206, 86, 37, 3, 184, 209, 129, 160, + 202, 192, 210, 43, 32, 138, 51, 151, 70, 119, 47, 146, 57, 223, 154, 50, 4, 5, + 0, 191, 65, 91, 3, 171, 12, 85, 167, 171, 16, 104, 155, 253, 200, 141, 80, 208, + 150, 162, 213, 53, 57, 197, 121, 68, 106, 70, 96, 188, 175, 119, 160, 72, 148, + 223, 225, 199, 34, 97, 143, 5, 107, 219, 86, 93, 114, 31, 160, 6, 17, 195, 221, + 157, 186, 50, 144, 147, 117, 99, 25, 160, 173, 15, 167, 20, 15, 91, 127, 217, + 28, 255, 151, 248, 119, 197, 199, 201, 4, 248, 90, 115, 44, 13, 172, 116, 229, + 191, 216, 187, 111, 255, 104, 43, 62, 207, 138, 134, 126, 114, 34, 99, 248, + 241, 243, 25, 208, 2, 7, 247, 134, 6, 9, 213, 173, 242, 95, 39, 187, 214, 105, + 81, 94, 111, 53, 212, 160, 183, 216, 152, 137, 123, 32, 101, 253, 223, 108, 59, + 6, 176, 24, 161, 45, 42, 98, 92, 200, 74, 34, 7, 116, 231, 53, 86, 94, 40, 84, + 151, 129, 250, + ], + }, + rct_signatures: RctSignatures::NonCoinbase { + r#type: 3, + txnFee: 86000000, + ecdhInfo: vec![ + EcdhInfo::Original { + mask: HexBytes::<32>(hex!( + "95f1dcd5076d92d9592f1ad5d2e60e8b353c0048da1833db3d11634a9deff50f" + )), + amount: HexBytes::<32>(hex!( + "9386f5401e2577e66dea290aae981a85f75ab81d21cd8060b6a2070c0c3d4209" + )), + }, + EcdhInfo::Original { + mask: HexBytes::<32>(hex!( + "9a3015d73ee53f40c4a194c725aa5cea4822b99442ddb94223a52e365e02f70b" + )), + amount: HexBytes::<32>(hex!( + "40b471293514f4399782abfe2968f5bb297a77b16b58261df7cffc60b68a5b04" + )), + }, + EcdhInfo::Original { + mask: HexBytes::<32>(hex!( + "64b2b70d2e61fd4ac5c6d92f61d85dda1daf948853cc303a3a39baeeece41e08" + )), + amount: HexBytes::<32>(hex!( + "b388bdce5bd31493dae245af4dbfc8486d959ef28af4ad1c1338f43dd3bd5a01" + )), + }, + EcdhInfo::Original { + mask: HexBytes::<32>(hex!( + "e8d8b9380c446cace527ea1864d69f524b2c6b8eaf08f0f6c36621e73de49d0a" + )), + amount: HexBytes::<32>(hex!( + "c74b47b823b7e5f2744e9643e4490f236eb9de006acd7bb8a32cca2f56223b06" + )), + }, + EcdhInfo::Original { + mask: HexBytes::<32>(hex!( + "1ec895cc03e6831311a3ab6f86745312feec53de5aef1e1f204a984f082bff0c" + )), + amount: HexBytes::<32>(hex!( + "d16c02a92488cd7d5fdf3c461ff8f4f7e75a18644e810ddd55a153e79464af0a" + )), + }, + ], + outPk: vec![ + HexBytes::<32>(hex!( + "ff1a463fcb285d747df43612cc3bc925d4d27bebb08537b177a0dba960f90131" + )), + HexBytes::<32>(hex!( + "6b62f6ed7338cbf9b2639620400690676fa9d43aca93da4a9dc56d665a72b506" + )), + HexBytes::<32>(hex!( + "9363af049e5b0530fd77f884e84838efcabebf5fff57e2f00b49148c6415eafc" + )), + HexBytes::<32>(hex!( + "2fc11a581752a6465d59217e6a5841170de3ba96e27d2744ad280b5242efa9e7" + )), + HexBytes::<32>(hex!( + "56b6c2ca082d95600584ca429e6723816d4496cbf6f586cf1cfe2e0319224451" + )), + ], + }, + rctsig_prunable: Some(RctSigPrunable::MlsagBulletproofs { + nbp: 1, + bp: vec![Bulletproof { + A: HexBytes::<32>(hex!( + "4e3433b32bd6d349774eac4ad467e698125d9e0402d216609ff0f4cfc216c50c" + )), + S: HexBytes::<32>(hex!( + "7f6d8c127e4138c6153954836b127694786b890a138bae684eb951fb0fbf9be4" + )), + T1: HexBytes::<32>(hex!( + "40ee0b2925d7555c17dd38bb69b80e1cfc05aa8b9dc2bd088c1361321a09d4f4" + )), + T2: HexBytes::<32>(hex!( + "1488d918c2acdd6ff9e8d5bf82a08599733b9084cdfb0f891c9254742f2ea258" + )), + taux: HexBytes::<32>(hex!( + "9b26002cff6e74e3da8ce59cadea4c8a0d93b9d4d94e6764031c21ecbac5f600" + )), + mu: HexBytes::<32>(hex!( + "a414b36b00a290c62a443282295f50f381a44197af5db74f518a1b77dd8c120a" + )), + L: vec![ + HexBytes::<32>(hex!( + "d4c3360932332dd3cc407d857c6e759d7c29d10deede96a0649bba89fbdb0e04" + )), + HexBytes::<32>(hex!( + "33d7311748c6ee1fa92311513a3f32acf0bbcbd1c507e4378db19f108914f6c1" + )), + HexBytes::<32>(hex!( + "aeedddc3feaa374880a388a992e38027d97c8e0e3728fd536fb2f0a560f5f481" + )), + HexBytes::<32>(hex!( + "662e94760e3d51cf89a39db06c8f95e08b923ed97e883e9144d15f21e1639011" + )), + HexBytes::<32>(hex!( + "c07d35cb78309eec01052227df1967c0e0d553f6ca5d592f34bbeebcecdc78a8" + )), + HexBytes::<32>(hex!( + "9954f3a6c818fd5aed6fd7c94fdaf4f49d2159c47e31b953c3e44e11aa4c9943" + )), + HexBytes::<32>(hex!( + "a22d2b47f1a051daece177803252b976c33ac5e2a8c487afd89d61f3a08180f0" + )), + HexBytes::<32>(hex!( + "3ce357034185a6f540d00b2ab8229e64d2d6cad27a2b141d6f6e202307d959ae" + )), + HexBytes::<32>(hex!( + "5906da535fbd816292692880fe4630e9ed1dd2dc94495a4f7db080e18fd4a8e0" + )), + ], + R: vec![ + HexBytes::<32>(hex!( + "0b40204226678fee886140e7c20e809165a58e1355101a2c5bdf7c797811ac21" + )), + HexBytes::<32>(hex!( + "94a1da201d9e85ad6ac564fe2e6a1fa62873d78e33a5931fd143ed165b360eba" + )), + HexBytes::<32>(hex!( + "fc458a6c42264f6c8890a386b7a68543a952ecc2b6239138b548c25d6bfa6c68" + )), + HexBytes::<32>(hex!( + "052da59d062001df5d95d3117deecb9b3175ed59a44aba9b92f84add748c1698" + )), + HexBytes::<32>(hex!( + "5aa7cf7545d4859a62903b29500449813a231a0c25cdb133a4780a9b0a411cd0" + )), + HexBytes::<32>(hex!( + "5366ad21b6b33b8f43aecfda087f0aee9cfdc2836e59f7721856058685965b39" + )), + HexBytes::<32>(hex!( + "960c4764aea3c0dff74c88728514da160bd79712cd50a948bd8b52d9569e69b1" + )), + HexBytes::<32>(hex!( + "6db5c54be77c08460e4581ee7709c0179108b46a55c3858e0897bd4f12e3e913" + )), + HexBytes::<32>(hex!( + "ffb4d75cab91763dc3e556fce046413382c84abe24615ada0605a43f8de88309" + )), + ], + a: HexBytes::<32>(hex!( + "43bf84ef0f596d1d76990c5202261f0963dade1affc1eee92a0508f5ce8d2900" + )), + b: HexBytes::<32>(hex!( + "747be0d98f642649d2925a459238ed13f65bd6f12019683d4ede505394341604" + )), + t: HexBytes::<32>(hex!( + "8592adba69d884c48e52135909a9738eafae80e590ae245b1a9ca65eea3a8b0f" + )), + }], + MGs: vec![Mg { + ss: vec![ + [ + HexBytes::<32>(hex!( + "8a8838d965aa1bb49448c12ea1aabb680b393f5bf02e3b73874aa545cde6dc04" + )), + HexBytes::<32>(hex!( + "e16bf1d0c4c2639af6bed0c0205181b2a03bc5cdc22207906aac710acdd5170e" + )), + ], + [ + HexBytes::<32>(hex!( + "208d25cad34bcc9c49a5516102990814c75e0bbe2335b601880d9c6ce4fb400a" + )), + HexBytes::<32>(hex!( + "279a89826548b8b15ea342d892ca6f8bf9e6a5a14077a57edaa4fd676b0b9f0f" + )), + ], + [ + HexBytes::<32>(hex!( + "9edbd1d2082bad9dd9ca98baf82b4d70014dee720c758ed0944a9fb82ae55206" + )), + HexBytes::<32>(hex!( + "3314001eeec40a2e0ca83f48af1ade8b4139418da49e2c6d95aa3a1d4427de07" + )), + ], + [ + HexBytes::<32>(hex!( + "1837f42c1a4bd0747ed86c1e99bfe058031858c47ff4f066cfcdaf107499bf0f" + )), + HexBytes::<32>(hex!( + "963bd0ed98a01be7c847b393ad0c2c25c3052148d67126c12b25ec2239373005" + )), + ], + [ + HexBytes::<32>(hex!( + "e41e7dd0430ccbc17f717db7fa1720241ab4de24249c607b9f882143d266ff0e" + )), + HexBytes::<32>(hex!( + "95c4a4ec2756ec57caacb64f17a7e5306103f030dfb12dd53b42c72e68b6e60b" + )), + ], + [ + HexBytes::<32>(hex!( + "8ecfab987a8697c58f4b183620b2fa0e11972fa666b71c138e067621ab5d1703" + )), + HexBytes::<32>(hex!( + "2e070ae83ab7f01f91766c2fd6de425dc0f18ae4e34fdcb3ac18db4dfec77a0c" + )), + ], + [ + HexBytes::<32>(hex!( + "187cd1a318666e9f7a9f2f9d4eaf7c662c6162c5bc2be94219992f261f46b90b" + )), + HexBytes::<32>(hex!( + "97ca174ff4bcf1e5d139bf0ad85577b9c6247f9e4782cd69100e683bf2e3f80b" + )), + ], + [ + HexBytes::<32>(hex!( + "28eb6f60cfa35b52cbf74b7e68ce795ebfa0d3db6f00e69677fc98aef963bf05" + )), + HexBytes::<32>(hex!( + "6662186aa949465b7b2174d6da077ab8ffdddb710bdab42386e7d8ae20f1890d" + )), + ], + [ + HexBytes::<32>(hex!( + "577c9cf99480b0633121737756bcc7f4887fc7fdf3a9344c84578886e60d1404" + )), + HexBytes::<32>(hex!( + "2d241b48e63acc39c8c899f7c009fcbc09025ea1211930a338e193d17aed890a" + )), + ], + [ + HexBytes::<32>(hex!( + "7a3f489532743f117999a1b375789cd0863541cae0b8633e8cd4c7dedc740305" + )), + HexBytes::<32>(hex!( + "500c1033ca2b4b47c39e70a1c563553571e0e25a2e1fa984cb5ba08546bc4907" + )), + ], + [ + HexBytes::<32>(hex!( + "82efb453a98454e07e8f4b367ee0db2f957e6222e720a69354fdf910fe5fe803" + )), + HexBytes::<32>(hex!( + "1c3204cf63c8ba3ebd817d603a4e5cadfa6a9af5999648eabff7605b5de8b306" + )), + ], + ], + cc: HexBytes::<32>(hex!( + "8b579f973b9395a175fb2fc1df7d66511166c606903a3c082b63fa831e833b00" + )), + }], + pseudoOuts: vec![HexBytes::<32>(hex!( + "bd6260cafa1afbe44d24cf7c42ac9e2b451424472eb1334b3c042e82196be0d7" + ))], + }), + }; + + test(tx, JSON); + } + + #[test] + fn tx_rct_5() { + const JSON: &str = r#"{"version":2,"unlock_time":0,"vin":[{"key":{"amount":0,"key_offsets":[21656060,186727,69935,9151,6868,5611,37323,11548,1080,2867,1193],"k_image":"2969fedfe8eff9fe1aa29c73ea55e8a9080c25dc565d2703e4d2776991a158bd"}}],"vout":[{"amount":0,"target":{"key":"4a46669165c842dcc4529cb0ca7e34b47073a96d5b29862c9f052a6113ac4db6"}},{"amount":0,"target":{"key":"264b1dcf7eebde1f4eb9ec87eca25dd963d7281ab5efaa5cfa994a4265fd9b4c"}}],"extra":[1,137,144,107,99,61,229,55,205,33,49,82,78,22,98,81,68,252,30,53,33,0,208,38,36,247,66,155,149,65,35,254,12,2,9,1,151,243,81,31,198,0,170,41],"rct_signatures":{"type":5,"txnFee":13210000,"ecdhInfo":[{"amount":"5db75ce558a47531"},{"amount":"0499d909aafd0109"}],"outPk":["70cbcd5105fcb33f29c8f58b7515f98cfdfcbc425239f65eac3804fbee069995","0aba72c6895d733b7cf59d2cf9c4cd7c82dedf23f9424148d63f138291e6b142"]},"rctsig_prunable":{"nbp":1,"bp":[{"A":"86765eb65aac879a755822a69a54dbf69d2d3495439eff917dc01667b72d30f8","S":"1a1e62a9ca8142cafdd8a8b74517d17f2e883d3495b7722e27750fa3fed44d84","T1":"a6513e0191d0561c16f06dda675e9d21a6f7a074dbf1af498530711a4c0a3b8e","T2":"47a1197d264c8becf36fe2e45bebbe9ff86ab7c141dd99db218ba691a412190b","taux":"cc5aa963d74e48c673f5079b0968060db5c408d8ef50ca8cba9fc58f5e11ff06","mu":"869813119eb1c88103d3b396bb1ee604df3c2ecfd7fab9a70da41f9cb95b2309","L":["34d1b4db37ad7d522d273c134a80d08eb6a22c1e009d3ab7db950090d35accdf","e7b41adc55ec0887b1a982f25c11d50a6191aa0e3de7f92ba944b0967b7b0cd5","343b5ad8c7abe7753ddba2fadb3cef36de91a2757167c102c4bb95c3e6778028","c132bb4bab3e60b86637ce2a3a563ecf92635b4a972083adacf6ede475467eb6","3303f34042776e60631352f687a4508b6e0e96ba58e05da825649c0b342527a8","c927d1a85fab1d83e1d3312e4f136e58f13853e529e3d2738d69e7885713a402","8a440a513f9e71d1a1a6357954b9a90123da3cfde7ed50b9cb389f6963090e49"],"R":["60cec37d53635e0f7cfddf7ab7bd4fc092ac69444aa8ebe1029cdac3505e028d","4b4c26bae4ee65f66246f45a83d8f2b4aca580d3ec53bfb62ed0d33e3e80ea60","f1e6aa90b3ae9e72ce487c1728f73a550b5dc41d971a85a90785b922760b0dcd","66e773ab75baa86936bd6653102be3518470f98b1357abb3251da54f273b0e40","792e4c055a4483088698a828e6b35447a4f890ad590d9e37709e53b7a8d63d0e","f6a43739cc3895d297c42179c9cacc31715f675b332a269f7fdf7c3c645f47c3","483a9954d40d1a9ce9082a7e10b8c06fd6e5e925d87dea0d04c4b05c6e74eda7"],"a":"65b1099198e6b20e5d810147bb0f9b4de297da33fb8ffbde3721a600b91ab504","b":"40280b8a652159007d7e36d2a3d4872ae3b7e25617f486a8eeca993d1146c002","t":"aa7d0c7b795de8736e1881fe4b9457cca1e370352c9a2f791d8902292d67de0d"}],"CLSAGs":[{"s":["27c6ca7f8cbdb7d8e6a1e0d3cc0805e48a809b827ccb70a9b297b7e9fd506f04","25212da093e8a866fe693e31022f8f37547cb38643f0a8373ad20032c0d0970a","c41751c335a147960f4daf5d4f18544eab8661e4509e1def78e3c2a08800ab0e","7a82c4e2e693ad5cf288b369ed647847e2b3ada1faab0727331aebce7e026507","690685c5ecab57799fed9067c88c172c466f1ca2ce6768900af0d7d46d474f0a","1891173b4f269dbeb1e13eecd8deecf3ee9bb864476b85a5639257cf6e9f8402","737980e8606d2da628368934c5c05fd2b6c2d43a2b56c5c6c2163b70c0836b06","274a23f3b8baabb020c4e5315174d12049409cae36af0016a0993cdf97957809","de2f2b04ac951975fda136268e60126a6ca53e7cd6cbbff0c9515256d5a1c50f","d5747b07bc733144c8ef9574213731a30d1239596467e25b6aac4427647b1d0c","5fd4c201cfd87e8fb155c1975e02c06c8de1ab49c84c7948e429798a90d52101"],"c1":"0e118c43701bf377e13d9693f6783963d1e6e2a7bff9d75640eb9e1684c26205","D":"deb55a8e4de5b9c84b8d94d63988ce04048497f91bdd3e3878a3f9e7c313e01c"}],"pseudoOuts":["48604572eb550295c16f5fe4282131ed4fc5de297611f813b12e752b6b67865f"]}}"#; + + let tx = Transaction::V2 { + prefix: TransactionPrefix { + version: 2, + unlock_time: 0, + vin: vec![Input { + key: Key { + amount: 0, + key_offsets: vec![ + 21656060, 186727, 69935, 9151, 6868, 5611, 37323, 11548, 1080, 2867, + 1193, + ], + k_image: HexBytes::<32>(hex!( + "2969fedfe8eff9fe1aa29c73ea55e8a9080c25dc565d2703e4d2776991a158bd" + )), + }, + }], + vout: vec![ + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "4a46669165c842dcc4529cb0ca7e34b47073a96d5b29862c9f052a6113ac4db6" + )), + }, + }, + Output { + amount: 0, + target: Target::Key { + key: HexBytes::<32>(hex!( + "264b1dcf7eebde1f4eb9ec87eca25dd963d7281ab5efaa5cfa994a4265fd9b4c" + )), + }, + }, + ], + extra: vec![ + 1, 137, 144, 107, 99, 61, 229, 55, 205, 33, 49, 82, 78, 22, 98, 81, 68, 252, + 30, 53, 33, 0, 208, 38, 36, 247, 66, 155, 149, 65, 35, 254, 12, 2, 9, 1, 151, + 243, 81, 31, 198, 0, 170, 41, + ], + }, + rct_signatures: RctSignatures::NonCoinbase { + r#type: 5, + txnFee: 13210000, + ecdhInfo: vec![ + EcdhInfo::Compact { + amount: HexBytes::<8>(hex!("5db75ce558a47531")), + }, + EcdhInfo::Compact { + amount: HexBytes::<8>(hex!("0499d909aafd0109")), + }, + ], + outPk: vec![ + HexBytes::<32>(hex!( + "70cbcd5105fcb33f29c8f58b7515f98cfdfcbc425239f65eac3804fbee069995" + )), + HexBytes::<32>(hex!( + "0aba72c6895d733b7cf59d2cf9c4cd7c82dedf23f9424148d63f138291e6b142" + )), + ], + }, + rctsig_prunable: Some(RctSigPrunable::ClsagBulletproofs { + nbp: 1, + bp: vec![Bulletproof { + A: HexBytes::<32>(hex!( + "86765eb65aac879a755822a69a54dbf69d2d3495439eff917dc01667b72d30f8" + )), + S: HexBytes::<32>(hex!( + "1a1e62a9ca8142cafdd8a8b74517d17f2e883d3495b7722e27750fa3fed44d84" + )), + T1: HexBytes::<32>(hex!( + "a6513e0191d0561c16f06dda675e9d21a6f7a074dbf1af498530711a4c0a3b8e" + )), + T2: HexBytes::<32>(hex!( + "47a1197d264c8becf36fe2e45bebbe9ff86ab7c141dd99db218ba691a412190b" + )), + taux: HexBytes::<32>(hex!( + "cc5aa963d74e48c673f5079b0968060db5c408d8ef50ca8cba9fc58f5e11ff06" + )), + mu: HexBytes::<32>(hex!( + "869813119eb1c88103d3b396bb1ee604df3c2ecfd7fab9a70da41f9cb95b2309" + )), + L: vec![ + HexBytes::<32>(hex!( + "34d1b4db37ad7d522d273c134a80d08eb6a22c1e009d3ab7db950090d35accdf" + )), + HexBytes::<32>(hex!( + "e7b41adc55ec0887b1a982f25c11d50a6191aa0e3de7f92ba944b0967b7b0cd5" + )), + HexBytes::<32>(hex!( + "343b5ad8c7abe7753ddba2fadb3cef36de91a2757167c102c4bb95c3e6778028" + )), + HexBytes::<32>(hex!( + "c132bb4bab3e60b86637ce2a3a563ecf92635b4a972083adacf6ede475467eb6" + )), + HexBytes::<32>(hex!( + "3303f34042776e60631352f687a4508b6e0e96ba58e05da825649c0b342527a8" + )), + HexBytes::<32>(hex!( + "c927d1a85fab1d83e1d3312e4f136e58f13853e529e3d2738d69e7885713a402" + )), + HexBytes::<32>(hex!( + "8a440a513f9e71d1a1a6357954b9a90123da3cfde7ed50b9cb389f6963090e49" + )), + ], + R: vec![ + HexBytes::<32>(hex!( + "60cec37d53635e0f7cfddf7ab7bd4fc092ac69444aa8ebe1029cdac3505e028d" + )), + HexBytes::<32>(hex!( + "4b4c26bae4ee65f66246f45a83d8f2b4aca580d3ec53bfb62ed0d33e3e80ea60" + )), + HexBytes::<32>(hex!( + "f1e6aa90b3ae9e72ce487c1728f73a550b5dc41d971a85a90785b922760b0dcd" + )), + HexBytes::<32>(hex!( + "66e773ab75baa86936bd6653102be3518470f98b1357abb3251da54f273b0e40" + )), + HexBytes::<32>(hex!( + "792e4c055a4483088698a828e6b35447a4f890ad590d9e37709e53b7a8d63d0e" + )), + HexBytes::<32>(hex!( + "f6a43739cc3895d297c42179c9cacc31715f675b332a269f7fdf7c3c645f47c3" + )), + HexBytes::<32>(hex!( + "483a9954d40d1a9ce9082a7e10b8c06fd6e5e925d87dea0d04c4b05c6e74eda7" + )), + ], + a: HexBytes::<32>(hex!( + "65b1099198e6b20e5d810147bb0f9b4de297da33fb8ffbde3721a600b91ab504" + )), + b: HexBytes::<32>(hex!( + "40280b8a652159007d7e36d2a3d4872ae3b7e25617f486a8eeca993d1146c002" + )), + t: HexBytes::<32>(hex!( + "aa7d0c7b795de8736e1881fe4b9457cca1e370352c9a2f791d8902292d67de0d" + )), + }], + CLSAGs: vec![Clsag { + s: vec![ + HexBytes::<32>(hex!( + "27c6ca7f8cbdb7d8e6a1e0d3cc0805e48a809b827ccb70a9b297b7e9fd506f04" + )), + HexBytes::<32>(hex!( + "25212da093e8a866fe693e31022f8f37547cb38643f0a8373ad20032c0d0970a" + )), + HexBytes::<32>(hex!( + "c41751c335a147960f4daf5d4f18544eab8661e4509e1def78e3c2a08800ab0e" + )), + HexBytes::<32>(hex!( + "7a82c4e2e693ad5cf288b369ed647847e2b3ada1faab0727331aebce7e026507" + )), + HexBytes::<32>(hex!( + "690685c5ecab57799fed9067c88c172c466f1ca2ce6768900af0d7d46d474f0a" + )), + HexBytes::<32>(hex!( + "1891173b4f269dbeb1e13eecd8deecf3ee9bb864476b85a5639257cf6e9f8402" + )), + HexBytes::<32>(hex!( + "737980e8606d2da628368934c5c05fd2b6c2d43a2b56c5c6c2163b70c0836b06" + )), + HexBytes::<32>(hex!( + "274a23f3b8baabb020c4e5315174d12049409cae36af0016a0993cdf97957809" + )), + HexBytes::<32>(hex!( + "de2f2b04ac951975fda136268e60126a6ca53e7cd6cbbff0c9515256d5a1c50f" + )), + HexBytes::<32>(hex!( + "d5747b07bc733144c8ef9574213731a30d1239596467e25b6aac4427647b1d0c" + )), + HexBytes::<32>(hex!( + "5fd4c201cfd87e8fb155c1975e02c06c8de1ab49c84c7948e429798a90d52101" + )), + ], + c1: HexBytes::<32>(hex!( + "0e118c43701bf377e13d9693f6783963d1e6e2a7bff9d75640eb9e1684c26205" + )), + D: HexBytes::<32>(hex!( + "deb55a8e4de5b9c84b8d94d63988ce04048497f91bdd3e3878a3f9e7c313e01c" + )), + }], + pseudoOuts: vec![HexBytes::<32>(hex!( + "48604572eb550295c16f5fe4282131ed4fc5de297611f813b12e752b6b67865f" + ))], + }), + }; + + test(tx, JSON); + } + + #[test] + fn tx_rct_6() { + const JSON: &str = r#"{"version":2,"unlock_time":0,"vin":[{"key":{"amount":0,"key_offsets":[56619444,517411,383964,1514827,38358,263974,91303,3018,14681,34540,7767,8131,20234,16575,18300,3587],"k_image":"ec1636db12f12cffa66e8e3286d8216ad7900128c996ffcc96196856daf10585"}},{"key":{"amount":0,"key_offsets":[49738606,2766321,6291275,92656,166783,91733,286477,1130,5724,9633,44284,24605,8133,20600,9906,2115],"k_image":"953c1d93684671eb658284061b6f7724f37c68c3bc24732fb81a09f7056426d0"}},{"key":{"amount":0,"key_offsets":[2971790,44215494,8487702,3226387,861,158991,281736,74021,24277,10705,51824,25824,4951,1235,7824,15715],"k_image":"41a34e8637c3974c9a0444f9c45b361775cc178e4d7d8e07e7d4afdc8e591675"}},{"key":{"amount":0,"key_offsets":[57701146,641169,170653,321459,625073,40514,6448,5687,13246,14743,7359,1788,1054,1061,4460,4059],"k_image":"2d57a890ff948dd7f0ba17940b6b76db2c87163322f0bd5aca29462f9224c777"}}],"vout":[{"amount":0,"target":{"tagged_key":{"key":"570482299e724f78b8441d700aa63388a842c7f5dbcbe5fa859c2c0abad96b30","view_tag":"9f"}}},{"amount":0,"target":{"tagged_key":{"key":"50c1a24ef57aeba07beecd8ddbf294e2501d6fa90ad9712829c00b7293eead96","view_tag":"06"}}}],"extra":[1,254,81,251,73,229,142,177,14,82,43,62,58,255,63,24,202,118,195,65,161,185,96,142,214,48,255,145,202,52,3,199,202,2,9,1,53,17,236,142,199,122,102,77],"rct_signatures":{"type":6,"txnFee":71860000,"ecdhInfo":[{"amount":"b0af37c16a8f08a0"},{"amount":"4cc0843dec9af6b4"}],"outPk":["3c51d83c816a0cb8585641a165e866e0215302af9b498db762db27141e673e15","96eba06bfd2781e65e9a1b1506abfd9ae29dc60fcd29007cd6ad94a8abbf1ecc"]},"rctsig_prunable":{"nbp":1,"bpp":[{"A":"28197d8ac07948082b50274fb8d5bea0f81561d02c88981e0d9b5ffd6e5ee169","A1":"efe6eda671d68a894e1b8aff4a1992f85c4269e17196916cfcdf8519cc94d35e","B":"7e374ac72276298148217d99568c3d4e09f2442864b5be228cd2d01328abe2d2","r1":"a2e06c25825774e5a130944c6c3eaa3c8afea2ca7d2c09e024615ff700be240a","s1":"6ee7e6624941d1e9ea18024f39a9a694ac798fb69084e10bf982d6a58d416c0a","d1":"d30bea1ffb8e79d0fe37d60c55f6e654d1ca388b102b29a6b28c48c2c617b70f","L":["cf6e067c87b9161c424620e83645f13284e64c803b9d7af729d0550d475d2199","159a03db0d038f6691816d9c31b52a325ad13941222ce1791a0285ca0cf0169d","f0276445ea2ec07957fa58675c89aec4dab57d163290e95845dccd484c3e1790","40c19df50385f55b4d53fc101c8eef7d411b76c8b94eadbf464d1401d171ea0a","6b9a8da4219da8f3e944351825eaf66e99ea954ed0e3b4eed0782379f8fd5509","567d12ccd952444055c9a595024f1229a8e0d3ad816f6fd28a448f021603bcc1","44616a4203c430653b12e5a2504e79ea390719a1d6a9557eeb55067ba7efc9d3"],"R":["a7dd6caebda761e8c2ca07e65f9f5b5868777bdc9a4af810d55c691ee62922aa","e8db14727596359e8b2e52bd55ceea81e102028d979f79d464d8afe3fd183de3","0f808f768cec8fe9f5e41d5925eb5c4955a2c16f650ba26e8cf7be2246b4e559","4931dd8eb664e60d86ff259b0e8af24329aefd550170683c324bf8e68ca97169","ce05c6ddb73f36dcd5d94cd6a92220c5c42df15f649d4029c9046fb8a8bf4003","ae2d51efb12a81062c7a6c9d2c4cdb4b6d7561f76e6f8aa554d98717716b8dda","ab4a29f9efa41472ae9dfb13d80c929d904a2fbc72a9d2bce063b19baf3bbdbe"]}],"CLSAGs":[{"s":["fa3c832924a4716bac410b9708ac11ed35d9cb01f3e0c1d3958e77791f9ce905","6b4dfe306de3f55c5507d802347f4c94ae55e0db4f3bf25e1af3ba1ecd993e0d","71c7c612a3dd9d123609df658aaff494787b5cabb5624d5c5d519120f29f5407","d72c30a667f22dbc5bbc8479a4e70094bff1980eb962f3f5ce43954da9a5b009","869470794715faa72ec2cbbb78743448f9dc5bb6383ac2030484adbb760e7a09","6247f181b491a4da82cadbca6272b58365e9160030ed92a1ac5641f9d4163b06","9269814384a16ff2bd297fbce5a614ed67529551ba0c21a26abdaff55c96870a","b10aeaac7f08f1782a2eb4094864f26fcb6c43559b7610ccd7809b90b1c4f003","f38ce2ac13fcdee7be79d0bd98bc17f3df4b1c266a45e1fede7582b12e3a3c0d","1b9f3aee12c9fd4e5aae9cf64bd65f0ad20dbc779f472db0bd338229638a6401","a04b7e6791b7ea10af2a8b0ff2dbfe63fb6036beed0bd09e9319d4728e33130b","a0cd570e0cb80e0fc111468add13b0fc0d8eb4df6942ce3caafedb6c9eee0f07","14b38cbfb7012d1c96a25ea5dcb9bfdfb1a92ffe727dd7a1cb332a9bd630d10f","5f9be3bc2f667e41baaad111e34ac14eefa493b565c4be4ab6eeab286903870b","549bc3275bafd26ab4b89ba14b43976dd317d8d344e37ccbd5a20351a084e005","a93847d26171a9194cfa5a94d7f40576b2e808b4bde927e3398bb0a6e9ad0f0e"],"c1":"794f4e50841235043b39fbcb5b50babf5c4b98339fec9538c2538644ac104f01","D":"6d50f7b691c0bc174aa11d9953b3452b0f249aa4e7edd560ff1e5772f592a618"},{"s":["e8140f6e69981917d78656f0b1572ff748771f3106f6134cca77ae83bc2ff201","7970c1856b630f213e52c825c507f331145c55104611a908c91998dcc76dd40f","8b6899f8eef5bb4c0c830fbb53e34b6089215e0c18b174648fe482675eb0740e","8ff4173d836bddc7fd430b0e2cd7d377f9a81025ebdee6030c19114b7964dc05","8f14171c429fbf9bd4aa5fe67d180e99a6092f8a7e27a97e6fd85c02613a0209","9208e8cc2fd66d6c154202c42bde24f14917b79ccc1b2f31d54880fa14c51202","11da8c69a612d2090b375efb61f9be10a16d6ac3d95e8777cb4d3b0cce028509","f0b097956d07aaf27a4d19463b63bed427b4945321f203be856a1c45e058ed0e","0ad2af34567c40ea4166cd49c368a9ac3bac132c2507f444657de8152465ff0c","ded4f3f699c692d01999b858cb752bb6691418393543fa0b1805115847be8f04","6ef1fa94a6448488fdc4fdc629813940f78706f9045f48b8d69ce717510b7b0e","fbc95294de167bb8a431ff2eacec66637a49927eb15bb443f0ec7c909e301a06","03eec8ccae4fd9942376e3df671ed3de9743b03331ee518a69e0f8fb09644e0e","861c4a794793dd3eaedd1770263124530035378121bde995b27cbf50bfeb0d08","043d02997ff017b110590482dba8a69d2a97092ef7640b8cba5d4271ffc67e04","23f12cabd4d7d69a1c6c6cb960b82a427a0ad2df276786312e2b1c60cb17de06"],"c1":"c0f8e42ef1b220d60fa76532b84dd5def896a25b62eec08691ca438c6abcc40d","D":"9d0844d4ac7c5988e336514ba17841a3bd1aefd5fa024b692ccd6ef6e9364e34"},{"s":["bf3a319fd83070d2c97049e1e2d4636f498a5c0547e5e0144f9eb58247f9a50d","70b626b51f5e934fad8f6df0db79c3d2af88b6a93e3fcf872d64e89d1909b10b","71b200df8b8c5132ba533f1f18924114d451d2b9cca454ea36b7e4b559962307","99cc6995a942ad4e9f993d980a077113d46da70f003539190c5bb9ffb4f6310f","4dac904bc896e0f8690353924bc98f0baf2d3a2e39da272fd34444664eede404","158c1087ae06422bd71a0b59ff7e8f2caa6bbc651b4d0e105551bf91a51f2002","e4d119f8c6d39a02b06aca1627078c37b962463d733a4b25d3b6410bdaad6f0f","16d5e70dc9bd9f8e9d8d74d75db0bf3a144952d7eaab3abc78ce7c66cb958d06","3a0ee94b516a8596bd718ffd87efb76e10b61904033fd0225543680064c5120e","354d44ea658710784c4b3389d4048399302e4d7bfa676ea3de53feba2012e30b","ce00bbc38aa3e018f1231972232a076f42d38e6d75dececee6561c6336c4be00","85094c21f620b87e976f42b742449a048eb303597b1ef362c1a44f76f8d9fa08","8e88e960c771bdd2b3df0e0fddbc0cd0a692807d8432c54d6b6ad2114007d10f","976274603af385a4079a970a5ddba77a01ac7411e9b2303e76207b288830a107","a7f760605b4dffb5b76943e8097b11fb4f2db2fea6354cffc2b96c21aef7a300","7e378e64b7a3ece77d88d966e386e939f56976109ad395b4712cf916f50b4c01"],"c1":"edecc915049e5ead7e5fe36dd70c558ace09f4d3a0c6216be148a51e3a72e302","D":"197665d3b405f42a2053f9e946483435e75d6c4e801427bfeb66cc58c72e2670"},{"s":["20c7f0d492ecf79f1d29305f4e8387238a5927fe676674fe479c129431841607","b9b98379560d7e22a09fcc72db5b1d05870ffdbded5cf560fcaf5303033f7d04","8fc79c2b767ea73f7f552f48d0603b5ee369cdd9535ca06f03fd11e16f08ea0b","7e2bdb348f8a719ffed9d995a35d83ae93a63abe1090fae68a3d23ae47c64402","aa0f6221cc1454b4dbf84b7f8c6e7b89a1c2a3d0f56a2d6302106e47b6b1b50d","08a9283d8b34426eb7b7547fa8fb1573430b99f1c119f2ff9612e82acee98e03","250d2ac44e26782f293eca3deb70fc5c52cb942166b1efb2f78ec32640e02d06","1bc1bcc3de357a4652c03815e59e14cb13668946366746dca3dad2f4c44c9000","9f8b446e373e3e19871f22b9bc95017d4411e555477afb34114b428c8296470a","e49d0313e969fb8c4e451388309280a96b8e3216fa1e28ab2efab49f38e86f07","0cee07c99293507ad558838f2fa07af1c4ddc86886658c6207c1f25f343afb06","39bd17be3aaaeda4fb8aa8dafcf5748581f7bb8b92b0dfe3add14a8481570003","0048e1ca905806551cd210c40356cc80935a98f63163a087ea89585915e8770d","3c46eea5308dbdff7376d89378998376cb722d08604d6ecb2b3cb795f91dc607","7d13be56b2e858d2fca81b3a6b0312943d33e501b4e09814818edb96fb28aa0c","313a2021350abd25bd79c22ea33fe071367da2e208913d788d50101c90f0e702"],"c1":"9d96220cd0d49340e06b915f7204cd1f68c4c2069389bf4c011b4fa6c24c0d02","D":"91d00727ba3655729ce88981e803967946403e970f0a6749625f59d4e6d7ebc9"}],"pseudoOuts":["a6785a3aca529db1da40944bb1826519d7caaa31f4549e6854cb97e5234d3e8e","f5cda4db5f83f1c1edea0b66461d1848daf01054c24a690e1438add59dc4f206","dff30968b66355b9c7890db508692e9620c999e0025ca9395fa53732e6432606","6b78d37b63714ebe1d09981766c61a07bf0bfbc9fc7f7a8998396aa99d43e0cc"]}}"#; + + let tx = Transaction::V2 { + prefix: TransactionPrefix { + version: 2, + unlock_time: 0, + vin: vec![ + Input { + key: Key { + amount: 0, + key_offsets: vec![ + 56619444, 517411, 383964, 1514827, 38358, 263974, 91303, 3018, + 14681, 34540, 7767, 8131, 20234, 16575, 18300, 3587, + ], + k_image: HexBytes::<32>(hex!( + "ec1636db12f12cffa66e8e3286d8216ad7900128c996ffcc96196856daf10585" + )), + }, + }, + Input { + key: Key { + amount: 0, + key_offsets: vec![ + 49738606, 2766321, 6291275, 92656, 166783, 91733, 286477, 1130, + 5724, 9633, 44284, 24605, 8133, 20600, 9906, 2115, + ], + k_image: HexBytes::<32>(hex!( + "953c1d93684671eb658284061b6f7724f37c68c3bc24732fb81a09f7056426d0" + )), + }, + }, + Input { + key: Key { + amount: 0, + key_offsets: vec![ + 2971790, 44215494, 8487702, 3226387, 861, 158991, 281736, 74021, + 24277, 10705, 51824, 25824, 4951, 1235, 7824, 15715, + ], + k_image: HexBytes::<32>(hex!( + "41a34e8637c3974c9a0444f9c45b361775cc178e4d7d8e07e7d4afdc8e591675" + )), + }, + }, + Input { + key: Key { + amount: 0, + key_offsets: vec![ + 57701146, 641169, 170653, 321459, 625073, 40514, 6448, 5687, 13246, + 14743, 7359, 1788, 1054, 1061, 4460, 4059, + ], + k_image: HexBytes::<32>(hex!( + "2d57a890ff948dd7f0ba17940b6b76db2c87163322f0bd5aca29462f9224c777" + )), + }, + }, + ], + vout: vec![ + Output { + amount: 0, + target: Target::TaggedKey { + tagged_key: TaggedKey { + key: HexBytes::<32>(hex!( + "570482299e724f78b8441d700aa63388a842c7f5dbcbe5fa859c2c0abad96b30" + )), + view_tag: HexBytes::<1>(hex!("9f")), + }, + }, + }, + Output { + amount: 0, + target: Target::TaggedKey { + tagged_key: TaggedKey { + key: HexBytes::<32>(hex!( + "50c1a24ef57aeba07beecd8ddbf294e2501d6fa90ad9712829c00b7293eead96" + )), + view_tag: HexBytes::<1>(hex!("06")), + }, + }, + }, + ], + extra: vec![ + 1, 254, 81, 251, 73, 229, 142, 177, 14, 82, 43, 62, 58, 255, 63, 24, 202, 118, + 195, 65, 161, 185, 96, 142, 214, 48, 255, 145, 202, 52, 3, 199, 202, 2, 9, 1, + 53, 17, 236, 142, 199, 122, 102, 77, + ], + }, + rct_signatures: RctSignatures::NonCoinbase { + r#type: 6, + txnFee: 71860000, + ecdhInfo: vec![ + EcdhInfo::Compact { + amount: HexBytes::<8>(hex!("b0af37c16a8f08a0")), + }, + EcdhInfo::Compact { + amount: HexBytes::<8>(hex!("4cc0843dec9af6b4")), + }, + ], + outPk: vec![ + HexBytes::<32>(hex!( + "3c51d83c816a0cb8585641a165e866e0215302af9b498db762db27141e673e15" + )), + HexBytes::<32>(hex!( + "96eba06bfd2781e65e9a1b1506abfd9ae29dc60fcd29007cd6ad94a8abbf1ecc" + )), + ], + }, + rctsig_prunable: Some(RctSigPrunable::ClsagBulletproofsPlus { + nbp: 1, + bpp: vec![BulletproofPlus { + A: HexBytes::<32>(hex!( + "28197d8ac07948082b50274fb8d5bea0f81561d02c88981e0d9b5ffd6e5ee169" + )), + A1: HexBytes::<32>(hex!( + "efe6eda671d68a894e1b8aff4a1992f85c4269e17196916cfcdf8519cc94d35e" + )), + B: HexBytes::<32>(hex!( + "7e374ac72276298148217d99568c3d4e09f2442864b5be228cd2d01328abe2d2" + )), + r1: HexBytes::<32>(hex!( + "a2e06c25825774e5a130944c6c3eaa3c8afea2ca7d2c09e024615ff700be240a" + )), + s1: HexBytes::<32>(hex!( + "6ee7e6624941d1e9ea18024f39a9a694ac798fb69084e10bf982d6a58d416c0a" + )), + d1: HexBytes::<32>(hex!( + "d30bea1ffb8e79d0fe37d60c55f6e654d1ca388b102b29a6b28c48c2c617b70f" + )), + L: vec![ + HexBytes::<32>(hex!( + "cf6e067c87b9161c424620e83645f13284e64c803b9d7af729d0550d475d2199" + )), + HexBytes::<32>(hex!( + "159a03db0d038f6691816d9c31b52a325ad13941222ce1791a0285ca0cf0169d" + )), + HexBytes::<32>(hex!( + "f0276445ea2ec07957fa58675c89aec4dab57d163290e95845dccd484c3e1790" + )), + HexBytes::<32>(hex!( + "40c19df50385f55b4d53fc101c8eef7d411b76c8b94eadbf464d1401d171ea0a" + )), + HexBytes::<32>(hex!( + "6b9a8da4219da8f3e944351825eaf66e99ea954ed0e3b4eed0782379f8fd5509" + )), + HexBytes::<32>(hex!( + "567d12ccd952444055c9a595024f1229a8e0d3ad816f6fd28a448f021603bcc1" + )), + HexBytes::<32>(hex!( + "44616a4203c430653b12e5a2504e79ea390719a1d6a9557eeb55067ba7efc9d3" + )), + ], + R: vec![ + HexBytes::<32>(hex!( + "a7dd6caebda761e8c2ca07e65f9f5b5868777bdc9a4af810d55c691ee62922aa" + )), + HexBytes::<32>(hex!( + "e8db14727596359e8b2e52bd55ceea81e102028d979f79d464d8afe3fd183de3" + )), + HexBytes::<32>(hex!( + "0f808f768cec8fe9f5e41d5925eb5c4955a2c16f650ba26e8cf7be2246b4e559" + )), + HexBytes::<32>(hex!( + "4931dd8eb664e60d86ff259b0e8af24329aefd550170683c324bf8e68ca97169" + )), + HexBytes::<32>(hex!( + "ce05c6ddb73f36dcd5d94cd6a92220c5c42df15f649d4029c9046fb8a8bf4003" + )), + HexBytes::<32>(hex!( + "ae2d51efb12a81062c7a6c9d2c4cdb4b6d7561f76e6f8aa554d98717716b8dda" + )), + HexBytes::<32>(hex!( + "ab4a29f9efa41472ae9dfb13d80c929d904a2fbc72a9d2bce063b19baf3bbdbe" + )), + ], + }], + CLSAGs: vec![ + Clsag { + s: vec![ + HexBytes::<32>(hex!( + "fa3c832924a4716bac410b9708ac11ed35d9cb01f3e0c1d3958e77791f9ce905" + )), + HexBytes::<32>(hex!( + "6b4dfe306de3f55c5507d802347f4c94ae55e0db4f3bf25e1af3ba1ecd993e0d" + )), + HexBytes::<32>(hex!( + "71c7c612a3dd9d123609df658aaff494787b5cabb5624d5c5d519120f29f5407" + )), + HexBytes::<32>(hex!( + "d72c30a667f22dbc5bbc8479a4e70094bff1980eb962f3f5ce43954da9a5b009" + )), + HexBytes::<32>(hex!( + "869470794715faa72ec2cbbb78743448f9dc5bb6383ac2030484adbb760e7a09" + )), + HexBytes::<32>(hex!( + "6247f181b491a4da82cadbca6272b58365e9160030ed92a1ac5641f9d4163b06" + )), + HexBytes::<32>(hex!( + "9269814384a16ff2bd297fbce5a614ed67529551ba0c21a26abdaff55c96870a" + )), + HexBytes::<32>(hex!( + "b10aeaac7f08f1782a2eb4094864f26fcb6c43559b7610ccd7809b90b1c4f003" + )), + HexBytes::<32>(hex!( + "f38ce2ac13fcdee7be79d0bd98bc17f3df4b1c266a45e1fede7582b12e3a3c0d" + )), + HexBytes::<32>(hex!( + "1b9f3aee12c9fd4e5aae9cf64bd65f0ad20dbc779f472db0bd338229638a6401" + )), + HexBytes::<32>(hex!( + "a04b7e6791b7ea10af2a8b0ff2dbfe63fb6036beed0bd09e9319d4728e33130b" + )), + HexBytes::<32>(hex!( + "a0cd570e0cb80e0fc111468add13b0fc0d8eb4df6942ce3caafedb6c9eee0f07" + )), + HexBytes::<32>(hex!( + "14b38cbfb7012d1c96a25ea5dcb9bfdfb1a92ffe727dd7a1cb332a9bd630d10f" + )), + HexBytes::<32>(hex!( + "5f9be3bc2f667e41baaad111e34ac14eefa493b565c4be4ab6eeab286903870b" + )), + HexBytes::<32>(hex!( + "549bc3275bafd26ab4b89ba14b43976dd317d8d344e37ccbd5a20351a084e005" + )), + HexBytes::<32>(hex!( + "a93847d26171a9194cfa5a94d7f40576b2e808b4bde927e3398bb0a6e9ad0f0e" + )), + ], + c1: HexBytes::<32>(hex!( + "794f4e50841235043b39fbcb5b50babf5c4b98339fec9538c2538644ac104f01" + )), + D: HexBytes::<32>(hex!( + "6d50f7b691c0bc174aa11d9953b3452b0f249aa4e7edd560ff1e5772f592a618" + )), + }, + Clsag { + s: vec![ + HexBytes::<32>(hex!( + "e8140f6e69981917d78656f0b1572ff748771f3106f6134cca77ae83bc2ff201" + )), + HexBytes::<32>(hex!( + "7970c1856b630f213e52c825c507f331145c55104611a908c91998dcc76dd40f" + )), + HexBytes::<32>(hex!( + "8b6899f8eef5bb4c0c830fbb53e34b6089215e0c18b174648fe482675eb0740e" + )), + HexBytes::<32>(hex!( + "8ff4173d836bddc7fd430b0e2cd7d377f9a81025ebdee6030c19114b7964dc05" + )), + HexBytes::<32>(hex!( + "8f14171c429fbf9bd4aa5fe67d180e99a6092f8a7e27a97e6fd85c02613a0209" + )), + HexBytes::<32>(hex!( + "9208e8cc2fd66d6c154202c42bde24f14917b79ccc1b2f31d54880fa14c51202" + )), + HexBytes::<32>(hex!( + "11da8c69a612d2090b375efb61f9be10a16d6ac3d95e8777cb4d3b0cce028509" + )), + HexBytes::<32>(hex!( + "f0b097956d07aaf27a4d19463b63bed427b4945321f203be856a1c45e058ed0e" + )), + HexBytes::<32>(hex!( + "0ad2af34567c40ea4166cd49c368a9ac3bac132c2507f444657de8152465ff0c" + )), + HexBytes::<32>(hex!( + "ded4f3f699c692d01999b858cb752bb6691418393543fa0b1805115847be8f04" + )), + HexBytes::<32>(hex!( + "6ef1fa94a6448488fdc4fdc629813940f78706f9045f48b8d69ce717510b7b0e" + )), + HexBytes::<32>(hex!( + "fbc95294de167bb8a431ff2eacec66637a49927eb15bb443f0ec7c909e301a06" + )), + HexBytes::<32>(hex!( + "03eec8ccae4fd9942376e3df671ed3de9743b03331ee518a69e0f8fb09644e0e" + )), + HexBytes::<32>(hex!( + "861c4a794793dd3eaedd1770263124530035378121bde995b27cbf50bfeb0d08" + )), + HexBytes::<32>(hex!( + "043d02997ff017b110590482dba8a69d2a97092ef7640b8cba5d4271ffc67e04" + )), + HexBytes::<32>(hex!( + "23f12cabd4d7d69a1c6c6cb960b82a427a0ad2df276786312e2b1c60cb17de06" + )), + ], + c1: HexBytes::<32>(hex!( + "c0f8e42ef1b220d60fa76532b84dd5def896a25b62eec08691ca438c6abcc40d" + )), + D: HexBytes::<32>(hex!( + "9d0844d4ac7c5988e336514ba17841a3bd1aefd5fa024b692ccd6ef6e9364e34" + )), + }, + Clsag { + s: vec![ + HexBytes::<32>(hex!( + "bf3a319fd83070d2c97049e1e2d4636f498a5c0547e5e0144f9eb58247f9a50d" + )), + HexBytes::<32>(hex!( + "70b626b51f5e934fad8f6df0db79c3d2af88b6a93e3fcf872d64e89d1909b10b" + )), + HexBytes::<32>(hex!( + "71b200df8b8c5132ba533f1f18924114d451d2b9cca454ea36b7e4b559962307" + )), + HexBytes::<32>(hex!( + "99cc6995a942ad4e9f993d980a077113d46da70f003539190c5bb9ffb4f6310f" + )), + HexBytes::<32>(hex!( + "4dac904bc896e0f8690353924bc98f0baf2d3a2e39da272fd34444664eede404" + )), + HexBytes::<32>(hex!( + "158c1087ae06422bd71a0b59ff7e8f2caa6bbc651b4d0e105551bf91a51f2002" + )), + HexBytes::<32>(hex!( + "e4d119f8c6d39a02b06aca1627078c37b962463d733a4b25d3b6410bdaad6f0f" + )), + HexBytes::<32>(hex!( + "16d5e70dc9bd9f8e9d8d74d75db0bf3a144952d7eaab3abc78ce7c66cb958d06" + )), + HexBytes::<32>(hex!( + "3a0ee94b516a8596bd718ffd87efb76e10b61904033fd0225543680064c5120e" + )), + HexBytes::<32>(hex!( + "354d44ea658710784c4b3389d4048399302e4d7bfa676ea3de53feba2012e30b" + )), + HexBytes::<32>(hex!( + "ce00bbc38aa3e018f1231972232a076f42d38e6d75dececee6561c6336c4be00" + )), + HexBytes::<32>(hex!( + "85094c21f620b87e976f42b742449a048eb303597b1ef362c1a44f76f8d9fa08" + )), + HexBytes::<32>(hex!( + "8e88e960c771bdd2b3df0e0fddbc0cd0a692807d8432c54d6b6ad2114007d10f" + )), + HexBytes::<32>(hex!( + "976274603af385a4079a970a5ddba77a01ac7411e9b2303e76207b288830a107" + )), + HexBytes::<32>(hex!( + "a7f760605b4dffb5b76943e8097b11fb4f2db2fea6354cffc2b96c21aef7a300" + )), + HexBytes::<32>(hex!( + "7e378e64b7a3ece77d88d966e386e939f56976109ad395b4712cf916f50b4c01" + )), + ], + c1: HexBytes::<32>(hex!( + "edecc915049e5ead7e5fe36dd70c558ace09f4d3a0c6216be148a51e3a72e302" + )), + D: HexBytes::<32>(hex!( + "197665d3b405f42a2053f9e946483435e75d6c4e801427bfeb66cc58c72e2670" + )), + }, + Clsag { + s: vec![ + HexBytes::<32>(hex!( + "20c7f0d492ecf79f1d29305f4e8387238a5927fe676674fe479c129431841607" + )), + HexBytes::<32>(hex!( + "b9b98379560d7e22a09fcc72db5b1d05870ffdbded5cf560fcaf5303033f7d04" + )), + HexBytes::<32>(hex!( + "8fc79c2b767ea73f7f552f48d0603b5ee369cdd9535ca06f03fd11e16f08ea0b" + )), + HexBytes::<32>(hex!( + "7e2bdb348f8a719ffed9d995a35d83ae93a63abe1090fae68a3d23ae47c64402" + )), + HexBytes::<32>(hex!( + "aa0f6221cc1454b4dbf84b7f8c6e7b89a1c2a3d0f56a2d6302106e47b6b1b50d" + )), + HexBytes::<32>(hex!( + "08a9283d8b34426eb7b7547fa8fb1573430b99f1c119f2ff9612e82acee98e03" + )), + HexBytes::<32>(hex!( + "250d2ac44e26782f293eca3deb70fc5c52cb942166b1efb2f78ec32640e02d06" + )), + HexBytes::<32>(hex!( + "1bc1bcc3de357a4652c03815e59e14cb13668946366746dca3dad2f4c44c9000" + )), + HexBytes::<32>(hex!( + "9f8b446e373e3e19871f22b9bc95017d4411e555477afb34114b428c8296470a" + )), + HexBytes::<32>(hex!( + "e49d0313e969fb8c4e451388309280a96b8e3216fa1e28ab2efab49f38e86f07" + )), + HexBytes::<32>(hex!( + "0cee07c99293507ad558838f2fa07af1c4ddc86886658c6207c1f25f343afb06" + )), + HexBytes::<32>(hex!( + "39bd17be3aaaeda4fb8aa8dafcf5748581f7bb8b92b0dfe3add14a8481570003" + )), + HexBytes::<32>(hex!( + "0048e1ca905806551cd210c40356cc80935a98f63163a087ea89585915e8770d" + )), + HexBytes::<32>(hex!( + "3c46eea5308dbdff7376d89378998376cb722d08604d6ecb2b3cb795f91dc607" + )), + HexBytes::<32>(hex!( + "7d13be56b2e858d2fca81b3a6b0312943d33e501b4e09814818edb96fb28aa0c" + )), + HexBytes::<32>(hex!( + "313a2021350abd25bd79c22ea33fe071367da2e208913d788d50101c90f0e702" + )), + ], + c1: HexBytes::<32>(hex!( + "9d96220cd0d49340e06b915f7204cd1f68c4c2069389bf4c011b4fa6c24c0d02" + )), + D: HexBytes::<32>(hex!( + "91d00727ba3655729ce88981e803967946403e970f0a6749625f59d4e6d7ebc9" + )), + }, + ], + pseudoOuts: vec![ + HexBytes::<32>(hex!( + "a6785a3aca529db1da40944bb1826519d7caaa31f4549e6854cb97e5234d3e8e" + )), + HexBytes::<32>(hex!( + "f5cda4db5f83f1c1edea0b66461d1848daf01054c24a690e1438add59dc4f206" + )), + HexBytes::<32>(hex!( + "dff30968b66355b9c7890db508692e9620c999e0025ca9395fa53732e6432606" + )), + HexBytes::<32>(hex!( + "6b78d37b63714ebe1d09981766c61a07bf0bfbc9fc7f7a8998396aa99d43e0cc" + )), + ], + }), + }; + + test(tx, JSON); + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs index 2d161f7..7aaf0b9 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1,83 +1,6 @@ -//! Cuprate shared data types. -//! -//! This crate is a kitchen-sink for data types that are shared across `Cuprate`. -//! -//! # Features flags -//! The [`blockchain`] module, containing the blockchain database request/response -//! types, must be enabled with the `blockchain` feature (on by default). - -//---------------------------------------------------------------------------------------------------- Lints -// Forbid lints. -// Our code, and code generated (e.g macros) cannot overrule these. -#![forbid( - // `unsafe` is allowed but it _must_ be - // commented with `SAFETY: reason`. - clippy::undocumented_unsafe_blocks, - - // Never. - unused_unsafe, - redundant_semicolons, - unused_allocation, - coherence_leak_check, - single_use_lifetimes, - while_true, - clippy::missing_docs_in_private_items, - - // Maybe can be put into `#[deny]`. - unconditional_recursion, - for_loops_over_fallibles, - unused_braces, - unused_doc_comments, - unused_labels, - keyword_idents, - non_ascii_idents, - variant_size_differences, - - // Probably can be put into `#[deny]`. - future_incompatible, - let_underscore, - break_with_label_and_loop, - duplicate_macro_attributes, - exported_private_dependencies, - large_assignments, - overlapping_range_endpoints, - semicolon_in_expressions_from_macros, - noop_method_call, - unreachable_pub, -)] -// Deny lints. -// Some of these are `#[allow]`'ed on a per-case basis. -#![deny( - clippy::all, - clippy::correctness, - clippy::suspicious, - clippy::style, - clippy::complexity, - clippy::perf, - clippy::pedantic, - clippy::nursery, - clippy::cargo, - unused_mut, - missing_docs, - deprecated, - unused_comparisons, - nonstandard_style -)] -#![allow( - // FIXME: this lint affects crates outside of - // `database/` for some reason, allow for now. - clippy::cargo_common_metadata, - - // FIXME: adding `#[must_use]` onto everything - // might just be more annoying than useful... - // although it is sometimes nice. - clippy::must_use_candidate, - - clippy::module_name_repetitions, - clippy::module_inception, - clippy::redundant_pub_crate, - clippy::option_if_let_else, -)] +#![doc = include_str!("../README.md")] +// `proptest` needs this internally. +#![cfg_attr(any(feature = "proptest"), allow(non_local_definitions))] // Allow some lints when running in debug mode. #![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] @@ -86,13 +9,35 @@ // // Documentation for each module is located in the respective file. +mod address_type; +mod block_complete_entry; +mod connection_state; +mod hard_fork; +mod transaction_verification_data; mod types; + +pub use address_type::AddressType; +pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; +pub use connection_state::ConnectionState; +pub use hard_fork::{HardFork, HardForkError}; +pub use transaction_verification_data::{ + CachedVerificationState, TransactionVerificationData, TxVersion, +}; pub use types::{ - ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation, VerifiedTransactionInformation, + AddAuxPow, AltBlockInformation, AuxPow, Chain, ChainId, ChainInfo, CoinbaseTxSum, + ExtendedBlockHeader, FeeEstimate, HardForkInfo, MinerData, MinerDataTxBacklogEntry, + OutputHistogramEntry, OutputHistogramInput, OutputOnChain, TxsInBlock, + VerifiedBlockInformation, VerifiedTransactionInformation, }; //---------------------------------------------------------------------------------------------------- Feature-gated #[cfg(feature = "blockchain")] pub mod blockchain; +#[cfg(feature = "json")] +pub mod json; + +#[cfg(feature = "hex")] +pub mod hex; + //---------------------------------------------------------------------------------------------------- Private diff --git a/types/src/transaction_verification_data.rs b/types/src/transaction_verification_data.rs new file mode 100644 index 0000000..3dfe5fd --- /dev/null +++ b/types/src/transaction_verification_data.rs @@ -0,0 +1,114 @@ +//! Contains [`TransactionVerificationData`] and the related types. + +use std::sync::Mutex; + +use monero_serai::transaction::{Timelock, Transaction}; + +use crate::{HardFork, VerifiedTransactionInformation}; + +/// An enum representing all valid Monero transaction versions. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum TxVersion { + /// Legacy ring signatures. + RingSignatures, + /// Ring-CT + RingCT, +} + +impl TxVersion { + /// Converts a `raw` version value to a [`TxVersion`]. + /// + /// This will return `None` on invalid values. + /// + /// ref: + /// && + pub const fn from_raw(version: u8) -> Option { + Some(match version { + 1 => Self::RingSignatures, + 2 => Self::RingCT, + _ => return None, + }) + } +} + +/// Represents if a transaction has been fully validated and under what conditions +/// the transaction is valid in the future. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum CachedVerificationState { + /// The transaction has not been validated. + NotVerified, + /// The transaction is valid* if the block represented by this hash is in the blockchain and the [`HardFork`] + /// is the same. + /// + /// *V1 transactions require checks on their ring-length even if this hash is in the blockchain. + ValidAtHashAndHF { + /// The block hash that was in the chain when this transaction was validated. + block_hash: [u8; 32], + /// The hf this transaction was validated against. + hf: HardFork, + }, + /// The transaction is valid* if the block represented by this hash is in the blockchain _and_ this + /// given time lock is unlocked. The time lock here will represent the youngest used time based lock + /// (If the transaction uses any time based time locks). This is because time locks are not monotonic + /// so unlocked outputs could become re-locked. + /// + /// *V1 transactions require checks on their ring-length even if this hash is in the blockchain. + ValidAtHashAndHFWithTimeBasedLock { + /// The block hash that was in the chain when this transaction was validated. + block_hash: [u8; 32], + /// The hf this transaction was validated against. + hf: HardFork, + /// The youngest used time based lock. + time_lock: Timelock, + }, +} + +impl CachedVerificationState { + /// Returns the block hash this is valid for if in state [`CachedVerificationState::ValidAtHashAndHF`] or [`CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock`]. + pub const fn verified_at_block_hash(&self) -> Option<[u8; 32]> { + match self { + Self::NotVerified => None, + Self::ValidAtHashAndHF { block_hash, .. } + | Self::ValidAtHashAndHFWithTimeBasedLock { block_hash, .. } => Some(*block_hash), + } + } +} + +/// Data needed to verify a transaction. +#[derive(Debug)] +pub struct TransactionVerificationData { + /// The transaction we are verifying + pub tx: Transaction, + /// The [`TxVersion`] of this tx. + pub version: TxVersion, + /// The serialised transaction. + pub tx_blob: Vec, + /// The weight of the transaction. + pub tx_weight: usize, + /// The fee this transaction has paid. + pub fee: u64, + /// The hash of this transaction. + pub tx_hash: [u8; 32], + /// The verification state of this transaction. + pub cached_verification_state: Mutex, +} + +#[derive(Debug, Copy, Clone, thiserror::Error)] +#[error("Error converting a verified tx to a cached verification data tx.")] +pub struct TxConversionError; + +impl TryFrom for TransactionVerificationData { + type Error = TxConversionError; + + fn try_from(value: VerifiedTransactionInformation) -> Result { + Ok(Self { + version: TxVersion::from_raw(value.tx.version()).ok_or(TxConversionError)?, + tx: value.tx, + tx_blob: value.tx_blob, + tx_weight: value.tx_weight, + fee: value.fee, + tx_hash: value.tx_hash, + cached_verification_state: Mutex::new(CachedVerificationState::NotVerified), + }) + } +} diff --git a/types/src/types.rs b/types/src/types.rs index 76ffd57..8a5b5aa 100644 --- a/types/src/types.rs +++ b/types/src/types.rs @@ -1,13 +1,15 @@ //! Various shared data types in Cuprate. -//---------------------------------------------------------------------------------------------------- Import +use std::num::NonZero; + use curve25519_dalek::edwards::EdwardsPoint; use monero_serai::{ block::Block, transaction::{Timelock, Transaction}, }; -//---------------------------------------------------------------------------------------------------- ExtendedBlockHeader +use crate::HardFork; + /// Extended header data of a block. /// /// This contains various metadata of a block, but not the block blob itself. @@ -15,15 +17,13 @@ use monero_serai::{ pub struct ExtendedBlockHeader { /// The block's major version. /// - /// This can also be represented with `cuprate_consensus::HardFork`. - /// - /// This is the same value as [`monero_serai::block::BlockHeader::major_version`]. - pub version: u8, + /// This is the same value as [`monero_serai::block::BlockHeader::hardfork_version`]. + pub version: HardFork, /// The block's hard-fork vote. /// - /// This can also be represented with `cuprate_consensus::HardFork`. + /// This can't be represented with [`HardFork`] as raw-votes can be out of the range of [`HardFork`]s. /// - /// This is the same value as [`monero_serai::block::BlockHeader::minor_version`]. + /// This is the same value as [`monero_serai::block::BlockHeader::hardfork_signal`]. pub vote: u8, /// The UNIX time at which the block was mined. pub timestamp: u64, @@ -35,10 +35,9 @@ pub struct ExtendedBlockHeader { pub long_term_weight: usize, } -//---------------------------------------------------------------------------------------------------- VerifiedTransactionInformation /// Verified information of a transaction. /// -/// This represents a transaction in a valid block. +/// This represents a valid transaction #[derive(Clone, Debug, PartialEq, Eq)] pub struct VerifiedTransactionInformation { /// The transaction itself. @@ -59,7 +58,6 @@ pub struct VerifiedTransactionInformation { pub tx_hash: [u8; 32], } -//---------------------------------------------------------------------------------------------------- VerifiedBlockInformation /// Verified information of a block. /// /// This represents a block that has already been verified to be correct. @@ -71,16 +69,17 @@ pub struct VerifiedBlockInformation { /// /// [`Block::serialize`]. pub block_blob: Vec, - /// All the transactions in the block, excluding the [`Block::miner_tx`]. + /// All the transactions in the block, excluding the [`Block::miner_transaction`]. pub txs: Vec, /// The block's hash. /// /// [`Block::hash`]. pub block_hash: [u8; 32], /// The block's proof-of-work hash. + // TODO: make this an option. pub pow_hash: [u8; 32], /// The block's height. - pub height: u64, + pub height: usize, /// The amount of generated coins (atomic units) in this block. pub generated_coins: u64, /// The adjusted block size, in bytes. @@ -91,12 +90,55 @@ pub struct VerifiedBlockInformation { pub cumulative_difficulty: u128, } -//---------------------------------------------------------------------------------------------------- OutputOnChain +/// A unique ID for an alt chain. +/// +/// The inner value is meaningless. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct ChainId(pub NonZero); + +/// An identifier for a chain. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum Chain { + /// The main chain. + Main, + /// An alt chain. + Alt(ChainId), +} + +/// A block on an alternative chain. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AltBlockInformation { + /// The block itself. + pub block: Block, + /// The serialized byte form of [`Self::block`]. + /// + /// [`Block::serialize`]. + pub block_blob: Vec, + /// All the transactions in the block, excluding the [`Block::miner_transaction`]. + pub txs: Vec, + /// The block's hash. + /// + /// [`Block::hash`]. + pub block_hash: [u8; 32], + /// The block's proof-of-work hash. + pub pow_hash: [u8; 32], + /// The block's height. + pub height: usize, + /// The adjusted block size, in bytes. + pub weight: usize, + /// The long term block weight, which is the weight factored in with previous block weights. + pub long_term_weight: usize, + /// The cumulative difficulty of all blocks up until and including this block. + pub cumulative_difficulty: u128, + /// The [`ChainId`] of the chain this alt block is on. + pub chain_id: ChainId, +} + /// An already existing transaction output. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct OutputOnChain { /// The block height this output belongs to. - pub height: u64, + pub height: usize, /// The timelock of this output, if any. pub time_lock: Timelock, /// The public key of this output, if any. @@ -105,6 +147,125 @@ pub struct OutputOnChain { pub commitment: EdwardsPoint, } +/// Input required to generate an output histogram. +/// +/// Used in RPC's `get_output_histogram`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OutputHistogramInput { + pub amounts: Vec, + pub min_count: u64, + pub max_count: u64, + pub unlocked: bool, + pub recent_cutoff: u64, +} + +/// A single entry in an output histogram. +/// +/// Used in RPC's `get_output_histogram`. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OutputHistogramEntry { + pub amount: u64, + pub total_instances: u64, + pub unlocked_instances: u64, + pub recent_instances: u64, +} + +/// Data of summed coinbase transactions. +/// +/// Used in RPC's `get_coinbase_tx_sum`. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CoinbaseTxSum { + pub emission_amount: u128, + pub fee_amount: u128, +} + +/// Data to create a custom block template. +/// +/// Used in RPC's `get_miner_data`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MinerData { + pub major_version: u8, + pub height: u64, + pub prev_id: [u8; 32], + pub seed_hash: [u8; 32], + pub difficulty: u128, + pub median_weight: u64, + pub already_generated_coins: u64, + pub tx_backlog: Vec, +} + +/// A transaction in the txpool. +/// +/// Used in [`MinerData::tx_backlog`]. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MinerDataTxBacklogEntry { + pub id: [u8; 32], + pub weight: u64, + pub fee: u64, +} + +/// Information on a [`HardFork`]. +/// +/// Used in RPC's `hard_fork_info`. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct HardForkInfo { + pub earliest_height: u64, + pub enabled: bool, + pub state: u32, + pub threshold: u32, + pub version: u8, + pub votes: u32, + pub voting: u8, + pub window: u32, +} + +/// Estimated fee data. +/// +/// Used in RPC's `get_fee_estimate`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FeeEstimate { + pub fee: u64, + pub fees: Vec, + pub quantization_mask: u64, +} + +/// Information on a (maybe alternate) chain. +/// +/// Used in RPC's `get_alternate_chains`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ChainInfo { + pub block_hash: [u8; 32], + pub block_hashes: Vec<[u8; 32]>, + pub difficulty: u128, + pub height: u64, + pub length: u64, + pub main_chain_parent_block: [u8; 32], +} + +/// Used in RPC's `add_aux_pow`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AuxPow { + pub id: [u8; 32], + pub hash: [u8; 32], +} + +/// Used in RPC's `add_aux_pow`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AddAuxPow { + pub blocktemplate_blob: Vec, + pub blockhashing_blob: Vec, + pub merkle_root: [u8; 32], + pub merkle_tree_depth: u64, + pub aux_pow: Vec, +} + +/// The inner response for a request for txs in a block. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TxsInBlock { + pub block: Vec, + pub txs: Vec>, +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/typos.toml b/typos.toml index abab190..fbd66d0 100644 --- a/typos.toml +++ b/typos.toml @@ -17,4 +17,6 @@ extend-ignore-identifiers-re = [ extend-exclude = [ "/misc/gpg_keys/", "cryptonight/", + "/test-utils/src/rpc/data/json.rs", + "rpc/types/src/json.rs", ] diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml new file mode 100644 index 0000000..78e7d00 --- /dev/null +++ b/zmq/types/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cuprate-zmq-types" +version = "0.1.0" +edition = "2021" +description = "Types for the ZMQ Pub/Sub API" +license = "MIT" +authors = ["dimalinux"] +repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" + +[dependencies] +serde = { workspace = true, features = ["derive"] } +hex = { workspace = true, features = ["std", "serde"] } +cuprate-types = { workspace = true, features = ["hex"] } + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } +assert-json-diff = "2.0.2" + +[lints] +workspace = true diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs new file mode 100644 index 0000000..2699600 --- /dev/null +++ b/zmq/types/src/json_message_types.rs @@ -0,0 +1,646 @@ +//! Objects for JSON serialization and deserialization in message bodies of +//! the ZMQ pub/sub interface. Handles JSON for the following subscriptions: +//! * `json-full-txpool_add` (`Vec`) +//! * `json-minimal-txpool_add` (`Vec`) +//! * `json-full-chain_main` (`Vec`) +//! * `json-minimal-chain_main` (`ChainMainMin`) +//! * `json-full-miner_data` (`MinerData`) +use cuprate_types::hex::HexBytes; +use serde::{Deserialize, Serialize}; + +/// ZMQ `json-full-txpool_add` packets contain an array of `TxPoolAdd`. +/// +/// Each `TxPoolAdd` object represents a new transaction in the mempool that was +/// not previously seen in a block. Miner coinbase transactions *are not* +/// included. `do-not-relay` transactions *are* included. Values are not +/// republished during a re-org. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TxPoolAdd { + /// transaction version number. `2` indicates Ring CT (all sub-variants). + pub version: u8, + /// if not `0` and less than `500_000_000`, this is the block height when + /// transaction output(s) are spendable; if >= `500_000_000` this is roughly + /// the unix epoch block timestamp when the output(s) are spendable. + pub unlock_time: u64, + /// transaction inputs (key images) with separate rings for each input + pub inputs: Vec, + /// transaction outputs + pub outputs: Vec, + /// extra data for the transaction with variable size, but limited to `1060` + /// bytes (`2120` hex nibbles). + #[serde(with = "hex::serde")] + pub extra: Vec, + /// obsolete, empty array in JSON + signatures: [Obsolete; 0], + /// ring confidential transaction data + pub ringct: PoolRingCt, +} + +/// ZMQ `json-minimal-txpool_add` subscriber messages contain an array of +/// `TxPoolAddMin` JSON objects. See `TxPoolAdd` for information on which +/// transactions are published to subscribers. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TxPoolAddMin { + /// transaction ID + pub id: HexBytes<32>, + /// size of the full transaction blob + pub blob_size: u64, + /// metric used to calculate transaction fee + pub weight: u64, + /// mining fee included in the transaction in piconeros + pub fee: u64, +} + +/// ZMQ `json-full-chain_main` subscriber messages contain an array of +/// `ChainMain` JSON objects. Each `ChainMain` object represents a new block. +/// Push messages only contain more than one block if a re-org occurred. +#[derive(Debug, Serialize, Deserialize)] +pub struct ChainMain { + /// major version of the monero protocol at this block's height + pub major_version: u8, + /// minor version of the monero protocol at this block's height + pub minor_version: u8, + /// epoch time, decided by the miner, at which the block was mined + pub timestamp: u64, + /// block id of the previous block + pub prev_id: HexBytes<32>, + /// cryptographic random one-time number used in mining a Monero block + pub nonce: u32, + /// coinbase transaction information + pub miner_tx: MinerTx, + /// non-coinbase transaction IDs in the block (can be empty) + pub tx_hashes: Vec>, +} + +/// ZMQ `json-minimal-chain_main` subscriber messages contain a single +/// `ChainMainMin` JSON object. Unlike the full version, only the topmost +/// block is sent in the case of a re-org. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ChainMainMin { + /// height of the block + pub first_height: u64, + /// block id of the previous block + pub first_prev_id: HexBytes<32>, + /// block ID of the current block is the 0th entry; additional block IDs + /// will only be included if this is the topmost block of a re-org. + pub ids: Vec>, +} + +/// ZMQ `json-full-miner_data` subscriber messages contain a single +/// `MinerData` object that provides the necessary data to create a +/// custom block template. There is no min version of this object. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct MinerData { + /// major version of the monero protocol for the next mined block + pub major_version: u8, + /// height on which to mine + pub height: u64, + /// block id of the most recent block on which to mine the next block + pub prev_id: HexBytes<32>, + /// hash of block to use as seed for Random-X proof-of-work + pub seed_hash: HexBytes<32>, + /// least-significant 64 bits of the 128-bit network difficulty + #[serde(with = "hex_difficulty")] + pub difficulty: u64, + /// median adjusted block size of the latest 100000 blocks + pub median_weight: u64, + /// fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission + pub already_generated_coins: u64, + /// mineable mempool transactions + pub tx_backlog: Vec, +} + +/// Holds a single input for the `TxPoolAdd` `inputs` array. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct PoolInput { + pub to_key: ToKey, +} + +/// Same as `PoolInput` (adds an extra JSON name layer) +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ToKey { + /// obsolete field (always 0), non-coinbase TX amounts are now encrypted + amount: u64, + /// integer offsets for ring members + pub key_offsets: Vec, + /// key image for the given input + pub key_image: HexBytes<32>, +} + +/// Holds the block height of the coinbase transaction. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct MinerInput { + /// namespace layer around the block height + pub r#gen: Gen, +} + +/// Additional namespace layer around the block height in `ChainMain`; gen is +/// another name for a coinbase transaction +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Gen { + /// block height when the coinbase transaction was created + pub height: u64, +} + +/// Transaction output data used by both `TxPoolAdd` and `MinerTx` +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub struct Output { + /// zero for non-coinbase transactions which use encrypted amounts or + /// an amount in piconeros for coinbase transactions + pub amount: u64, + /// public key of the output destination + pub to_tagged_key: ToTaggedKey, +} + +/// Holds the public key of an output destination with its view tag. +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub struct ToTaggedKey { + /// public key used to indicate the destination of a transaction output + pub key: HexBytes<32>, + /// 1st byte of a shared secret used to reduce wallet synchronization time + pub view_tag: HexBytes<1>, +} + +/// Ring CT information used inside `TxPoolAdd` +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct PoolRingCt { + /// ring CT type; `6` is CLSAG Bulletproof Plus + pub r#type: u8, + /// encrypted amount values of the transaction outputs + pub encrypted: Vec, + /// Ring CT commitments, 1 per transaction input + pub commitments: Vec>, + /// mining fee in piconeros + pub fee: u64, + /// data to validate the transaction that can be pruned from older blocks + pub prunable: Prunable, +} + +/// Ring CT information used inside `MinerTx`. Miner coinbase transactions don't +/// use Ring CT, so this only holds a block height. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +struct MinerRingCt { + /// always zero to indicate that Ring CT is not used + r#type: u8, +} + +/// Holds the encrypted amount of a non-coinbase transaction output. +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub struct Encrypted { + /// obsolete field, but present as zeros in JSON; this does not represent + /// the newer deterministically derived mask + mask: HexBytes<32>, + /// encrypted amount of the transaction output + pub amount: HexBytes<32>, +} + +/// Data needed to validate a transaction that can optionally be pruned from +/// older blocks. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Prunable { + /// obsolete, empty array in JSON + range_proofs: [Obsolete; 0], + /// obsolete, empty array in JSON + bulletproofs: [Obsolete; 0], + /// Bulletproofs+ data used to validate a Ring CT transaction + pub bulletproofs_plus: [BulletproofPlus; 1], + /// obsolete, empty array in JSON + mlsags: [Obsolete; 0], + /// CLSAG signatures; 1 per transaction input + pub clsags: Vec, + /// Ring CT pseudo output commitments; 1 per transaction input (*not* + /// output) + pub pseudo_outs: Vec>, +} + +/// Bulletproofs+ data used to validate the legitimacy of a Ring CT transaction. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[expect(non_snake_case)] +pub struct BulletproofPlus { + pub V: Vec>, + pub A: HexBytes<32>, + pub A1: HexBytes<32>, + pub B: HexBytes<32>, + pub r1: HexBytes<32>, + pub s1: HexBytes<32>, + pub d1: HexBytes<32>, + pub L: Vec>, + pub R: Vec>, +} + +/// Placeholder element type so obsolete fields can be deserialized +/// to the empty vector for backwards compatibility. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +struct Obsolete; + +/// CLSAG signature fields +#[expect(non_snake_case)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Clsag { + pub s: Vec>, + pub c1: HexBytes<32>, + pub D: HexBytes<32>, +} + +/// Part of the new block information in `ChainMain` +#[derive(Debug, Serialize, Deserialize)] +pub struct MinerTx { + /// transaction version number + pub version: u8, + /// block height when the coinbase transaction becomes spendable (currently + /// 60 blocks above the coinbase transaction height) + pub unlock_time: u64, + /// contains the block height in `inputs[0].gen.height` and nothing else as + /// coinbase transactions have no inputs + pub inputs: [MinerInput; 1], + /// transaction outputs + pub outputs: Vec, + /// extra data for the transaction with variable size; not limited to `1060` + /// bytes like the extra field of non-coinbase transactions + #[serde(with = "hex::serde")] + pub extra: Vec, + /// obsolete, empty array in JSON + signatures: [Obsolete; 0], + /// only for JSON compatibility; miners' don't use Ring CT + ringct: MinerRingCt, +} + +/// Holds a transaction entry in the `MinerData` `tx_backlog` field. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TxBacklog { + /// transaction ID + pub id: HexBytes<32>, + /// metric used to calculate transaction fee + pub weight: u64, + /// mining fee in piconeros + pub fee: u64, +} + +mod hex_difficulty { + //! Serializes the u64 difficulty field of `MinerData` in the same ways as + //! monerod. The difficulty value is inside a string, in big-endian hex, and + //! has a 0x prefix with no leading zeros. + use serde::{Deserialize, Deserializer, Serializer}; + + #[expect(clippy::trivially_copy_pass_by_ref)] + pub(super) fn serialize(difficulty: &u64, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("0x{difficulty:x}")) + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let s = s.strip_prefix("0x").unwrap_or(&s); + u64::from_str_radix(s, 16).map_err(serde::de::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use assert_json_diff::assert_json_eq; + use serde_json::{self, json}; + + use super::*; + + #[test] + fn test_txpooladd_json() { + let json1 = json!([ + { + "version": 2, + "unlock_time": 0, + "inputs": [ + { + "to_key": { + "amount": 0, + "key_offsets": [ + 82773133, + 30793552, + 578803, + 620532, + 114291, + 291870, + 111275, + 86455, + 19769, + 1238, + 15164, + 11374, + 5240, + 3547, + 7423, + 4198 + ], + "key_image": "89c060b57bba20c0b795bda4b618749e04eba5b40b30062b071dff6e8dd9071d" + } + } + ], + "outputs": [ + { + "amount": 0, + "to_tagged_key": { + "key": "05b4ff4c3ced6ba078a078af8fee5916512a1893f2b6d9373fb90e0eb4040095", + "view_tag": "7a" + } + }, + { + "amount": 0, + "to_tagged_key": { + "key": "60250376bca49bf24cef45c12738b86347df10954cd35630e81b90bf01e922af", + "view_tag": "b8" + } + } + ], + "extra": "01154b87b3334ce9f99d04635eae4e31252a20ba22acb96ff0764a03dc91d203ed020901be80cbce0723d0b4", + "signatures": [], + "ringct": { + "type": 6, + "encrypted": [ + { + "mask": "0000000000000000000000000000000000000000000000000000000000000000", + "amount": "a956be1858615454000000000000000000000000000000000000000000000000" + }, + { + "mask": "0000000000000000000000000000000000000000000000000000000000000000", + "amount": "72972be61af1210b000000000000000000000000000000000000000000000000" + } + ], + "commitments": [ + "cc2a17e43f0b183235a06e8582fcaaa7c21a07732077e66d4dcfaa0db691ea20", + "04e3cd1d3430bb7a1d9ede5ce9ec0ef2f6f9dd9fd31fb95c9e0b3148f1a660c8" + ], + "fee": 30660000, + "prunable": { + "range_proofs": [], + "bulletproofs": [], + "bulletproofs_plus": [ + { + "V": [ + "0196c1e9ba57ae053ae19c1bfd49e13146bd4b6e49401582f8a5a6f65ae560d0", + "aecd14b0e2d788315023601947c12d7e9227d8a1a0aee41f0b34fe196d96119f" + ], + "A": "8011fb75ba56d16b1ef1193e1fdfdb81e6b83afd726087427163857e8fcdf08e", + "A1": "ab91ab6863fbdee1fb71791e5297d007269f1b2cc050df40628ee7d0a1a5f3cb", + "B": "df1d082111b51d479b7fa72f6363bb731207c9343a528dc05b5798af56702521", + "r1": "2e212ae9ad704611a39b9b242453d2408045b303738b51d6f88f9dba06233401", + "s1": "36be53973fd971edff1f43cc5d04dda78d2b01f4caeaf38bbe195b04e309b30d", + "d1": "592116ca54b2d3ca0e9f222ffcc5fd63d3c992470473911fc70822f37672350a", + "L": [ + "98f1e11d62b90c665a8a96fb1b10332e37a790ea1e01a9e8ec8de74b7b27b0df", + "3a14689f3d743a3be719df9af28ca2f0f398e3a2731d5d6f342d0485bf81a525", + "bcb9e389fd494db66e4c796ff03795daa131426c0776ded6d37bfae51f29623d", + "5aa7e1f2bfcfcd74ac8305ce59a7baf5a901f84f8fbdd3a2d639e4058f35e98b", + "5939aa7ea012f88a26bab20270ce5b164c1880f793dc249ec215a0783b4d4ca7", + "08286f78d1bb0d7fc2efc7a3ac314707a4a1ac9656656d496180e131c1748496", + "7fc1de780305601aab95fda4b005927a4643f222e28407c31ad46cc935b7a27c" + ], + "R": [ + "69b4f329c0a5f8ae05891ac5ac35b947a7442b66e5b5693c99435deac3a62662", + "a193038cb8dc9d22abe6577fe44271c1693176cb636f9d101723670fb5ca5cda", + "90670e7083e503c2989b6548500234740dabf3451b0bd376979e03ca0cb5e50c", + "6ab149089f73799811f631eab272bd6c8f190f38efff4d49577364956d0148bf", + "62f2178cbdc760a0d3787b5fd42161c3c98394c2ff2b88efc039df59d2116e5d", + "536f91da278f730f2524260d2778dc5959d40a5c724dd789d35bbd309eabd933", + "e47c5c8181e692f3ad91733e7d9a52f8b7e3f5016c5e65f789eea367a13f16cd" + ] + } + ], + "mlsags": [], + "clsags": [ + { + "s": [ + "f70840a8d65da85e962d2ce5ed1293ae3de83318b464363db85505d99e317b01", + "b7c1125be139b4ed201ce85b8453920306cac7c5da11e0f8c0fd7702f15c6a06", + "5a04335699f5a816eed1cab79085814dbcf3be5cef51b078b1c3e0210bbba606", + "e4743e114fd6352ea29e0b48ac96688edaba1d5d0634c34301756902eeb1fb0e", + "34aae87ab091082356d2815a7c8e973124245ebc6d163b9f01fbfeb360edcf04", + "d2d0b6ddb44ed42096affec08ea9cd77d2c7cdc5b2e1e964f836d3717640ec00", + "79b34258c8be04ddd955389f7ee3b912286c23492c519a5687b81d770619620e", + "3c889c19693463160d6c7e642c46f5d41db052ee3358c7dcb4826f48bca26607", + "da04927a438fd0d9674e64f0c016f30fde27f251d3466f29dcd5b3d757fec90c", + "f3e08d83b11ca6529bc18748d3f732c325fca8ff79f69f0ed754bcd529898102", + "f00d7125909a9a8cc5283ffc7727fce945e85828459eecb836c7aedca414350e", + "0a635a193af37be1c9519309f25eaf9f37b7bc5892864646d8d2a2187fcec601", + "0c4154d575dff3699bd41f0c354601de6535161755bd2164526076f37e2c6908", + "f7b21e2698333285ea10a95edbe80fe0bb8740c30b35c25bd2002e3693867e02", + "a637f338ff2ed65fa96e5529abc575fc2a35ed1a3f62a9e7be495069d8438800", + "f7c355f1c3a663978c5fe1c9337aabd4085ee537a61eec2c5c1e837cb3728c09" + ], + "c1": "c5dd25e0e32dbefa6ac1d0dc9072620eb97a99224462cdd163287f2b60b9810b", + "D": "c4fa3f939ccf02e4c8842cbd417cf3690421986e558734a0a029f8a86d2791a8" + } + ], + "pseudo_outs": [ + "bcb08920f5476d74294aeb89c8001123bffd2f2ab84e105d553b807674c595ce" + ] + } + } + } + ]); + + let tx_pool_adds: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&tx_pool_adds).unwrap(); + assert_json_eq!(json1, json2); + } + + #[test] + fn test_txpooladd_min_json() { + let json1 = json!([ + { + "id": "b5086746e805d875cbbbbb49e19aac29d9b75019f656fab8516cdf64ac5cd346", + "blob_size": 1533, + "weight": 1533, + "fee": 30660000 + } + ]); + + let tx_pool_adds: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&tx_pool_adds).unwrap(); + assert_json_eq!(json1, json2); + } + + #[test] + fn test_chain_main_json() { + let json1 = json!([ + { + "major_version": 16, + "minor_version": 16, + "timestamp": 1726973843, + "prev_id": "ce3731311b7e4c1e58a2fe902dbb5c60bb2c0decc163d5397fa52a260d7f09c1", + "nonce": 537273946, + "miner_tx": { + "version": 2, + "unlock_time": 3242818, + "inputs": [ + { + "gen": { + "height": 3242758 + } + } + ], + "outputs": [ + { + "amount": 618188180000_u64, + "to_tagged_key": { + "key": "83faf44df7e9fb4cf54a8dd6a63868507d1a1896bdb35ea9110d739d5da6cf21", + "view_tag": "38" + } + } + ], + "extra": "010e3356a86dbb339354afbc693408dfe8648bffd0b276e6a431861eb73643d88d02115162e362c98e2d00000000000000000000", + "signatures": [], + "ringct": { + "type": 0 + } + }, + "tx_hashes": [ + "2c1b67d3f10b21270cac116e6d5278dc4024ee2d727e4ad56d6dedb1abc0270c", + "c2cfec0de23229a2ab80ca464cef66fc1cad53647a444f048834ec236c38c867", + "03c7649af2373c0f739d3c2eff9ee1580986b460d2abdd5e2aa332281e52da7e", + "1e0834cc658599e786040bdcd9b589a5e8d975233b72279d04ece1a3dd5572b0", + "ba65c30150e906a8799ee99bb2e6481873e42ed8b025cf967c5798528ddc81b4", + "6fc7b1da1cf433edafb142173e9ac13fe05142a36d8a72e9efdf7a3b94da11d6", + "847c06dcda4540d45cae868d4d031781bd87d9bfa4b2186a611428f52e68ccee", + "79f87a1b2fc17295d2cf25b6a65dd17fd8630829ee50f9c48f15e4a24e72d872", + "32b4f7ce6d864006b274dbd73fc8058151d0fd2dd0bb4b423120e32451fd59eb", + "430fe7fa00b63b68b301a4e4810bef2b5be1f651dba8c360e86eac61227382e7", + "9f8d2bf5e39071abccb336404ea72ab85cb731500a1d386a3bf537b9046df29d", + "f63893b8226ca28c290cb65541d60c1675dda1e2a77a629b6b2d7c3886240b23", + "ee8608b6e80cce14beaf95f747f4da8e40e40a49ad1adc20038843a6da3df3c6", + "05783765c150ed1e46d6380726e7ca1f788305754e553f5f2d49b9f09aaaf88d", + "20b4b95e62f45b72014d6ab14edb0b31e273cdc8c8d106068dd32ef6e92fc0a2", + "9230fb0a9dce8e2ca7e109ebf3480838251691de8ed73ea91f74723c5cf19bac", + "d59cf84a25f56ec0f1352bb05645efe9b9326598c4f7c5bc39a87eb7a20c48fc", + "465deb73c48a460df71861d61666dabb906648035a1fecfd0e988ee37616c655", + "5767bc633729ba4555561510f3db739431b16744234dcd549a0d346eaa6685b1", + "2c8d9af5d5774de96e67835ac5adbc6ca5579125b08bc907b395645eea6410ec", + "d385c884a0687c3360725dd3a3f6acf6f64bf38d8eeea1644d80bc23b13ee870", + "b2bc7e9fa9c1da08a8b6ee58505611c05bc388fd30aece00e9a0041470f7e950", + "69a4a79b50d42d372e91c6608c2652d1d5ddd343526c387ef6cf1e3c158b1765", + "ef508dfa79bbedd226835c42a9d000a64cc4abe0250c9aa55fd968224e2b45c3", + "0413c3b3fc621c472e10a102d77456db506f0df10a909833aed0c6738fb31eeb", + "e0c52d6d649c2f1abce4c6ffce4dd75a23308afbb6abe33af53da232c40caf5f", + "cd1fd68d2a15002ca6236083ff6ae165c8fd922f410da79640a4342fd8ebd1c8", + "ba746f80ca4ee496f4188ba278f1ed69a913238450d52bd2e2f3d3bf6fdd43d3", + "13c964bc13a55621b7bbbfe9a6d703536d951bfa19eedee93dd1286020959021", + "41a6f8d0df227a401a9bd6f5c0fbc21ed89f515ea5c8434a087e8b880080ee1f", + "41c2b5994284790b1ee158f7b87aa1231c14975d6456a91ff6f93c6f81277965", + "7e6b7f169cc6cab88e652771157cf8c2eb6f69dffb6939a79b34c6554fe6c00b", + "619517d9d138bf95c6b77eb801526b8419616de2b8618ccfd3b6d1c10364bc64", + "52cca64fb20fc2f6d06034a1a2d9b5665972ebc2569ec69f8d473caada309add", + "219c106d09da5a27b339ea0f070da090779b31ef9ccfa90d6d25e7388341eff9", + "e07ce6e96e73cff80c9cc4c1b349ad1ef53cff210b876d4e7afd89fcc8b2e5dd", + "e98f2a00b2892cd65c0252d956d88a4bb8024c7db98ca003c127b097f097f276", + "ed81aa398071fe495e37095e51ff50053e132bd11f27ba9c06ac4bf4063b756f", + "667d29a0cefa311e06fcfc22c98ef75edf81deb6c8a812492eb255a049c826db", + "8b16e8cbc1765247456bd67a3106498f686401b7529dc0f6b03360caf8671135", + "013e443e63259748f6d1a5653374826618ba066b7febcf55c829333f0dd9a6c3", + "517a05d82de59a973eb4d343c45558841c9165ccd75ca7c9d2e1a35f80c26c15", + "af74d5dd44cfed8f40f853a6fc405dae23d547482296f8dbbc13c1aed2c3d8c5", + "b5086746e805d875cbbbbb49e19aac29d9b75019f656fab8516cdf64ac5cd346", + "cfcda18d058656797a1272b384774dcfc26a504a24298aa49ba060eb6b4a19e0", + "1f380660a99030cc45f85ba8ee0e0541035c0fde719c84aa692796328974c9dd", + "53127181a0301a27b3a2749dc997556b211d949a99aa34d1c52d5c54220f49d2", + "5d50a66df97f4decc4ecc3f5030589ef966d5af84a995f7fb14f1c02ae9704db", + "cdab9628acdb57c460e292660e7a07caf2ddbcffdfff92f3e5e4fb12119a11ca", + "e740a098a74d7a66a821c4ac3c5f913a82fc7445b5593cc5fa3e48ad1b4589b1", + "760549176fec210cfe0ff58eabbf2670cf33b4cd3942a3b60a98bf8f328a6d01", + "961b0956aa6303ed8ca1687d93ed46b9aa8a0203ec4ce0cbc2e86b364fbfb613", + "b9db041b2c3bfc6b5b0facb638b0b4643eec76b060039a6b11fb43682ed77a97", + "1011c321eb386b9975e8124bdb130790dcf4ac0021da3103cabbf7dfa18ccea7", + "6a9d3d15be4b25bd544d96bb1d7685e53f9484735bb22994feffb9037009aeeb", + "bf20d6193890cf7fdead9e3b60197564c663b5a62eda782a49d4aa7819bb9665", + "472d28f9d25a95e625eb808ff3827e7f6792009e1ba0b3b21951f3058b65a75d", + "e3931b2b66da07f983d2235d9d0b3a3098008458bdc0c1ad4370fae73e1eaa9e", + "e18a0dea6382c95aa4089a971190683b171e9405c06fd4111924144600f3bcf3", + "1a336bcf24026307821b76b9ca18b178c285c591c5df9906e3ffbd2050ccd356", + "8ca2d0e5ae9b9981bb8b76ba0da383c585664b2a2f4e861d58aab00c9b0cc808", + "e1866c27023ccea276034c4d572eab42713132e4fdb2aafa9488f6d74cd49303", + "3674cfafba4cdea5775a72a82e5d553bf180beab456b3cbaa7b41a1574fe1948", + "9bb400dd317425f40176c3094a5573037b0217e0b60761cb66a8fa15b63b36c3", + "c078048028aca3e9bc40f68f4d42ef25c6af2cef4da20bf3be70dd6a23b82d52", + "c28cc85f945085e70259ed02131ae3f8c5992e789c9c75c2c6e257306beaf26e", + "4c2b121795fe2b90fda84813543952382daa29c7b96edd9f96040df13e48e347", + "63c6fba30b5471fd60e715cbaf4448badafde68dbc42c54d96b56dd2c4bf2d15", + "a4240138ecfe736113581f318f261a01992eaa8fa5b7bd6938d9dbeb65aa85d7", + "b9d088a7b21f655d0cf50f8404e874f4d1655fb5565a354d2c0dd6d113619c66", + "9133e7e98a83f6e10a7fd44c104d9124d93e0d3e920f5c160873b394dd3a2fcb", + "953985dbd0ea6f86746e83be144ec2ff2897ef1f3506eede083b893e98dd63ea", + "83af840c4cad46de96c86fcf700ade32e73260d4a16cefa330cb5a722ef59fdf", + "eea3c0c2b016ea0c269f954fd8172c3d118f08103c9842b81b05290c9faf3780", + "ac43a363fdb81fa4f6df1cb06ba49a5f4eeef411957cf2afad55cbc1e79bc4d1", + "ca72cf7bda22aed15c16ca67e7b6cc57109cdc86d4ffe38fd71210a5380fcada", + "477dc1cd62106d9df6b37f8515579a48d01b310387087c08ce7062a8eb5df98d", + "d47b6dcd3b13288825c954df6c6e30eb683d1f79434beaee7172082f8ae74280", + "9c64ef20c69589c56fcc5f3a0d10f6957ecea248e44acb432aaf16a88eeef946", + "d2aa256bfd61bdb64ac38da6cbc3e77fb315bb9fbaf422087c10345377df44f6", + "8b9623e4513594a6eaeb3475ea7d0eb585dd8f6e20e21c316db0b942fada2336", + "860725ed0bd18c744e6b8b02888ad88be1cf23d7153131b220a0f9fbb76976bf", + "387cc6e807efc263a0ad6a30e6313a27d16abef038264d0afa0e6ad943be55da" + ] + } + ]); + + let chain_main: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&chain_main).unwrap(); + assert_json_eq!(json1, json2); + } + + #[test] + fn test_chain_main_min_json() { + let json1 = json!({ + "first_height": 3242758, + "first_prev_id": "ce3731311b7e4c1e58a2fe902dbb5c60bb2c0decc163d5397fa52a260d7f09c1", + "ids": [ + "ee1238b884e64f7e438223aa8d42d0efc15e7640f1a432448fbad116dc72f1b2" + ] + }); + + let chain_main_min: ChainMainMin = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&chain_main_min).unwrap(); + assert_json_eq!(json1, json2); + } + + #[test] + fn test_miner_data_json() { + let json1 = json!({ + "major_version": 16, + "height": 3242764, + "prev_id": "dc53c24683dca14586fb2909b9aa4a44adb524e010d438e2491e7d8cc1c80831", + "seed_hash": "526577d6e6689ba8736c16ccc76e6ce4ada3b0ceeaa3a2260b96ba188a17d705", + "difficulty": "0x526f2623ce", + "median_weight": 300000, + "already_generated_coins": 18446744073709551615_u64, + "tx_backlog": [ + { + "id": "dbec64651bb4e83d0e9a05c2826bde605a940f12179fab0ab5dc8bc4392c776b", + "weight": 2905, + "fee": 929600000 + }, + { + "id": "ec5728dd1fbd98db1f93d612826e73b95f52cca49f247a6dbc35390f45766a7d", + "weight": 2222, + "fee": 44440000 + }, + { + "id": "41f613b1a470af494e0a705993e305dfaad3e365fcc0b0db0118256fc54559aa", + "weight": 2221, + "fee": 44420000 + }, + { + "id": "34fa33bf96dc2f825fe870e8f5402be6225c1623b345224e0dbc38b6407873de", + "weight": 2217, + "fee": 709440000 + } + ] + }); + + let miner_data: MinerData = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&miner_data).unwrap(); + assert_json_eq!(json1, json2); + } +} diff --git a/zmq/types/src/lib.rs b/zmq/types/src/lib.rs new file mode 100644 index 0000000..3f9562b --- /dev/null +++ b/zmq/types/src/lib.rs @@ -0,0 +1 @@ +pub mod json_message_types;