diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a670405614b..5eac7cc1003 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,28 +22,31 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Actions doc / www page: +# actions/checkout: https://github.com/actions/checkout +# Swatinem/rust-cache: https://github.com/Swatinem/rust-cache +# dtolnay/rust-toolchain: https://github.com/dtolnay/rust-toolchain +# codecov/codecov-action: https://github.com/codecov/codecov-action +# JamesIves/github-pages-deploy-action: https://github.com/JamesIves/github-pages-deploy-action +# typos: https://github.com/crate-ci/typos/blob/master/docs/github-action.md + jobs: # Quick tests on each commit/PR sanity: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.81.0 components: rustfmt - override: true - uses: Swatinem/rust-cache@v2 with: shared-key: "sanity" save-if: ${{ github.ref_name == 'main' }} - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - run: cargo fmt --all --check check: if: github.ref != 'refs/heads/staging' @@ -51,12 +54,11 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.81.0 - uses: Swatinem/rust-cache@v2 with: @@ -66,9 +68,7 @@ jobs: with: version: '23.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions-rs/cargo@v1 - with: - command: check + - run: cargo check clippy: if: github.ref != 'refs/heads/staging' @@ -76,15 +76,13 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.81.0 components: clippy - override: true - uses: Swatinem/rust-cache@v2 with: shared-key: "clippy" @@ -93,10 +91,7 @@ jobs: with: version: '23.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --no-deps --all-targets + - run: cargo clippy --no-deps --all-targets security: if: github.ref != 'refs/heads/staging' @@ -104,12 +99,41 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" # - uses: actions-rust-lang/audit@v1 # name: Audit Rust Dependencies + typos: + if: github.ref != 'refs/heads/staging' + needs: sanity + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + submodules: "recursive" + - uses: crate-ci/typos@master + + misc: + if: github.ref != 'refs/heads/staging' + needs: sanity + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + submodules: "recursive" + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.81.0 + - uses: Swatinem/rust-cache@v2 + with: + shared-key: "clippy" + save-if: ${{ github.ref_name == 'main' }} + - run: cargo xtask check_gas_cost_definitions + # Full cross-platform tests required to merge on main branch full: name: full @@ -185,14 +209,12 @@ jobs: if: runner.os == 'Linux' run: df -h - uses: ilammy/setup-nasm@v1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.81.0 - override: true - uses: Swatinem/rust-cache@v2 with: shared-key: "massa" @@ -202,22 +224,11 @@ jobs: version: "23.x" include-pre-releases: false repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-nextest --locked - - uses: actions-rs/cargo@v1 - with: - command: nextest - args: run --retries 10 --profile ci --all-features - - uses: actions-rs/cargo@v1 - with: - command: clean - - uses: actions-rs/cargo@v1 - with: - command: test - args: --doc - - uses: codecov/codecov-action@v3 + - run: cargo install cargo-nextest --locked + - run: cargo nextest run --retries 10 --profile ci --all-features + - run: cargo clean + - run: cargo test --doc + - uses: codecov/codecov-action@v4 with: files: lcov.info fail_ci_if_error: false @@ -233,21 +244,19 @@ jobs: if: github.ref != 'refs/heads/staging' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.81.0 components: rustfmt - override: true - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: massalabs/gas-calibration path: gas-calibration ref: main - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: massalabs/massa-as-sdk path: massa-as-sdk @@ -268,15 +277,13 @@ jobs: doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.81.0 components: rustfmt - override: true - uses: Swatinem/rust-cache@v2 with: shared-key: "doc" @@ -285,11 +292,8 @@ jobs: with: version: '23.x' repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions-rs/cargo@v1 - with: - command: doc - args: --no-deps --document-private-items - - uses: JamesIves/github-pages-deploy-action@4.1.7 + - run: cargo doc --no-deps --document-private-items + - uses: JamesIves/github-pages-deploy-action@v4 with: branch: gh-pages folder: target/doc @@ -297,7 +301,7 @@ jobs: unit_tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - name: setup_tests diff --git a/Cargo.lock b/Cargo.lock index 8ad5ac9fde8..08c69832ce0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.28.1", + "gimli 0.31.1", ] [[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 = "aead" @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -108,74 +108,75 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-ffi-bindings" version = "0.2.5" -source = "git+https://github.com/massalabs/as-ffi-bindings.git?tag=v0.5.5#a1c338eedc68502e85995123a384261a90cbb409" +source = "git+https://github.com/massalabs/as-ffi-bindings.git?branch=feature/update_wasmer_4_3_4#c0afa48488418a66d872a8c51bc7fa30fe4f8a11" dependencies = [ "anyhow", "wasmer", @@ -193,7 +194,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -203,10 +204,10 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -215,8 +216,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -226,20 +227,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener", -] - [[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", @@ -248,46 +240,51 @@ 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 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "async-trait" -version = "0.1.75" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", "itoa", "matchit", "memchr", @@ -296,42 +293,45 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper", - "tower", + "sync_wrapper 1.0.2", + "tower 0.5.1", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", ] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.32.2", + "object 0.36.5", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -342,24 +342,21 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "base64ct" -version = "1.6.0" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "beef" -version = "0.5.2" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bindgen" @@ -374,12 +371,12 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn 2.0.43", + "syn 2.0.90", ] [[package]] @@ -390,9 +387,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -409,9 +406,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", "arrayvec", @@ -440,9 +437,9 @@ dependencies = [ [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "sha2 0.10.8", "tinyvec", @@ -450,15 +447,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -467,12 +464,12 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -484,9 +481,21 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +dependencies = [ + "serde", +] + +[[package]] +name = "bytesize" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -513,14 +522,21 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", + "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -536,24 +552,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -562,15 +584,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -588,9 +610,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -599,9 +621,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -609,33 +631,33 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "heck 0.5.0", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clipboard-win" @@ -650,18 +672,28 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.50" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" dependencies = [ "cc", ] [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "config" @@ -678,21 +710,21 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml", + "toml 0.5.11", "yaml-rust", ] [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -703,9 +735,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -719,9 +751,9 @@ 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 = "corosensei" @@ -738,9 +770,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -830,9 +862,9 @@ checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -875,11 +907,10 @@ dependencies = [ [[package]] name = "crossbeam" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eb9105919ca8e40d437fc9cbb8f1975d916f1bd28afe795a48aae32a2cc8920" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ - "cfg-if", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", @@ -889,54 +920,46 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.17" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc6598521bb5a83d491e8c1fe51db7296019d2ca3cb93cc6c2a20369a4d78a2" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -976,12 +999,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" dependencies = [ - "nix 0.27.1", - "windows-sys 0.52.0", + "nix 0.29.0", + "windows-sys 0.59.0", ] [[package]] @@ -1006,9 +1029,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1023,12 +1046,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.3", - "darling_macro 0.20.3", + "darling_core 0.20.10", + "darling_macro 0.20.10", ] [[package]] @@ -1039,24 +1062,24 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.71", - "quote 1.0.33", - "strsim", + "proc-macro2 1.0.92", + "quote 1.0.37", + "strsim 0.10.0", "syn 1.0.109", ] [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.71", - "quote 1.0.33", - "strsim", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "strsim 0.11.1", + "syn 2.0.90", ] [[package]] @@ -1066,19 +1089,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", - "quote 1.0.33", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.3", - "quote 1.0.33", - "syn 2.0.43", + "darling_core 0.20.10", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1088,7 +1111,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -1096,15 +1133,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -1126,9 +1163,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -1140,8 +1177,39 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", "syn 1.0.109", ] @@ -1154,7 +1222,7 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -1207,13 +1275,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1222,12 +1290,27 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "downcast" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "dynasm" version = "1.2.3" @@ -1238,8 +1321,8 @@ dependencies = [ "byteorder", "lazy_static", "proc-macro-error", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -1266,9 +1349,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", @@ -1282,9 +1365,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" @@ -1313,8 +1396,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -1325,8 +1408,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8ea75f31022cba043afe037940d73684327e915f88f62478e778c3de914cd0a" dependencies = [ "enum_delegate_lib", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -1336,31 +1419,31 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e1f6c3800b304a6be0012039e2a45a322a093539c45ab818d9e6895a39c90fe" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "rand", "syn 1.0.109", ] [[package]] name = "enumset" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" dependencies = [ - "darling 0.20.3", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "darling 0.20.10", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1380,12 +1463,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1398,12 +1481,6 @@ dependencies = [ "str-buf", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1412,9 +1489,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fd-lock" @@ -1423,15 +1500,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix 0.38.28", + "rustix", "windows-sys 0.48.0", ] [[package]] name = "fiat-crypto" -version = "0.2.5" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] [[package]] name = "fixedbitset" @@ -1441,9 +1530,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1502,9 +1591,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", @@ -1517,9 +1606,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", @@ -1527,15 +1616,15 @@ 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-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1544,38 +1633,38 @@ dependencies = [ [[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 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[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-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", "send_wrapper", @@ -1583,9 +1672,9 @@ dependencies = [ [[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", @@ -1620,20 +1709,22 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", @@ -1652,9 +1743,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1664,20 +1755,20 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gloo-net" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" dependencies = [ "futures-channel", "futures-core", "futures-sink", "gloo-utils", - "http", + "http 1.2.0", "js-sys", "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1719,8 +1810,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.1.0", + "http 0.2.12", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -1728,18 +1819,32 @@ dependencies = [ ] [[package]] -name = "half" -version = "1.8.2" +name = "h2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.2.0", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] -name = "hashbrown" -version = "0.11.2" +name = "half" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "ahash 0.7.7", + "cfg-if", + "crunchy", ] [[package]] @@ -1748,7 +1853,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -1759,9 +1864,15 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hdrhistogram" @@ -1779,11 +1890,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -1838,9 +1955,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -1854,24 +1982,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] [[package]] -name = "http-range-header" -version = "0.3.1" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] [[package]] -name = "httparse" -version = "1.8.0" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" @@ -1884,17 +2029,16 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1906,40 +2050,82 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.2.0", + "hyper 1.5.1", + "hyper-util", "log", "rustls", - "rustls-native-certs", + "rustls-pki-types", "tokio", "tokio-rustls", - "webpki-roots", + "tower-service", ] [[package]] name = "hyper-timeout" -version = "0.4.1" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.5.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "hyper", + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.5.1", "pin-project-lite", + "socket2", "tokio", - "tokio-io-timeout", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1958,6 +2144,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1966,12 +2270,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1987,12 +2302,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.15.2", "serde", ] @@ -2007,24 +2322,13 @@ dependencies = [ [[package]] name = "intrusive-collections" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b694dc9f70c3bda874626d2aed13b780f137aab435f4e9814121955cf706122e" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" dependencies = [ "memoffset", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ip_rfc" version = "0.1.0" @@ -2033,15 +2337,21 @@ checksum = "6b8a253fc120dd6c624b7beb0a82330fc0135e0a9cec685bf039a07ba032af1e" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", - "rustix 0.38.28", - "windows-sys 0.48.0", + "libc", + "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -2053,43 +2363,64 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2106,9 +2437,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" +checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -2124,128 +2455,138 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" +checksum = "548125b159ba1314104f5bb5f38519e03a41862786aa3925cf349aae9cdd546e" dependencies = [ + "base64 0.22.1", "futures-channel", "futures-util", "gloo-net", - "http", + "http 1.2.0", "jsonrpsee-core", "pin-project", - "rustls-native-certs", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-rustls", "tokio-util", "tracing", "url", - "webpki-roots", ] [[package]] name = "jsonrpsee-core" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" +checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" dependencies = [ - "anyhow", - "async-lock", "async-trait", - "beef", + "bytes", "futures-timer", "futures-util", - "hyper", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", "jsonrpsee-types", "parking_lot", + "pin-project", "rand", - "rustc-hash", + "rustc-hash 2.1.0", "serde", "serde_json", - "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", + "tokio-stream", "tracing", "wasm-bindgen-futures", ] [[package]] name = "jsonrpsee-http-client" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" +checksum = "b3638bc4617f96675973253b3a45006933bde93c2fd8a6170b33c777cc389e5b" dependencies = [ "async-trait", - "hyper", + "base64 0.22.1", + "http-body 1.0.1", + "hyper 1.5.1", "hyper-rustls", + "hyper-util", "jsonrpsee-core", "jsonrpsee-types", + "rustls", + "rustls-platform-verifier", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", - "tower", + "tower 0.4.13", "tracing", "url", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" +checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" dependencies = [ - "heck", - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 1.0.109", + "heck 0.5.0", + "proc-macro-crate", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "jsonrpsee-server" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" +checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-util", "jsonrpsee-core", "jsonrpsee-types", + "pin-project", "route-recognizer", "serde", "serde_json", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.4.13", "tracing", ] [[package]] name = "jsonrpsee-types" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" +checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" dependencies = [ - "anyhow", - "beef", + "http 1.2.0", "serde", "serde_json", - "thiserror", - "tracing", + "thiserror 1.0.69", ] [[package]] name = "jsonrpsee-wasm-client" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7cbb3447cf14fd4d2f407c3cc96e6c9634d5440aa1fbed868a31f3c02b27f0" +checksum = "1a01cd500915d24ab28ca17527e23901ef1be6d659a2322451e1045532516c25" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -2254,11 +2595,11 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.20.3" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" +checksum = "0fe322e0896d0955a3ebdd5bf813571c53fea29edd713bc315b76620b327e86d" dependencies = [ - "http", + "http 1.2.0", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -2267,18 +2608,18 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[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 = "lazycell" @@ -2294,33 +2635,33 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.6", ] [[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" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "libc", "redox_syscall", ] @@ -2391,9 +2732,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "pkg-config", @@ -2408,21 +2749,27 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "linux-raw-sys" -version = "0.4.12" +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2430,9 +2777,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loupe" @@ -2450,25 +2797,25 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", ] [[package]] -name = "mach" -version = "0.3.2" +name = "mach2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] @@ -2486,7 +2833,7 @@ dependencies = [ [[package]] name = "massa-client" -version = "2.4.0" +version = "2.5.0" dependencies = [ "anyhow", "clap", @@ -2508,12 +2855,12 @@ dependencies = [ "strum", "strum_macros", "tokio", - "toml_edit 0.21.0", + "toml_edit 0.21.1", ] [[package]] name = "massa-node" -version = "2.4.0" +version = "2.5.0" dependencies = [ "anyhow", "cfg-if", @@ -2531,6 +2878,8 @@ dependencies = [ "massa_consensus_worker", "massa_db_exports", "massa_db_worker", + "massa_deferred_calls", + "massa_event_cache", "massa_executed_ops", "massa_execution_exports", "massa_execution_worker", @@ -2566,7 +2915,7 @@ dependencies = [ [[package]] name = "massa-proto-rs" version = "0.1.0" -source = "git+https://github.com/massalabs/massa-proto-rs?rev=38950875a7aa406fedc4f0b8336864e5ff290f2c#38950875a7aa406fedc4f0b8336864e5ff290f2c" +source = "git+https://github.com/massalabs/massa-proto-rs?rev=b5267178eaf266ec724691d7de163e4c34343416#b5267178eaf266ec724691d7de163e4c34343416" dependencies = [ "glob", "prost", @@ -2574,16 +2923,17 @@ dependencies = [ "prost-types", "tonic", "tonic-build", + "tower-service", ] [[package]] name = "massa-sc-runtime" version = "0.10.0" -source = "git+https://github.com/massalabs/massa-sc-runtime?rev=6738815dba5f3be7b7a03b88d6feb31f151a8682#6738815dba5f3be7b7a03b88d6feb31f151a8682" +source = "git+https://github.com/massalabs/massa-sc-runtime?rev=f5a584b9f8050f332c9ed332bd0a40f8e0372807#f5a584b9f8050f332c9ed332bd0a40f8e0372807" dependencies = [ "anyhow", "as-ffi-bindings", - "base64 0.21.5", + "base64 0.21.7", "chrono", "displaydoc", "function_name", @@ -2602,24 +2952,24 @@ dependencies = [ "serial_test", "sha2 0.10.8", "sha3", - "thiserror", + "thiserror 1.0.69", "tracing", "wasmer", "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", "wasmer-middlewares", "wasmer-types", - "which 5.0.0", + "which", ] [[package]] name = "massa_api" -version = "2.4.0" +version = "2.5.0" dependencies = [ "async-trait", "futures", - "hyper", - "itertools 0.12.0", + "http 1.2.0", + "itertools 0.12.1", "jsonrpsee", "massa_api_exports", "massa_channel", @@ -2645,17 +2995,18 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", - "tower", - "tower-http", + "tower 0.4.13", + "tower-http 0.6.2", "tracing", ] [[package]] name = "massa_api_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "jsonrpsee", + "massa_deferred_calls", "massa_final_state", "massa_hash", "massa_models", @@ -2667,12 +3018,12 @@ dependencies = [ "serde", "serial_test", "strum", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_async_pool" -version = "2.4.0" +version = "2.5.0" dependencies = [ "assert_matches", "massa-proto-rs", @@ -2695,7 +3046,7 @@ dependencies = [ [[package]] name = "massa_bootstrap" -version = "2.4.0" +version = "2.5.0" dependencies = [ "bitvec", "crossbeam", @@ -2706,6 +3057,7 @@ dependencies = [ "massa_consensus_exports", "massa_db_exports", "massa_db_worker", + "massa_deferred_calls", "massa_executed_ops", "massa_final_state", "massa_hash", @@ -2721,7 +3073,7 @@ dependencies = [ "massa_test_framework", "massa_time", "massa_versioning", - "mio", + "mio 0.8.11", "mockall", "nom", "num", @@ -2735,13 +3087,13 @@ dependencies = [ "stream_limiter", "substruct", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", ] [[package]] name = "massa_channel" -version = "2.4.0" +version = "2.5.0" dependencies = [ "crossbeam", "prometheus", @@ -2750,18 +3102,18 @@ dependencies = [ [[package]] name = "massa_cipher" -version = "2.4.0" +version = "2.5.0" dependencies = [ "aes-gcm", "displaydoc", "pbkdf2", "rand", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_consensus_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa_channel", @@ -2780,17 +3132,17 @@ dependencies = [ "nom", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "massa_consensus_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "crossbeam", "crossbeam-channel", - "itertools 0.12.0", + "itertools 0.12.1", "massa_channel", "massa_consensus_exports", "massa_execution_exports", @@ -2816,7 +3168,7 @@ dependencies = [ [[package]] name = "massa_db_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa_hash", @@ -2824,12 +3176,12 @@ dependencies = [ "mockall", "mockall_wrap", "parking_lot", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_db_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "assert_matches", "massa_db_exports", @@ -2841,9 +3193,47 @@ dependencies = [ "tempfile", ] +[[package]] +name = "massa_deferred_calls" +version = "2.2.0" +dependencies = [ + "massa-proto-rs", + "massa_db_exports", + "massa_db_worker", + "massa_ledger_exports", + "massa_models", + "massa_serialization", + "nom", + "parking_lot", + "serde", + "serde_json", + "serde_with", + "tempfile", +] + +[[package]] +name = "massa_event_cache" +version = "0.1.0" +dependencies = [ + "massa_models", + "massa_serialization", + "massa_time", + "mockall", + "mockall_wrap", + "more-asserts 0.3.1", + "nom", + "num_enum", + "parking_lot", + "rand", + "rocksdb", + "serial_test", + "tempfile", + "tracing", +] + [[package]] name = "massa_executed_ops" -version = "2.4.0" +version = "2.5.0" dependencies = [ "massa_db_exports", "massa_db_worker", @@ -2857,11 +3247,12 @@ dependencies = [ [[package]] name = "massa_execution_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa-proto-rs", "massa-sc-runtime", + "massa_deferred_calls", "massa_final_state", "massa_hash", "massa_models", @@ -2874,15 +3265,16 @@ dependencies = [ "num", "serde", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "massa_execution_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "anyhow", + "assert_matches", "blake3", "bs58", "cfg-if", @@ -2895,6 +3287,8 @@ dependencies = [ "massa_channel", "massa_db_exports", "massa_db_worker", + "massa_deferred_calls", + "massa_event_cache", "massa_executed_ops", "massa_execution_exports", "massa_final_state", @@ -2930,7 +3324,7 @@ dependencies = [ [[package]] name = "massa_factory_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa_consensus_exports", @@ -2942,12 +3336,12 @@ dependencies = [ "massa_signature", "massa_storage", "massa_time", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_factory_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "crossbeam-channel", "massa_channel", @@ -2971,7 +3365,7 @@ dependencies = [ [[package]] name = "massa_final_state" -version = "2.4.0" +version = "2.5.0" dependencies = [ "anyhow", "bs58", @@ -2980,6 +3374,7 @@ dependencies = [ "massa_async_pool", "massa_db_exports", "massa_db_worker", + "massa_deferred_calls", "massa_executed_ops", "massa_hash", "massa_ledger_exports", @@ -2998,19 +3393,19 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", ] [[package]] name = "massa_grpc" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "futures-util", - "h2", - "hyper", - "itertools 0.12.0", + "h2 0.3.26", + "hyper 1.5.1", + "itertools 0.12.1", "massa-proto-rs", "massa_bootstrap", "massa_consensus_exports", @@ -3032,20 +3427,21 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tonic", "tonic-health", "tonic-reflection", "tonic-web", - "tower-http", + "tower-http 0.6.2", + "tower-service", "tracing", ] [[package]] name = "massa_hash" -version = "2.4.0" +version = "2.5.0" dependencies = [ "blake3", "bs58", @@ -3055,12 +3451,12 @@ dependencies = [ "serde", "serde_json", "serial_test", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_ledger_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa-proto-rs", @@ -3076,12 +3472,12 @@ dependencies = [ "serde_json", "serde_with", "tempfile", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_ledger_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "massa_db_exports", "massa_db_worker", @@ -3097,7 +3493,7 @@ dependencies = [ [[package]] name = "massa_logging" -version = "2.4.0" +version = "2.5.0" dependencies = [ "serde_json", "tracing", @@ -3105,9 +3501,9 @@ dependencies = [ [[package]] name = "massa_metrics" -version = "2.4.0" +version = "2.5.0" dependencies = [ - "hyper", + "hyper 0.14.31", "lazy_static", "prometheus", "tokio", @@ -3116,7 +3512,7 @@ dependencies = [ [[package]] name = "massa_models" -version = "2.4.0" +version = "2.5.0" dependencies = [ "bitvec", "bs58", @@ -3137,14 +3533,14 @@ dependencies = [ "serde_json", "serde_with", "serial_test", - "thiserror", + "thiserror 1.0.69", "transition", "variant_count", ] [[package]] name = "massa_module_cache" -version = "2.4.0" +version = "2.5.0" dependencies = [ "anyhow", "displaydoc", @@ -3159,13 +3555,13 @@ dependencies = [ "schnellru", "serial_test", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", ] [[package]] name = "massa_pool_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "massa_execution_exports", "massa_models", @@ -3180,7 +3576,7 @@ dependencies = [ [[package]] name = "massa_pool_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "crossbeam-channel", "massa_execution_exports", @@ -3200,7 +3596,7 @@ dependencies = [ [[package]] name = "massa_pos_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "assert_matches", "bitvec", @@ -3220,13 +3616,13 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", ] [[package]] name = "massa_pos_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "massa_hash", "massa_models", @@ -3240,7 +3636,7 @@ dependencies = [ [[package]] name = "massa_protocol_exports" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa_hash", @@ -3258,12 +3654,12 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "massa_protocol_worker" -version = "2.4.0" +version = "2.5.0" dependencies = [ "crossbeam", "ip_rfc", @@ -3301,9 +3697,9 @@ dependencies = [ [[package]] name = "massa_sdk" -version = "2.4.0" +version = "2.5.0" dependencies = [ - "http", + "http 1.2.0", "jsonrpsee", "jsonrpsee-http-client", "jsonrpsee-ws-client", @@ -3312,26 +3708,26 @@ dependencies = [ "massa_models", "massa_time", "rcgen", - "thiserror", + "thiserror 1.0.69", "tonic", "tracing", ] [[package]] name = "massa_serialization" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "nom", "num", "paste", - "thiserror", + "thiserror 1.0.69", "unsigned-varint 0.8.0", ] [[package]] name = "massa_signature" -version = "2.4.0" +version = "2.5.0" dependencies = [ "bs58", "displaydoc", @@ -3343,13 +3739,13 @@ dependencies = [ "serde", "serde_json", "serial_test", - "thiserror", + "thiserror 1.0.69", "transition", ] [[package]] name = "massa_storage" -version = "2.4.0" +version = "2.5.0" dependencies = [ "massa_factory_exports", "massa_metrics", @@ -3360,7 +3756,7 @@ dependencies = [ [[package]] name = "massa_test_framework" -version = "2.4.0" +version = "2.5.0" dependencies = [ "massa_hash", "massa_models", @@ -3370,20 +3766,20 @@ dependencies = [ [[package]] name = "massa_time" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa-proto-rs", "massa_serialization", "nom", "serde", - "thiserror", + "thiserror 1.0.69", "time", ] [[package]] name = "massa_versioning" -version = "2.4.0" +version = "2.5.0" dependencies = [ "assert_matches", "machine", @@ -3401,14 +3797,14 @@ dependencies = [ "num_enum", "parking_lot", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing", "variant_count", ] [[package]] name = "massa_wallet" -version = "2.4.0" +version = "2.5.0" dependencies = [ "displaydoc", "massa_cipher", @@ -3419,16 +3815,17 @@ dependencies = [ "serde_qs", "serde_yaml", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] [[package]] name = "massa_xtask" -version = "2.4.0" +version = "2.5.0" dependencies = [ + "massa-sc-runtime", "massa_models", - "toml_edit 0.21.0", + "toml_edit 0.21.1", "walkdir", ] @@ -3449,9 +3846,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -3473,9 +3870,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -3506,11 +3903,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -3525,6 +3922,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "mockall" version = "0.11.4" @@ -3547,8 +3955,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -3558,9 +3966,9 @@ version = "0.11.2" source = "git+https://github.com/AurelienFT/mockall-wrap?rev=18f88253a000df96cf407dfe4b9158c69c0aeb96#18f88253a000df96cf407dfe4b9158c69c0aeb96" dependencies = [ "cfg-if", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -3577,9 +3985,9 @@ checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "nibble_vec" @@ -3603,12 +4011,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "cfg-if", + "cfg_aliases", "libc", ] @@ -3640,9 +4049,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -3654,11 +4063,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", "serde", @@ -3666,9 +4074,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", "serde", @@ -3682,19 +4090,18 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -3703,11 +4110,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -3716,62 +4122,52 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "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 = "num_enum" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 2.0.0", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro-crate", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "object" -version = "0.28.4" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "crc32fast", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "indexmap 1.9.3", "memchr", ] [[package]] name = "object" -version = "0.32.2" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -3793,21 +4189,21 @@ dependencies = [ [[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" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" @@ -3845,9 +4241,9 @@ checksum = "5ac1db209d9d6dc8e4435b744ed76198494406cd20eb8ca14baf9828664664c8" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -3855,9 +4251,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "backtrace", "cfg-if", @@ -3866,7 +4262,7 @@ dependencies = [ "redox_syscall", "smallvec", "thread-id", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3882,15 +4278,15 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pbkdf2" @@ -3918,22 +4314,22 @@ dependencies = [ "crossbeam", "enum_delegate", "log", - "mio", + "mio 0.8.11", "parking_lot", "quiche", "rand", "serde", "stream_limiter", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "pem" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "serde", ] @@ -3945,20 +4341,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.5" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.5" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -3966,22 +4362,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.5" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "pest_meta" -version = "2.7.5" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -3990,39 +4386,39 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.7.0", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -4042,15 +4438,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -4061,24 +4457,24 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", @@ -4094,9 +4490,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" @@ -4114,15 +4513,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -4130,31 +4529,21 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" -dependencies = [ - "proc-macro2 1.0.71", - "syn 2.0.43", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "proc-macro2 1.0.92", + "syn 2.0.90", ] [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.20.7", + "toml_edit 0.22.22", ] [[package]] @@ -4164,8 +4553,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", "version_check", ] @@ -4176,8 +4565,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "version_check", ] @@ -4192,31 +4581,41 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "procfs" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 1.3.2", - "byteorder", + "bitflags 2.6.0", "hex", "lazy_static", - "rustix 0.36.17", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.6.0", + "hex", ] [[package]] name = "prometheus" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", @@ -4226,14 +4625,14 @@ dependencies = [ "parking_lot", "procfs", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "prost" -version = "0.12.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -4241,13 +4640,12 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "bytes", - "heck", - "itertools 0.11.0", + "heck 0.5.0", + "itertools 0.13.0", "log", "multimap", "once_cell", @@ -4256,29 +4654,28 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.43", + "syn 2.0.90", "tempfile", - "which 4.4.2", ] [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.11.0", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "itertools 0.13.0", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "prost-types" -version = "0.12.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" dependencies = [ "prost", ] @@ -4304,8 +4701,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -4323,7 +4720,7 @@ dependencies = [ "log", "octets", "once_cell", - "ring 0.17.7", + "ring 0.17.8", "slab", "smallvec", "winapi", @@ -4340,11 +4737,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.71", + "proc-macro2 1.0.92", ] [[package]] @@ -4414,9 +4811,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4424,9 +4821,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -4447,22 +4844,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4479,14 +4876,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -4500,13 +4897,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] @@ -4517,27 +4914,27 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "region" -version = "3.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" dependencies = [ "bitflags 1.3.2", "libc", - "mach", - "winapi", + "mach2", + "windows-sys 0.52.0", ] [[package]] name = "rend" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] @@ -4559,23 +4956,24 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rkyv" -version = "0.7.43" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", @@ -4592,12 +4990,12 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.43" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] @@ -4640,9 +5038,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.33.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "num-traits", @@ -4650,9 +5048,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -4660,11 +5058,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[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", ] @@ -4680,79 +5084,103 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - -[[package]] -name = "rustix" -version = "0.38.28" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.12", - "windows-sys 0.52.0", + "linux-raw-sys", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.21.12" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", - "ring 0.17.7", + "once_cell", + "ring 0.17.8", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ - "base64 0.21.5", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", + "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rustyline" @@ -4760,7 +5188,7 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "cfg-if", "clipboard-win", "fd-lock", @@ -4783,16 +5211,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a32af5427251d2e4be14fc151eabe18abb4a7aad5efee7044da9f096c906a43" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -4805,20 +5233,45 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "serde_derive_internals", + "syn 2.0.90", ] [[package]] name = "schnellru" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "cfg-if", "hashbrown 0.13.2", ] @@ -4829,16 +5282,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.7", - "untrusted 0.9.0", -] - [[package]] name = "seahash" version = "4.1.0" @@ -4847,22 +5290,23 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -4870,15 +5314,18 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] [[package]] name = "send_wrapper" @@ -4888,9 +5335,9 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -4908,22 +5355,34 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.109" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -4936,21 +5395,31 @@ checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", +] + +[[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_with" -version = "3.4.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.1.0", + "indexmap 2.7.0", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -4958,23 +5427,23 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.4.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ - "darling 0.20.3", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "darling 0.20.10", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "serde_yaml" -version = "0.9.29" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.7.0", "itoa", "ryu", "serde", @@ -4987,7 +5456,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ - "dashmap", + "dashmap 5.5.3", "futures", "lazy_static", "log", @@ -5001,22 +5470,20 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] -name = "sha-1" -version = "0.9.8" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.7", ] [[package]] @@ -5086,9 +5553,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -5104,9 +5571,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" @@ -5125,37 +5592,37 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "soketto" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "bytes", "futures", - "http", + "http 1.2.0", "httparse", "log", "rand", - "sha-1", + "sha1", ] [[package]] @@ -5210,6 +5677,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.25.0" @@ -5225,11 +5698,11 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", - "proc-macro2 1.0.71", - "quote 1.0.33", + "heck 0.4.1", + "proc-macro2 1.0.92", + "quote 1.0.37", "rustversion", - "syn 2.0.43", + "syn 2.0.90", ] [[package]] @@ -5237,15 +5710,15 @@ name = "substruct" version = "0.1.0" source = "git+https://github.com/massalabs/substruct?rev=2fb3ae0dc9d913a0566ce6415eaa7a7ca1690fe1#2fb3ae0dc9d913a0566ce6415eaa7a7ca1690fe1" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -5264,19 +5737,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.43" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] @@ -5286,16 +5759,33 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", - "unicode-xid 0.2.4", + "unicode-xid 0.2.6", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -5304,23 +5794,34 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" -version = "0.12.12" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", - "rustix 0.38.28", - "windows-sys 0.48.0", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] @@ -5331,29 +5832,49 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.55" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.6", ] [[package]] name = "thiserror-impl" -version = "1.0.55" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "thread-id" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" dependencies = [ "libc", "winapi", @@ -5361,9 +5882,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -5371,9 +5892,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -5392,14 +5913,24 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -5412,9 +5943,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -5427,49 +5958,38 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", "tokio", @@ -5477,9 +5997,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -5489,9 +6009,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -5499,7 +6019,6 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -5512,70 +6031,78 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "0.6.5" +name = "toml" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "toml_datetime" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ - "indexmap 2.1.0", - "toml_datetime", - "winnow", + "serde", ] [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.7.0", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.7.0", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] name = "tonic" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.5", + "base64 0.22.1", "bytes", "flate2", - "h2", - "http", - "http-body", - "hyper", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", "hyper-timeout", + "hyper-util", "percent-encoding", "pin-project", "prost", - "rustls", "rustls-pemfile", + "socket2", "tokio", "tokio-rustls", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5583,22 +6110,23 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ "prettyplease", - "proc-macro2 1.0.71", + "proc-macro2 1.0.92", "prost-build", - "quote 1.0.33", - "syn 2.0.43", + "prost-types", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "tonic-health" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f80db390246dfb46553481f6024f0082ba00178ea495dbb99e70ba9a4fafb5e1" +checksum = "1eaf34ddb812120f5c601162d5429933c9b527d901ab0e7f930d3147e33a09b2" dependencies = [ "async-stream", "prost", @@ -5609,9 +6137,9 @@ dependencies = [ [[package]] name = "tonic-reflection" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa37c513df1339d197f4ba21d28c918b9ef1ac1768265f11ecb6b7f1cba1b76" +checksum = "878d81f52e7fcfd80026b7fdb6a9b578b3c3653ba987f87f0dce4b64043cba27" dependencies = [ "prost", "prost-types", @@ -5622,19 +6150,19 @@ dependencies = [ [[package]] name = "tonic-web" -version = "0.10.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fddb2a37b247e6adcb9f239f4e5cefdcc5ed526141a416b943929f13aea2cce" +checksum = "5299dd20801ad736dccb4a5ea0da7376e59cd98f213bf1c3d478cf53f4834b58" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "bytes", - "http", - "http-body", - "hyper", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", "pin-project", "tokio-stream", "tonic", - "tower-http", + "tower-http 0.5.2", "tower-layer", "tower-service", "tracing", @@ -5662,18 +6190,44 @@ dependencies = [ ] [[package]] -name = "tower-http" -version = "0.4.4" +name = "tower" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ - "bitflags 2.4.1", - "bytes", "futures-core", "futures-util", - "http", - "http-body", - "http-range-header", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.2.0", "pin-project-lite", "tower-layer", "tower-service", @@ -5681,21 +6235,21 @@ dependencies = [ [[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-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 = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5705,20 +6259,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -5737,9 +6291,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -5767,8 +6321,8 @@ version = "0.1.0" source = "git+https://github.com/massalabs/transition.git?rev=93fa3bf82f9f5ff421c78536879b7fd1b948ca75#93fa3bf82f9f5ff421c78536879b7fd1b948ca75" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", "unsigned-varint 0.7.1", ] @@ -5787,42 +6341,27 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unicode-bidi" -version = "0.3.14" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" @@ -5832,9 +6371,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -5848,9 +6387,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "unsigned-varint" @@ -5883,26 +6422,39 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.6.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "valuable" @@ -5916,7 +6468,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae2faf80ac463422992abf4de234731279c058aaf33171ca70277c98406b124" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "syn 1.0.109", ] @@ -5928,15 +6480,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -5959,69 +6511,70 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ - "quote 1.0.33", + "quote 1.0.37", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-encoder" @@ -6034,9 +6587,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce45cc009177ca345a6d041f9062305ad467d15e7d41494f5b81ab46d62d7a58" +checksum = "3be5fa49d7d97f83e095f090dcc178d923f2970f588443283cd7a94974ab8cbe" dependencies = [ "bytes", "cfg-if", @@ -6049,7 +6602,8 @@ dependencies = [ "serde-wasm-bindgen", "shared-buffer", "target-lexicon", - "thiserror", + "thiserror 1.0.69", + "tracing", "wasm-bindgen", "wasmer-compiler", "wasmer-compiler-cranelift", @@ -6057,14 +6611,14 @@ dependencies = [ "wasmer-types", "wasmer-vm", "wat", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "wasmer-compiler" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e044f6140c844602b920deb4526aea3cc9c0d7cf23f00730bb9b2034669f522a" +checksum = "9696a040f935903db440078cd287c0288ab152394122de442fdd21b3eaa8cd2c" dependencies = [ "backtrace", "bytes", @@ -6073,6 +6627,7 @@ dependencies = [ "enumset", "lazy_static", "leb128", + "libc", "memmap2 0.5.10", "more-asserts 0.2.2", "region", @@ -6080,19 +6635,20 @@ dependencies = [ "self_cell", "shared-buffer", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasmer-object", "wasmer-types", "wasmer-vm", "wasmparser", - "winapi", + "windows-sys 0.59.0", + "xxhash-rust", ] [[package]] name = "wasmer-compiler-cranelift" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ce02358eb44a149d791c1d6648fb7f8b2f99cd55e3c4eef0474653ec8cc889" +checksum = "c5959da148d41a5870d1b18a880e19353add47c0ca95e510061275ea467b6b44" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -6109,9 +6665,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-singlepass" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45dc438250a91d6c0a57912714f8b3b899a0f5bb3a5f1eae5bc97858b7a006a9" +checksum = "5fcaa0fa11a5680268d85b17dc032ec90b63138caa66c8484b8f8f6923d7619f" dependencies = [ "byteorder", "dynasm", @@ -6126,23 +6682,45 @@ dependencies = [ "wasmer-types", ] +[[package]] +name = "wasmer-config" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644b7e3547bd7e796d92220f60bf57734914254c6cee56607e80177a3e8a28da" +dependencies = [ + "anyhow", + "bytesize", + "ciborium", + "derive_builder", + "hex", + "indexmap 2.7.0", + "schemars", + "semver", + "serde", + "serde_json", + "serde_yaml", + "thiserror 1.0.69", + "toml 0.8.19", + "url", +] + [[package]] name = "wasmer-derive" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c782d80401edb08e1eba206733f7859db6c997fc5a7f5fb44edc3ecd801468f6" +checksum = "6f448efbe12d656ba96d997c9e338f15cd80934c81f2286c2730cb9224d4e41d" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "wasmer-middlewares" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4f27f76b7b5325476c8851f34920ae562ef0de3c830fdbc4feafff6782187" +checksum = "c4a3c1a7474e5abd75fe6bde4d34fee77c22261b45f157bb769d4a297749463c" dependencies = [ "wasmer", "wasmer-types", @@ -6151,67 +6729,73 @@ dependencies = [ [[package]] name = "wasmer-object" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66dc76ddf602e15266c6cc792dde7592cb3fcfe2bf55b792c51bb400d7a26c0b" +checksum = "70dabd8c3abd7b72a89052f0dd1991aeeabed2a783d474f6c06bca42c8ce73c4" dependencies = [ - "object 0.28.4", - "thiserror", + "object 0.29.0", + "thiserror 1.0.69", "wasmer-types", ] [[package]] name = "wasmer-types" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd09e80d4d74bb9fd0ce6c3c106b1ceba1a050f9948db9d9b78ae53c172d6157" +checksum = "c8b383ef63005176be3bc2056d3b4078ae1497b324f573d79acbf81036f1c9ec" dependencies = [ "bytecheck", "enum-iterator", "enumset", + "getrandom", + "hex", "indexmap 1.9.3", "more-asserts 0.2.2", "rkyv", + "sha2 0.10.8", "target-lexicon", - "thiserror", + "thiserror 1.0.69", + "webc", + "xxhash-rust", ] [[package]] name = "wasmer-vm" -version = "4.2.4" +version = "4.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcd8a4fd36414a7b6a003dbfbd32393bce3e155d715dd877c05c1b7a41d224d" +checksum = "3c371597ec33248e775de641c7a475173fb60f2b5ea085c74d34cee9fad06b83" dependencies = [ "backtrace", "cc", "cfg-if", "corosensei", "crossbeam-queue", - "dashmap", + "dashmap 6.1.0", "derivative", "enum-iterator", "fnv", "indexmap 1.9.3", "lazy_static", "libc", - "mach", + "mach2", "memoffset", "more-asserts 0.2.2", "region", "scopeguard", - "thiserror", + "thiserror 1.0.69", "wasmer-types", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "wasmparser" -version = "0.95.0" +version = "0.121.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" dependencies = [ - "indexmap 1.9.3", - "url", + "bitflags 2.6.0", + "indexmap 2.7.0", + "semver", ] [[package]] @@ -6237,30 +6821,50 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki-roots" -version = "0.25.3" +name = "webc" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "cdea84cf234555864ca9b7a5084c1a99dbdf2d148035f62a09b19ce5606532c1" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bytes", + "cfg-if", + "ciborium", + "document-features", + "flate2", + "indexmap 1.9.3", + "libc", + "once_cell", + "semver", + "serde", + "serde_json", + "sha2 0.10.8", + "shared-buffer", + "tar", + "tempfile", + "thiserror 1.0.69", + "toml 0.8.19", + "url", + "wasmer-config", +] [[package]] -name = "which" -version = "4.4.2" +name = "webpki-roots" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.28", + "rustls-pki-types", ] [[package]] @@ -6272,7 +6876,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.28", + "rustix", "windows-sys 0.48.0", ] @@ -6294,11 +6898,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -6309,11 +6913,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -6329,15 +6933,6 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -6353,22 +6948,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.6", ] [[package]] @@ -6388,25 +6977,20 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "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.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6415,9 +6999,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -6425,12 +7009,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6439,9 +7017,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -6449,12 +7027,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6463,21 +7035,21 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.33.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" [[package]] name = "windows_i686_msvc" @@ -6487,9 +7059,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -6497,12 +7069,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6511,15 +7077,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -6529,9 +7089,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -6541,31 +7101,46 @@ checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.5.31" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -6589,10 +7164,27 @@ dependencies = [ "oid-registry", "ring 0.16.20", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + [[package]] name = "yaml-rust" version = "0.4.5" @@ -6611,31 +7203,77 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", + "synstructure 0.13.1", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -6646,16 +7284,38 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.43", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 293ef47ba51..4842ee90c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "massa-versioning", "massa-grpc", "massa-xtask", + "massa-event-cache", ] resolver = "2" @@ -76,6 +77,7 @@ massa_consensus_exports = { path = "./massa-consensus-exports" } massa_consensus_worker = { path = "./massa-consensus-worker" } massa_db_exports = { path = "./massa-db-exports" } massa_db_worker = { path = "./massa-db-worker" } +massa_deferred_calls = { path = "./massa-deferred-calls" } massa_executed_ops = { path = "./massa-executed-ops" } massa_execution_exports = { path = "./massa-execution-exports" } massa_execution_worker = { path = "./massa-execution-worker" } @@ -104,11 +106,20 @@ massa_test_framework = { path = "./massa-test-framework" } massa_time = { path = "./massa-time" } massa_versioning = { path = "./massa-versioning" } massa_wallet = { path = "./massa-wallet" } +massa_event_cache = { path = "./massa-event-cache" } # Massa projects dependencies -massa-proto-rs = { git = "https://github.com/massalabs/massa-proto-rs", "rev" = "38950875a7aa406fedc4f0b8336864e5ff290f2c" } -massa-sc-runtime = { git = "https://github.com/massalabs/massa-sc-runtime", "rev" = "6738815dba5f3be7b7a03b88d6feb31f151a8682" } +# massa-proto-rs = { git = "https://github.com/massalabs/massa-proto-rs", branch = "deferred_calls" } +# massa-sc-runtime = { git = "https://github.com/massalabs/massa-sc-runtime", "branch" = "deferred_calls" } +massa-proto-rs = { git = "https://github.com/massalabs/massa-proto-rs", "rev" = "b5267178eaf266ec724691d7de163e4c34343416" } +massa-sc-runtime = { git = "https://github.com/massalabs/massa-sc-runtime", "rev" = "f5a584b9f8050f332c9ed332bd0a40f8e0372807" } + + peernet = { git = "https://github.com/massalabs/PeerNet", "rev" = "04b05ddd320fbe76cc858115af7b5fc28bdb8310" } +# Dev only - use local dependencies +# massa-proto-rs = { path = "../massa-proto-rs" } +# massa-sc-runtime = { path = "../massa-sc-runtime" } +# peernet = { path = "../peernet" } # Common dependencies transition = { git = "https://github.com/massalabs/transition.git", "rev" = "93fa3bf82f9f5ff421c78536879b7fd1b948ca75" } @@ -138,15 +149,15 @@ futures = "0.3" futures-util = "0.3" h2 = "0.3" hex-literal = "0.4" -http = "0.2" +http = "1.1.0" humantime = "2.1" -hyper = "0.14" +hyper = "1" ip_rfc = "0.1" is-terminal = "0.4" itertools = "0.12" -jsonrpsee = "0.20" -jsonrpsee-http-client = "0.20" -jsonrpsee-ws-client = "0.20" +jsonrpsee = "0.24" +jsonrpsee-http-client = "0.24" +jsonrpsee-ws-client = "0.24" lazy_static = "1.4" libsecp256k1 = "=0.7" mio = "0.8.11" @@ -175,7 +186,7 @@ serde = "1.0" serde_json = "1.0" serde_qs = "0.12" serde_with = "3.3" -serde_yaml = "0.9" +serde_yaml = "0.9.34" # 0.9.34+deprecated serial_test = "2.0" sha2 = "=0.10" sha3 = "=0.10" @@ -191,15 +202,17 @@ time = "0.3" tokio = "1.23" tokio-stream = "0.1" toml_edit = "0.21" -tonic = "0.10" -tonic-health = "0.10" -tonic-reflection = "0.10" -tonic-web = "0.10" -tower = "0.4.13" -tower-http = "0.4.0" +tonic = "0.12" +tonic-health = "0.12" +tonic-reflection = "0.12" +tonic-web = "0.12" +tower = "0.4" +tower-http = "0.6" +tower-service = "0.3" tracing = "0.1" tracing-subscriber = "0.3" unsigned-varint = "0.8" variant_count = "1.1" walkdir = "2.3" zeroize = { version = "1.7", features = ["derive"] } +prost = { version = "=0.13" } diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000000..5959126f53d --- /dev/null +++ b/_typos.toml @@ -0,0 +1,28 @@ +[files] +extend-exclude = [ + "lcov.info", + "massa-node/base_config/*.json", + "massa-node/src/tests/config.toml", + "*.patch" +] + +[default] +extend-ignore-re = [ + # Note: + # Address (AU, AS): 32 - 62 characters + # Secret key (S): 18 - 62 characters + # Public key (P): 18 - 62 characters + # NodeId (N) + # OperationId (O) + "(AU|AS|N|S|P|O)\\d\\w{18,62}", +] + +[default.extend-words] +# short: serialize +ser = "ser" +# short: deserialize +der = "der" +# short: numerator +numer = "numer" +# WONTFIX: grpc_model::operation_type::Type::ExecutSc +Execut = "Execut" \ No newline at end of file diff --git a/massa-api-exports/Cargo.toml b/massa-api-exports/Cargo.toml index ef61628010b..48f39472b43 100644 --- a/massa-api-exports/Cargo.toml +++ b/massa-api-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_api_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" @@ -22,6 +22,7 @@ massa_final_state = {workspace = true} massa_hash = {workspace = true} massa_wallet = {workspace = true} massa_versioning = {workspace = true} +massa_deferred_calls = { workspace = true } [dev-dependencies] serial_test = {workspace = true} diff --git a/massa-api-exports/src/address.rs b/massa-api-exports/src/address.rs index 2815fb17f14..a86c4509249 100644 --- a/massa-api-exports/src/address.rs +++ b/massa-api-exports/src/address.rs @@ -156,7 +156,7 @@ impl std::fmt::Display for CompactAddressInfo { } } -/// filter used when retrieving address informations +/// filter used when retrieving address information #[derive(Debug, Deserialize, Clone, Serialize)] pub struct AddressFilter { /// Address diff --git a/massa-api-exports/src/config.rs b/massa-api-exports/src/config.rs index 6ec7e31abc3..7412d3e4af9 100644 --- a/massa-api-exports/src/config.rs +++ b/massa-api-exports/src/config.rs @@ -1,5 +1,6 @@ // Copyright (c) 2022 MASSA LABS +use massa_deferred_calls::config::DeferredCallsConfig; use massa_models::amount::Amount; use massa_signature::KeyPair; use massa_time::MassaTime; @@ -84,4 +85,6 @@ pub struct APIConfig { pub deferred_credits_delta: MassaTime, /// minimal fees to include an operation in a block pub minimal_fees: Amount, + /// deferred calls config + pub deferred_calls_config: DeferredCallsConfig, } diff --git a/massa-api-exports/src/execution.rs b/massa-api-exports/src/execution.rs index c90cc7eb538..527d3cb574e 100644 --- a/massa-api-exports/src/execution.rs +++ b/massa-api-exports/src/execution.rs @@ -1,5 +1,6 @@ // Copyright (c) 2022 MASSA LABS +use massa_deferred_calls::DeferredCall; use massa_final_state::StateChanges; use massa_models::{ address::Address, amount::Amount, block_id::BlockId, operation::OperationId, @@ -121,3 +122,45 @@ pub struct Transfer { /// Context pub context: TransferContext, } + +/// request for deferred call quote +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DeferredCallsQuoteRequest { + /// The slot at which the deferred call is to be executed. + pub target_slot: Slot, + /// The maximum gas requested. + pub max_gas_request: u64, + /// Size of parameters + pub params_size: u64, +} + +/// The response to a request for a deferred call quote. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DeferredCallsQuoteResponse { + /// The slot at which the deferred call is to be executed. + pub target_slot: Slot, + /// The maximum gas requested. + pub max_gas_request: u64, + /// if the slot is bookable + pub available: bool, + /// the cost for booking the call + pub price: Amount, +} + +/// response for deferred call +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DeferredCallResponse { + /// deferred call id + pub call_id: String, + /// deferred call + pub call: DeferredCall, +} + +/// response for deferred calls by slot +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DeferredCallsSlotResponse { + /// deferred calls + pub slot: Slot, + /// deferred calls + pub call_ids: Vec, +} diff --git a/massa-api-exports/src/operation.rs b/massa-api-exports/src/operation.rs index b4caadf85e3..ba2f9aba8eb 100644 --- a/massa-api-exports/src/operation.rs +++ b/massa-api-exports/src/operation.rs @@ -54,7 +54,7 @@ impl std::fmt::Display for OperationInfo { "operation is not final", "finality unknown" ), - display_option_bool(self.op_exec_status, "succes", "failed", "status unknown") + display_option_bool(self.op_exec_status, "success", "failed", "status unknown") )?; writeln!(f, "In blocks:")?; for block_id in &self.in_blocks { diff --git a/massa-api/Cargo.toml b/massa-api/Cargo.toml index 1a70511ad20..f273060a818 100644 --- a/massa-api/Cargo.toml +++ b/massa-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_api" -version = "2.4.0" +version = "2.5.0" edition = "2021" [features] @@ -29,7 +29,6 @@ massa_wallet = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } -hyper = { workspace = true } itertools = { workspace = true } jsonrpsee = { workspace = true, "features" = ["server", "macros"] } parking_lot = { workspace = true, "features" = ["deadlock_detection"] } @@ -40,6 +39,7 @@ tokio-stream = { workspace = true, "features" = ["sync"] } tower = { workspace = true, "features" = ["full"] } tower-http = { workspace = true, "features" = ["cors"] } tracing = { workspace = true } +http = { workspace = true } [dev-dependencies] jsonrpsee = { workspace = true, "features" = ["full"] } diff --git a/massa-api/src/api.rs b/massa-api/src/api.rs index 51d10796e51..fc9919b9586 100644 --- a/massa-api/src/api.rs +++ b/massa-api/src/api.rs @@ -7,7 +7,7 @@ use crate::{ApiServer, ApiV2, StopHandle, API}; use async_trait::async_trait; use futures::future::{self, Either}; use futures::StreamExt; -use jsonrpsee::core::{Error as JsonRpseeError, RpcResult, SubscriptionResult}; +use jsonrpsee::core::{client::Error as JsonRpseeError, RpcResult, SubscriptionResult}; use jsonrpsee::{PendingSubscriptionSink, SubscriptionMessage}; use massa_api_exports::config::APIConfig; use massa_api_exports::error::ApiError; @@ -151,7 +151,7 @@ impl MassaApiServer for API { } } -// Brodcast the stream(sender) content via a WebSocket +// Broadcast the stream(sender) content via a WebSocket async fn broadcast_via_ws( sender: tokio::sync::broadcast::Sender, pending: PendingSubscriptionSink, diff --git a/massa-api/src/lib.rs b/massa-api/src/lib.rs index 8055167d489..c448292c12a 100644 --- a/massa-api/src/lib.rs +++ b/massa-api/src/lib.rs @@ -5,13 +5,16 @@ #![warn(unused_crate_dependencies)] use api_trait::MassaApiServer; -use hyper::Method; -use jsonrpsee::core::{Error as JsonRpseeError, RpcResult}; +use http::Method; +use jsonrpsee::core::{client::Error as JsonRpseeError, RpcResult}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::middleware::HostFilterLayer; -use jsonrpsee::server::{BatchRequestConfig, ServerBuilder, ServerHandle}; +use jsonrpsee::server::middleware::http::HostFilterLayer; +use jsonrpsee::server::{BatchRequestConfig, PingConfig, ServerBuilder, ServerHandle}; use jsonrpsee::RpcModule; -use massa_api_exports::execution::Transfer; +use massa_api_exports::execution::{ + DeferredCallResponse, DeferredCallsQuoteRequest, DeferredCallsQuoteResponse, + DeferredCallsSlotResponse, Transfer, +}; use massa_api_exports::{ address::{AddressFilter, AddressInfo}, block::{BlockInfo, BlockSummary}, @@ -108,11 +111,11 @@ pub struct Private { pub struct ApiV2 { /// link to the consensus component pub consensus_controller: Box, - /// channels with informations broadcasted by the consensus + /// channels with information broadcasted by the consensus pub consensus_broadcasts: ConsensusBroadcasts, /// link to the execution component pub execution_controller: Box, - /// channels with informations broadcasted by the pool + /// channels with information broadcasted by the pool pub pool_broadcasts: PoolBroadcasts, /// API settings pub api_settings: APIConfig, @@ -150,6 +153,7 @@ async fn serve( url: &SocketAddr, api_config: &APIConfig, ) -> Result { + let ping_config = PingConfig::new().ping_interval(api_config.ping_interval.to_duration()); let mut server_builder = ServerBuilder::new() .max_request_body_size(api_config.max_request_body_size) .max_response_body_size(api_config.max_response_body_size) @@ -159,7 +163,7 @@ async fn serve( } else { BatchRequestConfig::Disabled }) - .ping_interval(api_config.ping_interval.to_duration()); + .enable_ws_ping(ping_config); if api_config.enable_http && !api_config.enable_ws { server_builder = server_builder.http_only(); @@ -174,7 +178,7 @@ async fn serve( .allow_methods([Method::POST, Method::OPTIONS]) // Allow requests from any origin .allow_origin(Any) - .allow_headers([hyper::header::CONTENT_TYPE]); + .allow_headers([http::header::CONTENT_TYPE]); let hosts = if api_config.allow_hosts.is_empty() { vec!["*:*"] @@ -193,7 +197,7 @@ async fn serve( .layer(allowed_hosts); let server = server_builder - .set_middleware(middleware) + .set_http_middleware(middleware) .build(url) .await .expect("failed to build server"); @@ -400,6 +404,27 @@ pub trait MassaRpc { /// Get OpenRPC specification. #[method(name = "rpc.discover")] async fn get_openrpc_spec(&self) -> RpcResult; + + /// DeferredCall quote + #[method(name = "get_deferred_call_quote")] + async fn get_deferred_call_quote( + &self, + arg: Vec, + ) -> RpcResult>; + + /// DeferredCall get info + #[method(name = "get_deferred_call_info")] + async fn get_deferred_call_info( + &self, + arg: Vec, + ) -> RpcResult>; + + /// List deferred calls for given slot + #[method(name = "get_deferred_call_ids_by_slot")] + async fn get_deferred_call_ids_by_slot( + &self, + arg: Vec, + ) -> RpcResult>; } fn wrong_api() -> RpcResult { diff --git a/massa-api/src/private.rs b/massa-api/src/private.rs index a22adc3f7c4..ca144dc18af 100644 --- a/massa-api/src/private.rs +++ b/massa-api/src/private.rs @@ -3,7 +3,7 @@ use crate::{MassaRpcServer, Private, RpcServer, StopHandle, Value, API}; use async_trait::async_trait; -use jsonrpsee::core::{Error as JsonRpseeError, RpcResult}; +use jsonrpsee::core::{client::Error as JsonRpseeError, RpcResult}; use massa_api_exports::{ address::{AddressFilter, AddressInfo}, block::{BlockInfo, BlockSummary}, @@ -11,7 +11,11 @@ use massa_api_exports::{ datastore::{DatastoreEntryInput, DatastoreEntryOutput}, endorsement::EndorsementInfo, error::ApiError, - execution::{ExecuteReadOnlyResponse, ReadOnlyBytecodeExecution, ReadOnlyCall, Transfer}, + execution::{ + DeferredCallResponse, DeferredCallsQuoteRequest, DeferredCallsQuoteResponse, + DeferredCallsSlotResponse, ExecuteReadOnlyResponse, ReadOnlyBytecodeExecution, + ReadOnlyCall, Transfer, + }, node::NodeStatus, operation::{OperationInfo, OperationInput}, page::{PageRequest, PagedVec}, @@ -351,6 +355,26 @@ impl MassaRpcServer for API { ) } + async fn get_deferred_call_quote( + &self, + _req: Vec, + ) -> RpcResult> { + crate::wrong_api::>() + } + async fn get_deferred_call_info( + &self, + _arg: Vec, + ) -> RpcResult> { + crate::wrong_api::>() + } + + async fn get_deferred_call_ids_by_slot( + &self, + _slot: Vec, + ) -> RpcResult> { + crate::wrong_api::>() + } + async fn get_openrpc_spec(&self) -> RpcResult { crate::wrong_api::() } diff --git a/massa-api/src/public.rs b/massa-api/src/public.rs index 12fb80efada..391120833db 100644 --- a/massa-api/src/public.rs +++ b/massa-api/src/public.rs @@ -4,7 +4,7 @@ use crate::{MassaRpcServer, Public, RpcServer, StopHandle, Value, API}; use async_trait::async_trait; use itertools::{izip, Itertools}; -use jsonrpsee::core::{Error as JsonRpseeError, RpcResult}; +use jsonrpsee::core::{client::Error as JsonRpseeError, RpcResult}; use massa_api_exports::{ address::{AddressFilter, AddressInfo}, block::{BlockInfo, BlockInfoContent, BlockSummary}, @@ -13,7 +13,9 @@ use massa_api_exports::{ endorsement::EndorsementInfo, error::ApiError, execution::{ - ExecuteReadOnlyResponse, ReadOnlyBytecodeExecution, ReadOnlyCall, ReadOnlyResult, Transfer, + DeferredCallResponse, DeferredCallsQuoteRequest, DeferredCallsQuoteResponse, + DeferredCallsSlotResponse, ExecuteReadOnlyResponse, ReadOnlyBytecodeExecution, + ReadOnlyCall, ReadOnlyResult, Transfer, }, node::NodeStatus, operation::{OperationInfo, OperationInput}, @@ -37,20 +39,17 @@ use massa_models::{ composite::PubkeySig, config::CompactConfig, datastore::DatastoreDeserializer, - endorsement::EndorsementId, - endorsement::SecureShareEndorsement, + deferred_calls::DeferredCallId, + endorsement::{EndorsementId, SecureShareEndorsement}, error::ModelsError, execution::EventFilter, node::NodeId, - operation::OperationDeserializer, - operation::OperationId, - operation::{OperationType, SecureShareOperation}, + operation::{OperationDeserializer, OperationId, OperationType, SecureShareOperation}, output_event::SCOutputEvent, prehash::{PreHashMap, PreHashSet}, secure_share::SecureShareDeserializer, slot::{IndexedSlot, Slot}, - timeslots, - timeslots::{get_latest_block_slot_at_timestamp, time_range_to_slot_range}, + timeslots::{self, get_latest_block_slot_at_timestamp, time_range_to_slot_range}, version::Version, }; use massa_pool_exports::PoolController; @@ -63,8 +62,8 @@ use massa_versioning::versioning_factory::FactoryStrategy; use massa_versioning::{ keypair_factory::KeyPairFactory, versioning::MipStore, versioning_factory::VersioningFactory, }; -use std::collections::BTreeMap; use std::net::{IpAddr, SocketAddr}; +use std::{collections::BTreeMap, str::FromStr}; impl API { /// generate a new public API @@ -1157,6 +1156,132 @@ impl MassaRpcServer for API { Ok(res?) } + async fn get_deferred_call_quote( + &self, + req: Vec, + ) -> RpcResult> { + if req.len() as u64 > self.0.api_settings.max_arguments { + return Err(ApiError::BadRequest("too many arguments".into()).into()); + } + + let queries: Vec = req + .into_iter() + .map(|call| ExecutionQueryRequestItem::DeferredCallQuote { + target_slot: call.target_slot, + max_gas_request: call.max_gas_request, + params_size: call.params_size, + }) + .collect(); + + let result = self + .0 + .execution_controller + .query_state(ExecutionQueryRequest { requests: queries }) + .responses + .into_iter() + .map(|response| match response { + Ok(ExecutionQueryResponseItem::DeferredCallQuote( + target_slot, + max_gas_request, + available, + price, + )) => Ok(DeferredCallsQuoteResponse { + target_slot, + max_gas_request, + available, + price, + }), + Ok(_) => Err(ApiError::InternalServerError( + "unexpected response type".to_string(), + )), + Err(err) => Err(ApiError::InternalServerError(err.to_string())), + }) + .collect::, ApiError>>()?; + + Ok(result) + } + + async fn get_deferred_call_info( + &self, + arg: Vec, + ) -> RpcResult> { + if arg.len() as u64 > self.0.api_settings.max_arguments { + return Err(ApiError::BadRequest("too many arguments".into()).into()); + } + + let requests: Vec = arg + .into_iter() + .map(|id_str| { + DeferredCallId::from_str(&id_str) + .map_err(|e| ApiError::BadRequest(e.to_string())) + .map(ExecutionQueryRequestItem::DeferredCallInfo) + }) + .collect::>()?; + + let result: Vec = self + .0 + .execution_controller + .query_state(ExecutionQueryRequest { requests }) + .responses + .into_iter() + .map(|exec| match exec { + Ok(ExecutionQueryResponseItem::DeferredCallInfo(id, call)) => { + Ok(DeferredCallResponse { + call_id: id.to_string(), + call, + }) + } + Ok(_) => Err(ApiError::InternalServerError( + "unexpected response type".to_string(), + )), + Err(err) => Err(ApiError::InternalServerError(err.to_string())), + }) + .collect::>()?; + + Ok(result) + } + + async fn get_deferred_call_ids_by_slot( + &self, + slots: Vec, + ) -> RpcResult> { + if slots.len() as u64 > self.0.api_settings.max_arguments { + return Err(ApiError::BadRequest("too many arguments".into()).into()); + } + + let requests: Vec = slots + .into_iter() + .map(ExecutionQueryRequestItem::DeferredCallsBySlot) + .collect(); + + let mut slot_calls = Vec::new(); + + for exec in self + .0 + .execution_controller + .query_state(ExecutionQueryRequest { requests }) + .responses + .into_iter() + { + match exec { + Ok(ExecutionQueryResponseItem::DeferredCallsBySlot(slot, result)) => { + let call_ids = result.into_iter().map(|id| id.to_string()).collect(); + + slot_calls.push(DeferredCallsSlotResponse { slot, call_ids }); + } + Ok(_) => { + return Err(ApiError::InternalServerError( + "unexpected response type".to_string(), + ) + .into()) + } + Err(err) => return Err(ApiError::InternalServerError(err.to_string()).into()), + } + } + + Ok(slot_calls) + } + /// send operations async fn send_operations(&self, ops: Vec) -> RpcResult> { let mut cmd_sender = self.0.pool_command_sender.clone(); diff --git a/massa-api/src/tests/mock.rs b/massa-api/src/tests/mock.rs index 735da5266b9..9f1f714d215 100644 --- a/massa-api/src/tests/mock.rs +++ b/massa-api/src/tests/mock.rs @@ -70,6 +70,7 @@ pub(crate) fn get_apiv2_server(addr: &SocketAddr) -> (API, APIConfig) { chain_id: *CHAINID, deferred_credits_delta: MassaTime::from_millis(24 * 3600 * 2), minimal_fees: Amount::zero(), + deferred_calls_config: Default::default(), }; // let shared_storage: massa_storage::Storage = massa_storage::Storage::create_root(); @@ -146,6 +147,7 @@ pub(crate) fn start_public_api(addr: SocketAddr) -> (API, APIConfig) { chain_id: *CHAINID, deferred_credits_delta: MassaTime::from_millis(24 * 3600 * 2), minimal_fees: Amount::zero(), + deferred_calls_config: Default::default(), }; let shared_storage: massa_storage::Storage = massa_storage::Storage::create_root(); diff --git a/massa-api/src/tests/public.rs b/massa-api/src/tests/public.rs index 05d1ddd5903..2e10680d35b 100644 --- a/massa-api/src/tests/public.rs +++ b/massa-api/src/tests/public.rs @@ -8,7 +8,7 @@ use std::{ }; use jsonrpsee::{ - core::{client::ClientT, Error}, + core::{client::ClientT, client::Error}, http_client::HttpClientBuilder, rpc_params, }; diff --git a/massa-api/src/tests/server.rs b/massa-api/src/tests/server.rs index f15f76685f7..e67794f79ca 100644 --- a/massa-api/src/tests/server.rs +++ b/massa-api/src/tests/server.rs @@ -83,13 +83,11 @@ async fn max_request_size() { OperationId::from_str("O1q4CBcuYo8YANEV34W4JRWVHrzcYns19VJfyAB7jT4qfitAnMC").unwrap(), OperationId::from_str("O1q4CBcuYo8YANEV34W4JRWVHrzcYns19VJfyAB7jT4qfitAnMC").unwrap(), ]]; - let response: Result, jsonrpsee::core::Error> = + let response: Result, jsonrpsee::core::client::Error> = client.request("get_operations", params).await; - assert!(response - .unwrap_err() - .to_string() - .contains("status code: 413")); + let response_str = response.unwrap_err().to_string(); + assert!(response_str.contains("Request rejected `413`")); api_handle.stop().await; } @@ -142,13 +140,11 @@ async fn http_disabled() { )) .unwrap(); - let response: Result, jsonrpsee::core::Error> = + let response: Result, jsonrpsee::core::client::Error> = client.request("get_operations", rpc_params![]).await; - assert!(response - .unwrap_err() - .to_string() - .contains("status code: 403")); + let response_str = response.unwrap_err().to_string(); + assert!(response_str.contains("Request rejected `403`")); api_handle.stop().await; } @@ -178,7 +174,7 @@ async fn host_allowed() { )) .unwrap(); - let response: Result, jsonrpsee::core::Error> = + let response: Result, jsonrpsee::core::client::Error> = client.request("get_operations", rpc_params![]).await; // response OK but invalid params (no params provided) @@ -205,14 +201,12 @@ async fn host_allowed() { )) .unwrap(); - let response: Result, jsonrpsee::core::Error> = + let response: Result, jsonrpsee::core::client::Error> = client.request("get_operations", rpc_params![]).await; // host not allowed - assert!(response - .unwrap_err() - .to_string() - .contains("status code: 403")); + let response_str = response.unwrap_err().to_string(); + assert!(response_str.contains("Request rejected `403`")); api_handle.stop().await; api_handle2.stop().await; diff --git a/massa-async-pool/Cargo.toml b/massa-async-pool/Cargo.toml index f314d762a5b..55b288c9e25 100644 --- a/massa-async-pool/Cargo.toml +++ b/massa-async-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_async_pool" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-async-pool/src/changes.rs b/massa-async-pool/src/changes.rs index 22756446237..da81c2a0fd0 100644 --- a/massa-async-pool/src/changes.rs +++ b/massa-async-pool/src/changes.rs @@ -14,7 +14,7 @@ use crate::{ AsyncMessageDeserializer, AsyncMessageSerializer, }; -use massa_ledger_exports::{ +use massa_models::types::{ Applicable, SetOrKeep, SetUpdateOrDelete, SetUpdateOrDeleteDeserializer, SetUpdateOrDeleteSerializer, }; @@ -233,7 +233,7 @@ impl AsyncPoolChanges { mod tests { use std::str::FromStr; - use massa_ledger_exports::SetUpdateOrDelete; + use massa_models::types::SetUpdateOrDelete; use massa_models::{address::Address, amount::Amount, slot::Slot}; use massa_serialization::{DeserializeError, Deserializer, Serializer}; @@ -334,7 +334,7 @@ mod tests { assert!(msg.can_be_executed); } _ => { - panic!("Unexpect value"); + panic!("Unexpected value"); } } diff --git a/massa-async-pool/src/mapping_grpc.rs b/massa-async-pool/src/mapping_grpc.rs index 3aefef50024..4aa5b52c1b1 100644 --- a/massa-async-pool/src/mapping_grpc.rs +++ b/massa-async-pool/src/mapping_grpc.rs @@ -1,7 +1,7 @@ // Copyright (c) 2023 MASSA LABS use crate::{AsyncMessage, AsyncMessageTrigger, AsyncMessageUpdate}; -use massa_ledger_exports::SetOrKeep; +use massa_models::types::SetOrKeep; use massa_proto_rs::massa::model::v1 as grpc_model; impl From for grpc_model::AsyncMessage { diff --git a/massa-async-pool/src/message.rs b/massa-async-pool/src/message.rs index 9b5379426ea..0c1ff814a96 100644 --- a/massa-async-pool/src/message.rs +++ b/massa-async-pool/src/message.rs @@ -2,12 +2,12 @@ //! This file defines the structure representing an asynchronous message -use massa_ledger_exports::{Applicable, SetOrKeep, SetOrKeepDeserializer, SetOrKeepSerializer}; use massa_models::address::{AddressDeserializer, AddressSerializer}; use massa_models::amount::{AmountDeserializer, AmountSerializer}; use massa_models::config::GENESIS_KEY; use massa_models::serialization::{StringDeserializer, StringSerializer}; use massa_models::slot::{SlotDeserializer, SlotSerializer}; +use massa_models::types::{Applicable, SetOrKeep, SetOrKeepDeserializer, SetOrKeepSerializer}; use massa_models::{ address::Address, amount::Amount, @@ -1006,7 +1006,7 @@ impl Applicable for AsyncMessageInfo { #[cfg(test)] mod tests { - use massa_ledger_exports::{Applicable, SetOrKeep}; + use massa_models::types::{Applicable, SetOrKeep}; use massa_serialization::{DeserializeError, Deserializer, Serializer}; use num::rational::Ratio; diff --git a/massa-async-pool/src/pool.rs b/massa-async-pool/src/pool.rs index 403787e3388..5905380fa9a 100644 --- a/massa-async-pool/src/pool.rs +++ b/massa-async-pool/src/pool.rs @@ -13,8 +13,8 @@ use massa_db_exports::{ DBBatch, MassaDirection, MassaIteratorMode, ShareableMassaDBController, ASYNC_POOL_PREFIX, MESSAGE_ID_DESER_ERROR, MESSAGE_ID_SER_ERROR, MESSAGE_SER_ERROR, STATE_CF, }; -use massa_ledger_exports::{Applicable, SetOrKeep, SetUpdateOrDelete}; use massa_models::address::Address; +use massa_models::types::{Applicable, SetOrKeep, SetUpdateOrDelete}; use massa_serialization::{ DeserializeError, Deserializer, SerializeError, Serializer, U64VarIntDeserializer, U64VarIntSerializer, @@ -1284,7 +1284,7 @@ mod tests { .write_batch(batch, versioning_batch, Some(slot_1)); let content = dump_column(pool.db.clone(), "state"); - assert_eq!(content.len(), 26); // 2 entries added, splitted in 13 prefix + assert_eq!(content.len(), 26); // 2 entries added, split in 13 prefix let mut batch2 = DBBatch::new(); pool.delete_entry(&message_id, &mut batch2); diff --git a/massa-bootstrap/Cargo.toml b/massa-bootstrap/Cargo.toml index e35a118e7c2..6d20f3725e1 100644 --- a/massa-bootstrap/Cargo.toml +++ b/massa-bootstrap/Cargo.toml @@ -1,58 +1,68 @@ [package] name = "massa_bootstrap" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" [features] -test-exports = ["massa_final_state/test-exports", "massa_ledger_worker/test-exports", "massa_consensus_exports/test-exports", "massa_async_pool/test-exports"] -sandbox = ["massa_async_pool/sandbox", "massa_final_state/sandbox", "massa_models/sandbox"] +test-exports = [ + "massa_final_state/test-exports", + "massa_ledger_worker/test-exports", + "massa_consensus_exports/test-exports", + "massa_async_pool/test-exports", +] +sandbox = [ + "massa_async_pool/sandbox", + "massa_final_state/sandbox", + "massa_models/sandbox", +] heavy_testing = [] [dependencies] -displaydoc = {workspace = true} -num_enum = {workspace = true} -nom = {workspace = true} -rand = {workspace = true} -serde = {workspace = true, "features" = ["derive"]} -serde_json = {workspace = true} # BOM UPGRADE Revert to "1.0" if problem -humantime = {workspace = true} -thiserror = {workspace = true} -parking_lot = {workspace = true} -tracing = {workspace = true} -substruct = {workspace = true} -socket2 = {workspace = true} -crossbeam = {workspace = true} # BOM UPGRADE Revert to "0.8.2" if problem -mio = {workspace = true, "features" = ["net", "os-poll"]} +displaydoc = { workspace = true } +num_enum = { workspace = true } +nom = { workspace = true } +rand = { workspace = true } +serde = { workspace = true, "features" = ["derive"] } +serde_json = { workspace = true } # BOM UPGRADE Revert to "1.0" if problem +humantime = { workspace = true } +thiserror = { workspace = true } +parking_lot = { workspace = true } +tracing = { workspace = true } +substruct = { workspace = true } +socket2 = { workspace = true } +crossbeam = { workspace = true } # BOM UPGRADE Revert to "0.8.2" if problem +mio = { workspace = true, "features" = ["net", "os-poll"] } stream_limiter = { workspace = true } -massa_consensus_exports = {workspace = true} -massa_final_state = {workspace = true} -massa_hash = {workspace = true} -massa_logging = {workspace = true} -massa_models = {workspace = true} -massa_protocol_exports = {workspace = true} -massa_serialization = {workspace = true} -massa_signature = {workspace = true} -massa_pos_exports = {workspace = true} -massa_time = {workspace = true} -massa_db_exports = {workspace = true} -massa_versioning = {workspace = true} -massa_metrics = {workspace = true} +massa_consensus_exports = { workspace = true } +massa_final_state = { workspace = true } +massa_hash = { workspace = true } +massa_logging = { workspace = true } +massa_models = { workspace = true } +massa_protocol_exports = { workspace = true } +massa_serialization = { workspace = true } +massa_signature = { workspace = true } +massa_pos_exports = { workspace = true } +massa_time = { workspace = true } +massa_db_exports = { workspace = true } +massa_versioning = { workspace = true } +massa_metrics = { workspace = true } [dev-dependencies] -mockall = {workspace = true} -bitvec = {workspace = true, "features" = ["serde"]} -lazy_static = {workspace = true} # BOM UPGRADE Revert to "1.4" if problem -tempfile = {workspace = true} # BOM UPGRADE Revert to "3.3" if problem -serial_test = {workspace = true} # BOM UPGRADE Revert to "2.0.0" if problem -num = {workspace = true} -massa_final_state = {workspace = true, "features" = ["test-exports"]} -massa_async_pool = {workspace = true, "features" = ["test-exports"]} -massa_ledger_exports = {workspace = true} -massa_ledger_worker = {workspace = true, "features" = ["test-exports"]} -massa_executed_ops = {workspace = true} -massa_pos_exports = {workspace = true, "features" = ["test-exports"]} -massa_consensus_exports = {workspace = true, "features" = ["test-exports"]} -massa_db_worker = {workspace = true, "features" = ["test-exports"]} -massa_test_framework = {workspace = true, "features" = ["test-exports"]} +mockall = { workspace = true } +bitvec = { workspace = true, "features" = ["serde"] } +lazy_static = { workspace = true } # BOM UPGRADE Revert to "1.4" if problem +tempfile = { workspace = true } # BOM UPGRADE Revert to "3.3" if problem +serial_test = { workspace = true } # BOM UPGRADE Revert to "2.0.0" if problem +num = { workspace = true } +massa_final_state = { workspace = true, "features" = ["test-exports"] } +massa_async_pool = { workspace = true, "features" = ["test-exports"] } +massa_ledger_exports = { workspace = true } +massa_ledger_worker = { workspace = true, "features" = ["test-exports"] } +massa_executed_ops = { workspace = true } +massa_pos_exports = { workspace = true, "features" = ["test-exports"] } +massa_consensus_exports = { workspace = true, "features" = ["test-exports"] } +massa_db_worker = { workspace = true, "features" = ["test-exports"] } +massa_test_framework = { workspace = true, "features" = ["test-exports"] } +massa_deferred_calls = { workspace = true } diff --git a/massa-bootstrap/src/client.rs b/massa-bootstrap/src/client.rs index 98d5f07616f..989f4e094a1 100644 --- a/massa-bootstrap/src/client.rs +++ b/massa-bootstrap/src/client.rs @@ -402,7 +402,7 @@ pub fn get_state( genesis_timestamp: MassaTime, end_timestamp: Option, restart_from_snapshot_at_period: Option, - interupted: Arc<(Mutex, Condvar)>, + interrupted: Arc<(Mutex, Condvar)>, massa_metrics: MassaMetrics, ) -> Result { massa_trace!("bootstrap.lib.get_state", {}); @@ -490,8 +490,12 @@ pub fn get_state( let limit = bootstrap_config.rate_limit; loop { - // check for interuption - if *interupted.0.lock().expect("double-lock on interupt-mutex") { + // check for interruption + if *interrupted + .0 + .lock() + .expect("double-lock on interrupt-mutex") + { return Err(BootstrapError::Interrupted( "Sig INT received while getting state".to_string(), )); @@ -547,11 +551,11 @@ pub fn get_state( // Before, we would use a simple sleep(...), and that was fine // in a cancellable async context: the runtime could - // catch the interupt signal, and just cancel this thread: + // catch the interrupt signal, and just cancel this thread: // // let state = tokio::select!{ - // /* detect interupt */ => /* return, cancelling the async get_state */ - // get_state(...) => well, we got the state, and it didn't have to worry about interupts + // /* detect interrupt */ => /* return, cancelling the async get_state */ + // get_state(...) => well, we got the state, and it didn't have to worry about interrupts // }; // // Without an external system to preempt this context, we use a condvar to manage the sleep. @@ -562,14 +566,14 @@ pub fn get_state( // The _magic_ happens when, somewhere else, a clone of the Arc<(Mutex, Condvar)>\ // calls Condvar::notify_[one | all], which prompts this thread to wake up. Assuming that // the mutex-wrapped variable has been set appropriately before the notify, this thread - let int_sig = interupted + let int_sig = interrupted .0 .lock() - .expect("double-lock() on interupted signal mutex"); - let wake = interupted + .expect("double-lock() on interrupted signal mutex"); + let wake = interrupted .1 .wait_timeout(int_sig, bootstrap_config.retry_delay.to_duration()) - .expect("interupt signal mutex poisoned"); + .expect("interrupt signal mutex poisoned"); if *wake.0 { return Err(BootstrapError::Interrupted( "Sig INT during bootstrap retry-wait".to_string(), diff --git a/massa-bootstrap/src/messages.rs b/massa-bootstrap/src/messages.rs index e0cc2fa5669..23e55fceea0 100644 --- a/massa-bootstrap/src/messages.rs +++ b/massa-bootstrap/src/messages.rs @@ -871,10 +871,10 @@ impl Deserializer for BootstrapClientMessageDeserializer context("Failed last_slot deserialization", |input| { self.slot_deserializer.deserialize(input) }), - context("Faild last_state_step deserialization", |input| { + context("Failed last_state_step deserialization", |input| { self.state_step_deserializer.deserialize(input) }), - context("Faild last_versioning_step deserialization", |input| { + context("Failed last_versioning_step deserialization", |input| { self.state_step_deserializer.deserialize(input) }), context("Failed last_consensus_step deserialization", |input| { diff --git a/massa-bootstrap/src/server.rs b/massa-bootstrap/src/server.rs index c5d462c3abb..900e7699d14 100644 --- a/massa-bootstrap/src/server.rs +++ b/massa-bootstrap/src/server.rs @@ -13,13 +13,13 @@ //! Shares an `Arc>` guarded list of white and blacklists with the main worker. //! Periodically does a read-only check to see if list needs updating. //! Creates an updated list then swaps it out with write-locked list -//! Assuming no errors in code, this is the only write occurance, and is only a pointer-swap -//! under the hood, making write contention virtually non-existant. +//! Assuming no errors in code, this is the only write occurrence, and is only a pointer-swap +//! under the hood, making write contention virtually non-existent. //! //! # Worker loop //! //! 1. Checks if the stopper has been invoked. -//! 2. Checks if the client is permited under the white/black list rules +//! 2. Checks if the client is permitted under the white/black list rules //! 3. Checks if there are not too many active sessions already //! 4. Checks if the client has attempted too recently //! 5. All checks have passed: spawn a thread on which to run the bootstrap session @@ -101,7 +101,7 @@ impl BootstrapManager { /// stop the bootstrap server pub fn stop(self) -> Result<(), BootstrapError> { massa_trace!("bootstrap.lib.stop", {}); - // TODO: Refactor the waker so that its existance is tied to the life of the event-loop + // TODO: Refactor the waker so that its existence is tied to the life of the event-loop if self.listener_stopper.stop().is_err() { warn!("bootstrap server already dropped"); } diff --git a/massa-bootstrap/src/tests/binders.rs b/massa-bootstrap/src/tests/binders.rs index 46ef5b1c699..7b39473f18a 100644 --- a/massa-bootstrap/src/tests/binders.rs +++ b/massa-bootstrap/src/tests/binders.rs @@ -248,7 +248,7 @@ fn test_binders_simple() { // This test uses exactly the same principle as the `test_binders_simple` one // Except instead of passing a pair of (ServerMessage, ClientMessage), it will pass a // (bool, Vec, Vec) -// - The boolean defines wether the server or the client will transmit data first, or receive first +// - The boolean defines whether the server or the client will transmit data first, or receive first // - The first vector is a list of server messages generated that the server has to send // - The second vector is a list of client messages generated that the client has to send // Because the direction of the first message is randomly assigned, and the number of messages are random, diff --git a/massa-bootstrap/src/tests/scenarios.rs b/massa-bootstrap/src/tests/scenarios.rs index 226cf32fc1d..8a5efef7901 100644 --- a/massa-bootstrap/src/tests/scenarios.rs +++ b/massa-bootstrap/src/tests/scenarios.rs @@ -11,17 +11,45 @@ use massa_models::{address::Address, node::NodeId}; use massa_signature::KeyPair; use massa_test_framework::TestUniverse; use serial_test::serial; -use std::path::PathBuf; +use std::collections::HashSet; +use std::fs::File; +use std::net::IpAddr; +use std::str::FromStr; +use tempfile::NamedTempFile; #[test] #[serial] fn test_bootstrap_not_whitelisted() { let port = 8069; let server_keypair = KeyPair::generate(0).unwrap(); + + let bootstrap_whitelist_file = NamedTempFile::new().expect("unable to create temp file"); + + let ips_str = vec![ + "149.202.86.103", + "149.202.89.125", + "158.69.120.215", + "158.69.23.120", + "198.27.74.5", + "51.75.60.228", + "2001:41d0:1004:67::", + "2001:41d0:a:7f7d::", + "2001:41d0:602:21e4::", + ]; + + let mut bootstrap_whitelist = HashSet::new(); + for ip_str in ips_str { + bootstrap_whitelist.insert(IpAddr::from_str(ip_str).unwrap()); + } + + serde_json::to_writer_pretty::<&File, HashSet>( + bootstrap_whitelist_file.as_file(), + &bootstrap_whitelist, + ) + .expect("unable to write bootstrap whitelist temp file"); + let bootstrap_server_config = BootstrapConfig { - bootstrap_whitelist_path: PathBuf::from( - "../massa-node/base_config/bootstrap_whitelist.json", - ), + bootstrap_whitelist_path: bootstrap_whitelist_file.path().to_path_buf(), ..Default::default() }; let server_universe = BootstrapServerTestUniverseBuilder::new() diff --git a/massa-bootstrap/src/tests/tools.rs b/massa-bootstrap/src/tests/tools.rs index f4615ebbda6..e44614f342b 100644 --- a/massa-bootstrap/src/tests/tools.rs +++ b/massa-bootstrap/src/tests/tools.rs @@ -9,6 +9,8 @@ use massa_consensus_exports::{ bootstrapable_graph::BootstrapableGraph, export_active_block::ExportActiveBlock, }; use massa_db_exports::{DBBatch, ShareableMassaDBController, StreamBatch}; +use massa_deferred_calls::config::DeferredCallsConfig; +use massa_deferred_calls::DeferredCallRegistry; use massa_executed_ops::{ ExecutedDenunciations, ExecutedDenunciationsChanges, ExecutedDenunciationsConfig, ExecutedOps, ExecutedOpsConfig, @@ -16,7 +18,7 @@ use massa_executed_ops::{ use massa_final_state::test_exports::create_final_state; use massa_final_state::{FinalState, FinalStateConfig, FinalStateController}; use massa_hash::{Hash, HASH_SIZE_BYTES}; -use massa_ledger_exports::{LedgerEntry, SetUpdateOrDelete}; +use massa_ledger_exports::LedgerEntry; use massa_ledger_worker::test_exports::create_final_ledger; use massa_models::bytecode::Bytecode; use massa_models::config::{ @@ -36,6 +38,7 @@ use massa_models::denunciation::DenunciationIndex; use massa_models::node::NodeId; use massa_models::prehash::{CapacityAllocator, PreHashSet}; use massa_models::streaming_step::StreamingStep; +use massa_models::types::SetUpdateOrDelete; use massa_models::version::Version; use massa_models::{ address::Address, @@ -275,6 +278,9 @@ pub fn get_random_final_state_bootstrap( .write() .write_batch(batch, versioning_batch, None); + let deferred_call_registry = + DeferredCallRegistry::new(db.clone(), DeferredCallsConfig::default()); + let executed_ops = get_random_executed_ops( r_limit, slot, @@ -304,6 +310,7 @@ pub fn get_random_final_state_bootstrap( config, Box::new(final_ledger), async_pool, + deferred_call_registry, pos_state, executed_ops, executed_denunciations, @@ -783,7 +790,7 @@ impl BootstrapServerMessage { "Error in the code of the test for faulty_part 4" ); } - // No limit on the size of this except the u64 boundery + // No limit on the size of this except the u64 boundary let updates_on_previous_elements = BTreeMap::new(); let mut change_id = gen_random_slot(rng); if faulty_part == BootstrapServerMessageFaultyPart::StateChangeIdThreadOverflow { @@ -830,7 +837,7 @@ impl BootstrapServerMessage { new_elements.insert(key, value); new_elements_size += key_len + value_len; } - // No limit on the size of this except the u64 boundery + // No limit on the size of this except the u64 boundary let updates_on_previous_elements = BTreeMap::new(); let mut change_id = gen_random_slot(rng); if faulty_part == BootstrapServerMessageFaultyPart::VersioningChangeIdThreadOverflow { @@ -1020,7 +1027,7 @@ impl BootstrapServerMessage { }, ) => { let state_equal = stream_batch_equal(state1, state2); - let versionning_equal = stream_batch_equal(v1, v2); + let versioning_equal = stream_batch_equal(v1, v2); let mut consensus_equal = true; if c1.final_blocks.len() != c2.final_blocks.len() { return false; @@ -1034,7 +1041,7 @@ impl BootstrapServerMessage { } (s1 == s2) && state_equal - && versionning_equal + && versioning_equal && consensus_equal && (co1 == co2) && (lp1 == lp2) diff --git a/massa-channel/Cargo.toml b/massa-channel/Cargo.toml index 5f2870a0add..22b5270c18f 100644 --- a/massa-channel/Cargo.toml +++ b/massa-channel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_channel" -version = "2.4.0" +version = "2.5.0" edition = "2021" [features] diff --git a/massa-cipher/Cargo.toml b/massa-cipher/Cargo.toml index 6a1ba3c8d36..5e233503238 100644 --- a/massa-cipher/Cargo.toml +++ b/massa-cipher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_cipher" -version = "2.4.0" +version = "2.5.0" edition = "2021" [dependencies] diff --git a/massa-client/Cargo.toml b/massa-client/Cargo.toml index fee67fd3cde..54e540da11f 100644 --- a/massa-client/Cargo.toml +++ b/massa-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa-client" -version = "2.4.0" +version = "2.5.0" edition = "2021" [dependencies] diff --git a/massa-client/src/cmds.rs b/massa-client/src/cmds.rs index 897883e6c46..7e8a28376fe 100644 --- a/massa-client/src/cmds.rs +++ b/massa-client/src/cmds.rs @@ -114,13 +114,6 @@ pub enum Command { )] node_stop_staking, - #[strum( - ascii_case_insensitive, - props(args = "Address discord_id"), - message = "generate the testnet rewards program node/staker ownership proof" - )] - node_testnet_rewards_program_ownership_proof, - #[strum( ascii_case_insensitive, props(args = "(add, remove or allow-all) [IpAddr]", pwd_not_needed = "true"), @@ -531,39 +524,6 @@ impl Command { } } - Command::node_testnet_rewards_program_ownership_proof => { - let wallet = wallet_opt.as_mut().unwrap(); - - if parameters.len() != 2 { - bail!("wrong number of parameters"); - } - // parse - let addr = parameters[0].parse::
()?; - let msg = parameters[1].as_bytes().to_vec(); - // get address signature - if let Some(addr_sig) = wallet.sign_message(&addr, msg.clone()) { - // get node signature - match client.private.node_sign_message(msg).await { - // print concatenation - Ok(node_sig) => { - if !json { - println!("Enter the following in discord:"); - } - Ok(Box::new(format!( - "{}/{}/{}/{}", - node_sig.public_key, - node_sig.signature, - addr_sig.public_key, - addr_sig.signature - ))) - } - Err(e) => rpc_error!(e), - } - } else { - bail!("address not found") - } - } - Command::get_status => match client.public.get_status().await { Ok(node_status) => Ok(Box::new(node_status)), Err(e) => rpc_error!(e), diff --git a/massa-client/src/display.rs b/massa-client/src/display.rs index 7c4732094e5..5973228ef5b 100644 --- a/massa-client/src/display.rs +++ b/massa-client/src/display.rs @@ -82,7 +82,7 @@ pub enum Style { Wallet, /// For any secret information Secret, - /// To separate some informations on the screen by barely visible characters + /// To separate some information on the screen by barely visible characters Separator, /// When displaying a timestamp or date Time, diff --git a/massa-consensus-exports/Cargo.toml b/massa-consensus-exports/Cargo.toml index 6cc320cccfa..068c801c127 100644 --- a/massa-consensus-exports/Cargo.toml +++ b/massa-consensus-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_consensus_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-consensus-worker/Cargo.toml b/massa-consensus-worker/Cargo.toml index 43ee7ec7d17..821c1fd2baa 100644 --- a/massa-consensus-worker/Cargo.toml +++ b/massa-consensus-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_consensus_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-consensus-worker/src/lib.rs b/massa-consensus-worker/src/lib.rs index 48e617a34c2..c7e04d0a6ba 100644 --- a/massa-consensus-worker/src/lib.rs +++ b/massa-consensus-worker/src/lib.rs @@ -5,10 +5,10 @@ //! The consensus worker launches a persistent thread that will run in the background. //! This thread has a `run` function that triggers the consensus algorithm each slot. It can be interrupted by commands //! that are managed on the fly. The consensus worker share a state with a controller. This controller can be called by the others modules. -//! It avoid sending message to the thread just for getting informations on the consensus. +//! It avoids sending message to the thread just for getting information on the consensus. //! //! Communications with execution is blocking. Communications with protocol blocks on sending information to protocol but not blocking -//! when protocol sends informations to this module. +//! when protocol sends information to this module. //! //! This module doesn't use asynchronous code. //! diff --git a/massa-consensus-worker/src/state/mod.rs b/massa-consensus-worker/src/state/mod.rs index 07dee3ce7a4..5dc9b829581 100644 --- a/massa-consensus-worker/src/state/mod.rs +++ b/massa-consensus-worker/src/state/mod.rs @@ -334,7 +334,7 @@ impl ConsensusState { &self, end_slot: Option, ) -> Result, ConsensusError> { - // if an end_slot is provided compute the lastest final block for that given slot + // if an end_slot is provided compute the latest final block for that given slot // if not use the latest_final_blocks_periods let effective_latest_finals: Vec<(BlockId, u64)> = if let Some(slot) = end_slot { self.list_latest_final_blocks_at(slot)? diff --git a/massa-consensus-worker/src/state/process.rs b/massa-consensus-worker/src/state/process.rs index 64cf276a0ca..b80a4d6794f 100644 --- a/massa-consensus-worker/src/state/process.rs +++ b/massa-consensus-worker/src/state/process.rs @@ -31,7 +31,7 @@ use crate::state::{ use super::ConsensusState; -/// All informations necessary to add a block to the graph +/// All information necessary to add a block to the graph pub(crate) struct BlockInfos { /// The block creator pub creator: PublicKey, diff --git a/massa-consensus-worker/src/tests/three_four_threads_scenarios.rs b/massa-consensus-worker/src/tests/three_four_threads_scenarios.rs index e0f10d0e36f..2897921e1af 100644 --- a/massa-consensus-worker/src/tests/three_four_threads_scenarios.rs +++ b/massa-consensus-worker/src/tests/three_four_threads_scenarios.rs @@ -625,7 +625,7 @@ fn test_fts_multiple_max_cliques_2() { register_block(&consensus_controller, block_1_3.clone(), storage.clone()); // Period 2. - // Thread incompatibilies with every blocks of period 1 + // Thread incompatibilities with every block of period 1 let block_2_0 = create_block( Slot::new(2, 0), vec![genesis[0], genesis[1], genesis[2], genesis[3]], diff --git a/massa-db-exports/Cargo.toml b/massa-db-exports/Cargo.toml index 0e7e0444c38..934aec46166 100644 --- a/massa-db-exports/Cargo.toml +++ b/massa-db-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_db_exports" -version = "2.4.0" +version = "2.5.0" edition = "2021" [features] diff --git a/massa-db-exports/src/constants.rs b/massa-db-exports/src/constants.rs index 5716f078d63..771807b4a2f 100644 --- a/massa-db-exports/src/constants.rs +++ b/massa-db-exports/src/constants.rs @@ -29,6 +29,7 @@ pub const LEDGER_PREFIX: &str = "ledger/"; pub const MIP_STORE_PREFIX: &str = "versioning/"; pub const MIP_STORE_STATS_PREFIX: &str = "versioning_stats/"; pub const EXECUTION_TRAIL_HASH_PREFIX: &str = "execution_trail_hash/"; +pub const DEFERRED_CALLS_PREFIX: &str = "deferred_calls/"; // Async Pool pub const MESSAGE_DESER_ERROR: &str = "critical: message deserialization failed"; @@ -56,3 +57,7 @@ pub const EXECUTED_DENUNCIATIONS_INDEX_SER_ERROR: &str = pub const KEY_DESER_ERROR: &str = "critical: key deserialization failed"; pub const KEY_SER_ERROR: &str = "critical: key serialization failed"; pub const KEY_LEN_SER_ERROR: &str = "critical: key length serialization failed"; + +// deferred calls +pub const DEFERRED_CALL_DESER_ERROR: &str = "critical: message deserialization failed"; +pub const DEFERRED_CALL_SER_ERROR: &str = "critical: message serialization failed"; diff --git a/massa-db-worker/Cargo.toml b/massa-db-worker/Cargo.toml index 162bd313058..82b22a75750 100644 --- a/massa-db-worker/Cargo.toml +++ b/massa-db-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_db_worker" -version = "2.4.0" +version = "2.5.0" edition = "2021" [features] diff --git a/massa-db-worker/src/lib.rs b/massa-db-worker/src/lib.rs index d2d0296968c..21a41903a6b 100644 --- a/massa-db-worker/src/lib.rs +++ b/massa-db-worker/src/lib.rs @@ -35,7 +35,7 @@ //! * if we want to delete item a: 1000 ^ 0011 == 1011 (== item b) //! * if we want to delete item b: 1000 ^ 1011 == 0011 (== item a) //! -//! Note that this does not provides "Proof of present" nor "Proof of Absence" +//! Note that this does not provide "Proof of present" nor "Proof of Absence" //! (operations avail with Merkle trees) //! //! For more details here: https://github.com/massalabs/massa/discussions/3852#discussioncomment-6188158 @@ -45,10 +45,10 @@ //! # Caches //! //! A cache of db changes is kept in memory allowing to easily stream it -//! (by streaming, we means: sending it to another massa node (aka bootstrap)) +//! (by streaming, we mean: sending it to another massa node (aka bootstrap)) //! There is 2 separate caches: one for 'state' and one for 'versioning' //! -//! These caches is stored as a key, value: slot -> insertion_data|deletion_data. +//! These caches are stored as a key, value: slot -> insertion_data|deletion_data. //! //! # Streaming steps //! diff --git a/massa-db-worker/src/massa_db.rs b/massa-db-worker/src/massa_db.rs index a86ffd9475b..6a4e2e018f0 100644 --- a/massa-db-worker/src/massa_db.rs +++ b/massa-db-worker/src/massa_db.rs @@ -446,6 +446,7 @@ where if reset_history { self.change_history.clear(); + self.change_history_versioning.clear(); } while self.change_history.len() > self.config.max_history_length { @@ -719,9 +720,16 @@ impl MassaDBController for RawMassaDB { } /// Reset the database, and attach it to the given slot. + /// + /// This function is used in the FinalStateController::reset method which is used in the Bootstrap + /// process when the bootstrap fails (Bootstrap slot too old). A bootstrap to another node will likely occur + /// after this reset. fn reset(&mut self, slot: Slot) { - self.set_initial_change_id(slot); + // For dev: please take care of correctly reset the db to avoid any issue when the bootstrap + // process is restarted + self.set_initial_change_id(slot); // Note: this also reset the field: current_batch self.change_history.clear(); + self.change_history_versioning.clear(); } fn get_cf(&self, handle_cf: &str, key: Key) -> Result, MassaDBError> { @@ -1087,7 +1095,7 @@ mod test { // assert!(dump_column(db_.clone(), "versioning").is_empty()); assert!(dump_column(db.clone(), "versioning").is_empty()); - // Add some datas then remove using prefix + // Add some data then remove using prefix batch.clear(); db.read() .put_or_update_entry_value(&mut batch, vec![97, 98, 1], &[1]); diff --git a/massa-deferred-calls/Cargo.toml b/massa-deferred-calls/Cargo.toml new file mode 100644 index 00000000000..6df8d5a9b71 --- /dev/null +++ b/massa-deferred-calls/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "massa_deferred_calls" +version = "2.2.0" +authors = ["Massa Labs "] +edition = "2021" + +[features] +test-exports = [] +sandbox = [] + +[dependencies] +serde = { workspace = true, "features" = ["derive"] } +nom = { workspace = true } +massa_db_exports = { workspace = true } +massa_ledger_exports = { workspace = true } +massa_models = { workspace = true } +massa_serialization = { workspace = true } +serde_with = { workspace = true } +serde_json = { workspace = true } +massa-proto-rs = { workspace = true, "features" = ["tonic"] } + +[dev-dependencies] +tempfile = { workspace = true } +massa_db_worker = { workspace = true } +parking_lot = { workspace = true } diff --git a/massa-deferred-calls/src/call.rs b/massa-deferred-calls/src/call.rs new file mode 100644 index 00000000000..99b11b8a717 --- /dev/null +++ b/massa-deferred-calls/src/call.rs @@ -0,0 +1,295 @@ +use massa_models::{ + address::{Address, AddressDeserializer, AddressSerializer}, + amount::{Amount, AmountDeserializer, AmountSerializer}, + serialization::{StringDeserializer, StringSerializer, VecU8Deserializer, VecU8Serializer}, + slot::{Slot, SlotDeserializer, SlotSerializer}, +}; +use massa_proto_rs::massa::api::v1 as grpc_api; +use massa_serialization::{ + BoolDeserializer, BoolSerializer, Deserializer, SerializeError, Serializer, + U16VarIntDeserializer, U16VarIntSerializer, U64VarIntDeserializer, U64VarIntSerializer, +}; +use nom::{ + error::{context, ContextError, ParseError}, + sequence::tuple, + IResult, Parser, +}; +use serde::{Deserialize, Serialize}; +use std::ops::Bound; + +use crate::config::DeferredCallsConfig; + +/// Definition of a call in the future +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DeferredCall { + // Sender address + pub sender_address: Address, + // The slot in which the call will be executed + pub target_slot: Slot, + // The address of the contract to call + pub target_address: Address, + // The function to call + pub target_function: String, + // The parameters of the call + pub parameters: Vec, + // The amount of coins to send to the contract + pub coins: Amount, + // The maximum amount of gas usable for the call (excluding the vm allocation cost) + // to get the effective gas, use get_effective_gas(&self) + pub max_gas: u64, + // The fee to pay for the reservation of the space for the call + pub fee: Amount, + // Whether the call is cancelled + pub cancelled: bool, +} + +impl DeferredCall { + #[allow(clippy::too_many_arguments)] + pub fn new( + sender_address: Address, + target_slot: Slot, + target_address: Address, + target_function: String, + parameters: Vec, + coins: Amount, + max_gas: u64, + fee: Amount, + cancelled: bool, + ) -> Self { + DeferredCall { + sender_address, + target_slot, + target_address, + target_function, + parameters, + coins, + max_gas, + fee, + cancelled, + } + } + + /// Get the effective gas of a call + /// This is the maximum gas of the call + vm allocation cost + pub fn get_effective_gas(&self, alloc_gas_cost: u64) -> u64 { + self.max_gas.saturating_add(alloc_gas_cost) + } + + /// Get the storage cost for a call + pub fn get_storage_cost( + cost_per_byte: Amount, + params_size: u64, + max_function_name_length: u16, + ) -> Amount { + // 35 (sender_address) + 16 (target_slot) + 35 (target_address) + target_function.len() + params_size + 8 (coins) + 8 (max_gas) + 8 (fee) + 1 (cancelled) + let total_size = params_size + .saturating_add(max_function_name_length as u64) + .saturating_add(111); // 35 + 16 + 35 + 8 + 8 + 8 + 1 + cost_per_byte.saturating_mul_u64(total_size) + } +} + +impl From for grpc_api::DeferredCallInfoEntry { + fn from(call: DeferredCall) -> Self { + grpc_api::DeferredCallInfoEntry { + sender_address: call.sender_address.to_string(), + target_slot: Some(call.target_slot.into()), + target_address: call.target_address.to_string(), + target_function: call.target_function, + parameters: call.parameters, + coins: Some(call.coins.into()), + max_gas: call.max_gas, + fee: Some(call.fee.into()), + cancelled: call.cancelled, + } + } +} + +/// Serializer for `AsyncCall` +#[derive(Clone)] +pub struct DeferredCallSerializer { + pub(crate) slot_serializer: SlotSerializer, + pub(crate) address_serializer: AddressSerializer, + pub(crate) string_serializer: StringSerializer, + pub(crate) vec_u8_serializer: VecU8Serializer, + pub(crate) amount_serializer: AmountSerializer, + pub(crate) u64_var_int_serializer: U64VarIntSerializer, + pub(crate) bool_serializer: BoolSerializer, +} + +impl DeferredCallSerializer { + /// Serializes an `DeferredCall` into a `Vec` + pub fn new() -> Self { + Self { + slot_serializer: SlotSerializer::new(), + address_serializer: AddressSerializer::new(), + string_serializer: StringSerializer::new(U16VarIntSerializer::new()), + vec_u8_serializer: VecU8Serializer::new(), + amount_serializer: AmountSerializer::new(), + u64_var_int_serializer: U64VarIntSerializer::new(), + bool_serializer: BoolSerializer::new(), + } + } +} + +impl Serializer for DeferredCallSerializer { + fn serialize(&self, value: &DeferredCall, buffer: &mut Vec) -> Result<(), SerializeError> { + self.address_serializer + .serialize(&value.sender_address, buffer)?; + self.slot_serializer.serialize(&value.target_slot, buffer)?; + self.address_serializer + .serialize(&value.target_address, buffer)?; + self.string_serializer + .serialize(&value.target_function, buffer)?; + self.vec_u8_serializer + .serialize(&value.parameters, buffer)?; + self.amount_serializer.serialize(&value.coins, buffer)?; + self.u64_var_int_serializer + .serialize(&value.max_gas, buffer)?; + self.amount_serializer.serialize(&value.fee, buffer)?; + self.bool_serializer.serialize(&value.cancelled, buffer)?; + Ok(()) + } +} + +/// Deserializer for `AsyncCall` +#[derive(Clone)] +pub struct DeferredCallDeserializer { + slot_deserializer: SlotDeserializer, + address_deserializer: AddressDeserializer, + string_deserializer: StringDeserializer, + vec_u8_deserializer: VecU8Deserializer, + pub(crate) amount_deserializer: AmountDeserializer, + pub(crate) u64_var_int_deserializer: U64VarIntDeserializer, + bool_deserializer: BoolDeserializer, +} + +impl DeferredCallDeserializer { + /// Deserializes a `Vec` into an `AsyncCall` + pub fn new(config: DeferredCallsConfig) -> Self { + Self { + slot_deserializer: SlotDeserializer::new( + (Bound::Included(0), Bound::Included(u64::MAX)), + (Bound::Included(0), Bound::Excluded(config.thread_count)), + ), + address_deserializer: AddressDeserializer::new(), + string_deserializer: StringDeserializer::new(U16VarIntDeserializer::new( + Bound::Included(0), + Bound::Included(config.max_function_name_length), + )), + vec_u8_deserializer: VecU8Deserializer::new( + std::ops::Bound::Included(0), + std::ops::Bound::Included(config.max_parameter_size as u64), + ), + amount_deserializer: AmountDeserializer::new( + Bound::Included(Amount::MIN), + Bound::Included(Amount::MAX), + ), + u64_var_int_deserializer: U64VarIntDeserializer::new( + Bound::Included(0), + Bound::Included(u64::MAX), + ), + bool_deserializer: BoolDeserializer::new(), + } + } +} + +impl Deserializer for DeferredCallDeserializer { + fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>( + &self, + buffer: &'a [u8], + ) -> IResult<&'a [u8], DeferredCall, E> { + context( + "Failed AsyncCall deserialization", + tuple(( + context("Failed sender_address deserialization", |input| { + self.address_deserializer.deserialize(input) + }), + context("Failed target_slot deserialization", |input| { + self.slot_deserializer.deserialize(input) + }), + context("Failed target_address deserialization", |input| { + self.address_deserializer.deserialize(input) + }), + context("Failed target_function deserialization", |input| { + self.string_deserializer.deserialize(input) + }), + context("Failed parameters deserialization", |input| { + self.vec_u8_deserializer.deserialize(input) + }), + context("Failed coins deserialization", |input| { + self.amount_deserializer.deserialize(input) + }), + context("Failed max_gas deserialization", |input| { + self.u64_var_int_deserializer.deserialize(input) + }), + context("Failed fee deserialization", |input| { + self.amount_deserializer.deserialize(input) + }), + context("Failed cancelled deserialization", |input| { + self.bool_deserializer.deserialize(input) + }), + )), + ) + .map( + |( + sender_address, + target_slot, + target_address, + target_function, + parameters, + coins, + max_gas, + fee, + cancelled, + )| { + DeferredCall::new( + sender_address, + target_slot, + target_address, + target_function, + parameters, + coins, + max_gas, + fee, + cancelled, + ) + }, + ) + .parse(buffer) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use massa_serialization::DeserializeError; + + use super::*; + + #[test] + fn test_serialization_deserialization() { + let call = DeferredCall::new( + Address::from_str("AU12dG5xP1RDEB5ocdHkymNVvvSJmUL9BgHwCksDowqmGWxfpm93x").unwrap(), + Slot::new(42, 0), + Address::from_str("AU12dG5xP1RDEB5ocdHkymNVvvSJmUL9BgHwCksDowqmGWxfpm93x").unwrap(), + "function".to_string(), + vec![0, 1, 2, 3], + Amount::from_raw(100), + 500000, + Amount::from_raw(25), + false, + ); + let serializer = DeferredCallSerializer::new(); + + let deserializer = DeferredCallDeserializer::new(DeferredCallsConfig::default()); + let mut buffer = Vec::new(); + serializer.serialize(&call, &mut buffer).unwrap(); + let (rest, deserialized_call) = deserializer + .deserialize::(&buffer) + .unwrap(); + assert_eq!(call, deserialized_call); + assert!(rest.is_empty()); + } +} diff --git a/massa-deferred-calls/src/config.rs b/massa-deferred-calls/src/config.rs new file mode 100644 index 00000000000..8f1264e73e0 --- /dev/null +++ b/massa-deferred-calls/src/config.rs @@ -0,0 +1,62 @@ +use massa_models::{ + amount::Amount, + config::{ + DEFERRED_CALL_BASE_FEE_MAX_CHANGE_DENOMINATOR, DEFERRED_CALL_CST_GAS_COST, + DEFERRED_CALL_GLOBAL_OVERBOOKING_PENALTY, DEFERRED_CALL_MAX_ASYNC_GAS, + DEFERRED_CALL_MAX_FUTURE_SLOTS, DEFERRED_CALL_MAX_POOL_CHANGES, DEFERRED_CALL_MIN_GAS_COST, + DEFERRED_CALL_MIN_GAS_INCREMENT, DEFERRED_CALL_SLOT_OVERBOOKING_PENALTY, + LEDGER_COST_PER_BYTE, MAX_FUNCTION_NAME_LENGTH, MAX_PARAMETERS_SIZE, THREAD_COUNT, + }, +}; +use serde::Deserialize; + +#[derive(Debug, Clone, Copy, Deserialize)] +pub struct DeferredCallsConfig { + /// thread count + pub thread_count: u8, + /// max function name length + pub max_function_name_length: u16, + /// Maximum size of deferred call future slots (1 week) + pub max_future_slots: u64, + /// base fee max max change denominator + pub base_fee_max_max_change_denominator: usize, + /// min gas increment (1 nanomassa) + pub min_gas_increment: u64, + /// min gas cost (10 nanomassa) + pub min_gas_cost: u64, + /// call gas cost + pub call_cst_gas_cost: u64, + /// global overbooking penalty + pub global_overbooking_penalty: Amount, + /// slot overbooking penalty + pub slot_overbooking_penalty: Amount, + + /// max parameter size + pub max_parameter_size: u32, + + pub max_pool_changes: u64, + + pub max_gas: u64, + + pub ledger_cost_per_byte: Amount, +} + +impl Default for DeferredCallsConfig { + fn default() -> Self { + Self { + thread_count: THREAD_COUNT, + max_function_name_length: MAX_FUNCTION_NAME_LENGTH, + max_future_slots: DEFERRED_CALL_MAX_FUTURE_SLOTS, + max_parameter_size: MAX_PARAMETERS_SIZE, + max_pool_changes: DEFERRED_CALL_MAX_POOL_CHANGES, + max_gas: DEFERRED_CALL_MAX_ASYNC_GAS, + base_fee_max_max_change_denominator: DEFERRED_CALL_BASE_FEE_MAX_CHANGE_DENOMINATOR, + min_gas_increment: DEFERRED_CALL_MIN_GAS_INCREMENT, + min_gas_cost: DEFERRED_CALL_MIN_GAS_COST, + global_overbooking_penalty: DEFERRED_CALL_GLOBAL_OVERBOOKING_PENALTY, + slot_overbooking_penalty: DEFERRED_CALL_SLOT_OVERBOOKING_PENALTY, + call_cst_gas_cost: DEFERRED_CALL_CST_GAS_COST, + ledger_cost_per_byte: LEDGER_COST_PER_BYTE, + } + } +} diff --git a/massa-deferred-calls/src/lib.rs b/massa-deferred-calls/src/lib.rs new file mode 100644 index 00000000000..d6098aee8d9 --- /dev/null +++ b/massa-deferred-calls/src/lib.rs @@ -0,0 +1,499 @@ +use call::{DeferredCallDeserializer, DeferredCallSerializer}; +use config::DeferredCallsConfig; +use macros::{DEFERRED_CALL_TOTAL_GAS, DEFERRED_CALL_TOTAL_REGISTERED}; +use massa_db_exports::{ + DBBatch, ShareableMassaDBController, CRUD_ERROR, DEFERRED_CALLS_PREFIX, + DEFERRED_CALL_DESER_ERROR, DEFERRED_CALL_SER_ERROR, KEY_DESER_ERROR, STATE_CF, +}; +use massa_serialization::{DeserializeError, Deserializer, Serializer}; +use registry_changes::{ + DeferredCallRegistryChanges, DeferredRegistryChangesDeserializer, + DeferredRegistryChangesSerializer, +}; + +/// This module implements a new version of the Autonomous Smart Contracts. (ASC) +/// This new version allow asynchronous calls to be registered for a specific slot and ensure his execution. +mod call; +pub mod config; +pub mod registry_changes; +pub mod slot_changes; + +#[cfg(test)] +mod tests; + +#[macro_use] +mod macros; + +pub use call::DeferredCall; +use massa_models::types::{SetOrDelete, SetOrKeep}; +use massa_models::{ + amount::Amount, + deferred_calls::{DeferredCallId, DeferredCallIdDeserializer, DeferredCallIdSerializer}, + slot::Slot, +}; +use std::collections::{BTreeMap, HashSet}; + +// #[derive(Debug)] +pub struct DeferredCallRegistry { + db: ShareableMassaDBController, + call_serializer: DeferredCallSerializer, + call_id_serializer: DeferredCallIdSerializer, + call_deserializer: DeferredCallDeserializer, + call_id_deserializer: DeferredCallIdDeserializer, + registry_changes_deserializer: DeferredRegistryChangesDeserializer, + registry_changes_serializer: DeferredRegistryChangesSerializer, +} + +impl DeferredCallRegistry { + /* + DB layout: + [DEFERRED_CALL_TOTAL_GAS] -> u128 // total currently booked gas + [DEFERRED_CALL_TOTAL_REGISTERED] -> u64 // total call registered + [DEFERRED_CALLS_PREFIX][slot][SLOT_TOTAL_GAS] -> u64 // total gas booked for a slot (optional, default 0, deleted when set to 0) + [DEFERRED_CALLS_PREFIX][slot][SLOT_BASE_FEE] -> u64 // deleted when set to 0 + [DEFERRED_CALLS_PREFIX][slot][CALLS_TAG][id][CALL_FIELD_X_TAG] -> DeferredCalls.x // call data + */ + + pub fn new(db: ShareableMassaDBController, config: DeferredCallsConfig) -> Self { + Self { + db, + call_serializer: DeferredCallSerializer::new(), + call_id_serializer: DeferredCallIdSerializer::new(), + call_deserializer: DeferredCallDeserializer::new(config), + call_id_deserializer: DeferredCallIdDeserializer::new(), + registry_changes_deserializer: DeferredRegistryChangesDeserializer::new(config), + registry_changes_serializer: DeferredRegistryChangesSerializer::new(), + } + } + + pub fn get_nb_call_registered(&self) -> u64 { + match self + .db + .read() + .get_cf(STATE_CF, DEFERRED_CALL_TOTAL_REGISTERED.as_bytes().to_vec()) + .expect(CRUD_ERROR) + { + Some(v) => { + self.registry_changes_deserializer + .u64_deserializer + .deserialize::(&v) + .expect(DEFERRED_CALL_DESER_ERROR) + .1 + } + None => 0, + } + } + + /// Returns the DeferredSlotCalls for a given slot + pub fn get_slot_calls(&self, slot: Slot) -> DeferredSlotCalls { + let mut to_return = DeferredSlotCalls::new(slot); + let key = deferred_slot_call_prefix_key!(slot.to_bytes_key()); + + let mut temp = HashSet::new(); + + for (serialized_key, _serialized_value) in self.db.read().prefix_iterator_cf(STATE_CF, &key) + { + if !serialized_key.starts_with(&key) { + break; + } + + let rest_key = &serialized_key[key.len()..]; + + let (_rest, call_id) = self + .call_id_deserializer + .deserialize::(rest_key) + .expect(KEY_DESER_ERROR); + + if !temp.insert(call_id.clone()) { + continue; + } + + if let Some(call) = self.get_call(&slot, &call_id) { + to_return.slot_calls.insert(call_id, call); + } + } + + to_return.slot_base_fee = self.get_slot_base_fee(&slot); + to_return.effective_slot_gas = self.get_slot_gas(&slot); + to_return.effective_total_gas = self.get_total_gas(); + + to_return + } + + /// Returns the DeferredCall for a given slot and id + pub fn get_call(&self, slot: &Slot, id: &DeferredCallId) -> Option { + let mut buf_id = Vec::new(); + self.call_id_serializer + .serialize(id, &mut buf_id) + .expect(DEFERRED_CALL_SER_ERROR); + let key = deferred_call_prefix_key!(buf_id, slot.to_bytes_key()); + + let mut serialized_call: Vec = Vec::new(); + for (serialized_key, serialized_value) in self.db.read().prefix_iterator_cf(STATE_CF, &key) + { + if !serialized_key.starts_with(&key) { + break; + } + + serialized_call.extend(serialized_value.iter()); + } + + match self + .call_deserializer + .deserialize::(&serialized_call) + { + Ok((_rest, call)) => Some(call), + Err(_) => None, + } + } + + /// Returns the total effective amount of gas booked for a slot + pub fn get_slot_gas(&self, slot: &Slot) -> u64 { + // By default, if it is absent, it is 0 + let key = deferred_call_slot_total_gas_key!(slot.to_bytes_key()); + match self.db.read().get_cf(STATE_CF, key) { + Ok(Some(v)) => { + let result = self + .call_deserializer + .u64_var_int_deserializer + .deserialize::(&v) + .expect(DEFERRED_CALL_DESER_ERROR) + .1; + result + } + _ => 0, + } + } + + /// Returns the base fee for a slot + pub fn get_slot_base_fee(&self, slot: &Slot) -> Amount { + let key = deferred_call_slot_base_fee_key!(slot.to_bytes_key()); + match self.db.read().get_cf(STATE_CF, key) { + Ok(Some(v)) => { + self.call_deserializer + .amount_deserializer + .deserialize::(&v) + .expect(DEFERRED_CALL_DESER_ERROR) + .1 + } + _ => Amount::zero(), + } + } + + /// Returns the total amount of effective gas booked + pub fn get_total_gas(&self) -> u128 { + match self + .db + .read() + .get_cf(STATE_CF, DEFERRED_CALL_TOTAL_GAS.as_bytes().to_vec()) + .expect(CRUD_ERROR) + { + Some(v) => { + let result = self + .registry_changes_deserializer + .effective_total_gas_deserializer + .deserialize::(&v) + .expect(DEFERRED_CALL_DESER_ERROR) + .1; + + match result { + DeferredRegistryGasChange::Set(v) => v, + DeferredRegistryGasChange::Keep => 0, + } + } + None => 0, + } + } + + pub fn put_entry( + &self, + slot: &Slot, + call_id: &DeferredCallId, + call: &DeferredCall, + batch: &mut DBBatch, + ) { + let mut buffer_id = Vec::new(); + self.call_id_serializer + .serialize(call_id, &mut buffer_id) + .expect(DEFERRED_CALL_SER_ERROR); + + let slot_bytes = slot.to_bytes_key(); + + let db = self.db.read(); + + { + // sender address + let mut buffer = Vec::new(); + self.call_serializer + .address_serializer + .serialize(&call.sender_address, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value( + batch, + sender_address_key!(buffer_id, slot_bytes), + &buffer, + ); + } + + { + // target slot + let mut buffer = Vec::new(); + self.call_serializer + .slot_serializer + .serialize(&call.target_slot, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value(batch, target_slot_key!(buffer_id, slot_bytes), &buffer); + } + + { + // target address + let mut buffer = Vec::new(); + self.call_serializer + .address_serializer + .serialize(&call.target_address, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value( + batch, + target_address_key!(buffer_id, slot_bytes), + &buffer, + ); + } + + { + // target function + let mut buffer = Vec::new(); + self.call_serializer + .string_serializer + .serialize(&call.target_function, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value( + batch, + target_function_key!(buffer_id, slot_bytes), + &buffer, + ); + } + + { + // parameters + let mut buffer = Vec::new(); + self.call_serializer + .vec_u8_serializer + .serialize(&call.parameters, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value(batch, parameters_key!(buffer_id, slot_bytes), &buffer); + } + + { + // coins + let mut buffer = Vec::new(); + self.call_serializer + .amount_serializer + .serialize(&call.coins, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value(batch, coins_key!(buffer_id, slot_bytes), &buffer); + } + + { + // max gas + let mut buffer = Vec::new(); + self.call_serializer + .u64_var_int_serializer + .serialize(&call.max_gas, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value(batch, max_gas_key!(buffer_id, slot_bytes), &buffer); + } + + { + // fee + let mut buffer = Vec::new(); + self.call_serializer + .amount_serializer + .serialize(&call.fee, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value(batch, fee_key!(buffer_id, slot_bytes), &buffer); + } + + // cancelled + let mut buffer = Vec::new(); + self.call_serializer + .bool_serializer + .serialize(&call.cancelled, &mut buffer) + .expect(DEFERRED_CALL_SER_ERROR); + db.put_or_update_entry_value(batch, cancelled_key!(buffer_id, slot_bytes), &buffer); + } + + fn delete_entry(&self, id: &DeferredCallId, slot: &Slot, batch: &mut DBBatch) { + let mut buffer_id = Vec::new(); + self.call_id_serializer + .serialize(id, &mut buffer_id) + .expect(DEFERRED_CALL_SER_ERROR); + + let slot_bytes = slot.to_bytes_key(); + + let db = self.db.read(); + + db.delete_key(batch, sender_address_key!(buffer_id, slot_bytes)); + db.delete_key(batch, target_slot_key!(buffer_id, slot_bytes)); + db.delete_key(batch, target_address_key!(buffer_id, slot_bytes)); + db.delete_key(batch, target_function_key!(buffer_id, slot_bytes)); + db.delete_key(batch, parameters_key!(buffer_id, slot_bytes)); + db.delete_key(batch, coins_key!(buffer_id, slot_bytes)); + db.delete_key(batch, max_gas_key!(buffer_id, slot_bytes)); + db.delete_key(batch, fee_key!(buffer_id, slot_bytes)); + db.delete_key(batch, cancelled_key!(buffer_id, slot_bytes)); + } + + pub fn apply_changes_to_batch( + &self, + changes: DeferredCallRegistryChanges, + batch: &mut DBBatch, + ) { + //Note: if a slot gas is zet to 0, delete the slot gas entry + // same for base fee + + for change in changes.slots_change.iter() { + let slot = change.0; + let slot_changes = change.1; + for (id, call_change) in slot_changes.calls.iter() { + match call_change { + DeferredRegistryCallChange::Set(call) => { + self.put_entry(slot, id, call, batch); + } + DeferredRegistryCallChange::Delete => { + self.delete_entry(id, slot, batch); + } + } + } + match slot_changes.effective_slot_gas { + DeferredRegistryGasChange::Set(v) => { + let key = deferred_call_slot_total_gas_key!(slot.to_bytes_key()); + //Note: if a slot gas is zet to 0, delete the slot gas entry + if v.eq(&0) { + self.db.read().delete_key(batch, key); + } else { + let mut value_ser = Vec::new(); + self.call_serializer + .u64_var_int_serializer + .serialize(&v, &mut value_ser) + .expect(DEFERRED_CALL_SER_ERROR); + self.db + .read() + .put_or_update_entry_value(batch, key, &value_ser); + } + } + DeferredRegistryGasChange::Keep => {} + } + match slot_changes.base_fee { + DeferredRegistryBaseFeeChange::Set(v) => { + let key = deferred_call_slot_base_fee_key!(slot.to_bytes_key()); + //Note: if a base fee is zet to 0, delete the base fee entry + if v.eq(&Amount::zero()) { + self.db.read().delete_key(batch, key); + } else { + let mut value_ser = Vec::new(); + self.call_serializer + .amount_serializer + .serialize(&v, &mut value_ser) + .expect(DEFERRED_CALL_SER_ERROR); + self.db + .read() + .put_or_update_entry_value(batch, key, &value_ser); + } + } + DeferredRegistryBaseFeeChange::Keep => {} + } + } + + match changes.effective_total_gas { + DeferredRegistryGasChange::Set(_) => { + let key = DEFERRED_CALL_TOTAL_GAS.as_bytes().to_vec(); + let mut value_ser = Vec::new(); + self.registry_changes_serializer + .effective_total_gas_serializer + .serialize(&changes.effective_total_gas, &mut value_ser) + .expect(DEFERRED_CALL_SER_ERROR); + self.db + .read() + .put_or_update_entry_value(batch, key, &value_ser); + } + DeferredRegistryGasChange::Keep => {} + } + + match changes.total_calls_registered { + DeferredRegistryGasChange::Set(_) => { + let key = DEFERRED_CALL_TOTAL_REGISTERED.as_bytes().to_vec(); + let mut value_ser = Vec::new(); + self.registry_changes_serializer + .total_calls_registered_serializer + .serialize(&changes.total_calls_registered, &mut value_ser) + .expect(DEFERRED_CALL_SER_ERROR); + self.db + .read() + .put_or_update_entry_value(batch, key, &value_ser); + } + DeferredRegistryGasChange::Keep => {} + } + } +} + +pub type DeferredRegistryCallChange = SetOrDelete; +pub type DeferredRegistryGasChange = SetOrKeep; +pub type DeferredRegistryBaseFeeChange = SetOrKeep; + +/// A structure that lists slot calls for a given slot, +/// as well as global gas usage statistics. +#[derive(Debug, Clone)] +pub struct DeferredSlotCalls { + pub slot: Slot, + pub slot_calls: BTreeMap, + + // calls gas + gas_alloc_cost + // effective_slot_gas doesn't include gas of cancelled calls + pub effective_slot_gas: u64, + + pub slot_base_fee: Amount, + + // total gas booked + gas_alloc_cost + // effective_total_gas doesn't include gas of cancelled calls + pub effective_total_gas: u128, +} + +impl DeferredSlotCalls { + pub fn new(slot: Slot) -> Self { + Self { + slot, + slot_calls: BTreeMap::new(), + effective_slot_gas: 0, + slot_base_fee: Amount::zero(), + effective_total_gas: 0, + } + } + + pub fn apply_changes(&mut self, changes: &DeferredCallRegistryChanges) { + let Some(slot_changes) = changes.slots_change.get(&self.slot) else { + return; + }; + for (id, change) in &slot_changes.calls { + match change { + DeferredRegistryCallChange::Set(call) => { + self.slot_calls.insert(id.clone(), call.clone()); + } + DeferredRegistryCallChange::Delete => { + self.slot_calls.remove(id); + } + } + } + match slot_changes.effective_slot_gas { + DeferredRegistryGasChange::Set(v) => self.effective_slot_gas = v, + DeferredRegistryGasChange::Keep => {} + } + match slot_changes.base_fee { + DeferredRegistryGasChange::Set(v) => self.slot_base_fee = v, + DeferredRegistryGasChange::Keep => {} + } + match changes.effective_total_gas { + DeferredRegistryGasChange::Set(v) => self.effective_total_gas = v, + DeferredRegistryGasChange::Keep => {} + } + } +} diff --git a/massa-deferred-calls/src/macros.rs b/massa-deferred-calls/src/macros.rs new file mode 100644 index 00000000000..93b902b9760 --- /dev/null +++ b/massa-deferred-calls/src/macros.rs @@ -0,0 +1,183 @@ +pub(crate) const DEFERRED_CALL_TOTAL_GAS: &str = "deferred_call_total_gas"; + +pub(crate) const DEFERRED_CALL_TOTAL_REGISTERED: &str = "deferred_call_total_registered"; + +pub(crate) const CALLS_TAG: u8 = 0u8; +// slot fields +pub(crate) const SLOT_TOTAL_GAS: u8 = 1u8; +pub(crate) const SLOT_BASE_FEE: u8 = 2u8; + +// call fields +pub(crate) const CALL_FIELD_SENDER_ADDRESS: u8 = 1u8; +pub(crate) const CALL_FIELD_TARGET_SLOT: u8 = 2u8; +pub(crate) const CALL_FIELD_TARGET_ADDRESS: u8 = 3u8; +pub(crate) const CALL_FIELD_TARGET_FUNCTION: u8 = 4u8; +pub(crate) const CALL_FIELD_PARAMETERS: u8 = 5u8; +pub(crate) const CALL_FIELD_COINS: u8 = 6u8; +pub(crate) const CALL_FIELD_MAX_GAS: u8 = 7u8; +pub(crate) const CALL_FIELD_FEE: u8 = 8u8; +pub(crate) const CALL_FIELD_CANCELED: u8 = 9u8; + +#[macro_export] +macro_rules! deferred_call_slot_total_gas_key { + ($slot:expr) => { + [ + DEFERRED_CALLS_PREFIX.as_bytes(), + &$slot[..], + &[$crate::macros::SLOT_TOTAL_GAS], + ] + .concat() + }; +} + +#[macro_export] +macro_rules! deferred_call_slot_base_fee_key { + ($slot:expr) => { + [ + DEFERRED_CALLS_PREFIX.as_bytes(), + &$slot[..], + &[$crate::macros::SLOT_BASE_FEE], + ] + .concat() + }; +} + +#[macro_export] +macro_rules! deferred_slot_call_prefix_key { + ($slot:expr) => { + [ + DEFERRED_CALLS_PREFIX.as_bytes(), + &$slot[..], + &[$crate::macros::CALLS_TAG], + ] + .concat() + }; +} + +#[macro_export] +macro_rules! deferred_call_prefix_key { + ($id:expr, $slot:expr) => { + [&deferred_slot_call_prefix_key!($slot), &$id[..]].concat() + }; +} + +/// key formatting macro +#[macro_export] +macro_rules! deferred_call_field_key { + ($id:expr, $slot:expr, $field:expr) => { + [deferred_call_prefix_key!($id, $slot), vec![$field]].concat() + }; +} + +/// sender address key formatting macro +#[macro_export] +macro_rules! sender_address_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_SENDER_ADDRESS) + }; +} + +/// target slot key formatting macro +#[macro_export] +macro_rules! target_slot_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_TARGET_SLOT) + }; +} + +/// target address key formatting macro +#[macro_export] +macro_rules! target_address_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_TARGET_ADDRESS) + }; +} + +/// target function key formatting macro +#[macro_export] +macro_rules! target_function_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_TARGET_FUNCTION) + }; +} + +/// parameters key formatting macro +#[macro_export] +macro_rules! parameters_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_PARAMETERS) + }; +} + +/// coins key formatting macro +#[macro_export] +macro_rules! coins_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_COINS) + }; +} + +/// max gas key formatting macro +#[macro_export] +macro_rules! max_gas_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_MAX_GAS) + }; +} + +/// fee key formatting macro +#[macro_export] +macro_rules! fee_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_FEE) + }; +} + +/// cancelled key formatting macro +#[macro_export] +macro_rules! cancelled_key { + ($id:expr, $slot:expr) => { + deferred_call_field_key!($id, $slot, $crate::macros::CALL_FIELD_CANCELED) + }; +} + +#[cfg(test)] +mod tests { + use massa_db_exports::DEFERRED_CALLS_PREFIX; + use massa_models::{ + deferred_calls::{DeferredCallId, DeferredCallIdSerializer}, + slot::Slot, + }; + use massa_serialization::Serializer; + + #[test] + fn test_deferred_call_prefix_key() { + let slot_ser = Slot { + period: 1, + thread: 5, + } + .to_bytes_key(); + + let id = DeferredCallId::new( + 0, + Slot { + thread: 5, + period: 1, + }, + 1, + &[], + ) + .unwrap(); + + let id_ser = DeferredCallIdSerializer::new(); + let mut buf_id = Vec::new(); + id_ser.serialize(&id, &mut buf_id).unwrap(); + + let prefix = ["deferred_calls/".as_bytes(), &slot_ser, &[0u8], &buf_id].concat(); + + assert_eq!(deferred_call_prefix_key!(buf_id, slot_ser), prefix); + + // let to_check = [prefix[..], &[1u8]].concat(); + // assert_eq!(sender_address_key!(buf_id, slot_ser), to_check); + } +} diff --git a/massa-deferred-calls/src/registry_changes.rs b/massa-deferred-calls/src/registry_changes.rs new file mode 100644 index 00000000000..c6475df304f --- /dev/null +++ b/massa-deferred-calls/src/registry_changes.rs @@ -0,0 +1,332 @@ +use std::{collections::BTreeMap, ops::Bound}; + +use massa_models::{ + amount::Amount, + deferred_calls::DeferredCallId, + slot::{Slot, SlotDeserializer, SlotSerializer}, + types::{SetOrKeep, SetOrKeepDeserializer, SetOrKeepSerializer}, +}; +use massa_serialization::{ + Deserializer, SerializeError, Serializer, U128VarIntDeserializer, U128VarIntSerializer, + U64VarIntDeserializer, U64VarIntSerializer, +}; +use nom::{ + error::{context, ContextError, ParseError}, + multi::length_count, + sequence::tuple, + IResult, Parser, +}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::{ + config::DeferredCallsConfig, + slot_changes::{ + DeferredRegistrySlotChanges, DeferredRegistrySlotChangesDeserializer, + DeferredRegistrySlotChangesSerializer, + }, + DeferredCall, DeferredRegistryGasChange, +}; +use std::ops::Bound::Included; + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeferredCallRegistryChanges { + #[serde_as(as = "Vec<(_, _)>")] + pub slots_change: BTreeMap, + + pub effective_total_gas: DeferredRegistryGasChange, + pub total_calls_registered: SetOrKeep, +} + +impl Default for DeferredCallRegistryChanges { + fn default() -> Self { + Self { + slots_change: Default::default(), + effective_total_gas: DeferredRegistryGasChange::Keep, + total_calls_registered: SetOrKeep::Keep, + } + } +} + +impl DeferredCallRegistryChanges { + pub fn delete_call(&mut self, target_slot: Slot, id: &DeferredCallId) { + self.slots_change + .entry(target_slot) + .or_default() + .delete_call(id) + } + + pub fn set_call(&mut self, id: DeferredCallId, call: DeferredCall) { + self.slots_change + .entry(call.target_slot) + .or_default() + .set_call(id, call); + } + + pub fn get_call(&self, target_slot: &Slot, id: &DeferredCallId) -> Option<&DeferredCall> { + self.slots_change + .get(target_slot) + .and_then(|slot_changes| slot_changes.get_call(id)) + } + + pub fn get_effective_slot_gas(&self, target_slot: &Slot) -> Option { + self.slots_change + .get(target_slot) + .and_then(|slot_changes| slot_changes.get_effective_slot_gas()) + } + + pub fn set_effective_slot_gas(&mut self, target_slot: Slot, gas: u64) { + self.slots_change + .entry(target_slot) + .or_default() + .set_effective_slot_gas(gas); + } + + pub fn set_slot_base_fee(&mut self, target_slot: Slot, base_fee: Amount) { + self.slots_change + .entry(target_slot) + .or_default() + .set_base_fee(base_fee); + } + + pub fn get_slot_base_fee(&self, target_slot: &Slot) -> Option { + self.slots_change + .get(target_slot) + .and_then(|slot_changes| slot_changes.get_base_fee()) + } + + pub fn set_effective_total_gas(&mut self, gas: u128) { + self.effective_total_gas = DeferredRegistryGasChange::Set(gas); + } + + pub fn get_effective_total_gas(&self) -> Option { + match self.effective_total_gas { + DeferredRegistryGasChange::Set(v) => Some(v), + DeferredRegistryGasChange::Keep => None, + } + } + + pub fn set_total_calls_registered(&mut self, total_calls_registered: u64) { + self.total_calls_registered = SetOrKeep::Set(total_calls_registered); + } + + pub fn get_total_calls_registered(&self) -> Option { + match self.total_calls_registered { + SetOrKeep::Set(v) => Some(v), + SetOrKeep::Keep => None, + } + } +} + +pub struct DeferredRegistryChangesSerializer { + pub(crate) u64_serializer: U64VarIntSerializer, + slot_changes_serializer: DeferredRegistrySlotChangesSerializer, + slot_serializer: SlotSerializer, + pub(crate) effective_total_gas_serializer: SetOrKeepSerializer, + pub(crate) total_calls_registered_serializer: SetOrKeepSerializer, +} + +impl DeferredRegistryChangesSerializer { + pub fn new() -> Self { + Self { + u64_serializer: U64VarIntSerializer::new(), + slot_changes_serializer: DeferredRegistrySlotChangesSerializer::new(), + slot_serializer: SlotSerializer::new(), + effective_total_gas_serializer: SetOrKeepSerializer::new(U128VarIntSerializer::new()), + total_calls_registered_serializer: SetOrKeepSerializer::new(U64VarIntSerializer::new()), + } + } +} + +impl Default for DeferredRegistryChangesSerializer { + fn default() -> Self { + Self::new() + } +} + +impl Serializer for DeferredRegistryChangesSerializer { + fn serialize( + &self, + value: &DeferredCallRegistryChanges, + buffer: &mut Vec, + ) -> Result<(), SerializeError> { + self.u64_serializer.serialize( + &(value.slots_change.len().try_into().map_err(|_| { + SerializeError::GeneralError("Fail to transform usize to u64".to_string()) + })?), + buffer, + )?; + + for (slot, changes) in &value.slots_change { + self.slot_serializer.serialize(slot, buffer)?; + self.slot_changes_serializer.serialize(changes, buffer)?; + } + + self.effective_total_gas_serializer + .serialize(&value.effective_total_gas, buffer)?; + + self.total_calls_registered_serializer + .serialize(&value.total_calls_registered, buffer)?; + + Ok(()) + } +} + +pub struct DeferredRegistryChangesDeserializer { + pub(crate) u64_deserializer: U64VarIntDeserializer, + slot_changes_deserializer: DeferredRegistrySlotChangesDeserializer, + slot_deserializer: SlotDeserializer, + pub(crate) effective_total_gas_deserializer: + SetOrKeepDeserializer, + pub(crate) total_calls_registered_deserializer: + SetOrKeepDeserializer, +} + +impl DeferredRegistryChangesDeserializer { + pub fn new(config: DeferredCallsConfig) -> Self { + Self { + u64_deserializer: U64VarIntDeserializer::new(Included(u64::MIN), Included(u64::MAX)), + slot_changes_deserializer: DeferredRegistrySlotChangesDeserializer::new(config), + slot_deserializer: SlotDeserializer::new( + (Bound::Included(0), Bound::Included(u64::MAX)), + (Bound::Included(0), Bound::Excluded(config.thread_count)), + ), + effective_total_gas_deserializer: SetOrKeepDeserializer::new( + U128VarIntDeserializer::new(Included(u128::MIN), Included(u128::MAX)), + ), + total_calls_registered_deserializer: SetOrKeepDeserializer::new( + U64VarIntDeserializer::new(Included(u64::MIN), Included(u64::MAX)), + ), + } + } +} + +impl Deserializer for DeferredRegistryChangesDeserializer { + fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>( + &self, + buffer: &'a [u8], + ) -> IResult<&'a [u8], DeferredCallRegistryChanges, E> { + context( + "Failed DeferredRegistryChanges deserialization", + tuple(( + length_count( + context("Failed length deserialization", |input| { + self.u64_deserializer.deserialize(input) + }), + |input| { + tuple(( + context("Failed slot deserialization", |input| { + self.slot_deserializer.deserialize(input) + }), + context( + "Failed set_update_or_delete_message deserialization", + |input| self.slot_changes_deserializer.deserialize(input), + ), + ))(input) + }, + ), + context("Failed total_gas deserialization", |input| { + self.effective_total_gas_deserializer.deserialize(input) + }), + context("Failed total call registered deserialization", |input| { + self.total_calls_registered_deserializer.deserialize(input) + }), + )), + ) + .map( + |(changes, total_gas, total_calls_registered)| DeferredCallRegistryChanges { + slots_change: changes.into_iter().collect::>(), + effective_total_gas: total_gas, + total_calls_registered, + }, + ) + .parse(buffer) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use massa_models::{address::Address, amount::Amount, deferred_calls::DeferredCallId}; + use massa_serialization::DeserializeError; + + use crate::{ + config::DeferredCallsConfig, + registry_changes::{ + DeferredRegistryChangesDeserializer, DeferredRegistryChangesSerializer, + }, + slot_changes::DeferredRegistrySlotChanges, + DeferredCall, + }; + + #[test] + fn test_deferred_registry_ser_deser() { + use crate::DeferredCallRegistryChanges; + use massa_models::slot::Slot; + use massa_serialization::{Deserializer, Serializer}; + use std::collections::BTreeMap; + + let mut changes = DeferredCallRegistryChanges { + slots_change: BTreeMap::new(), + effective_total_gas: Default::default(), + total_calls_registered: Default::default(), + }; + + let mut registry_slot_changes = DeferredRegistrySlotChanges::default(); + registry_slot_changes.set_base_fee(Amount::from_str("100").unwrap()); + registry_slot_changes.set_effective_slot_gas(100_000); + let target_slot = Slot { + thread: 5, + period: 1, + }; + + let call = DeferredCall::new( + Address::from_str("AU12dG5xP1RDEB5ocdHkymNVvvSJmUL9BgHwCksDowqmGWxfpm93x").unwrap(), + target_slot, + Address::from_str("AS127QtY6Hzm6BnJc9wqCBfPNvEH9fKer3LiMNNQmcX3MzLwCL6G6").unwrap(), + "receive".to_string(), + vec![42, 42, 42, 42], + Amount::from_raw(100), + 3000000, + Amount::from_raw(1), + false, + ); + let id = DeferredCallId::new( + 0, + Slot { + thread: 5, + period: 1, + }, + 1, + &[], + ) + .unwrap(); + registry_slot_changes.set_call(id, call); + + changes + .slots_change + .insert(target_slot, registry_slot_changes); + + changes.set_effective_total_gas(100_000); + + let mut buffer = Vec::new(); + let serializer = DeferredRegistryChangesSerializer::new(); + serializer.serialize(&changes, &mut buffer).unwrap(); + + let deserializer = DeferredRegistryChangesDeserializer::new(DeferredCallsConfig::default()); + let (rest, deserialized) = deserializer + .deserialize::(&buffer) + .unwrap(); + + assert_eq!(rest.len(), 0); + let base = changes.slots_change.get(&target_slot).unwrap(); + let slot_changes_deser = deserialized.slots_change.get(&target_slot).unwrap(); + assert_eq!(base.calls, slot_changes_deser.calls); + assert_eq!( + changes.effective_total_gas, + deserialized.effective_total_gas + ); + } +} diff --git a/massa-deferred-calls/src/slot_changes.rs b/massa-deferred-calls/src/slot_changes.rs new file mode 100644 index 00000000000..7b73d570703 --- /dev/null +++ b/massa-deferred-calls/src/slot_changes.rs @@ -0,0 +1,283 @@ +use std::collections::BTreeMap; + +use crate::{ + call::{DeferredCallDeserializer, DeferredCallSerializer}, + config::DeferredCallsConfig, + DeferredCall, DeferredRegistryBaseFeeChange, DeferredRegistryCallChange, + DeferredRegistryGasChange, +}; +use massa_models::types::{ + SetOrDelete, SetOrDeleteDeserializer, SetOrDeleteSerializer, SetOrKeepDeserializer, + SetOrKeepSerializer, +}; +use massa_models::{ + amount::{Amount, AmountDeserializer, AmountSerializer}, + deferred_calls::{DeferredCallId, DeferredCallIdDeserializer, DeferredCallIdSerializer}, +}; +use massa_serialization::{ + Deserializer, SerializeError, Serializer, U64VarIntDeserializer, U64VarIntSerializer, +}; +use nom::{ + error::{context, ContextError, ParseError}, + multi::length_count, + sequence::tuple, + IResult, Parser, +}; +use serde::{Deserialize, Serialize}; +use std::ops::Bound::Included; + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct DeferredRegistrySlotChanges { + pub calls: BTreeMap, + pub effective_slot_gas: DeferredRegistryGasChange, + pub base_fee: DeferredRegistryBaseFeeChange, +} + +impl DeferredRegistrySlotChanges { + pub fn calls_len(&self) -> usize { + self.calls.len() + } + + /// add Delete changes will delete the call from the db registry when the slot is finalized + pub fn delete_call(&mut self, id: &DeferredCallId) { + match self.calls.entry(id.clone()) { + std::collections::btree_map::Entry::Occupied(mut v) => { + *v.get_mut() = DeferredRegistryCallChange::Delete; + } + std::collections::btree_map::Entry::Vacant(v) => { + v.insert(DeferredRegistryCallChange::Delete); + } + } + } + + pub fn set_call(&mut self, id: DeferredCallId, call: DeferredCall) { + self.calls.insert(id, DeferredRegistryCallChange::Set(call)); + } + + pub fn get_call(&self, id: &DeferredCallId) -> Option<&DeferredCall> { + match self.calls.get(id) { + Some(SetOrDelete::Set(call)) => Some(call), + _ => None, + } + } + + pub fn set_effective_slot_gas(&mut self, gas: u64) { + self.effective_slot_gas = DeferredRegistryGasChange::Set(gas); + } + + pub fn get_effective_slot_gas(&self) -> Option { + match self.effective_slot_gas { + DeferredRegistryGasChange::Set(v) => Some(v), + DeferredRegistryGasChange::Keep => None, + } + } + + pub fn get_base_fee(&self) -> Option { + match self.base_fee { + DeferredRegistryGasChange::Set(v) => Some(v), + DeferredRegistryGasChange::Keep => None, + } + } + + pub fn set_base_fee(&mut self, base_fee: Amount) { + self.base_fee = DeferredRegistryGasChange::Set(base_fee); + } +} + +pub struct DeferredRegistrySlotChangesSerializer { + deferred_registry_slot_changes_length: U64VarIntSerializer, + call_id_serializer: DeferredCallIdSerializer, + calls_set_or_delete_serializer: SetOrDeleteSerializer, + gas_serializer: SetOrKeepSerializer, + base_fee_serializer: SetOrKeepSerializer, +} + +impl DeferredRegistrySlotChangesSerializer { + pub fn new() -> Self { + Self { + deferred_registry_slot_changes_length: U64VarIntSerializer::new(), + call_id_serializer: DeferredCallIdSerializer::new(), + calls_set_or_delete_serializer: SetOrDeleteSerializer::new( + DeferredCallSerializer::new(), + ), + gas_serializer: SetOrKeepSerializer::new(U64VarIntSerializer::new()), + base_fee_serializer: SetOrKeepSerializer::new(AmountSerializer::new()), + } + } +} + +impl Default for DeferredRegistrySlotChangesSerializer { + fn default() -> Self { + Self::new() + } +} + +impl Serializer for DeferredRegistrySlotChangesSerializer { + fn serialize( + &self, + value: &DeferredRegistrySlotChanges, + buffer: &mut Vec, + ) -> Result<(), massa_serialization::SerializeError> { + self.deferred_registry_slot_changes_length.serialize( + &(value.calls.len().try_into().map_err(|_| { + SerializeError::GeneralError("Fail to transform usize to u64".to_string()) + })?), + buffer, + )?; + for (id, call) in &value.calls { + self.call_id_serializer.serialize(id, buffer)?; + self.calls_set_or_delete_serializer + .serialize(call, buffer)?; + } + self.gas_serializer + .serialize(&value.effective_slot_gas, buffer)?; + self.base_fee_serializer + .serialize(&value.base_fee, buffer)?; + + Ok(()) + } +} + +pub struct DeferredRegistrySlotChangesDeserializer { + deferred_registry_slot_changes_length: U64VarIntDeserializer, + call_id_deserializer: DeferredCallIdDeserializer, + calls_set_or_delete_deserializer: + SetOrDeleteDeserializer, + gas_deserializer: SetOrKeepDeserializer, + base_fee_deserializer: SetOrKeepDeserializer, +} + +impl DeferredRegistrySlotChangesDeserializer { + pub fn new(config: DeferredCallsConfig) -> Self { + Self { + deferred_registry_slot_changes_length: U64VarIntDeserializer::new( + Included(u64::MIN), + Included(config.max_pool_changes), + ), + call_id_deserializer: DeferredCallIdDeserializer::new(), + calls_set_or_delete_deserializer: SetOrDeleteDeserializer::new( + DeferredCallDeserializer::new(config), + ), + gas_deserializer: SetOrKeepDeserializer::new(U64VarIntDeserializer::new( + Included(0), + Included(config.max_gas), + )), + base_fee_deserializer: SetOrKeepDeserializer::new(AmountDeserializer::new( + Included(Amount::MIN), + Included(Amount::MAX), + )), + } + } +} + +impl Deserializer for DeferredRegistrySlotChangesDeserializer { + fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>( + &self, + buffer: &'a [u8], + ) -> IResult<&'a [u8], DeferredRegistrySlotChanges, E> { + context( + "Failed DeferredRegistrySlotChanges deserialization", + tuple(( + length_count( + context("Failed length deserialization", |input| { + self.deferred_registry_slot_changes_length + .deserialize(input) + }), + |input: &'a [u8]| { + tuple(( + context("Failed id deserialization", |input| { + self.call_id_deserializer.deserialize(input) + }), + context( + "Failed set_update_or_delete_message deserialization", + |input| self.calls_set_or_delete_deserializer.deserialize(input), + ), + ))(input) + }, + ), + context("Failed gas deserialization", |input| { + self.gas_deserializer.deserialize(input) + }), + context("Failed base fee deserialize", |input| { + self.base_fee_deserializer.deserialize(input) + }), + )), + ) + .map(|(vec, gas, base_fee)| { + let calls = vec.into_iter().collect::>(); + + DeferredRegistrySlotChanges { + calls, + effective_slot_gas: gas, + base_fee, + } + }) + .parse(buffer) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use massa_models::{ + address::Address, amount::Amount, deferred_calls::DeferredCallId, slot::Slot, + }; + use massa_serialization::{DeserializeError, Deserializer, Serializer}; + + use crate::{config::DeferredCallsConfig, DeferredCall}; + + use super::{ + DeferredRegistrySlotChanges, DeferredRegistrySlotChangesDeserializer, + DeferredRegistrySlotChangesSerializer, + }; + + #[test] + fn test_slot_changes_ser_deser() { + let mut registry_slot_changes = DeferredRegistrySlotChanges::default(); + registry_slot_changes.set_base_fee(Amount::from_str("100").unwrap()); + registry_slot_changes.set_effective_slot_gas(100_000); + let target_slot = Slot { + thread: 5, + period: 1, + }; + + let call = DeferredCall::new( + Address::from_str("AU12dG5xP1RDEB5ocdHkymNVvvSJmUL9BgHwCksDowqmGWxfpm93x").unwrap(), + target_slot, + Address::from_str("AS127QtY6Hzm6BnJc9wqCBfPNvEH9fKer3LiMNNQmcX3MzLwCL6G6").unwrap(), + "receive".to_string(), + vec![42, 42, 42, 42], + Amount::from_raw(100), + 3000000, + Amount::from_raw(1), + false, + ); + let id = DeferredCallId::new( + 0, + Slot { + thread: 5, + period: 1, + }, + 1, + &[], + ) + .unwrap(); + + registry_slot_changes.set_call(id, call); + + let mut buffer = Vec::new(); + let serializer = DeferredRegistrySlotChangesSerializer::new(); + serializer + .serialize(®istry_slot_changes, &mut buffer) + .unwrap(); + + let deserializer = + DeferredRegistrySlotChangesDeserializer::new(DeferredCallsConfig::default()); + let (rest, changes_deser) = deserializer + .deserialize::(&buffer) + .unwrap(); + assert!(rest.is_empty()); + assert_eq!(changes_deser.calls, registry_slot_changes.calls); + } +} diff --git a/massa-deferred-calls/src/tests/mod.rs b/massa-deferred-calls/src/tests/mod.rs new file mode 100644 index 00000000000..16879e738be --- /dev/null +++ b/massa-deferred-calls/src/tests/mod.rs @@ -0,0 +1,132 @@ +use super::*; +use crate::{DeferredCall, DeferredCallRegistry, DeferredCallRegistryChanges}; +use massa_db_exports::{DBBatch, MassaDBConfig, MassaDBController, ShareableMassaDBController}; +use massa_db_worker::MassaDB; +use massa_models::{ + address::Address, + amount::Amount, + config::THREAD_COUNT, + deferred_calls::{DeferredCallId, DeferredCallIdSerializer}, + slot::Slot, +}; +use parking_lot::RwLock; +use std::{str::FromStr, sync::Arc}; +use tempfile::tempdir; + +#[test] +fn call_registry_apply_changes() { + let temp_dir = tempdir().expect("Unable to create a temp folder"); + let db_config = MassaDBConfig { + path: temp_dir.path().to_path_buf(), + max_history_length: 100, + max_final_state_elements_size: 100, + max_versioning_elements_size: 100, + thread_count: THREAD_COUNT, + max_ledger_backups: 100, + }; + let call_id_serializer = DeferredCallIdSerializer::new(); + let db: ShareableMassaDBController = Arc::new(RwLock::new( + Box::new(MassaDB::new(db_config)) as Box<(dyn MassaDBController + 'static)> + )); + + let registry = DeferredCallRegistry::new(db, DeferredCallsConfig::default()); + + let mut changes = DeferredCallRegistryChanges::default(); + + let target_slot = Slot { + thread: 5, + period: 1, + }; + + let call = DeferredCall::new( + Address::from_str("AU12dG5xP1RDEB5ocdHkymNVvvSJmUL9BgHwCksDowqmGWxfpm93x").unwrap(), + target_slot, + Address::from_str("AS127QtY6Hzm6BnJc9wqCBfPNvEH9fKer3LiMNNQmcX3MzLwCL6G6").unwrap(), + "receive".to_string(), + vec![42, 42, 42, 42], + Amount::from_raw(100), + 3000000, + Amount::from_raw(1), + false, + ); + let id = DeferredCallId::new(0, target_slot, 1, &[]).unwrap(); + let mut buf_id = Vec::new(); + call_id_serializer.serialize(&id, &mut buf_id).unwrap(); + + changes.set_call(id.clone(), call.clone()); + + let mut batch = DBBatch::new(); + registry.apply_changes_to_batch(changes, &mut batch); + + registry.db.write().write_batch(batch, DBBatch::new(), None); + + let result = registry.get_call(&target_slot, &id).unwrap(); + assert!(result.target_function.eq(&call.target_function)); + assert_eq!(result.sender_address, call.sender_address); +} + +#[test] +fn call_registry_get_slot_calls() { + let temp_dir = tempdir().expect("Unable to create a temp folder"); + let db_config = MassaDBConfig { + path: temp_dir.path().to_path_buf(), + max_history_length: 100, + max_final_state_elements_size: 100, + max_versioning_elements_size: 100, + thread_count: THREAD_COUNT, + max_ledger_backups: 100, + }; + let call_id_serializer = DeferredCallIdSerializer::new(); + let db: ShareableMassaDBController = Arc::new(RwLock::new( + Box::new(MassaDB::new(db_config)) as Box<(dyn MassaDBController + 'static)> + )); + + let registry = DeferredCallRegistry::new(db, DeferredCallsConfig::default()); + + let mut changes = DeferredCallRegistryChanges::default(); + + let target_slot = Slot { + thread: 5, + period: 1, + }; + + let call = DeferredCall::new( + Address::from_str("AU12dG5xP1RDEB5ocdHkymNVvvSJmUL9BgHwCksDowqmGWxfpm93x").unwrap(), + target_slot, + Address::from_str("AS127QtY6Hzm6BnJc9wqCBfPNvEH9fKer3LiMNNQmcX3MzLwCL6G6").unwrap(), + "receive".to_string(), + vec![42, 42, 42, 42], + Amount::from_raw(100), + 3000000, + Amount::from_raw(1), + false, + ); + let id = DeferredCallId::new(0, target_slot, 1, &[]).unwrap(); + + let id2 = DeferredCallId::new(0, target_slot, 1, &[123]).unwrap(); + + let mut buf_id = Vec::new(); + call_id_serializer.serialize(&id, &mut buf_id).unwrap(); + + changes.set_call(id.clone(), call.clone()); + changes.set_call(id2.clone(), call.clone()); + changes.set_effective_total_gas(100); + changes.set_effective_slot_gas(target_slot, 100_000); + + changes.set_slot_base_fee(target_slot, Amount::from_raw(10000000)); + + let mut batch = DBBatch::new(); + // 2 calls + registry.apply_changes_to_batch(changes, &mut batch); + + registry.db.write().write_batch(batch, DBBatch::new(), None); + + let result = registry.get_slot_calls(target_slot); + + assert!(result.slot_calls.len() == 2); + assert!(result.slot_calls.contains_key(&id)); + assert!(result.slot_calls.contains_key(&id2)); + assert_eq!(result.effective_total_gas, 100); + assert_eq!(result.slot_base_fee, Amount::from_raw(10000000)); + assert_eq!(result.effective_slot_gas, 100_000); +} diff --git a/massa-event-cache/Cargo.toml b/massa-event-cache/Cargo.toml new file mode 100644 index 00000000000..0a3bef49fc7 --- /dev/null +++ b/massa-event-cache/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "massa_event_cache" +version = "0.1.0" +edition = "2021" + +[features] +test-exports = [ + "massa_models/test-exports", + "mockall", + "mockall_wrap" +] + + +[dependencies] +nom = {workspace = true} +rocksdb = {workspace = true} +tracing = {workspace = true} +parking_lot = { workspace = true } +num_enum = { workspace = true } +massa_models = {workspace = true} +massa_serialization = {workspace = true} +massa_time = {workspace = true} +mockall = {workspace = true, optional = true} +mockall_wrap = {workspace = true, optional = true} + +[dev-dependencies] +tempfile = {workspace = true} +serial_test = {workspace = true} +more-asserts = {workspace = true} +rand = {workspace = true} +mockall = {workspace = true} +mockall_wrap = {workspace = true} +massa_models = { workspace = true, features = ["test-exports"] } diff --git a/massa-event-cache/src/config.rs b/massa-event-cache/src/config.rs new file mode 100644 index 00000000000..9eab23e0d5b --- /dev/null +++ b/massa-event-cache/src/config.rs @@ -0,0 +1,22 @@ +use std::path::PathBuf; + +pub struct EventCacheConfig { + /// Path to the hard drive cache storage + pub event_cache_path: PathBuf, + /// Maximum number of entries we want to keep in the event cache + pub max_event_cache_length: usize, + /// Amount of entries removed when `event_cache_size` is reached + pub snip_amount: usize, + /// Maximum length of an event data (aka event message) + pub max_event_data_length: u64, + /// Thread count + pub thread_count: u8, + /// Call stack max length + pub max_call_stack_length: u16, + /// Maximum number of events per operation + pub max_events_per_operation: u64, + /// Maximum number of operations per block + pub max_operations_per_block: u64, + /// Maximum events returned in a query + pub max_events_per_query: usize, +} diff --git a/massa-event-cache/src/controller.rs b/massa-event-cache/src/controller.rs new file mode 100644 index 00000000000..a4fe2148a58 --- /dev/null +++ b/massa-event-cache/src/controller.rs @@ -0,0 +1,136 @@ +// std +use std::collections::{BTreeSet, VecDeque}; +use std::sync::Arc; +// third-party +use parking_lot::{Condvar, Mutex, RwLock}; +// internal +use crate::event_cache::EventCache; +use massa_models::execution::EventFilter; +use massa_models::output_event::SCOutputEvent; + +/// structure used to communicate with controller +#[derive(Debug, Default)] +pub(crate) struct EventCacheWriterInputData { + /// set stop to true to stop the thread + pub stop: bool, + pub(crate) events: VecDeque, +} + +impl EventCacheWriterInputData { + pub fn new() -> Self { + Self { + stop: Default::default(), + events: Default::default(), + } + } + + /* + /// Takes the current input data into a clone that is returned, + /// and resets self. + pub fn take(&mut self) -> Self { + Self { + stop: std::mem::take(&mut self.stop), + events: std::mem::take(&mut self.events), + } + } + */ +} + +/// interface that communicates with the worker thread +#[cfg_attr(feature = "test-exports", mockall_wrap::wrap, mockall::automock)] +pub trait EventCacheController: Send + Sync { + fn save_events(&self, events: VecDeque); + + fn get_filtered_sc_output_events(&self, filter: &EventFilter) -> Vec; +} + +#[derive(Clone)] +/// implementation of the event cache controller +pub struct EventCacheControllerImpl { + /// input data to process in the VM loop + /// with a wake-up condition variable that needs to be triggered when the data changes + pub(crate) input_data: Arc<(Condvar, Mutex)>, + /// Event cache + pub(crate) cache: Arc>, +} + +impl EventCacheController for EventCacheControllerImpl { + fn save_events(&self, events: VecDeque) { + // lock input data + let mut input_data = self.input_data.1.lock(); + input_data.events.extend(events); + // Wake up the condvar in EventCacheWriterThread waiting for events + self.input_data.0.notify_all(); + } + + fn get_filtered_sc_output_events(&self, filter: &EventFilter) -> Vec { + let mut res_0 = { + // Read from new events first + let lock_0 = self.input_data.1.lock(); + #[allow(clippy::unnecessary_filter_map)] + let it = lock_0.events.iter().filter_map(|event| { + if let Some(start) = filter.start { + if event.context.slot < start { + return None; + } + } + if let Some(end) = filter.end { + if event.context.slot >= end { + return None; + } + } + if let Some(is_final) = filter.is_final { + if event.context.is_final != is_final { + return None; + } + } + if let Some(is_error) = filter.is_error { + if event.context.is_error != is_error { + return None; + } + } + match ( + filter.original_caller_address, + event.context.call_stack.front(), + ) { + (Some(addr1), Some(addr2)) if addr1 != *addr2 => return None, + (Some(_), None) => return None, + _ => (), + } + match (filter.emitter_address, event.context.call_stack.back()) { + (Some(addr1), Some(addr2)) if addr1 != *addr2 => return None, + (Some(_), None) => return None, + _ => (), + } + match ( + filter.original_operation_id, + event.context.origin_operation_id, + ) { + (Some(addr1), Some(addr2)) if addr1 != addr2 => return None, + (Some(_), None) => return None, + _ => (), + } + Some(event) + }); + + let res_0: BTreeSet = it.cloned().collect(); + // Drop the lock on the queue as soon as possible to avoid deadlocks + drop(lock_0); + res_0 + }; + + let res_1 = { + // Read from db (on disk) events + let lock = self.cache.read(); + let (_, res_1) = lock.get_filtered_sc_output_events(filter); + // Drop the lock on the event cache db asap + drop(lock); + res_1 + }; + + // Merge results + let res_1: BTreeSet = BTreeSet::from_iter(res_1); + res_0.extend(res_1); + Vec::from_iter(res_0) + } +} diff --git a/massa-event-cache/src/event_cache.rs b/massa-event-cache/src/event_cache.rs new file mode 100644 index 00000000000..5db636c3d26 --- /dev/null +++ b/massa-event-cache/src/event_cache.rs @@ -0,0 +1,1818 @@ +// std +use std::cmp::max; +use std::collections::{BTreeMap, BTreeSet}; +use std::path::Path; +// third-party +use num_enum::IntoPrimitive; +use rocksdb::{IteratorMode, Options, WriteBatch, DB}; +use tracing::{debug, warn}; +// internal +use crate::rocksdb_operator::counter_merge; +use crate::ser_deser::{ + SCOutputEventDeserializer, SCOutputEventDeserializerArgs, SCOutputEventSerializer, +}; +use massa_models::address::Address; +use massa_models::error::ModelsError; +use massa_models::execution::EventFilter; +use massa_models::operation::{OperationId, OperationIdSerializer}; +use massa_models::output_event::SCOutputEvent; +use massa_models::slot::Slot; +use massa_serialization::{DeserializeError, Deserializer, Serializer}; + +const OPEN_ERROR: &str = "critical: rocksdb open operation failed"; +// const COUNTER_INIT_ERROR: &str = "critical: cannot init rocksdb counters"; +const DESTROY_ERROR: &str = "critical: rocksdb delete operation failed"; +const CRUD_ERROR: &str = "critical: rocksdb crud operation failed"; +const EVENT_DESER_ERROR: &str = "critical: event deserialization failed"; +const OPERATION_ID_DESER_ERROR: &str = "critical: deserialization failed for op id in rocksdb"; +const COUNTER_ERROR: &str = "critical: cannot get counter"; +const COUNTER_KEY_CREATION_ERROR: &str = "critical: cannot create counter key"; + +#[allow(dead_code)] +/// Prefix u8 used to identify rocksdb keys +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, IntoPrimitive)] +#[repr(u8)] +enum KeyIndent { + Counter = 0, + Event, + EmitterAddress, + OriginalCallerAddress, + OriginalOperationId, + IsError, + IsFinal, +} + +/// Key for this type of data that we want to get +enum KeyBuilderType<'a> { + Slot(&'a Slot), + Event(&'a Slot, u64), + Address(&'a Address), + OperationId(&'a OperationId), + Bool(bool), + None, +} + +enum KeyKind { + Regular, + Prefix, + Counter, +} + +struct DbKeyBuilder { + /// Operation Id Serializer + op_id_ser: OperationIdSerializer, +} + +impl DbKeyBuilder { + fn new() -> Self { + Self { + op_id_ser: OperationIdSerializer::new(), + } + } + + /// Low level key builder function + /// There is no guarantees that the key will be unique + /// Recommended to use high level function like: + /// `key_from_event`, `prefix_key_from_indent`, + /// `prefix_key_from_filter_item` or `counter_key_from_filter_item` + fn key(&self, indent: &KeyIndent, key_type: KeyBuilderType, key_kind: &KeyKind) -> Vec { + let mut key_base = if matches!(key_kind, KeyKind::Counter) { + vec![u8::from(KeyIndent::Counter), u8::from(*indent)] + } else { + vec![u8::from(*indent)] + }; + + match key_type { + KeyBuilderType::Slot(slot) => { + key_base.extend(slot.to_bytes_key()); + } + KeyBuilderType::Event(slot, index) => { + key_base.extend(slot.to_bytes_key()); + key_base.extend(index.to_be_bytes()); + } + KeyBuilderType::Address(addr) => { + let addr_bytes = addr.to_prefixed_bytes(); + let addr_bytes_len = addr_bytes.len(); + key_base.extend(addr_bytes); + key_base.push(addr_bytes_len as u8); + } + KeyBuilderType::OperationId(op_id) => { + let mut buffer = Vec::new(); + self.op_id_ser + .serialize(op_id, &mut buffer) + .expect(OPERATION_ID_DESER_ERROR); + key_base.extend(&buffer); + key_base.extend(u32::to_be_bytes(buffer.len() as u32)); + } + KeyBuilderType::Bool(value) => { + key_base.push(u8::from(value)); + } + KeyBuilderType::None => {} + } + + key_base + } + + /// Key usually used to populate the DB + fn key_from_event( + &self, + event: &SCOutputEvent, + indent: &KeyIndent, + key_kind: &KeyKind, + ) -> Option> { + // Db format: + // * Regular keys: + // * Event key: [Event Indent][Slot][Index] -> Event value: Event serialized + // * Emitter address key: [Emitter Address Indent][Addr][Addr len][Event key] -> [] + // * Prefix keys: + // * Emitter address prefix key: [Emitter Address Indent][Addr][Addr len] + // * Counter keys: + // * Emitter address counter key: [Counter indent][Emitter Address Indent][Addr][Addr len][Event key] -> u64 + + let key = match indent { + KeyIndent::Event => { + let item = KeyBuilderType::Event(&event.context.slot, event.context.index_in_slot); + Some(self.key(indent, item, key_kind)) + } + KeyIndent::EmitterAddress => { + if let Some(addr) = event.context.call_stack.back() { + let item = KeyBuilderType::Address(addr); + let mut key = self.key(indent, item, key_kind); + let item = + KeyBuilderType::Event(&event.context.slot, event.context.index_in_slot); + if matches!(key_kind, KeyKind::Regular) { + key.extend(self.key(&KeyIndent::Event, item, &KeyKind::Regular)); + } + Some(key) + } else { + None + } + } + KeyIndent::OriginalCallerAddress => { + if let Some(addr) = event.context.call_stack.front() { + let item = KeyBuilderType::Address(addr); + let mut key = self.key(indent, item, key_kind); + let item = + KeyBuilderType::Event(&event.context.slot, event.context.index_in_slot); + if matches!(key_kind, KeyKind::Regular) { + key.extend(self.key(&KeyIndent::Event, item, &KeyKind::Regular)); + } + Some(key) + } else { + None + } + } + KeyIndent::OriginalOperationId => { + if let Some(op_id) = event.context.origin_operation_id.as_ref() { + let item = KeyBuilderType::OperationId(op_id); + let mut key = self.key(indent, item, key_kind); + let item = + KeyBuilderType::Event(&event.context.slot, event.context.index_in_slot); + if matches!(key_kind, KeyKind::Regular) { + key.extend(self.key(&KeyIndent::Event, item, &KeyKind::Regular)); + } + Some(key) + } else { + None + } + } + KeyIndent::IsError => { + let item = KeyBuilderType::Bool(event.context.is_error); + let mut key = self.key(indent, item, key_kind); + let item = KeyBuilderType::Event(&event.context.slot, event.context.index_in_slot); + if matches!(key_kind, KeyKind::Regular) { + key.extend(self.key(&KeyIndent::Event, item, &KeyKind::Regular)); + } + Some(key) + } + _ => unreachable!(), + }; + + key + } + + /// Prefix key to iterate over all events / emitter_address / ... + fn prefix_key_from_indent(&self, indent: &KeyIndent) -> Vec { + self.key(indent, KeyBuilderType::None, &KeyKind::Regular) + } + + /// Prefix key to iterate over specific emitter_address / operation_id / ... + fn prefix_key_from_filter_item(&self, filter_item: &FilterItem, indent: &KeyIndent) -> Vec { + match (indent, filter_item) { + (KeyIndent::Event, FilterItem::SlotStartEnd(_start, _end)) => { + unimplemented!() + } + (KeyIndent::Event, FilterItem::SlotStart(start)) => { + self.key(indent, KeyBuilderType::Slot(start), &KeyKind::Prefix) + } + (KeyIndent::Event, FilterItem::SlotEnd(end)) => { + self.key(indent, KeyBuilderType::Slot(end), &KeyKind::Prefix) + } + (KeyIndent::EmitterAddress, FilterItem::EmitterAddress(addr)) => { + self.key(indent, KeyBuilderType::Address(addr), &KeyKind::Prefix) + } + (KeyIndent::OriginalCallerAddress, FilterItem::OriginalCallerAddress(addr)) => { + self.key(indent, KeyBuilderType::Address(addr), &KeyKind::Prefix) + } + (KeyIndent::OriginalOperationId, FilterItem::OriginalOperationId(op_id)) => { + self.key(indent, KeyBuilderType::OperationId(op_id), &KeyKind::Prefix) + } + (KeyIndent::IsError, FilterItem::IsError(v)) => { + self.key(indent, KeyBuilderType::Bool(*v), &KeyKind::Prefix) + } + _ => { + unreachable!() + } + } + } + + /// Counter key for specific emitter_address / operation_id / ... + fn counter_key_from_filter_item( + &self, + filter_item: &FilterItem, + indent: &KeyIndent, + ) -> Vec { + // High level key builder function + match (indent, filter_item) { + (KeyIndent::Event, FilterItem::SlotStartEnd(_start, _end)) => { + unimplemented!() + } + (KeyIndent::Event, FilterItem::SlotStart(start)) => { + self.key(indent, KeyBuilderType::Slot(start), &KeyKind::Counter) + } + (KeyIndent::Event, FilterItem::SlotEnd(end)) => { + self.key(indent, KeyBuilderType::Slot(end), &KeyKind::Counter) + } + (KeyIndent::EmitterAddress, FilterItem::EmitterAddress(addr)) => { + self.key(indent, KeyBuilderType::Address(addr), &KeyKind::Counter) + } + (KeyIndent::OriginalCallerAddress, FilterItem::OriginalCallerAddress(addr)) => { + self.key(indent, KeyBuilderType::Address(addr), &KeyKind::Counter) + } + (KeyIndent::OriginalOperationId, FilterItem::OriginalOperationId(op_id)) => self.key( + indent, + KeyBuilderType::OperationId(op_id), + &KeyKind::Counter, + ), + (KeyIndent::IsError, FilterItem::IsError(v)) => { + self.key(indent, KeyBuilderType::Bool(*v), &KeyKind::Counter) + } + _ => { + unreachable!() + } + } + } +} + +/// Disk based event cache db (rocksdb based) +pub(crate) struct EventCache { + /// RocksDB database + db: DB, + /// How many entries are in the db. Count is initialized at creation time by iterating + /// over all the entries in the db then it is maintained in memory + entry_count: usize, + /// Maximum number of entries we want to keep in the db. + /// When this maximum is reached `snip_amount` entries are removed + max_entry_count: usize, + /// How many entries are removed when `entry_count` reaches `max_entry_count` + snip_amount: usize, + /// Event serializer + event_ser: SCOutputEventSerializer, + /// Event deserializer + event_deser: SCOutputEventDeserializer, + /// Key builder + key_builder: DbKeyBuilder, + /// First event slot in db + first_slot: Slot, + /// Last event slot in db + last_slot: Slot, + /// Thread count + thread_count: u8, + /// Maximum number of events per operation + max_events_per_operation: u64, + /// Maximum number of operations per block + max_operations_per_block: u64, + /// Max number of events returned by a query + max_events_per_query: usize, +} + +impl EventCache { + #[allow(clippy::too_many_arguments)] + /// Create a new EventCache + pub fn new( + path: &Path, + max_entry_count: usize, + snip_amount: usize, + thread_count: u8, + max_recursive_call_depth: u16, + max_event_data_length: u64, + max_events_per_operation: u64, + max_operations_per_block: u64, + max_events_per_query: usize, + ) -> Self { + // Clear the db + DB::destroy(&Options::default(), path).expect(DESTROY_ERROR); + let options = { + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.set_merge_operator_associative("counter merge operator", counter_merge); + opts + }; + let db = DB::open(&options, path).expect(OPEN_ERROR); + + let key_builder_2 = DbKeyBuilder::new(); + + Self { + db, + entry_count: 0, + max_entry_count, + snip_amount, + event_ser: SCOutputEventSerializer::new(), + event_deser: SCOutputEventDeserializer::new(SCOutputEventDeserializerArgs { + thread_count, + max_call_stack_length: max_recursive_call_depth, + max_event_data_length, + }), + key_builder: key_builder_2, + first_slot: Slot::new(0, 0), + last_slot: Slot::new(0, 0), + thread_count, + max_events_per_operation, + max_operations_per_block, + max_events_per_query, + } + } + + /// From an event add keys & values into a rocksdb batch + fn insert_into_batch(&mut self, event: SCOutputEvent, batch: &mut WriteBatch) { + let mut event_buffer = Vec::new(); + self.event_ser.serialize(&event, &mut event_buffer).unwrap(); + + batch.put( + self.key_builder + .key_from_event(&event, &KeyIndent::Event, &KeyKind::Regular) + .unwrap(), + event_buffer, + ); + + if let Some(key) = + self.key_builder + .key_from_event(&event, &KeyIndent::EmitterAddress, &KeyKind::Regular) + { + let key_counter = self.key_builder.key_from_event( + &event, + &KeyIndent::EmitterAddress, + &KeyKind::Counter, + ); + batch.put(key, vec![]); + let key_counter = key_counter.expect(COUNTER_KEY_CREATION_ERROR); + batch.merge(key_counter, 1i64.to_be_bytes()); + } + + if let Some(key) = self.key_builder.key_from_event( + &event, + &KeyIndent::OriginalCallerAddress, + &KeyKind::Regular, + ) { + let key_counter = self.key_builder.key_from_event( + &event, + &KeyIndent::OriginalCallerAddress, + &KeyKind::Counter, + ); + batch.put(key, vec![]); + let key_counter = key_counter.expect(COUNTER_KEY_CREATION_ERROR); + batch.merge(key_counter, 1i64.to_be_bytes()); + } + + if let Some(key) = self.key_builder.key_from_event( + &event, + &KeyIndent::OriginalOperationId, + &KeyKind::Regular, + ) { + let key_counter = self.key_builder.key_from_event( + &event, + &KeyIndent::OriginalOperationId, + &KeyKind::Counter, + ); + batch.put(key, vec![]); + let key_counter = key_counter.expect(COUNTER_KEY_CREATION_ERROR); + batch.merge(key_counter, 1i64.to_be_bytes()); + } + + { + if let Some(key) = + self.key_builder + .key_from_event(&event, &KeyIndent::IsError, &KeyKind::Regular) + { + let key_counter = + self.key_builder + .key_from_event(&event, &KeyIndent::IsError, &KeyKind::Counter); + let key_counter = key_counter.expect(COUNTER_KEY_CREATION_ERROR); + batch.put(key, vec![]); + batch.merge(key_counter, 1i64.to_be_bytes()); + } + } + + // Keep track of last slot (and start slot) of events in the DB + // Help for event filtering + self.last_slot = max(self.last_slot, event.context.slot); + } + + #[allow(dead_code)] + /// Insert a new event in the cache + pub fn insert(&mut self, event: SCOutputEvent) { + if self.entry_count >= self.max_entry_count { + self.snip(None); + } + + let mut batch = WriteBatch::default(); + self.insert_into_batch(event, &mut batch); + self.db.write(batch).expect(CRUD_ERROR); + + // Note: + // This assumes that events are always added, never overwritten + self.entry_count = self.entry_count.saturating_add(1); + + debug!("(Event insert) entry_count is: {}", self.entry_count); + } + + /// Insert new events in the cache + pub fn insert_multi_it( + &mut self, + events: impl ExactSizeIterator + Clone, + ) { + let events_len = events.len(); + + if self.entry_count + events_len >= self.max_entry_count { + let snip_amount = max(self.snip_amount, events_len); + self.snip(Some(snip_amount)); + } + + let mut batch = WriteBatch::default(); + for event in events { + self.insert_into_batch(event, &mut batch); + } + self.db.write(batch).expect(CRUD_ERROR); + // Note: + // This assumes that events are always added, never overwritten + self.entry_count = self.entry_count.saturating_add(events_len); + + debug!("(Events insert) entry_count is: {}", self.entry_count); + } + + /// Get events filtered by the given argument + pub(crate) fn get_filtered_sc_output_events( + &self, + filter: &EventFilter, + ) -> (Vec, Vec) { + // Step 1 + // Build a (sorted) map with key: (counter value, indent), value: filter + // Will be used to iterate from the lower count index to the highest count index + // e.g. if index for emitter address is 10 (index count), and origin operation id is 20 + // iter over emitter address index then origin operation id index + + let filter_items = from_event_filter(filter); + + if filter_items.is_empty() { + // Note: will return too many event - user should restrict the filter + warn!("Filter item only on is final field, please add more filter parameters"); + return (vec![], vec![]); + } + + let it = filter_items.iter().map(|(key_indent, filter_item)| { + let count = self + .filter_item_estimate_count(key_indent, filter_item) + .unwrap_or_else(|e| { + warn!( + "Could not estimate count for key indent: {:?} - filter_item: {:?}: {}", + key_indent, filter_item, e + ); + self.max_entry_count as u64 + }); + ((count, key_indent), filter_item) + }); + + let map = BTreeMap::from_iter(it); + debug!("Filter items map: {:?}", map); + + // Step 2: apply filter from the lowest counter to the highest counter + + let mut query_counts = Vec::with_capacity(map.len()); + let mut filter_res_prev = None; + for ((_counter, indent), filter_item) in map.iter() { + let mut filter_res = BTreeSet::new(); + let query_count = self.filter_for( + indent, + filter_item, + &mut filter_res, + filter_res_prev.as_ref(), + ); + query_counts.push(query_count); + filter_res_prev = Some(filter_res); + } + + // Step 3: get values & deserialize + + let multi_args = filter_res_prev + .unwrap() + .into_iter() + .take(self.max_events_per_query) + .collect::>>(); + + let res = self.db.multi_get(multi_args); + debug!( + "Filter will try to deserialize to SCOutputEvent {} values", + res.len() + ); + + let events = res + .into_iter() + .map(|value| { + let value = value.unwrap().unwrap(); + let (_, event) = self + .event_deser + .deserialize::(&value) + .unwrap(); + event + }) + .collect::>(); + + (query_counts, events) + } + + fn filter_for( + &self, + indent: &KeyIndent, + filter_item: &FilterItem, + result: &mut BTreeSet>, + seen: Option<&BTreeSet>>, + ) -> u64 { + let mut query_count: u64 = 0; + + if *indent == KeyIndent::Event { + let opts = match filter_item { + FilterItem::SlotStart(_start) => { + let key_start = self + .key_builder + .prefix_key_from_filter_item(filter_item, indent); + let mut options = rocksdb::ReadOptions::default(); + options.set_iterate_lower_bound(key_start); + options + } + FilterItem::SlotEnd(_end) => { + let key_end = self + .key_builder + .prefix_key_from_filter_item(filter_item, indent); + let mut options = rocksdb::ReadOptions::default(); + options.set_iterate_upper_bound(key_end); + options + } + FilterItem::SlotStartEnd(start, end) => { + let key_start = self + .key_builder + .prefix_key_from_filter_item(&FilterItem::SlotStart(*start), indent); + let key_end = self + .key_builder + .prefix_key_from_filter_item(&FilterItem::SlotEnd(*end), indent); + let mut options = rocksdb::ReadOptions::default(); + options.set_iterate_range(key_start..key_end); + options + } + _ => unreachable!(), + }; + + #[allow(clippy::manual_flatten)] + for kvb in self.db.iterator_opt(IteratorMode::Start, opts) { + if let Ok(kvb) = kvb { + if !kvb.0.starts_with(&[*indent as u8]) { + // Stop as soon as our key does not start with the right indent + break; + } + + let found = kvb.0.to_vec(); + query_count = query_count.saturating_add(1); + + if let Some(filter_set_seen) = seen { + if filter_set_seen.contains(&found) { + result.insert(found); + } + + // We have already found as many items as in a previous search + // As we search from the lowest count to the highest count, we will never add new items + // in our result, so we can break here + if filter_set_seen.len() == result.len() { + break; + } + } else { + result.insert(found); + } + } + } + } else { + let prefix_filter = match filter_item { + FilterItem::EmitterAddress(_addr) => self + .key_builder + .prefix_key_from_filter_item(filter_item, indent), + FilterItem::OriginalCallerAddress(_addr) => self + .key_builder + .prefix_key_from_filter_item(filter_item, indent), + FilterItem::OriginalOperationId(_op_id) => self + .key_builder + .prefix_key_from_filter_item(filter_item, indent), + FilterItem::IsError(_is_error) => self + .key_builder + .prefix_key_from_filter_item(filter_item, indent), + _ => unreachable!(), + }; + + #[allow(clippy::manual_flatten)] + for kvb in self.db.prefix_iterator(prefix_filter.as_slice()) { + if let Ok(kvb) = kvb { + if !kvb.0.starts_with(&[*indent as u8]) { + // Stop as soon as our key does not start with the right indent + break; + } + + if !kvb.0.starts_with(prefix_filter.as_slice()) { + // Stop as soon as our key does not start with our current prefix + break; + } + + let found = kvb + .0 + .strip_prefix(prefix_filter.as_slice()) + .unwrap() // safe to unwrap() - already tested + .to_vec(); + + query_count = query_count.saturating_add(1); + + if let Some(filter_set_seen) = seen { + if filter_set_seen.contains(&found) { + result.insert(found); + } + + // We have already found as many items as in a previous search + // As we search from the lowest count to the highest count, we will never add new items + // in our result, so we can break here + if filter_set_seen.len() == result.len() { + break; + } + } else { + result.insert(found); + } + } + } + } + + query_count + } + + /// Estimate for a given KeyIndent & FilterItem the number of row to iterate + fn filter_item_estimate_count( + &self, + key_indent: &KeyIndent, + filter_item: &FilterItem, + ) -> Result { + match filter_item { + FilterItem::SlotStart(start) => { + let diff = self.last_slot.slots_since(start, self.thread_count)?; + // Note: Pessimistic estimation - should we keep an average count of events per slot + // and use that instead? + Ok(diff + .saturating_mul(self.max_events_per_operation) + .saturating_mul(self.max_operations_per_block)) + } + FilterItem::SlotStartEnd(start, end) => { + let diff = end.slots_since(start, self.thread_count)?; + Ok(diff + .saturating_mul(self.max_events_per_operation) + .saturating_mul(self.max_operations_per_block)) + } + FilterItem::SlotEnd(end) => { + let diff = end.slots_since(&self.first_slot, self.thread_count)?; + Ok(diff + .saturating_mul(self.max_events_per_operation) + .saturating_mul(self.max_operations_per_block)) + } + FilterItem::EmitterAddress(_addr) => { + let counter_key = self + .key_builder + .counter_key_from_filter_item(filter_item, key_indent); + let counter = self.db.get(counter_key).expect(COUNTER_ERROR); + let counter_value = counter + .map(|b| u64::from_be_bytes(b.try_into().unwrap())) + .unwrap_or(0); + Ok(counter_value) + } + FilterItem::OriginalCallerAddress(_addr) => { + let counter_key = self + .key_builder + .counter_key_from_filter_item(filter_item, key_indent); + let counter = self.db.get(counter_key).expect(COUNTER_ERROR); + let counter_value = counter + .map(|b| u64::from_be_bytes(b.try_into().unwrap())) + .unwrap_or(0); + Ok(counter_value) + } + FilterItem::OriginalOperationId(_op_id) => { + let counter_key = self + .key_builder + .counter_key_from_filter_item(filter_item, key_indent); + let counter = self.db.get(counter_key).expect(COUNTER_ERROR); + let counter_value = counter + .map(|b| u64::from_be_bytes(b.try_into().unwrap())) + .unwrap_or(0); + Ok(counter_value) + } + FilterItem::IsError(_is_error) => { + let counter_key = self + .key_builder + .counter_key_from_filter_item(filter_item, key_indent); + let counter = self.db.get(counter_key).expect(COUNTER_ERROR); + let counter_value = counter + .map(|b| u64::from_be_bytes(b.try_into().unwrap())) + .unwrap_or(0); + Ok(counter_value) + } + } + } + + /// Try to remove some entries from the db + fn snip(&mut self, snip_amount: Option) { + let mut iter = self.db.iterator(IteratorMode::Start); + let mut batch = WriteBatch::default(); + let mut snipped_count: usize = 0; + let snip_amount = snip_amount.unwrap_or(self.snip_amount); + + let mut counter_keys = vec![]; + + while snipped_count < snip_amount { + let key_value = iter.next(); + if key_value.is_none() { + break; + } + let kvb = key_value + .unwrap() // safe to unwrap - just tested it + .expect(EVENT_DESER_ERROR); + + let key = kvb.0; + if !key.starts_with(&[u8::from(KeyIndent::Event)]) { + continue; + } + + let (_, event) = self + .event_deser + .deserialize::(&kvb.1) + .unwrap(); + + // delete all associated key + if let Some(key) = self.key_builder.key_from_event( + &event, + &KeyIndent::EmitterAddress, + &KeyKind::Regular, + ) { + let key_counter = self + .key_builder + .key_from_event(&event, &KeyIndent::EmitterAddress, &KeyKind::Counter) + .expect(COUNTER_ERROR); + batch.delete(key); + counter_keys.push(key_counter.clone()); + batch.merge(key_counter, (-1i64).to_be_bytes()); + } + if let Some(key) = self.key_builder.key_from_event( + &event, + &KeyIndent::OriginalCallerAddress, + &KeyKind::Regular, + ) { + let key_counter = self + .key_builder + .key_from_event(&event, &KeyIndent::OriginalCallerAddress, &KeyKind::Counter) + .expect(COUNTER_ERROR); + batch.delete(key); + counter_keys.push(key_counter.clone()); + batch.merge(key_counter, (-1i64).to_be_bytes()); + } + + if let Some(key) = self.key_builder.key_from_event( + &event, + &KeyIndent::OriginalOperationId, + &KeyKind::Regular, + ) { + let key_counter = self + .key_builder + .key_from_event(&event, &KeyIndent::OriginalOperationId, &KeyKind::Counter) + .expect(COUNTER_ERROR); + batch.delete(key); + counter_keys.push(key_counter.clone()); + batch.merge(key_counter, (-1i64).to_be_bytes()); + } + if let Some(key) = + self.key_builder + .key_from_event(&event, &KeyIndent::IsError, &KeyKind::Regular) + { + let key_counter = self + .key_builder + .key_from_event(&event, &KeyIndent::IsError, &KeyKind::Counter) + .expect(COUNTER_ERROR); + batch.delete(key); + counter_keys.push(key_counter.clone()); + batch.merge(key_counter, (-1i64).to_be_bytes()); + } + + batch.delete(key); + snipped_count += 1; + } + + // delete the key and reduce entry_count + self.db.write(batch).expect(CRUD_ERROR); + self.entry_count = self.entry_count.saturating_sub(snipped_count); + + // delete key counters where value == 0 + let mut batch_counters = WriteBatch::default(); + const U64_ZERO_BYTES: [u8; 8] = 0u64.to_be_bytes(); + for (value, key) in self.db.multi_get(&counter_keys).iter().zip(counter_keys) { + if let Ok(Some(value)) = value { + if *value == U64_ZERO_BYTES.to_vec() { + batch_counters.delete(key); + } + } + } + self.db.write(batch_counters).expect(CRUD_ERROR); + + // Update first_slot / last_slot in the DB + if self.entry_count == 0 { + // Reset + self.first_slot = Slot::new(0, 0); + self.last_slot = Slot::new(0, 0); + } else { + // Get the first event in the db + // By using a prefix iterator this should be fast + + let key_prefix = self.key_builder.prefix_key_from_indent(&KeyIndent::Event); + let mut it_slot = self.db.prefix_iterator(key_prefix); + + let key_value = it_slot.next(); + let kvb = key_value.unwrap().expect(EVENT_DESER_ERROR); + + let (_, event) = self + .event_deser + .deserialize::(&kvb.1) + .unwrap(); + self.first_slot = event.context.slot; + } + } +} + +/// A filter parameter - used to decompose an EventFilter in multiple filters +#[derive(Debug)] +enum FilterItem { + SlotStart(Slot), + SlotStartEnd(Slot, Slot), + SlotEnd(Slot), + EmitterAddress(Address), + OriginalCallerAddress(Address), + OriginalOperationId(OperationId), + IsError(bool), +} + +/// Convert a EventFilter into a list of (KeyIndent, FilterItem) +fn from_event_filter(event_filter: &EventFilter) -> Vec<(KeyIndent, FilterItem)> { + let mut filter_items = vec![]; + if event_filter.start.is_some() && event_filter.end.is_some() { + let start = event_filter.start.unwrap(); + let end = event_filter.end.unwrap(); + filter_items.push((KeyIndent::Event, FilterItem::SlotStartEnd(start, end))); + } else if event_filter.start.is_some() { + let start = event_filter.start.unwrap(); + filter_items.push((KeyIndent::Event, FilterItem::SlotStart(start))); + } else if event_filter.end.is_some() { + let end = event_filter.end.unwrap(); + filter_items.push((KeyIndent::Event, FilterItem::SlotEnd(end))); + } + + if let Some(addr) = event_filter.emitter_address { + filter_items.push((KeyIndent::EmitterAddress, FilterItem::EmitterAddress(addr))); + } + + if let Some(addr) = event_filter.original_caller_address { + filter_items.push(( + KeyIndent::OriginalCallerAddress, + FilterItem::OriginalCallerAddress(addr), + )); + } + + if let Some(op_id) = event_filter.original_operation_id { + filter_items.push(( + KeyIndent::OriginalOperationId, + FilterItem::OriginalOperationId(op_id), + )); + } + + if let Some(is_error) = event_filter.is_error { + filter_items.push((KeyIndent::IsError, FilterItem::IsError(is_error))); + } + + filter_items +} + +#[cfg(test)] +impl EventCache { + /// Iterate over all keys & values in the db - test only + fn iter_all( + &self, + mode: Option, + ) -> impl Iterator, Box<[u8]>)> + '_ { + self.db + .iterator(mode.unwrap_or(IteratorMode::Start)) + .flatten() + } +} + +#[cfg(test)] +mod tests { + use super::*; + // std + use std::collections::VecDeque; + use std::str::FromStr; + // third-party + use more_asserts::assert_gt; + use rand::seq::SliceRandom; + use rand::thread_rng; + use serial_test::serial; + use tempfile::TempDir; + // internal + use massa_models::config::{ + MAX_EVENT_DATA_SIZE, MAX_EVENT_PER_OPERATION, MAX_OPERATIONS_PER_BLOCK, + MAX_RECURSIVE_CALLS_DEPTH, THREAD_COUNT, + }; + use massa_models::operation::OperationId; + use massa_models::output_event::EventExecutionContext; + use massa_models::slot::Slot; + + fn setup() -> EventCache { + let tmp_path = TempDir::new().unwrap().path().to_path_buf(); + EventCache::new( + &tmp_path, + 1000, + 300, + THREAD_COUNT, + MAX_RECURSIVE_CALLS_DEPTH, + MAX_EVENT_DATA_SIZE as u64, + MAX_EVENT_PER_OPERATION as u64, + MAX_OPERATIONS_PER_BLOCK as u64, + 5000, // MAX_EVENTS_PER_QUERY, + ) + } + + #[test] + #[serial] + fn test_db_insert_order() { + // Test that the data will be correctly ordered (when iterated from start) in db + + let mut cache = setup(); + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let max_entry_count = cache.max_entry_count - 5; + let mut events = (0..max_entry_count) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = 0u64; + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in DB + events.shuffle(&mut thread_rng()); + + for event in events { + cache.insert(event); + } + + // Now check that we are going to iter in correct order + // let db_it = cache.db_iter(Some(IteratorMode::Start)); + let mut prev_slot = None; + let mut prev_event_index = None; + for kvb in cache.iter_all(None) { + let bytes = kvb.0.iter().as_slice(); + + if bytes[0] != u8::from(KeyIndent::Event) { + continue; + } + + let slot = Slot::from_bytes_key(&bytes[1..=9].try_into().unwrap()); + let event_index = u64::from_be_bytes(bytes[10..].try_into().unwrap()); + if prev_slot.is_some() && prev_event_index.is_some() { + assert_gt!( + (slot, event_index), + (prev_slot.unwrap(), prev_event_index.unwrap()) + ); + } else { + assert_eq!(slot, slot_1); + assert_eq!(event_index, index_1_0); + } + prev_slot = Some(slot); + prev_event_index = Some(event_index); + } + + assert_eq!(prev_slot, Some(slot_2)); + assert_eq!(prev_event_index, Some(index_2_2)); + } + + #[test] + #[serial] + fn test_insert_more_than_max_entry() { + // Test insert (and snip) so we do not store too many event in the cache + + let mut cache = setup(); + let event = SCOutputEvent { + context: EventExecutionContext { + slot: Slot::new(1, 0), + block: None, + read_only: false, + index_in_slot: 0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + // fill the db: add cache.max_entry_count entries + for count in 0..cache.max_entry_count { + let mut event = event.clone(); + event.context.index_in_slot = count as u64; + cache.insert(event.clone()); + } + assert_eq!(cache.entry_count, cache.max_entry_count); + + // insert one more entry + let mut event_last = event.clone(); + event_last.context.index_in_slot = u64::MAX; + cache.insert(event_last); + assert_eq!( + cache.entry_count, + cache.max_entry_count - cache.snip_amount + 1 + ); + dbg!(cache.entry_count); + } + + #[test] + #[serial] + fn test_insert_more_than_max_entry_2() { + // Test insert_multi_it (and snip) so we do not store too many event in the cache + + let mut cache = setup(); + let event = SCOutputEvent { + context: EventExecutionContext { + slot: Slot::new(1, 0), + block: None, + read_only: false, + index_in_slot: 0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let it = (0..cache.max_entry_count).map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + event + }); + cache.insert_multi_it(it); + + assert_eq!(cache.entry_count, cache.max_entry_count); + + // insert one more entry + let mut event_last = event.clone(); + event_last.context.index_in_slot = u64::MAX; + cache.insert(event_last); + assert_eq!( + cache.entry_count, + cache.max_entry_count - cache.snip_amount + 1 + ); + dbg!(cache.entry_count); + } + + #[test] + #[serial] + fn test_snip() { + // Test snip so we enforce that all db keys are removed + + let mut cache = setup(); + cache.max_entry_count = 10; + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: Slot::new(1, 0), + block: None, + read_only: false, + index_in_slot: 0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let it = (0..cache.max_entry_count).map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + event + }); + cache.insert_multi_it(it); + + assert_eq!(cache.entry_count, cache.max_entry_count); + + cache.snip(Some(cache.entry_count)); + + assert_eq!(cache.entry_count, 0); + assert_eq!(cache.iter_all(None).count(), 0); + } + + #[test] + #[serial] + fn test_counter_0() { + // Test snip so we enforce that all db keys are removed + + let mut cache = setup(); + cache.max_entry_count = 10; + + let dummy_addr = + Address::from_str("AU12qePoXhNbYWE1jZuafqJong7bbq1jw3k89RgbMawbrdZpaasoA").unwrap(); + let emit_addr_1 = + Address::from_str("AU122Em8qkqegdLb1eyH8rdkSCNEf7RZLeTJve4Q2inRPGiTJ2xNv").unwrap(); + let emit_addr_2 = + Address::from_str("AU12WuVR1Td74q9eAbtYZUnk5jnRbUuUacyhQFwm217bV5v1mNqTZ").unwrap(); + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: Slot::new(1, 0), + block: None, + read_only: false, + index_in_slot: 0, + call_stack: VecDeque::from(vec![dummy_addr, emit_addr_1]), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let event_2 = { + let mut evt = event.clone(); + evt.context.slot = Slot::new(2, 0); + evt.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + evt + }; + + cache.insert_multi_it([event, event_2].into_iter()); + + // Check counters key length + let key_counters = cache + .key_builder + .prefix_key_from_indent(&KeyIndent::Counter); + let kvbs: Result, _> = cache + .db + .prefix_iterator(key_counters) + .take_while(|kvb| { + kvb.as_ref() + .unwrap() + .0 + .starts_with(&[u8::from(KeyIndent::Counter)]) + }) + .collect(); + // println!("kvbs: {:#?}", kvbs); + + // Expected 4 counters: + // 2 for emitter address (emit_addr_1 & emit_addr_2) + // 1 for original caller address (dummy_addr) + // 1 for is_error(false) + assert_eq!(kvbs.unwrap().len(), 4); + + let key_counter_1 = cache.key_builder.counter_key_from_filter_item( + &FilterItem::EmitterAddress(emit_addr_1), + &KeyIndent::EmitterAddress, + ); + let key_counter_2 = cache.key_builder.counter_key_from_filter_item( + &FilterItem::EmitterAddress(emit_addr_2), + &KeyIndent::EmitterAddress, + ); + + let v1 = cache.db.get(key_counter_1.clone()); + let v2 = cache.db.get(key_counter_2.clone()); + + // println!("v1: {:?} - v2: {:?}", v1, v2); + assert_eq!(v1, Ok(Some(1u64.to_be_bytes().to_vec()))); + assert_eq!(v2, Ok(Some(1u64.to_be_bytes().to_vec()))); + + cache.snip(Some(1)); + + let v1 = cache.db.get(key_counter_1); + let v2 = cache.db.get(key_counter_2); + + // println!("v1: {:?} - v2: {:?}", v1, v2); + assert_eq!(v1, Ok(None)); // counter has been removed + assert_eq!(v2, Ok(Some(1u64.to_be_bytes().to_vec()))); + } + + #[test] + #[serial] + fn test_event_filter() { + // Test that the data will be correctly ordered (when filtered) in db + + let mut cache = setup(); + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let mut events = (0..cache.max_entry_count - 5) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let index_2_1 = 0u64; + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_1; + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in the DB + events.shuffle(&mut thread_rng()); + + cache.insert_multi_it(events.into_iter()); + + let filter_1 = EventFilter { + start: Some(Slot::new(2, 0)), + ..Default::default() + }; + + let (_, filtered_events_1) = cache.get_filtered_sc_output_events(&filter_1); + + assert_eq!(filtered_events_1.len(), 2); + assert_eq!(filtered_events_1[0].context.slot, slot_2); + assert_eq!(filtered_events_1[0].context.index_in_slot, index_2_1); + assert_eq!(filtered_events_1[1].context.slot, slot_2); + assert_eq!(filtered_events_1[1].context.index_in_slot, index_2_2); + } + + #[test] + #[serial] + fn test_event_filter_2() { + // Test get_filtered_sc_output_events + op id + + let mut cache = setup(); + cache.max_entry_count = 10; + + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + let op_id_1 = + OperationId::from_str("O12n1vt8uTLh3H65J4TVuztaWfBh3oumjjVtRCkke7Ba5qWdXdjD").unwrap(); + let op_id_2 = + OperationId::from_str("O1p5P691KF672fQ8tQougxzSERBwDKZF8FwtkifMSJbP14sEuGc").unwrap(); + let op_id_unknown = + OperationId::from_str("O1kvXTfsnVbQcmDERkC89vqAd2xRTLCb3q5b2E5WaVPHwFd7Qth").unwrap(); + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: Some(op_id_1), + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let mut events = (0..cache.max_entry_count - 5) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let index_2_1 = 0u64; + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_1; + event.context.origin_operation_id = Some(op_id_2); + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event.context.origin_operation_id = Some(op_id_2); + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in the DB + events.shuffle(&mut thread_rng()); + cache.insert_multi_it(events.into_iter()); + + let mut filter_1 = EventFilter { + original_operation_id: Some(op_id_1), + ..Default::default() + }; + + let (_, filtered_events_1) = cache.get_filtered_sc_output_events(&filter_1); + + assert_eq!(filtered_events_1.len(), cache.max_entry_count - 5); + filtered_events_1.iter().enumerate().for_each(|(i, event)| { + assert_eq!(event.context.slot, slot_1); + assert_eq!(event.context.index_in_slot, i as u64); + }); + + { + filter_1.original_operation_id = Some(op_id_2); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), 2); + filtered_events_2.iter().enumerate().for_each(|(i, event)| { + assert_eq!(event.context.slot, slot_2); + if i == 0 { + assert_eq!(event.context.index_in_slot, i as u64); + } else { + assert_eq!(event.context.index_in_slot, 256u64); + } + }); + } + + { + filter_1.original_operation_id = Some(op_id_unknown); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), 0); + } + } + + #[test] + #[serial] + fn test_event_filter_3() { + // Test get_filtered_sc_output_events + emitter address + + let mut cache = setup(); + cache.max_entry_count = 10; + + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + + let dummy_addr = + Address::from_str("AU12qePoXhNbYWE1jZuafqJong7bbq1jw3k89RgbMawbrdZpaasoA").unwrap(); + let emit_addr_1 = + Address::from_str("AU122Em8qkqegdLb1eyH8rdkSCNEf7RZLeTJve4Q2inRPGiTJ2xNv").unwrap(); + let emit_addr_2 = + Address::from_str("AU12WuVR1Td74q9eAbtYZUnk5jnRbUuUacyhQFwm217bV5v1mNqTZ").unwrap(); + let emit_addr_unknown = + Address::from_str("AU1zLC4TFUiaKDg7quQyusMPQcHT4ykWVs3FsFpuhdNSmowUG2As").unwrap(); + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let to_insert_count = cache.max_entry_count - 5; + let threshold = to_insert_count / 2; + let mut events = (0..cache.max_entry_count - 5) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + if i < threshold { + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_1]); + } else { + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + } + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let index_2_1 = 0u64; + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_1; + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in the DB + events.shuffle(&mut thread_rng()); + + cache.insert_multi_it(events.into_iter()); + + let mut filter_1 = EventFilter { + emitter_address: Some(emit_addr_1), + ..Default::default() + }; + + let (_, filtered_events_1) = cache.get_filtered_sc_output_events(&filter_1); + + assert_eq!(filtered_events_1.len(), threshold); + filtered_events_1.iter().for_each(|event| { + assert_eq!(event.context.slot, slot_1); + assert_eq!(*event.context.call_stack.back().unwrap(), emit_addr_1) + }); + + // filter with emit_addr_2 + { + filter_1.emitter_address = Some(emit_addr_2); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), threshold + 1 + 2); + filtered_events_2.iter().for_each(|event| { + assert_eq!(*event.context.call_stack.back().unwrap(), emit_addr_2) + }); + } + // filter with dummy_addr + { + filter_1.emitter_address = Some(dummy_addr); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), 0); + } + // filter with address that is not in the DB + { + filter_1.emitter_address = Some(emit_addr_unknown); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), 0); + } + } + + #[test] + #[serial] + fn test_event_filter_4() { + // Test get_filtered_sc_output_events + original caller addr + + let mut cache = setup(); + cache.max_entry_count = 10; + + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + + let dummy_addr = + Address::from_str("AU12qePoXhNbYWE1jZuafqJong7bbq1jw3k89RgbMawbrdZpaasoA").unwrap(); + let caller_addr_1 = + Address::from_str("AU122Em8qkqegdLb1eyH8rdkSCNEf7RZLeTJve4Q2inRPGiTJ2xNv").unwrap(); + let caller_addr_2 = + Address::from_str("AU12WuVR1Td74q9eAbtYZUnk5jnRbUuUacyhQFwm217bV5v1mNqTZ").unwrap(); + let caller_addr_unknown = + Address::from_str("AU1zLC4TFUiaKDg7quQyusMPQcHT4ykWVs3FsFpuhdNSmowUG2As").unwrap(); + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let to_insert_count = cache.max_entry_count - 5; + let threshold = to_insert_count / 2; + let mut events = (0..cache.max_entry_count - 5) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + if i < threshold { + event.context.call_stack = VecDeque::from(vec![caller_addr_1, dummy_addr]); + } else { + event.context.call_stack = VecDeque::from(vec![caller_addr_2, dummy_addr]); + } + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let index_2_1 = 0u64; + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_1; + event.context.call_stack = VecDeque::from(vec![caller_addr_2, dummy_addr]); + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event.context.call_stack = VecDeque::from(vec![caller_addr_2, dummy_addr]); + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in the DB + events.shuffle(&mut thread_rng()); + cache.insert_multi_it(events.into_iter()); + + let mut filter_1 = EventFilter { + original_caller_address: Some(caller_addr_1), + ..Default::default() + }; + + let (_, filtered_events_1) = cache.get_filtered_sc_output_events(&filter_1); + + assert_eq!(filtered_events_1.len(), threshold); + filtered_events_1.iter().for_each(|event| { + assert_eq!(event.context.slot, slot_1); + assert_eq!(*event.context.call_stack.front().unwrap(), caller_addr_1); + }); + + { + filter_1.original_caller_address = Some(caller_addr_2); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), threshold + 1 + 2); + filtered_events_2.iter().for_each(|event| { + assert_eq!(*event.context.call_stack.front().unwrap(), caller_addr_2); + }); + } + { + filter_1.original_caller_address = Some(dummy_addr); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), 0); + } + { + filter_1.original_caller_address = Some(caller_addr_unknown); + let (_, filtered_events_2) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_2.len(), 0); + } + } + + #[test] + #[serial] + fn test_event_filter_5() { + // Test get_filtered_sc_output_events + is error + + let mut cache = setup(); + cache.max_entry_count = 10; + + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + + let dummy_addr = + Address::from_str("AU12qePoXhNbYWE1jZuafqJong7bbq1jw3k89RgbMawbrdZpaasoA").unwrap(); + let emit_addr_1 = + Address::from_str("AU122Em8qkqegdLb1eyH8rdkSCNEf7RZLeTJve4Q2inRPGiTJ2xNv").unwrap(); + let emit_addr_2 = + Address::from_str("AU12WuVR1Td74q9eAbtYZUnk5jnRbUuUacyhQFwm217bV5v1mNqTZ").unwrap(); + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let to_insert_count = cache.max_entry_count - 5; + let threshold = to_insert_count / 2; + let mut events = (0..cache.max_entry_count - 5) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + if i < threshold { + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_1]); + } else { + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + } + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let index_2_1 = 0u64; + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_1; + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + event.context.is_error = true; + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in the DB + events.shuffle(&mut thread_rng()); + cache.insert_multi_it(events.into_iter()); + + let filter_1 = EventFilter { + is_error: Some(true), + ..Default::default() + }; + + let (_, filtered_events_1) = cache.get_filtered_sc_output_events(&filter_1); + assert_eq!(filtered_events_1.len(), 1); + assert!(filtered_events_1[0].context.is_error); + assert_eq!(filtered_events_1[0].context.slot, slot_2); + assert_eq!(filtered_events_1[0].context.index_in_slot, index_2_2); + } + + #[test] + #[serial] + fn test_filter_optimisations() { + // Test we iterate over the right number of rows when filtering + + let mut cache = setup(); + cache.max_entry_count = 10; + + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + + let dummy_addr = + Address::from_str("AU12qePoXhNbYWE1jZuafqJong7bbq1jw3k89RgbMawbrdZpaasoA").unwrap(); + let emit_addr_1 = + Address::from_str("AU122Em8qkqegdLb1eyH8rdkSCNEf7RZLeTJve4Q2inRPGiTJ2xNv").unwrap(); + let emit_addr_2 = + Address::from_str("AU12WuVR1Td74q9eAbtYZUnk5jnRbUuUacyhQFwm217bV5v1mNqTZ").unwrap(); + + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: true, + }, + data: "error foo bar".to_string(), + }; + + let to_insert_count = cache.max_entry_count - 5; + let threshold = to_insert_count / 2; + let mut events = (0..cache.max_entry_count - 5) + .map(|i| { + let mut event = event.clone(); + event.context.index_in_slot = i as u64; + if i < threshold { + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_1]); + } else { + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + } + event + }) + .collect::>(); + + let slot_2 = Slot::new(2, 0); + let index_2_1 = 0u64; + let event_slot_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_1; + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + event + }; + let index_2_2 = 256u64; + let event_slot_2_2 = { + let mut event = event.clone(); + event.context.slot = slot_2; + event.context.index_in_slot = index_2_2; + event.context.call_stack = VecDeque::from(vec![dummy_addr, emit_addr_2]); + // event.context.is_error = true; + event + }; + events.push(event_slot_2.clone()); + events.push(event_slot_2_2.clone()); + // Randomize the events so we insert in random orders in the DB + events.shuffle(&mut thread_rng()); + cache.insert_multi_it(events.into_iter()); + + // Check if we correctly count the number of events in the DB with emit_addr_1 & emit_addr_2 + let emit_addr_1_count = cache + .filter_item_estimate_count( + &KeyIndent::EmitterAddress, + &FilterItem::EmitterAddress(emit_addr_1), + ) + .unwrap(); + let emit_addr_2_count = cache + .filter_item_estimate_count( + &KeyIndent::EmitterAddress, + &FilterItem::EmitterAddress(emit_addr_2), + ) + .unwrap(); + + assert_eq!(emit_addr_1_count, (threshold) as u64); + assert_eq!(emit_addr_2_count, (threshold + 1 + 2) as u64); + + // Check if we query first by emitter address then is_error + + let filter_1 = EventFilter { + emitter_address: Some(emit_addr_1), + is_error: Some(true), + ..Default::default() + }; + + let (query_counts, _filtered_events_1) = cache.get_filtered_sc_output_events(&filter_1); + println!("threshold: {:?}", threshold); + println!("query_counts: {:?}", query_counts); + + // Check that we iter no more than needed (here: only 2 (== threshold) event with emit addr 1) + assert_eq!(query_counts[0], threshold as u64); + // For second filter (is_error) we could have iter more (all events have is_error = true) + // but as soon as we found 2 items we could return (as the previous filter already limit the final count) + assert_eq!(query_counts[1], threshold as u64); + } +} diff --git a/massa-event-cache/src/lib.rs b/massa-event-cache/src/lib.rs new file mode 100644 index 00000000000..d56da234a5c --- /dev/null +++ b/massa-event-cache/src/lib.rs @@ -0,0 +1,10 @@ +pub mod config; + +pub mod controller; +mod event_cache; +mod rocksdb_operator; +mod ser_deser; +pub mod worker; + +#[cfg(feature = "test-exports")] +pub use controller::{MockEventCacheController, MockEventCacheControllerWrapper}; diff --git a/massa-event-cache/src/rocksdb_operator.rs b/massa-event-cache/src/rocksdb_operator.rs new file mode 100644 index 00000000000..d3518852e0e --- /dev/null +++ b/massa-event-cache/src/rocksdb_operator.rs @@ -0,0 +1,110 @@ +use rocksdb::MergeOperands; + +pub fn counter_merge( + _key: &[u8], + existing_val: Option<&[u8]>, + operands: &MergeOperands, +) -> Option> { + let counter_current_value = if let Some(existing_val) = existing_val { + u64::from_be_bytes(existing_val.try_into().unwrap()) + } else { + 0 + }; + + let counter_value = operands.iter().fold(counter_current_value, |mut acc, x| { + let incr_value = i64::from_be_bytes(x.try_into().unwrap()); + acc = acc.saturating_add_signed(incr_value); + acc + }); + + Some(counter_value.to_be_bytes().to_vec()) +} + +#[cfg(test)] +mod tests { + use super::*; + // std + // third-party + use rocksdb::{Options, DB}; + use serial_test::serial; + use tempfile::TempDir; + + #[test] + #[serial] + fn test_operator() { + let tmp_path = TempDir::new().unwrap().path().to_path_buf(); + let options = { + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.set_merge_operator_associative("counter merge operator", counter_merge); + opts + }; + let db = DB::open(&options, tmp_path).unwrap(); + let key_1 = "foo1"; + let key_2 = "baz42"; + db.put(key_1, 0u64.to_be_bytes()).unwrap(); + db.put(key_2, 0u64.to_be_bytes()).unwrap(); + + let value = db.get(key_1).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value.try_into().unwrap()), 0); + let value2 = db.get(key_2).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value2.try_into().unwrap()), 0); + + // key_1 counter += 1 + db.merge(key_1, 1i64.to_be_bytes()).unwrap(); + + let value = db.get(key_1).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value.try_into().unwrap()), 1); + let value2 = db.get(key_2).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value2.try_into().unwrap()), 0); + + // key_2 counter += 9 + db.merge(key_2, 9i64.to_be_bytes()).unwrap(); + // key_2 counter += 1 + db.merge(key_2, 1i64.to_be_bytes()).unwrap(); + // key_2 counter += 32 + db.merge(key_2, 32i64.to_be_bytes()).unwrap(); + + let value = db.get(key_1).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value.try_into().unwrap()), 1); + let value2 = db.get(key_2).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value2.try_into().unwrap()), 42); + } + + #[test] + #[serial] + fn test_operator_2() { + let tmp_path = TempDir::new().unwrap().path().to_path_buf(); + let options = { + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.set_merge_operator_associative("counter merge operator", counter_merge); + opts + }; + let db = DB::open(&options, tmp_path).unwrap(); + let key_1 = "foo1"; + let key_2 = "baz42"; + db.put(key_1, 0u64.to_be_bytes()).unwrap(); + db.put(key_2, 0u64.to_be_bytes()).unwrap(); + + let value = db.get(key_1).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value.try_into().unwrap()), 0); + let value2 = db.get(key_2).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value2.try_into().unwrap()), 0); + + // key_1 counter += 1 + db.merge(key_1, 1i64.to_be_bytes()).unwrap(); + + let value = db.get(key_1).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value.try_into().unwrap()), 1); + let value2 = db.get(key_2).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value2.try_into().unwrap()), 0); + + db.merge(key_1, (-3i64).to_be_bytes()).unwrap(); + + let value = db.get(key_1).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value.try_into().unwrap()), 0); + let value2 = db.get(key_2).unwrap().unwrap(); + assert_eq!(u64::from_be_bytes(value2.try_into().unwrap()), 0); + } +} diff --git a/massa-event-cache/src/ser_deser.rs b/massa-event-cache/src/ser_deser.rs new file mode 100644 index 00000000000..09819793947 --- /dev/null +++ b/massa-event-cache/src/ser_deser.rs @@ -0,0 +1,304 @@ +use std::collections::Bound::{Excluded, Included}; +use std::collections::VecDeque; +// third-party +use nom::{ + bytes::complete::take, + error::{context, ContextError, ParseError}, + multi::length_count, + sequence::tuple, + IResult, Parser, +}; +// internal +use massa_models::address::{AddressDeserializer, AddressSerializer}; +use massa_models::block_id::{BlockId, BlockIdDeserializer, BlockIdSerializer}; +use massa_models::operation::{OperationId, OperationIdDeserializer, OperationIdSerializer}; +use massa_models::output_event::{EventExecutionContext, SCOutputEvent}; +use massa_models::serialization::{StringDeserializer, StringSerializer}; +use massa_models::slot::{SlotDeserializer, SlotSerializer}; +use massa_serialization::{ + Deserializer, OptionDeserializer, OptionSerializer, SerializeError, Serializer, + U32VarIntDeserializer, U32VarIntSerializer, U64VarIntDeserializer, U64VarIntSerializer, +}; + +/// Metadata serializer +pub struct SCOutputEventSerializer { + index_in_slot_ser: U64VarIntSerializer, + addr_len_ser: U32VarIntSerializer, + slot_ser: SlotSerializer, + addr_ser: AddressSerializer, + block_id_ser: OptionSerializer, + op_id_ser: OptionSerializer, + data_ser: StringSerializer, +} + +impl SCOutputEventSerializer { + pub fn new() -> Self { + Self { + index_in_slot_ser: U64VarIntSerializer::new(), + addr_len_ser: U32VarIntSerializer::new(), + slot_ser: SlotSerializer::new(), + addr_ser: AddressSerializer::new(), + block_id_ser: OptionSerializer::new(BlockIdSerializer::new()), + op_id_ser: OptionSerializer::new(OperationIdSerializer::new()), + data_ser: StringSerializer::new(U64VarIntSerializer::new()), + } + } +} + +impl Default for SCOutputEventSerializer { + fn default() -> Self { + Self::new() + } +} + +impl Serializer for SCOutputEventSerializer { + fn serialize(&self, value: &SCOutputEvent, buffer: &mut Vec) -> Result<(), SerializeError> { + // context + self.slot_ser.serialize(&value.context.slot, buffer)?; + self.block_id_ser.serialize(&value.context.block, buffer)?; + buffer.push(u8::from(value.context.read_only)); + self.index_in_slot_ser + .serialize(&value.context.index_in_slot, buffer)?; + // Components + let call_stack_len_ = value.context.call_stack.len(); + let call_stack_len = u32::try_from(call_stack_len_).map_err(|_| { + SerializeError::GeneralError(format!( + "Cannot convert component_len ({}) to u32", + call_stack_len_ + )) + })?; + // ser vec len + self.addr_len_ser.serialize(&call_stack_len, buffer)?; + for address in value.context.call_stack.iter() { + self.addr_ser.serialize(address, buffer)?; + } + self.op_id_ser + .serialize(&value.context.origin_operation_id, buffer)?; + buffer.push(u8::from(value.context.is_final)); + buffer.push(u8::from(value.context.is_error)); + + // data + self.data_ser.serialize(&value.data, buffer)?; + Ok(()) + } +} + +/// SCOutputEvent deserializer +pub struct SCOutputEventDeserializer { + index_in_slot_deser: U64VarIntDeserializer, + addr_len_deser: U32VarIntDeserializer, + slot_deser: SlotDeserializer, + addr_deser: AddressDeserializer, + block_id_deser: OptionDeserializer, + op_id_deser: OptionDeserializer, + data_deser: StringDeserializer, +} + +impl SCOutputEventDeserializer { + pub fn new(args: SCOutputEventDeserializerArgs) -> Self { + Self { + index_in_slot_deser: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)), + addr_len_deser: U32VarIntDeserializer::new( + Included(0), + Included(u32::from(args.max_call_stack_length)), + ), + slot_deser: SlotDeserializer::new( + (Included(0), Included(u64::MAX)), + (Included(0), Excluded(args.thread_count)), + ), + addr_deser: Default::default(), + block_id_deser: OptionDeserializer::new(BlockIdDeserializer::new()), + op_id_deser: OptionDeserializer::new(OperationIdDeserializer::new()), + data_deser: StringDeserializer::new(U64VarIntDeserializer::new( + Included(0), + Included(args.max_event_data_length), + )), + } + } +} + +impl Deserializer for SCOutputEventDeserializer { + fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>( + &self, + buffer: &'a [u8], + ) -> IResult<&'a [u8], SCOutputEvent, E> { + context( + "Failed ScOutputEvent deserialization", + tuple(( + context("Failed slot deserialization", |input| { + self.slot_deser.deserialize(input) + }), + context("Failed BlockId deserialization", |input| { + self.block_id_deser.deserialize(input) + }), + context("Failed read_only deserialization", |input: &'a [u8]| { + let (rem, read_only) = take(1usize)(input)?; + let read_only = match read_only.first() { + None => { + return IResult::Err(nom::Err::Error(ParseError::from_error_kind( + input, + nom::error::ErrorKind::Fail, + ))); + } + Some(0) => false, + _ => true, + }; + IResult::Ok((rem, read_only)) + }), + context("Failed index_in_slot deser", |input| { + self.index_in_slot_deser.deserialize(input) + }), + length_count( + context("Failed call stack entry count deser", |input| { + self.addr_len_deser.deserialize(input) + }), + context("Failed call stack items deser", |input| { + self.addr_deser.deserialize(input) + }), + ), + context("Failed OperationId deserialization", |input| { + self.op_id_deser.deserialize(input) + }), + context("Failed is_final deserialization", |input: &'a [u8]| { + let (rem, read_only) = take(1usize)(input)?; + let read_only = match read_only.first() { + None => { + return IResult::Err(nom::Err::Error(ParseError::from_error_kind( + input, + nom::error::ErrorKind::Fail, + ))); + } + Some(0) => false, + _ => true, + }; + IResult::Ok((rem, read_only)) + }), + context("Failed is_error deserialization", |input: &'a [u8]| { + let (rem, read_only) = take(1usize)(input)?; + let read_only = match read_only.first() { + None => { + return IResult::Err(nom::Err::Error(ParseError::from_error_kind( + input, + nom::error::ErrorKind::Fail, + ))); + } + Some(0) => false, + _ => true, + }; + IResult::Ok((rem, read_only)) + }), + context("Failed data deserialization", |input| { + self.data_deser.deserialize(input) + }), + )), + ) + .map( + |(slot, bid, read_only, idx, call_stack, oid, is_final, is_error, data)| { + SCOutputEvent { + context: EventExecutionContext { + slot, + block: bid, + read_only, + index_in_slot: idx, + call_stack: VecDeque::from(call_stack), + origin_operation_id: oid, + is_final, + is_error, + }, + data, + } + }, + ) + .parse(buffer) + } +} + +/// SCOutputEvent deserializer args +#[allow(missing_docs)] +pub struct SCOutputEventDeserializerArgs { + pub thread_count: u8, + pub max_call_stack_length: u16, + pub max_event_data_length: u64, +} + +#[cfg(test)] +mod test { + use super::*; + use massa_models::slot::Slot; + use massa_serialization::DeserializeError; + use serial_test::serial; + + #[test] + #[serial] + fn test_sc_output_event_ser_der() { + let slot_1 = Slot::new(1, 0); + let index_1_0 = 0; + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let event_ser = SCOutputEventSerializer::new(); + let event_deser = SCOutputEventDeserializer::new(SCOutputEventDeserializerArgs { + thread_count: 16, + max_call_stack_length: 25, + max_event_data_length: 512, + }); + + let mut buffer = Vec::new(); + event_ser.serialize(&event, &mut buffer).unwrap(); + + let (rem, event_new) = event_deser + .deserialize::(&buffer) + .unwrap(); + + assert_eq!(event.context, event_new.context); + assert_eq!(event.data, event_new.data); + assert!(rem.is_empty()); + } + + #[test] + #[serial] + fn test_sc_output_event_ser_der_err() { + // Test serialization / deserialization with a slot with thread too high + + let slot_1 = Slot::new(1, 99); + let index_1_0 = 0; + let event = SCOutputEvent { + context: EventExecutionContext { + slot: slot_1, + block: None, + read_only: false, + index_in_slot: index_1_0, + call_stack: Default::default(), + origin_operation_id: None, + is_final: true, + is_error: false, + }, + data: "message foo bar".to_string(), + }; + + let event_ser = SCOutputEventSerializer::new(); + let event_deser = SCOutputEventDeserializer::new(SCOutputEventDeserializerArgs { + thread_count: 16, + max_call_stack_length: 25, + max_event_data_length: 512, + }); + + let mut buffer = Vec::new(); + event_ser.serialize(&event, &mut buffer).unwrap(); + + let res = event_deser.deserialize::(&buffer); + // Expect deserialization to fail (slot thread too high) + assert!(res.is_err()); + } +} diff --git a/massa-event-cache/src/worker.rs b/massa-event-cache/src/worker.rs new file mode 100644 index 00000000000..b020aa1569e --- /dev/null +++ b/massa-event-cache/src/worker.rs @@ -0,0 +1,162 @@ +// std +use std::sync::Arc; +use std::thread; +// third-party +// use massa_time::MassaTime; +use parking_lot::{Condvar, Mutex, RwLock}; +use tracing::{debug, info}; +// internal +use crate::config::EventCacheConfig; +use crate::controller::{ + EventCacheController, EventCacheControllerImpl, EventCacheWriterInputData, +}; +use crate::event_cache::EventCache; + +/// Structure gathering all elements needed by the event cache thread +pub(crate) struct EventCacheWriterThread { + // A copy of the input data allowing access to incoming requests + input_data: Arc<(Condvar, Mutex)>, + /// Event cache + cache: Arc>, +} + +impl EventCacheWriterThread { + fn new( + input_data: Arc<(Condvar, Mutex)>, + event_cache: Arc>, + ) -> Self { + Self { + input_data, + cache: event_cache, + } + } + + /// Waits for an event to trigger a new iteration in the event cache main loop. + /// + /// # Returns + /// `ExecutionInputData` representing the input requests, + /// and a boolean saying whether we should stop the loop. + fn wait_loop_event(&mut self) -> (EventCacheWriterInputData, bool) { + loop { + // lock input data + let mut input_data_lock = self.input_data.1.lock(); + + // take current input data, resetting it + let input_data: EventCacheWriterInputData = std::mem::take(&mut input_data_lock); + + // Check if there is some input data + if !input_data.events.is_empty() { + return (input_data, false); + } + + // if we need to stop, return None + if input_data.stop { + return (input_data, true); + } + + self.input_data.0.wait(&mut input_data_lock); + } + } + + /// Main loop of the worker + pub fn main_loop(&mut self) { + loop { + let (input_data, stop) = self.wait_loop_event(); + debug!( + "Event cache writer loop triggered, input_data = {:?}", + input_data + ); + + if stop { + // we need to stop + break; + } + + { + let mut lock = self.cache.write(); + lock.insert_multi_it(input_data.events.into_iter()); + // drop the lock as early as possible + drop(lock); + } + } + } +} + +/// Event cache manager trait used to stop the event cache thread +pub trait EventCacheManager { + /// Stop the event cache thread + /// Note that we do not take self by value to consume it + /// because it is not allowed to move out of `Box` + /// This will improve if the `unsized_fn_params` feature stabilizes enough to be safely usable. + fn stop(&mut self); +} + +/// ... manager +/// Allows stopping the ... worker +pub struct EventCacheWriterManagerImpl { + /// input data to process in the VM loop + /// with a wake-up condition variable that needs to be triggered when the data changes + pub(crate) input_data: Arc<(Condvar, Mutex)>, + /// handle used to join the worker thread + pub(crate) thread_handle: Option>, +} + +impl EventCacheManager for EventCacheWriterManagerImpl { + /// stops the worker + fn stop(&mut self) { + info!("Stopping Execution controller..."); + // notify the worker thread to stop + { + let mut input_wlock = self.input_data.1.lock(); + input_wlock.stop = true; + self.input_data.0.notify_one(); + } + // join the thread + if let Some(join_handle) = self.thread_handle.take() { + join_handle.join().expect("VM controller thread panicked"); + } + info!("Execution controller stopped"); + } +} + +pub fn start_event_cache_writer_worker( + cfg: EventCacheConfig, +) -> (Box, Box) { + let event_cache = Arc::new(RwLock::new(EventCache::new( + cfg.event_cache_path.as_path(), + cfg.max_event_cache_length, + cfg.snip_amount, + cfg.thread_count, + cfg.max_call_stack_length, + cfg.max_event_data_length, + cfg.max_events_per_operation, + cfg.max_operations_per_block, + cfg.max_events_per_query, + ))); + + // define the input data interface + let input_data = Arc::new((Condvar::new(), Mutex::new(EventCacheWriterInputData::new()))); + let input_data_clone = input_data.clone(); + + // create a controller + let controller = EventCacheControllerImpl { + input_data: input_data.clone(), + cache: event_cache.clone(), + }; + + let thread_builder = thread::Builder::new().name("event_cache".into()); + let thread_handle = thread_builder + .spawn(move || { + EventCacheWriterThread::new(input_data_clone, event_cache).main_loop(); + }) + .expect("failed to spawn thread : event_cache"); + + // create a manager + let manager = EventCacheWriterManagerImpl { + input_data, + thread_handle: Some(thread_handle), + }; + + // return the manager and controller pair + (Box::new(manager), Box::new(controller)) +} diff --git a/massa-executed-ops/Cargo.toml b/massa-executed-ops/Cargo.toml index 9d38e318cde..05f72dac68c 100644 --- a/massa-executed-ops/Cargo.toml +++ b/massa-executed-ops/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_executed_ops" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-execution-exports/Cargo.toml b/massa-execution-exports/Cargo.toml index 5cab6f8c5b8..a004bb533ee 100644 --- a/massa-execution-exports/Cargo.toml +++ b/massa-execution-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_execution_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" @@ -12,23 +12,26 @@ dump-block = [] execution-info = ["execution-trace"] [dependencies] -displaydoc = {workspace = true} -thiserror = {workspace = true} -num = {workspace = true, "features" = ["serde"]} # BOM UPGRADE Revert to {"version": "0.4", "features": ["serde"]} if problem -tempfile = {workspace = true, "optional" = true} # BOM UPGRADE Revert to {"version": "3.3", "optional": true} if problem -tokio = {workspace = true, "features" = ["sync"]} -mockall = {workspace = true, "optional" = true} # BOM UPGRADE Revert to {"version": "0.11.4", "optional": true} if problem -massa-proto-rs = {workspace = true, "features" = ["tonic"]} -massa_hash = {workspace = true} -massa_models = {workspace = true} -massa_time = {workspace = true} -massa_storage = {workspace = true} -massa_final_state = {workspace = true} -massa_pos_exports = {workspace = true} -massa_module_cache = {workspace = true} -massa_versioning = {workspace = true} -massa-sc-runtime = {workspace = true} -serde = {workspace = true, "features" = ["derive"]} +displaydoc = { workspace = true } +thiserror = { workspace = true } +num = { workspace = true, "features" = [ + "serde", +] } # BOM UPGRADE Revert to {"version": "0.4", "features": ["serde"]} if problem +tempfile = { workspace = true, "optional" = true } # BOM UPGRADE Revert to {"version": "3.3", "optional": true} if problem +tokio = { workspace = true, "features" = ["sync"] } +mockall = { workspace = true, "optional" = true } # BOM UPGRADE Revert to {"version": "0.11.4", "optional": true} if problem +massa_deferred_calls = { workspace = true } +massa-proto-rs = { workspace = true, "features" = ["tonic"] } +massa_hash = { workspace = true } +massa_models = { workspace = true } +massa_time = { workspace = true } +massa_storage = { workspace = true } +massa_final_state = { workspace = true } +massa_pos_exports = { workspace = true } +massa_module_cache = { workspace = true } +massa_versioning = { workspace = true } +massa-sc-runtime = { workspace = true } +serde = { workspace = true, "features" = ["derive"] } [dev-dependencies] -mockall = {workspace = true} +mockall = { workspace = true } diff --git a/massa-execution-exports/src/error.rs b/massa-execution-exports/src/error.rs index 21fcbe3f85d..1f92cc75a44 100644 --- a/massa-execution-exports/src/error.rs +++ b/massa-execution-exports/src/error.rs @@ -67,6 +67,9 @@ pub enum ExecutionError { /// Factory error: {0} FactoryError(#[from] FactoryError), + + /// Autonomous smart contract call error: {0} + DeferredCallsError(String), } /// Execution query errors diff --git a/massa-execution-exports/src/event_store.rs b/massa-execution-exports/src/event_store.rs index a01de21306a..fe2096aeee8 100644 --- a/massa-execution-exports/src/event_store.rs +++ b/massa-execution-exports/src/event_store.rs @@ -55,48 +55,61 @@ impl EventStore { /// * operation id /// * is final pub fn get_filtered_sc_output_events(&self, filter: &EventFilter) -> VecDeque { - self.0 - .iter() - .filter(|x| { - if let Some(start) = filter.start { - if x.context.slot < start { - return false; - } - } - if let Some(end) = filter.end { - if x.context.slot >= end { - return false; - } - } - if let Some(is_final) = filter.is_final { - if x.context.is_final != is_final { - return false; - } - } - if let Some(is_error) = filter.is_error { - if x.context.is_error != is_error { - return false; - } + self.get_filtered_sc_output_events_iter(filter) + .cloned() + .collect() + } + + /// Get events iterator optionally filtered by given EventFilter + pub fn get_filtered_sc_output_events_iter<'b, 'a: 'b>( + &'a self, + filter: &'b EventFilter, + ) -> impl Iterator + 'b { + // Note on lifetimes: + // 'a -> is the lifetime for self -> because the iterator returns items from self + // 'b -> is the lifetime for filter -> because the returning iterator captures filter + // , and we have lifetime 'a > 'b because filter can live less than self + + self.0.iter().filter(|x| { + if let Some(start) = filter.start { + if x.context.slot < start { + return false; } - match (filter.original_caller_address, x.context.call_stack.front()) { - (Some(addr1), Some(addr2)) if addr1 != *addr2 => return false, - (Some(_), None) => return false, - _ => (), + } + if let Some(end) = filter.end { + if x.context.slot >= end { + return false; } - match (filter.emitter_address, x.context.call_stack.back()) { - (Some(addr1), Some(addr2)) if addr1 != *addr2 => return false, - (Some(_), None) => return false, - _ => (), + } + if let Some(is_final) = filter.is_final { + if x.context.is_final != is_final { + return false; } - match (filter.original_operation_id, x.context.origin_operation_id) { - (Some(addr1), Some(addr2)) if addr1 != addr2 => return false, - (Some(_), None) => return false, - _ => (), + } + if let Some(is_error) = filter.is_error { + if x.context.is_error != is_error { + return false; } - true - }) - .cloned() - .collect() + } + + match (filter.original_caller_address, x.context.call_stack.front()) { + (Some(addr1), Some(addr2)) if addr1 != *addr2 => return false, + (Some(_), None) => return false, + _ => (), + } + match (filter.emitter_address, x.context.call_stack.back()) { + (Some(addr1), Some(addr2)) if addr1 != *addr2 => return false, + (Some(_), None) => return false, + _ => (), + } + match (filter.original_operation_id, x.context.origin_operation_id) { + (Some(addr1), Some(addr2)) if addr1 != addr2 => return false, + (Some(_), None) => return false, + _ => (), + } + + true + }) } } diff --git a/massa-execution-exports/src/lib.rs b/massa-execution-exports/src/lib.rs index 8d76e76f470..1ae8c035587 100644 --- a/massa-execution-exports/src/lib.rs +++ b/massa-execution-exports/src/lib.rs @@ -12,9 +12,9 @@ //! an instance of `ExecutionManager` is returned (see the documentation of `start_execution_worker` in `massa-execution-worker`), //! as well as an instance of `ExecutionController`. //! -//! The non-clonable `ExecutionManager` allows stopping the execution worker thread. +//! The non-cloneable `ExecutionManager` allows stopping the execution worker thread. //! -//! The clonable `ExecutionController` allows sending updates on the latest blockclique changes to the execution worker +//! The cloneable `ExecutionController` allows sending updates on the latest blockclique changes to the execution worker //! for it to keep track of them and execute the operations present in blocks. //! It also allows various read-only queries such as executing bytecode //! while ignoring all the changes it would cause to the consensus state (read-only execution), @@ -59,7 +59,7 @@ pub use controller_traits::MockExecutionController; pub use controller_traits::{ExecutionController, ExecutionManager}; pub use error::{ExecutionError, ExecutionQueryError}; pub use event_store::EventStore; -pub use massa_sc_runtime::GasCosts; +pub use massa_sc_runtime::{CondomLimits, GasCosts}; pub use settings::{ExecutionConfig, StorageCostsConstants}; pub use types::{ ExecutedBlockInfo, ExecutionAddressInfo, ExecutionBlockMetadata, ExecutionOutput, diff --git a/massa-execution-exports/src/mapping_grpc.rs b/massa-execution-exports/src/mapping_grpc.rs index 828e226a0c8..425d8d2942f 100644 --- a/massa-execution-exports/src/mapping_grpc.rs +++ b/massa-execution-exports/src/mapping_grpc.rs @@ -9,6 +9,7 @@ use crate::{ }; use grpc_api::execution_query_request_item as exec; use massa_models::address::Address; +use massa_models::deferred_calls::DeferredCallId; use massa_models::error::ModelsError; use massa_models::execution::EventFilter; use massa_models::mapping_grpc::to_denunciation_index; @@ -117,13 +118,13 @@ pub fn to_querystate_filter( } //TODO to be checked exec::RequestItem::CycleInfos(value) => { - let addreses = value + let addresses = value .restrict_to_addresses .into_iter() .map(|address| Address::from_str(&address)) .collect::, _>>()?; - let mut addresses_set = PreHashSet::with_capacity(addreses.len()); - addresses_set.extend(addreses); + let mut addresses_set = PreHashSet::with_capacity(addresses.len()); + addresses_set.extend(addresses); Ok(ExecutionQueryRequestItem::CycleInfos { cycle: value.cycle, restrict_to_addresses: Some(addresses_set), @@ -133,6 +134,30 @@ pub fn to_querystate_filter( let event_filter = to_event_filter(value.filters)?; Ok(ExecutionQueryRequestItem::Events(event_filter)) } + exec::RequestItem::DeferredCallQuote(value) => { + Ok(ExecutionQueryRequestItem::DeferredCallQuote { + target_slot: value + .target_slot + .ok_or(ModelsError::ErrorRaised( + "target slot is required".to_string(), + ))? + .into(), + max_gas_request: value.max_gas, + params_size: value.params_size, + }) + } + exec::RequestItem::DeferredCallInfo(info) => { + let id = DeferredCallId::from_str(&info.call_id)?; + Ok(ExecutionQueryRequestItem::DeferredCallInfo(id)) + } + exec::RequestItem::DeferredCallsBySlot(value) => { + Ok(ExecutionQueryRequestItem::DeferredCallsBySlot( + value + .slot + .ok_or(ModelsError::ErrorRaised("slot is required".to_string()))? + .into(), + )) + } } } else { Err(ModelsError::ErrorRaised("no filter provided".to_string())) @@ -265,6 +290,36 @@ fn to_execution_query_result( }, ) } + ExecutionQueryResponseItem::DeferredCallQuote( + target_slot, + max_gas_request, + available, + price, + ) => grpc_api::execution_query_response_item::ResponseItem::DeferredCallQuote( + grpc_api::DeferredCallQuoteResponse { + target_slot: Some(target_slot.into()), + max_gas_request, + available, + price: Some(price.into()), + }, + ), + ExecutionQueryResponseItem::DeferredCallInfo(call_id, call) => { + grpc_api::execution_query_response_item::ResponseItem::DeferredCallInfo( + grpc_api::DeferredCallInfoResponse { + call_id: call_id.to_string(), + call: Some(call.into()), + }, + ) + } + ExecutionQueryResponseItem::DeferredCallsBySlot(slot, ids) => { + let arr = ids.into_iter().map(|id| id.to_string()).collect(); + grpc_api::execution_query_response_item::ResponseItem::DeferredCallsBySlot( + grpc_api::DeferredCallsBySlotResponse { + slot: Some(slot.into()), + call_ids: arr, + }, + ) + } }; grpc_api::ExecutionQueryResponseItem { diff --git a/massa-execution-exports/src/settings.rs b/massa-execution-exports/src/settings.rs index 49ae83b4559..7ddc042e19e 100644 --- a/massa-execution-exports/src/settings.rs +++ b/massa-execution-exports/src/settings.rs @@ -2,8 +2,9 @@ //! This module provides the structures used to provide configuration parameters to the Execution system +use massa_deferred_calls::config::DeferredCallsConfig; use massa_models::amount::Amount; -use massa_sc_runtime::GasCosts; +use massa_sc_runtime::{CondomLimits, GasCosts}; use massa_time::MassaTime; use num::rational::Ratio; use std::path::PathBuf; @@ -102,4 +103,19 @@ pub struct ExecutionConfig { pub max_execution_traces_slot_limit: usize, /// Where to dump blocks pub block_dump_folder_path: PathBuf, + /// Max recursive calls depth in SC + /// Used to limit the recursion_counter value in the context, to avoid stack overflow issues. + pub max_recursive_calls_depth: u16, + /// Runtime condom middleware limits + pub condom_limits: CondomLimits, + /// deferred calls config + pub deferred_calls_config: DeferredCallsConfig, + /// Maximum number of event that an operation can emit + pub max_event_per_operation: usize, + /// Path to the hard drive event cache storage + pub event_cache_path: PathBuf, + /// Maximum number of entries we want to keep in the Event cache + pub event_cache_size: usize, + /// Amount of entries removed when `event_cache_size` is reached + pub event_snip_amount: usize, } diff --git a/massa-execution-exports/src/test_exports/config.rs b/massa-execution-exports/src/test_exports/config.rs index f42f9bd53d2..06cb1cb3dc2 100644 --- a/massa-execution-exports/src/test_exports/config.rs +++ b/massa-execution-exports/src/test_exports/config.rs @@ -3,8 +3,9 @@ //! This file defines testing tools related to the configuration use crate::{ExecutionConfig, StorageCostsConstants}; +use massa_deferred_calls::config::DeferredCallsConfig; use massa_models::config::*; -use massa_sc_runtime::GasCosts; +use massa_sc_runtime::{CondomLimits, GasCosts}; use massa_time::MassaTime; use tempfile::TempDir; @@ -24,9 +25,33 @@ impl Default for ExecutionConfig { // So we need to create it manually (not really safe but ok for unit testing) let hd_cache_path = TempDir::new().unwrap().path().to_path_buf(); std::fs::create_dir_all(hd_cache_path.clone()).unwrap(); + let event_cache_path = TempDir::new().unwrap().path().to_path_buf(); + std::fs::create_dir_all(event_cache_path.clone()).unwrap(); let block_dump_folder_path = TempDir::new().unwrap().path().to_path_buf(); std::fs::create_dir_all(block_dump_folder_path.clone()).unwrap(); + #[cfg(feature = "gas_calibration")] + let limits = CondomLimits::default(); + + #[cfg(not(feature = "gas_calibration"))] + let limits = CondomLimits { + max_exports: Some(100), + max_functions: Some(100), + max_signature_len: Some(100), + max_name_len: Some(100), + max_imports_len: Some(100), + max_table_initializers_len: Some(100), + max_passive_elements_len: Some(100), + max_passive_data_len: Some(100), + max_global_initializers_len: Some(100), + max_function_names_len: Some(100), + max_tables_count: Some(16), + max_memories_len: Some(1), + max_globals_len: Some(100), + max_custom_sections_len: Some(100), + max_custom_sections_data_len: Some(1_000_000), + }; + Self { readonly_queue_length: 100, max_final_events: 1000, @@ -56,11 +81,6 @@ impl Default for ExecutionConfig { "/../massa-node/base_config/gas_costs/abi_gas_costs.json" ) .into(), - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../massa-node/base_config/gas_costs/wasm_gas_costs.json" - ) - .into(), ) .unwrap(), base_operation_gas_cost: BASE_OPERATION_GAS_COST, @@ -73,7 +93,8 @@ impl Default for ExecutionConfig { denunciation_expire_periods: DENUNCIATION_EXPIRE_PERIODS, broadcast_enabled: true, broadcast_slot_execution_output_channel_capacity: 5000, - max_event_size: 50_000, + max_event_size: 512, + max_event_per_operation: 25, max_function_length: 1000, max_parameter_length: 1000, chain_id: *CHAINID, @@ -81,6 +102,13 @@ impl Default for ExecutionConfig { broadcast_slot_execution_traces_channel_capacity: 5000, max_execution_traces_slot_limit: 320, block_dump_folder_path, + max_recursive_calls_depth: 25, + condom_limits: limits, + deferred_calls_config: DeferredCallsConfig::default(), + + event_cache_path, + event_cache_size: 100, + event_snip_amount: 10, } } } diff --git a/massa-execution-exports/src/types.rs b/massa-execution-exports/src/types.rs index dee597eb67d..ce92ad62aab 100644 --- a/massa-execution-exports/src/types.rs +++ b/massa-execution-exports/src/types.rs @@ -4,11 +4,13 @@ use crate::error::ExecutionQueryError; use crate::event_store::EventStore; +use massa_deferred_calls::DeferredCall; use massa_final_state::StateChanges; use massa_hash::Hash; use massa_models::block_id::BlockId; use massa_models::bytecode::Bytecode; use massa_models::datastore::Datastore; +use massa_models::deferred_calls::DeferredCallId; use massa_models::denunciation::DenunciationIndex; use massa_models::execution::EventFilter; use massa_models::operation::OperationId; @@ -99,6 +101,21 @@ pub enum ExecutionQueryRequestItem { OpExecutionStatusCandidate(OperationId), /// gets the execution status (final) for an operation, returns ExecutionQueryResponseItem::ExecutionStatus(status) OpExecutionStatusFinal(OperationId), + + /// gets the deferred call quote (candidate) for a slot, returns ExecutionQueryResponseItem::DeferredCallQuote(available, price) + DeferredCallQuote { + /// slot to query + target_slot: Slot, + /// gas request + max_gas_request: u64, + /// params size + params_size: u64, + }, + /// get info of deferred calls + DeferredCallInfo(DeferredCallId), + /// retrieves the deferred call for given slot + DeferredCallsBySlot(Slot), + /// gets the execution status (candidate) for an denunciation, returns ExecutionQueryResponseItem::ExecutionStatus(status) DenunciationExecutionStatusCandidate(DenunciationIndex), /// gets the execution status (final) for an denunciation, returns ExecutionQueryResponseItem::ExecutionStatus(status) @@ -139,6 +156,12 @@ pub enum ExecutionQueryResponseItem { DatastoreValue(Vec), /// list of keys KeyList(BTreeSet>), + /// deferred call quote (target_slot, gas_request, available, price) + DeferredCallQuote(Slot, u64, bool, Amount), + /// deferred call info value + DeferredCallInfo(DeferredCallId, DeferredCall), + /// deferred call slot calls value + DeferredCallsBySlot(Slot, Vec), /// deferred credits value DeferredCredits(BTreeMap), /// execution status value diff --git a/massa-execution-exports/src/types_trace_info.rs b/massa-execution-exports/src/types_trace_info.rs index 9655e473513..c37888157bf 100644 --- a/massa-execution-exports/src/types_trace_info.rs +++ b/massa-execution-exports/src/types_trace_info.rs @@ -23,6 +23,8 @@ pub struct SlotAbiCallStack { pub slot: Slot, /// asc call stacks pub asc_call_stacks: Vec>, + /// deferred call stacks + pub deferred_call_stacks: Vec>, /// operation call stacks pub operation_call_stacks: PreHashMap>, } @@ -88,7 +90,7 @@ impl AbiTrace { while !to_process.is_empty() { let t = to_process.pop_front(); if let Some(trace) = t { - if abi_names.iter().find(|t| *(*t) == trace.name).is_some() { + if abi_names.iter().any(|t| *(*t) == trace.name) { // filtered.extend(&trace) filtered.push(trace); } diff --git a/massa-execution-worker/Cargo.toml b/massa-execution-worker/Cargo.toml index b489d2d8803..c4fc5cc7915 100644 --- a/massa-execution-worker/Cargo.toml +++ b/massa-execution-worker/Cargo.toml @@ -4,7 +4,7 @@ harness = false [package] name = "massa_execution_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" @@ -29,6 +29,7 @@ test-exports = [ "massa_metrics/test-exports", "massa_metrics/test-exports", "massa_db_worker", + "massa_event_cache/test-exports", "tempfile", ] benchmarking = [ @@ -69,6 +70,7 @@ criterion = { workspace = true, "optional" = true } massa_pos_worker = { workspace = true, "optional" = true } massa_async_pool = { workspace = true } massa_channel = { workspace = true } +massa_deferred_calls = { workspace = true } massa_executed_ops = { workspace = true } massa_execution_exports = { workspace = true } massa_models = { workspace = true } @@ -85,11 +87,12 @@ massa_final_state = { workspace = true } massa_versioning = { workspace = true } massa_db_exports = { workspace = true } massa_db_worker = { workspace = true, optional = true } +massa_event_cache = { workspace = true } tempfile = { workspace = true, optional = true } massa_wallet = { workspace = true } massa-proto-rs = { workspace = true } schnellru = { workspace = true } -prost = { version = "=0.12", optional = true } +prost = { workspace = true, optional = true } cfg-if = { workspace = true } rocksdb = { workspace = true } @@ -109,3 +112,5 @@ massa_test_framework = { workspace = true, "features" = ["test-exports"] } tokio = { workspace = true, features = ["sync"] } hex-literal = { workspace = true } mockall = { workspace = true } +assert_matches = { workspace = true } +massa_event_cache = { workspace = true, features = ["test-exports"] } diff --git a/massa-execution-worker/src/active_history.rs b/massa-execution-worker/src/active_history.rs index 021e8e8a97d..db6079080bf 100644 --- a/massa-execution-worker/src/active_history.rs +++ b/massa-execution-worker/src/active_history.rs @@ -1,10 +1,9 @@ use massa_async_pool::{AsyncMessage, AsyncMessageId, AsyncMessageUpdate}; use massa_execution_exports::ExecutionOutput; -use massa_ledger_exports::{ - Applicable, LedgerEntry, LedgerEntryUpdate, SetOrDelete, SetOrKeep, SetUpdateOrDelete, -}; +use massa_ledger_exports::{LedgerEntry, LedgerEntryUpdate}; use massa_models::denunciation::DenunciationIndex; use massa_models::prehash::{CapacityAllocator, PreHashMap, PreHashSet}; +use massa_models::types::{Applicable, SetOrDelete, SetOrKeep, SetUpdateOrDelete}; use massa_models::{ address::Address, amount::Amount, bytecode::Bytecode, operation::OperationId, slot::Slot, }; @@ -83,13 +82,18 @@ impl ActiveHistory { return HistorySearchResult::Present(SetUpdateOrDelete::Set(msg)); } Some(SetUpdateOrDelete::Update(msg_update)) => { - current_updates.apply(msg_update.clone()); + let mut combined_message_update = msg_update.clone(); + combined_message_update.apply(current_updates); + current_updates = combined_message_update; } Some(SetUpdateOrDelete::Delete) => return HistorySearchResult::Absent, _ => (), } } + // Note: + // Return Present here as we can have a message in the final state and only an update + // in the active history. So in this case, we return the current updates HistorySearchResult::Present(SetUpdateOrDelete::Update(current_updates)) } @@ -211,7 +215,7 @@ impl ActiveHistory { } /// Gets the deferred credits for a given address that will be credited at a given slot - pub(crate) fn get_adress_deferred_credit_for( + pub(crate) fn get_address_deferred_credit_for( &self, addr: &Address, slot: &Slot, @@ -348,3 +352,224 @@ impl ActiveHistory { .collect() } } + +#[cfg(test)] +mod test { + // std + use std::cmp::Reverse; + use std::collections::BTreeMap; + use std::fmt::Formatter; + use std::str::FromStr; + // third-party + use assert_matches::assert_matches; + use num::rational::Ratio; + // internal + use super::*; + use massa_async_pool::AsyncPoolChanges; + use massa_final_state::StateChanges; + + impl std::fmt::Debug for HistorySearchResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + HistorySearchResult::Present(obj) => write!(f, "{:?}", obj), + HistorySearchResult::Absent => write!(f, "Absent"), + HistorySearchResult::NoInfo => write!(f, "NoInfo"), + } + } + } + + #[test] + fn test_fetch_message() { + // Unit testing ActiveHistory.fetch_msg + + // Setup some addresses + + let addr_sender = + Address::from_str("AU12fZLkHnLED3okr8Lduyty7dz9ZKkd24xMCc2JJWPcdmfn2eUEx").unwrap(); + let addr_dest = + Address::from_str("AS12fZLkHnLED3okr8Lduyty7dz9ZKkd24xMCc2JJWPcdmfn2eUEx").unwrap(); + + // Create 3 async messages (+ 3 message id's) + + let rev: Reverse> = Default::default(); + let message_id: AsyncMessageId = (rev, Slot::new(1, 0), 1u64); + let emission_slot_2 = Slot::new(1, 0); + let emission_index_2 = 2; + let message_id_2: AsyncMessageId = (rev, emission_slot_2, emission_index_2); + let msg_2 = AsyncMessage::new( + emission_slot_2, + emission_index_2, + addr_sender, + addr_dest, + "send_fee_to".to_string(), // SC function name + 0, + Default::default(), + Default::default(), + Slot::new(3, 0), + Slot::new(4, 0), + vec![], + None, + None, + ); + + let emission_slot_3 = Slot::new(2, 0); + let emission_index_3 = 3; + let message_id_3: AsyncMessageId = (rev, emission_slot_3, emission_index_3); + let msg_3 = AsyncMessage::new( + emission_slot_3, + emission_index_3, + addr_sender, + addr_dest, + "send_max_fee_to".to_string(), // SC function name + 0, + Default::default(), + Default::default(), + Slot::new(4, 0), + Slot::new(5, 0), + vec![], + None, + None, + ); + + let msg_3_function_new_2 = "send_max_fee_to_v2".to_string(); + let msg_3_coins_new = Amount::from_raw(1000); + let msg_update_3_2 = AsyncMessageUpdate { + coins: SetOrKeep::Set(msg_3_coins_new), + function: SetOrKeep::Set(msg_3_function_new_2.clone()), + ..Default::default() + }; + + let emission_slot_3_2 = Slot::new(2, 2); + let emission_index_3_2 = 4; + let message_id_3_2: AsyncMessageId = (rev, emission_slot_3_2, emission_index_3_2); + + // Put them in 2 async pool changes + + let async_pool_changes_1 = AsyncPoolChanges(BTreeMap::from([ + (message_id, SetUpdateOrDelete::Delete), + (message_id_2, SetUpdateOrDelete::Set(msg_2.clone())), + (message_id_3, SetUpdateOrDelete::Set(msg_3.clone())), + ])); + let async_pool_changes_2 = AsyncPoolChanges(BTreeMap::from([( + message_id_3, + SetUpdateOrDelete::Update(msg_update_3_2.clone()), + )])); + + // Then put into 2 state changes + + let state_changes_1 = StateChanges { + async_pool_changes: async_pool_changes_1, + ..Default::default() + }; + let state_changes_2 = StateChanges { + async_pool_changes: async_pool_changes_2, + ..Default::default() + }; + + let exec_output_1 = ExecutionOutput { + slot: Slot::new(1, 0), + block_info: None, + state_changes: state_changes_1.clone(), + events: Default::default(), + #[cfg(feature = "execution-trace")] + slot_trace: None, + #[cfg(feature = "dump-block")] + storage: None, + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], + }; + let exec_output_2 = ExecutionOutput { + slot: Slot::new(1, 1), + block_info: None, + state_changes: state_changes_2.clone(), + events: Default::default(), + #[cfg(feature = "execution-trace")] + slot_trace: None, + #[cfg(feature = "dump-block")] + storage: None, + deferred_credits_execution: vec![], + cancel_async_message_execution: vec![], + auto_sell_execution: vec![], + }; + + let active_history = ActiveHistory(VecDeque::from([exec_output_1, exec_output_2])); + + // Test fetch_message with message_id (expect HistorySearchResult::Absent) + { + let current_updates = AsyncMessageUpdate::default(); + let fetched = active_history.fetch_message(&message_id, current_updates); + assert_matches!(fetched, HistorySearchResult::Absent); + } + + // Test fetch_message with message_id_2 (expect HistorySearchResult::Set) + { + let current_updates = AsyncMessageUpdate::default(); + let fetched = active_history.fetch_message(&message_id_2, current_updates); + + if let HistorySearchResult::Present(SetUpdateOrDelete::Set(msg)) = fetched { + assert_eq!(msg, msg_2); + } else { + panic!( + "Expected a HistorySearchRestul::Set(...) and not: {:?}", + fetched + ) + } + } + + { + // Test fetch_message with message_id_2 (expect HistorySearchResult::Set) + current_updates + // (which modifies validity_end) + + let validity_end_new = Slot::new(5, 0); + let current_updates = AsyncMessageUpdate { + validity_end: SetOrKeep::Set(validity_end_new), + ..Default::default() + }; + let fetched = active_history.fetch_message(&message_id_2, current_updates); + + if let HistorySearchResult::Present(SetUpdateOrDelete::Set(msg)) = fetched { + assert_ne!(msg, msg_2); + assert_eq!(msg.validity_end, Slot::new(5, 0)); + } else { + panic!( + "Expected a HistorySearchRestul::Set(...) and not: {:?}", + fetched + ) + } + } + + // Test fetch_message with message_id_3 (expect HistorySearchResult::Present) + { + let current_updates = AsyncMessageUpdate::default(); + let fetched = active_history.fetch_message(&message_id_3, current_updates); + + if let HistorySearchResult::Present(SetUpdateOrDelete::Set(msg)) = fetched { + // Check the updates were applied correctly + assert_eq!(msg.coins, msg_3_coins_new); + // function should == "send_max_fee_to_v2" (latest value) and not "send_max_fee_to" + assert_eq!(msg.function, msg_3_function_new_2); + } else { + panic!( + "Expected a HistorySearchResult::Set(...) and not: {:?}", + fetched + ); + } + } + + // Test fetch_message with message_id_3_2 (expect HistorySearchResult::Update) + // Expect updates to be empty (or default) here + { + let current_updates = AsyncMessageUpdate::default(); + let fetched = active_history.fetch_message(&message_id_3_2, current_updates); + if let HistorySearchResult::Present(SetUpdateOrDelete::Update(updates)) = fetched { + assert_eq!(updates, AsyncMessageUpdate::default()); + } else { + panic!( + "Expected a HistorySearchResult::Present(...) and not: {:?}", + fetched + ); + } + } + } +} diff --git a/massa-execution-worker/src/context.rs b/massa-execution-worker/src/context.rs index 319f7e17b74..7ead166159c 100644 --- a/massa-execution-worker/src/context.rs +++ b/massa-execution-worker/src/context.rs @@ -9,12 +9,15 @@ use crate::active_history::HistorySearchResult; use crate::speculative_async_pool::SpeculativeAsyncPool; +use crate::speculative_deferred_calls::SpeculativeDeferredCallRegistry; use crate::speculative_executed_denunciations::SpeculativeExecutedDenunciations; use crate::speculative_executed_ops::SpeculativeExecutedOps; use crate::speculative_ledger::SpeculativeLedger; use crate::{active_history::ActiveHistory, speculative_roll_state::SpeculativeRollState}; use massa_async_pool::{AsyncMessage, AsyncPoolChanges}; use massa_async_pool::{AsyncMessageId, AsyncMessageInfo}; +use massa_deferred_calls::registry_changes::DeferredCallRegistryChanges; +use massa_deferred_calls::{DeferredCall, DeferredSlotCalls}; use massa_executed_ops::{ExecutedDenunciationsChanges, ExecutedOpsChanges}; use massa_execution_exports::{ EventStore, ExecutedBlockInfo, ExecutionConfig, ExecutionError, ExecutionOutput, @@ -22,12 +25,14 @@ use massa_execution_exports::{ }; use massa_final_state::{FinalStateController, StateChanges}; use massa_hash::Hash; -use massa_ledger_exports::{LedgerChanges, SetOrKeep}; +use massa_ledger_exports::LedgerChanges; use massa_models::address::ExecutionAddressCycleInfo; use massa_models::block_id::BlockIdSerializer; use massa_models::bytecode::Bytecode; +use massa_models::deferred_calls::DeferredCallId; use massa_models::denunciation::DenunciationIndex; use massa_models::timeslots::get_block_slot_timestamp; +use massa_models::types::SetOrKeep; use massa_models::{ address::Address, amount::Amount, @@ -58,6 +63,9 @@ pub struct ExecutionContextSnapshot { /// speculative asynchronous pool messages emitted so far in the context pub async_pool_changes: AsyncPoolChanges, + /// speculative deferred calls changes + pub deferred_calls_changes: DeferredCallRegistryChanges, + /// the associated message infos for the speculative async pool pub message_infos: BTreeMap, @@ -91,6 +99,11 @@ pub struct ExecutionContextSnapshot { /// The gas remaining before the last subexecution. /// so *excluding* the gas used by the last sc call. pub gas_remaining_before_subexecution: Option, + + /// recursion counter, incremented for each new nested call + /// This is used to avoid stack overflow issues in the VM (that would crash the node instead of failing the call), + /// by limiting the depth of recursion contracts can have with the max_recursive_calls_depth value. + pub recursion_counter: u16, } /// An execution context that needs to be initialized before executing bytecode, @@ -121,6 +134,9 @@ pub struct ExecutionContext { /// as seen after everything that happened so far in the context speculative_async_pool: SpeculativeAsyncPool, + /// speculative deferred calls state, + speculative_deferred_calls: SpeculativeDeferredCallRegistry, + /// speculative roll state, /// as seen after everything that happened so far in the context speculative_roll_state: SpeculativeRollState, @@ -179,6 +195,9 @@ pub struct ExecutionContext { /// The gas remaining before the last subexecution. /// so *excluding* the gas used by the last sc call. pub gas_remaining_before_subexecution: Option, + + /// recursion counter, incremented for each new nested call + pub recursion_counter: u16, } impl ExecutionContext { @@ -215,6 +234,11 @@ impl ExecutionContext { final_state.clone(), active_history.clone(), ), + speculative_deferred_calls: SpeculativeDeferredCallRegistry::new( + final_state.clone(), + active_history.clone(), + config.deferred_calls_config, + ), speculative_roll_state: SpeculativeRollState::new( final_state.clone(), active_history.clone(), @@ -244,6 +268,7 @@ impl ExecutionContext { address_factory: AddressFactory { mip_store }, execution_trail_hash, gas_remaining_before_subexecution: None, + recursion_counter: 0, } } @@ -254,6 +279,7 @@ impl ExecutionContext { ExecutionContextSnapshot { ledger_changes: self.speculative_ledger.get_snapshot(), async_pool_changes, + deferred_calls_changes: self.speculative_deferred_calls.get_snapshot(), message_infos, pos_changes: self.speculative_roll_state.get_snapshot(), executed_ops: self.speculative_executed_ops.get_snapshot(), @@ -265,6 +291,7 @@ impl ExecutionContext { event_count: self.events.0.len(), unsafe_rng: self.unsafe_rng.clone(), gas_remaining_before_subexecution: self.gas_remaining_before_subexecution, + recursion_counter: self.recursion_counter, } } @@ -276,11 +303,24 @@ impl ExecutionContext { /// * `snapshot`: a saved snapshot to be restored /// * `error`: an execution error to emit as an event conserved after snapshot reset. pub fn reset_to_snapshot(&mut self, snapshot: ExecutionContextSnapshot, error: ExecutionError) { + // Emit the error event. + // Note that the context event counter is properly handled by event_emit (see doc). + let mut event = self.event_create( + serde_json::json!({ "massa_execution_error": format!("{}", error) }).to_string(), + true, + ); + if event.data.len() > self.config.max_event_size { + event.data.truncate(self.config.max_event_size); + } + self.event_emit(event); + // Reset context to snapshot. self.speculative_ledger .reset_to_snapshot(snapshot.ledger_changes); self.speculative_async_pool .reset_to_snapshot((snapshot.async_pool_changes, snapshot.message_infos)); + self.speculative_deferred_calls + .reset_to_snapshot(snapshot.deferred_calls_changes); self.speculative_roll_state .reset_to_snapshot(snapshot.pos_changes); self.speculative_executed_ops @@ -293,18 +333,12 @@ impl ExecutionContext { self.stack = snapshot.stack; self.unsafe_rng = snapshot.unsafe_rng; self.gas_remaining_before_subexecution = snapshot.gas_remaining_before_subexecution; + self.recursion_counter = snapshot.recursion_counter; // For events, set snapshot delta to error events. for event in self.events.0.range_mut(snapshot.event_count..) { event.context.is_error = true; } - - // Emit the error event. - // Note that the context event counter is properly handled by event_emit (see doc). - self.event_emit(self.event_create( - serde_json::json!({ "massa_execution_error": format!("{}", error) }).to_string(), - true, - )); } /// Create a new `ExecutionContext` for read-only execution @@ -365,12 +399,12 @@ impl ExecutionContext { &mut self, max_gas: u64, async_msg_cst_gas_cost: u64, - ) -> Vec<(Option, AsyncMessage)> { - self.speculative_async_pool - .take_batch_to_execute(self.slot, max_gas, async_msg_cst_gas_cost) - .into_iter() - .map(|(_id, msg)| (self.get_bytecode(&msg.destination), msg)) - .collect() + ) -> Vec<(AsyncMessageId, AsyncMessage)> { + self.speculative_async_pool.take_batch_to_execute( + self.slot, + max_gas, + async_msg_cst_gas_cost, + ) } /// Create a new `ExecutionContext` for executing an active slot. @@ -671,24 +705,12 @@ impl ExecutionContext { ) -> Result<(), ExecutionError> { if let Some(from_addr) = &from_addr { // check access rights - if check_rights { - // ensure we can't spend from an address on which we have no write access - if !self.has_write_rights_on(from_addr) { - return Err(ExecutionError::RuntimeError(format!( - "spending from address {} is not allowed in this context", - from_addr - ))); - } - - // ensure we can't transfer towards SC addresses on which we have no write access - if let Some(to_addr) = &to_addr { - if matches!(to_addr, Address::SC(..)) && !self.has_write_rights_on(to_addr) { - return Err(ExecutionError::RuntimeError(format!( - "crediting SC address {} is not allowed without write access to it", - to_addr - ))); - } - } + // ensure we can't spend from an address on which we have no write access + if check_rights && !self.has_write_rights_on(from_addr) { + return Err(ExecutionError::RuntimeError(format!( + "spending from address {} is not allowed in this context", + from_addr + ))); } } @@ -781,7 +803,7 @@ impl ExecutionContext { .try_slash_rolls(denounced_addr, roll_count); // convert slashed rolls to coins (as deferred credits => coins) - let mut slashed_coins = self + let slashed_coins_from_rolls = self .config .roll_price .checked_mul_u64(slashed_rolls.unwrap_or_default()) @@ -803,7 +825,9 @@ impl ExecutionContext { roll_count )) })? - .saturating_sub(slashed_coins); + .saturating_sub(slashed_coins_from_rolls); + + let mut total_slashed_coins = slashed_coins_from_rolls; if amount_remaining_to_slash > Amount::zero() { // There is still an amount to slash for this denunciation so we need to slash @@ -812,19 +836,21 @@ impl ExecutionContext { .speculative_roll_state .try_slash_deferred_credits(&self.slot, denounced_addr, &amount_remaining_to_slash); - slashed_coins = slashed_coins.saturating_add(slashed_coins_in_deferred_credits); + total_slashed_coins = + total_slashed_coins.saturating_add(slashed_coins_in_deferred_credits); + let amount_remaining_to_slash_2 = - slashed_coins.saturating_sub(slashed_coins_in_deferred_credits); + amount_remaining_to_slash.saturating_sub(slashed_coins_in_deferred_credits); if amount_remaining_to_slash_2 > Amount::zero() { // Use saturating_mul_u64 to avoid an error (for just a warn!(..)) warn!("Slashed {} coins (by selling rolls) and {} coins from deferred credits of address: {} but cumulative amount is lower than expected: {} coins", - slashed_coins, slashed_coins_in_deferred_credits, denounced_addr, + slashed_coins_from_rolls, slashed_coins_in_deferred_credits, denounced_addr, self.config.roll_price.saturating_mul_u64(roll_count) ); } } - Ok(slashed_coins) + Ok(total_slashed_coins) } /// Update production statistics of an address. @@ -894,13 +920,10 @@ impl ExecutionContext { // execute the deferred credits coming from roll sells let deferred_credits_transfers = self.execute_deferred_credits(&slot); - // take the ledger changes first as they are needed for async messages and cache - let ledger_changes = self.speculative_ledger.take(); - // settle emitted async messages and reimburse the senders of deleted messages let deleted_messages = self .speculative_async_pool - .settle_slot(&slot, &ledger_changes); + .settle_slot(&slot, &self.speculative_ledger.added_changes); let mut cancel_async_message_transfers = vec![]; for (_msg_id, msg) in deleted_messages { @@ -910,7 +933,7 @@ impl ExecutionContext { } // update module cache - let bc_updates = ledger_changes.get_bytecode_updates(); + let bc_updates = self.speculative_ledger.added_changes.get_bytecode_updates(); { let mut cache_write_lock = self.module_cache.write(); for bytecode in bc_updates { @@ -936,8 +959,9 @@ impl ExecutionContext { // generate the execution output let state_changes = StateChanges { - ledger_changes, + ledger_changes: self.speculative_ledger.take(), async_pool_changes: self.speculative_async_pool.take(), + deferred_call_changes: self.speculative_deferred_calls.take(), pos_changes: self.speculative_roll_state.take(), executed_ops_changes: self.speculative_executed_ops.take(), executed_denunciations_changes: self.speculative_executed_denunciations.take(), @@ -1021,7 +1045,7 @@ impl ExecutionContext { // Set the event index event.context.index_in_slot = self.created_event_index; - // Increment the event counter fot this slot + // Increment the event counter for this slot self.created_event_index += 1; // Add the event to the context store @@ -1117,6 +1141,127 @@ impl ExecutionContext { ))), } } + + pub fn deferred_calls_advance_slot(&mut self, current_slot: Slot) -> DeferredSlotCalls { + self.speculative_deferred_calls.advance_slot(current_slot) + } + + /// Get the price it would cost to reserve "gas" with params at target slot "slot". + pub fn deferred_calls_compute_call_fee( + &self, + target_slot: Slot, + max_gas_request: u64, + current_slot: Slot, + params_size: u64, + ) -> Result { + self.speculative_deferred_calls.compute_call_fee( + target_slot, + max_gas_request, + current_slot, + params_size, + ) + } + + pub fn deferred_call_register( + &mut self, + call: DeferredCall, + ) -> Result { + self.speculative_deferred_calls + .register_call(call, self.execution_trail_hash) + } + + /// Check if a deferred call exists + /// If it exists, check if it has been cancelled + /// If it has been cancelled, return false + pub fn deferred_call_exists(&self, call_id: &DeferredCallId) -> bool { + if let Some(call) = self.speculative_deferred_calls.get_call(call_id) { + return !call.cancelled; + } + false + } + + /// Get a deferred call by its id + pub fn get_deferred_call(&self, call_id: &DeferredCallId) -> Option { + self.speculative_deferred_calls.get_call(call_id) + } + + /// when a deferred call execution fails we need to refund the coins to the caller + pub fn deferred_call_fail_exec( + &mut self, + id: &DeferredCallId, + call: &DeferredCall, + ) -> Option<(Address, Result)> { + #[allow(unused_assignments, unused_mut)] + let mut result = None; + + let transfer_result = + self.transfer_coins(None, Some(call.sender_address), call.coins, false); + if let Err(e) = transfer_result.as_ref() { + debug!( + "deferred call cancel: reimbursement of {} failed: {}", + call.sender_address, e + ); + } + + let mut event = + self.event_create(format!("DeferredCall execution fail call_id:{}", id), true); + if event.data.len() > self.config.max_event_size { + event.data.truncate(self.config.max_event_size); + } + self.event_emit(event); + + #[cfg(feature = "execution-info")] + if let Err(e) = transfer_result { + result = Some((call.sender_address, Err(e.to_string()))) + } else { + result = Some((call.sender_address, Ok(call.coins))); + } + + result + } + + /// when a deferred call is cancelled we need to refund the coins to the caller + pub fn deferred_call_cancel( + &mut self, + call_id: &DeferredCallId, + caller_address: Address, + ) -> Result<(), ExecutionError> { + match self.speculative_deferred_calls.get_call(call_id) { + Some(call) => { + // check that the caller is the one who registered the deferred call + if call.sender_address != caller_address { + return Err(ExecutionError::DeferredCallsError(format!( + "only the caller {} can cancel the deferred call", + call.sender_address + ))); + } + + let (address, amount) = self.speculative_deferred_calls.cancel_call(call_id)?; + + // refund the coins to the caller + let transfer_result = self.transfer_coins(None, Some(address), amount, false); + if let Err(e) = transfer_result.as_ref() { + debug!( + "deferred call cancel: reimbursement of {} failed: {}", + address, e + ); + } + + Ok(()) + } + _ => Err(ExecutionError::DeferredCallsError(format!( + "deferred call {} does not exist", + call_id + )))?, + } + } + + /// find the deferred calls for a given slot + pub fn get_deferred_calls_by_slot(&self, slot: Slot) -> BTreeMap { + self.speculative_deferred_calls + .get_calls_by_slot(slot) + .slot_calls + } } /// Generate the execution trail hash diff --git a/massa-execution-worker/src/controller.rs b/massa-execution-worker/src/controller.rs index 02d017b5c08..1ce76c3a84e 100644 --- a/massa-execution-worker/src/controller.rs +++ b/massa-execution-worker/src/controller.rs @@ -98,7 +98,7 @@ impl ExecutionInputData { #[derive(Clone)] /// implementation of the execution controller pub struct ExecutionControllerImpl { - /// input data to process in the VM loop + /// input data to process in the Execution controller loop /// with a wake-up condition variable that needs to be triggered when the data changes pub(crate) input_data: Arc<(Condvar, Mutex)>, /// current execution state (see execution.rs for details) @@ -132,7 +132,7 @@ impl ExecutionController for ExecutionControllerImpl { input_data.new_blockclique = new_blockclique; } - // wake up VM loop + // wake up execution controller loop self.input_data.0.notify_one(); } @@ -331,6 +331,35 @@ impl ExecutionController for ExecutionControllerImpl { execution_lock.get_filtered_sc_output_event(filter), )) } + ExecutionQueryRequestItem::DeferredCallQuote { + target_slot, + max_gas_request, + params_size, + } => { + let result = execution_lock.deferred_call_quote( + target_slot, + max_gas_request, + params_size, + ); + Ok(ExecutionQueryResponseItem::DeferredCallQuote( + result.0, result.1, result.2, result.3, + )) + } + ExecutionQueryRequestItem::DeferredCallInfo(deferred_call_id) => execution_lock + .deferred_call_info(&deferred_call_id) + .ok_or_else(|| { + ExecutionQueryError::NotFound(format!( + "Deferred call id {}", + deferred_call_id + )) + }) + .map(|call| { + ExecutionQueryResponseItem::DeferredCallInfo(deferred_call_id, call) + }), + ExecutionQueryRequestItem::DeferredCallsBySlot(slot) => { + let res = execution_lock.get_deferred_calls_by_slot(slot); + Ok(ExecutionQueryResponseItem::DeferredCallsBySlot(slot, res)) + } }; resp.responses.push(resp_item); } @@ -525,7 +554,7 @@ impl ExecutionController for ExecutionControllerImpl { /// Execution manager /// Allows stopping the execution worker pub struct ExecutionManagerImpl { - /// input data to process in the VM loop + /// input data to process in the Execution manager loop /// with a wake-up condition variable that needs to be triggered when the data changes pub(crate) input_data: Arc<(Condvar, Mutex)>, /// handle used to join the worker thread @@ -535,7 +564,7 @@ pub struct ExecutionManagerImpl { impl ExecutionManager for ExecutionManagerImpl { /// stops the worker fn stop(&mut self) { - info!("stopping Execution controller..."); + info!("Stopping Execution manager..."); // notify the worker thread to stop { let mut input_wlock = self.input_data.1.lock(); @@ -544,8 +573,10 @@ impl ExecutionManager for ExecutionManagerImpl { } // join the execution thread if let Some(join_handle) = self.thread_handle.take() { - join_handle.join().expect("VM controller thread panicked"); + join_handle + .join() + .expect("Execution manager thread panicked"); } - info!("execution controller stopped"); + info!("Execution manager stopped"); } } diff --git a/massa-execution-worker/src/execution.rs b/massa-execution-worker/src/execution.rs index 350ea935cd7..a11b8ddda75 100644 --- a/massa-execution-worker/src/execution.rs +++ b/massa-execution-worker/src/execution.rs @@ -15,25 +15,27 @@ use crate::stats::ExecutionStatsCounter; #[cfg(feature = "dump-block")] use crate::storage_backend::StorageBackend; use massa_async_pool::AsyncMessage; +use massa_deferred_calls::DeferredCall; +use massa_event_cache::controller::EventCacheController; use massa_execution_exports::{ - EventStore, ExecutedBlockInfo, ExecutionBlockMetadata, ExecutionChannels, ExecutionConfig, - ExecutionError, ExecutionOutput, ExecutionQueryCycleInfos, ExecutionQueryStakerInfo, - ExecutionStackElement, ReadOnlyExecutionOutput, ReadOnlyExecutionRequest, - ReadOnlyExecutionTarget, SlotExecutionOutput, + ExecutedBlockInfo, ExecutionBlockMetadata, ExecutionChannels, ExecutionConfig, ExecutionError, + ExecutionOutput, ExecutionQueryCycleInfos, ExecutionQueryStakerInfo, ExecutionStackElement, + ReadOnlyExecutionOutput, ReadOnlyExecutionRequest, ReadOnlyExecutionTarget, + SlotExecutionOutput, }; use massa_final_state::FinalStateController; -use massa_ledger_exports::{SetOrDelete, SetUpdateOrDelete}; use massa_metrics::MassaMetrics; use massa_models::address::ExecutionAddressCycleInfo; use massa_models::bytecode::Bytecode; - use massa_models::datastore::get_prefix_bounds; +use massa_models::deferred_calls::DeferredCallId; use massa_models::denunciation::{Denunciation, DenunciationIndex}; use massa_models::execution::EventFilter; use massa_models::output_event::SCOutputEvent; use massa_models::prehash::PreHashSet; use massa_models::stats::ExecutionStats; use massa_models::timeslots::get_block_slot_timestamp; +use massa_models::types::{SetOrDelete, SetUpdateOrDelete}; use massa_models::{ address::Address, block_id::BlockId, @@ -51,7 +53,9 @@ use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; use tracing::{debug, info, trace, warn}; -use crate::execution_info::{AsyncMessageExecutionResult, DenunciationResult}; +use crate::execution_info::{ + AsyncMessageExecutionResult, DeferredCallExecutionResult, DenunciationResult, +}; #[cfg(feature = "execution-info")] use crate::execution_info::{ExecutionInfo, ExecutionInfoForSlot, OperationInfo}; #[cfg(feature = "execution-trace")] @@ -109,7 +113,7 @@ pub(crate) struct ExecutionState { // a cursor pointing to the highest executed final slot pub final_cursor: Slot, // store containing execution events that became final - final_events: EventStore, + final_events_cache: Box, // final state with atomic R/W access final_state: Arc>, // execution context (see documentation in context.rs) @@ -156,6 +160,7 @@ impl ExecutionState { channels: ExecutionChannels, wallet: Arc>, massa_metrics: MassaMetrics, + event_cache: Box, #[cfg(feature = "dump-block")] block_storage_backend: Arc>, ) -> ExecutionState { // Get the slot at the output of which the final state is attached. @@ -179,6 +184,7 @@ impl ExecutionState { hd_cache_size: config.hd_cache_size, snip_amount: config.snip_amount, max_module_length: config.max_bytecode_size, + condom_limits: config.condom_limits.clone(), }))); // Create an empty placeholder execution context, with shared atomic access @@ -205,7 +211,8 @@ impl ExecutionState { // empty execution output history: it is not recovered through bootstrap active_history, // empty final event store: it is not recovered through bootstrap - final_events: Default::default(), + // final_events: Default::default(), + final_events_cache: event_cache, // no active slots executed yet: set active_cursor to the last final block active_cursor: last_final_slot, final_cursor: last_final_slot, @@ -294,8 +301,7 @@ impl ExecutionState { // append generated events to the final event store exec_out.events.finalize(); - self.final_events.extend(exec_out.events); - self.final_events.prune(self.config.max_final_events); + self.final_events_cache.save_events(exec_out.events.0); // update the prometheus metrics self.massa_metrics @@ -458,11 +464,19 @@ impl ExecutionState { .saturating_sub(operation.get_max_spending(self.config.roll_price)), ); + // set the context origin operation ID + // Note: set operation ID early as if context.transfer_coins fails, event_create will use + // operation ID in the event message + context.origin_operation_id = Some(operation_id); + // debit the fee from the operation sender if let Err(err) = context.transfer_coins(Some(sender_addr), None, operation.content.fee, false) { - let error = format!("could not spend fees: {}", err); + let mut error = format!("could not spend fees: {}", err); + if error.len() > self.config.max_event_size { + error.truncate(self.config.max_event_size); + } let event = context.event_create(error.clone(), true); context.event_emit(event); return Err(ExecutionError::IncludeOperationError(error)); @@ -477,9 +491,6 @@ impl ExecutionState { // set the creator address context.creator_address = Some(operation.content_creator_address); - // set the context origin operation ID - context.origin_operation_id = Some(operation_id); - Ok(context_snapshot) } @@ -970,6 +981,7 @@ impl ExecutionState { module, *max_gas, self.config.gas_costs.clone(), + self.config.condom_limits.clone(), ) .map_err(|error| ExecutionError::VMError { context: "ExecuteSC".to_string(), @@ -1070,6 +1082,7 @@ impl ExecutionState { param, max_gas, self.config.gas_costs.clone(), + self.config.condom_limits.clone(), ); match response { Ok(Response { init_gas_cost, .. }) @@ -1123,7 +1136,7 @@ impl ExecutionState { context.stack = vec![ ExecutionStackElement { address: message.sender, - coins: message.coins, + coins: Default::default(), owned_addresses: vec![message.sender], operation_datastore: None, }, @@ -1175,10 +1188,19 @@ impl ExecutionState { // load and execute the compiled module // IMPORTANT: do not keep a lock here as `run_function` uses the `get_module` interface - let module = self + let Ok(module) = self .module_cache .write() - .load_module(&bytecode, message.max_gas)?; + .load_module(&bytecode, message.max_gas) + else { + let err = + ExecutionError::RuntimeError("could not load module for async execution".into()); + let mut context = context_guard!(self); + context.reset_to_snapshot(context_snapshot, err.clone()); + context.cancel_async_message(&message); + return Err(err); + }; + let response = massa_sc_runtime::run_function( &*self.execution_interface, module, @@ -1186,6 +1208,7 @@ impl ExecutionState { &message.function_params, message.max_gas, self.config.gas_costs.clone(), + self.config.condom_limits.clone(), ); match response { Ok(res) => { @@ -1221,6 +1244,146 @@ impl ExecutionState { } } + fn execute_deferred_call( + &self, + id: &DeferredCallId, + call: DeferredCall, + ) -> Result { + let mut result = DeferredCallExecutionResult::new(&call); + + let snapshot = { + let mut context = context_guard!(self); + + // refund the sender for the storage costs + let amount = DeferredCall::get_storage_cost( + self.config.storage_costs_constants.ledger_cost_per_byte, + call.parameters.len() as u64, + self.config.max_function_length, + ); + context + .transfer_coins(None, Some(call.sender_address), amount, false) + .expect("Error refunding storage costs"); + + context.get_snapshot() + }; + + if call.cancelled { + Ok(result) + } else { + let deferred_call_execution = || { + let bytecode = { + // acquire write access to the context + let mut context = context_guard!(self); + + // Set the call stack + // This needs to be defined before anything can fail, so that the emitted event contains the right stack + context.stack = vec![ + ExecutionStackElement { + address: call.sender_address, + coins: Default::default(), + owned_addresses: vec![call.sender_address], + operation_datastore: None, + }, + ExecutionStackElement { + address: call.target_address, + coins: call.coins, + owned_addresses: vec![call.target_address], + operation_datastore: None, + }, + ]; + + // Ensure that the target address is an SC address + // Ensure that the target address exists + context.check_target_sc_address(call.target_address)?; + + // credit coins to the target address + if let Err(err) = + context.transfer_coins(None, Some(call.target_address), call.coins, false) + { + // coin crediting failed: reset context to snapshot and reimburse sender + return Err(ExecutionError::DeferredCallsError(format!( + "could not credit coins to target of deferred call execution: {}", + err + ))); + } + + // quit if there is no function to be called + if call.target_function.is_empty() { + return Err(ExecutionError::DeferredCallsError( + "no function to call in the deferred call".to_string(), + )); + } + + // Load bytecode. Assume empty bytecode if not found. + context + .get_bytecode(&call.target_address) + .ok_or(ExecutionError::DeferredCallsError( + "no bytecode found".to_string(), + ))? + .0 + }; + + let module = self.module_cache.write().load_module( + &bytecode, + call.get_effective_gas(self.config.deferred_calls_config.call_cst_gas_cost), + )?; + let response = massa_sc_runtime::run_function( + &*self.execution_interface, + module, + &call.target_function, + &call.parameters, + call.get_effective_gas(self.config.deferred_calls_config.call_cst_gas_cost), + self.config.gas_costs.clone(), + self.config.condom_limits.clone(), + ); + + match response { + Ok(res) => { + self.module_cache + .write() + .set_init_cost(&bytecode, res.init_gas_cost); + #[cfg(feature = "execution-trace")] + { + result.traces = + Some((res.trace.into_iter().map(|t| t.into()).collect(), true)); + } + // #[cfg(feature = "execution-info")] + // { + // result.success = true; + // } + result.success = true; + Ok(result) + } + Err(error) => { + if let VMError::ExecutionError { init_gas_cost, .. } = error { + self.module_cache + .write() + .set_init_cost(&bytecode, init_gas_cost); + } + // execution failed: reset context to snapshot and reimburse sender + Err(ExecutionError::VMError { + context: "Deferred Call".to_string(), + error, + }) + } + } + }; + + // execute the deferred call + let execution_result = deferred_call_execution(); + + // if the execution failed, reset the context to the snapshot + if let Err(err) = &execution_result { + let mut context = context_guard!(self); + context.reset_to_snapshot(snapshot, err.clone()); + context.deferred_call_fail_exec(id, &call); + self.massa_metrics.inc_deferred_calls_failed(); + } else { + self.massa_metrics.inc_deferred_calls_executed(); + } + execution_result + } + } /// Executes a full slot (with or without a block inside) without causing any changes to the state, /// just yielding the execution output. /// @@ -1242,6 +1405,7 @@ impl ExecutionState { slot: *slot, operation_call_stacks: PreHashMap::default(), asc_call_stacks: vec![], + deferred_call_stacks: vec![], }; #[cfg(feature = "execution-trace")] let mut transfers = vec![]; @@ -1260,40 +1424,42 @@ impl ExecutionState { self.mip_store.clone(), ); - // Get asynchronous messages to execute - let messages = execution_context.take_async_batch( - self.config.max_async_gas, - self.config.async_msg_cst_gas_cost, - ); + // Deferred calls + let calls = execution_context.deferred_calls_advance_slot(*slot); // Apply the created execution context for slot execution *context_guard!(self) = execution_context; - // Try executing asynchronous messages. - // Effects are cancelled on failure and the sender is reimbursed. - for (opt_bytecode, message) in messages { - match self.execute_async_message(message, opt_bytecode) { - Ok(_message_return) => { + for (id, call) in calls.slot_calls { + let cancelled = call.cancelled; + match self.execute_deferred_call(&id, call) { + Ok(_exec) => { + if cancelled { + continue; + } + info!("executed deferred call: {:?}", id); cfg_if::cfg_if! { if #[cfg(feature = "execution-trace")] { // Safe to unwrap - slot_trace.asc_call_stacks.push(_message_return.traces.unwrap().0); + slot_trace.deferred_call_stacks.push(_exec.traces.unwrap().0); } else if #[cfg(feature = "execution-info")] { - slot_trace.asc_call_stacks.push(_message_return.traces.clone().unwrap().0); - exec_info.async_messages.push(Ok(_message_return)); + slot_trace.deferred_call_stacks.push(_exec.traces.clone().unwrap().0); + exec_info.deferred_calls_messages.push(Ok(_exec)); } } } Err(err) => { - let msg = format!("failed executing async message: {}", err); + let msg = format!("failed executing deferred call: {}", err); #[cfg(feature = "execution-info")] - exec_info.async_messages.push(Err(msg.clone())); - debug!(msg); + exec_info.deferred_calls_messages.push(Err(msg.clone())); + dbg!(msg); } } } let mut block_info: Option = None; + // Set block gas (max_gas_per_block - gas used by deferred calls) + let mut remaining_block_gas = self.config.max_gas_per_block; // Check if there is a block at this slot if let Some((block_id, block_metadata)) = exec_target { @@ -1345,10 +1511,9 @@ impl ExecutionState { .same_thread_parent_creator .expect("same thread parent creator missing"); - // Set remaining block gas - let mut remaining_block_gas = self.config.max_gas_per_block; - - // Set block credits + // Block credits count every operation fee, denunciation slash and endorsement reward. + // We initialize the block credits with the block reward to stimulate block production + // even in the absence of operations and denunciations. let mut block_credits = self.config.block_reward; // Try executing the operations of this block in the order in which they appear in the block. @@ -1474,13 +1639,29 @@ impl ExecutionState { // Update speculative rolls state production stats context.update_production_stats(&block_creator_addr, *slot, Some(*block_id)); - // Credit endorsement producers and endorsed block producers - let mut remaining_credit = block_credits; + // Divide the total block credits into parts + remainder + let block_credit_part_count = 3 * (1 + self.config.endorsement_count); let block_credit_part = block_credits - .checked_div_u64(3 * (1 + (self.config.endorsement_count))) + .checked_div_u64(block_credit_part_count) .expect("critical: block_credits checked_div factor is 0"); + let remainder = block_credits + .checked_rem_u64(block_credit_part_count) + .expect("critical: block_credits checked_rem factor is 0"); + + // Give 3 parts + remainder to the block producer to stimulate block production + // even in the absence of endorsements. + let mut block_producer_credit = block_credit_part + .saturating_mul_u64(3) + .saturating_add(remainder); + for endorsement_creator in endorsement_creators { - // credit creator of the endorsement with coins + // Credit the creator of the block with 1 part to stimulate endorsement inclusion of endorsements, + // and dissuade from emitting the block too early (before the endorsements have propageted). + block_producer_credit = block_producer_credit.saturating_add(block_credit_part); + + // Credit creator of the endorsement with 1 part to stimulate the production of endorsements. + // This also motivates endorsers to not publish their endorsements too early (will not endorse the right block), + // and to not publish too late (will not be included in the block). match context.transfer_coins( None, Some(endorsement_creator), @@ -1488,8 +1669,6 @@ impl ExecutionState { false, ) { Ok(_) => { - remaining_credit = remaining_credit.saturating_sub(block_credit_part); - #[cfg(feature = "execution-info")] exec_info .endorsement_creator_rewards @@ -1503,7 +1682,9 @@ impl ExecutionState { } } - // credit creator of the endorsed block with coins + // Credit the creator of the endorsed block with 1 part. + // This is done to incentivize block producers to be endorsed, + // typically by not publishing their blocks too late. match context.transfer_coins( None, Some(endorsement_target_creator), @@ -1511,7 +1692,6 @@ impl ExecutionState { false, ) { Ok(_) => { - remaining_credit = remaining_credit.saturating_sub(block_credit_part); #[cfg(feature = "execution-info")] { exec_info.endorsement_target_reward = @@ -1527,18 +1707,19 @@ impl ExecutionState { } } - // Credit block creator with remaining_credit + // Credit block producer if let Err(err) = - context.transfer_coins(None, Some(block_creator_addr), remaining_credit, false) + context.transfer_coins(None, Some(block_creator_addr), block_producer_credit, false) { debug!( "failed to credit {} coins to block creator {} on block execution: {}", - remaining_credit, block_creator_addr, err + block_producer_credit, block_creator_addr, err ) } else { #[cfg(feature = "execution-info")] { - exec_info.block_producer_reward = Some((block_creator_addr, remaining_credit)); + exec_info.block_producer_reward = + Some((block_creator_addr, block_producer_credit)); } } } else { @@ -1549,6 +1730,41 @@ impl ExecutionState { context_guard!(self).update_production_stats(&producer_addr, *slot, None); } + // Get asynchronous messages to execute + // The gas available for async messages is the remaining block gas + async remaining gas (max_async - gas used by deferred calls) + let async_msg_gas_available = self + .config + .max_async_gas + .saturating_sub(calls.effective_slot_gas) + .saturating_add(remaining_block_gas); + let messages = context_guard!(self) + .take_async_batch(async_msg_gas_available, self.config.async_msg_cst_gas_cost); + + // Try executing asynchronous messages. + // Effects are cancelled on failure and the sender is reimbursed. + for (_message_id, message) in messages { + let opt_bytecode = context_guard!(self).get_bytecode(&message.destination); + match self.execute_async_message(message, opt_bytecode) { + Ok(_message_return) => { + cfg_if::cfg_if! { + if #[cfg(feature = "execution-trace")] { + // Safe to unwrap + slot_trace.asc_call_stacks.push(_message_return.traces.unwrap().0); + } else if #[cfg(feature = "execution-info")] { + slot_trace.asc_call_stacks.push(_message_return.traces.clone().unwrap().0); + exec_info.async_messages.push(Ok(_message_return)); + } + } + } + Err(err) => { + let msg = format!("failed executing async message: {}", err); + #[cfg(feature = "execution-info")] + exec_info.async_messages.push(Err(msg.clone())); + debug!(msg); + } + } + } + #[cfg(feature = "execution-trace")] self.trace_history .write() @@ -1795,6 +2011,7 @@ impl ExecutionState { module, req.max_gas, self.config.gas_costs.clone(), + self.config.condom_limits.clone(), ) .map_err(|error| ExecutionError::VMError { context: "ReadOnlyExecutionTarget::BytecodeExecution".to_string(), @@ -1849,6 +2066,7 @@ impl ExecutionState { ¶meter, req.max_gas, self.config.gas_costs.clone(), + self.config.condom_limits.clone(), ); match response { @@ -2057,10 +2275,8 @@ impl ExecutionState { pub fn get_filtered_sc_output_event(&self, filter: EventFilter) -> Vec { match filter.is_final { Some(true) => self - .final_events - .get_filtered_sc_output_events(&filter) - .into_iter() - .collect(), + .final_events_cache + .get_filtered_sc_output_events(&filter), Some(false) => self .active_history .read() @@ -2069,7 +2285,7 @@ impl ExecutionState { .flat_map(|item| item.events.get_filtered_sc_output_events(&filter)) .collect(), None => self - .final_events + .final_events_cache .get_filtered_sc_output_events(&filter) .into_iter() .chain( @@ -2271,4 +2487,37 @@ impl ExecutionState { .map(|i| (i.current_version, i.announced_version)), ); } + + pub fn deferred_call_quote( + &self, + target_slot: Slot, + max_request_gas: u64, + params_size: u64, + ) -> (Slot, u64, bool, Amount) { + let gas_request = + max_request_gas.saturating_add(self.config.deferred_calls_config.call_cst_gas_cost); + let context = context_guard!(self); + + match context.deferred_calls_compute_call_fee( + target_slot, + gas_request, + context.slot, + params_size, + ) { + Ok(fee) => (target_slot, gas_request, true, fee), + Err(_) => (target_slot, gas_request, false, Amount::zero()), + } + } + + pub fn deferred_call_info(&self, call_id: &DeferredCallId) -> Option { + let context = context_guard!(self); + context.get_deferred_call(call_id) + } + + pub fn get_deferred_calls_by_slot(&self, slot: Slot) -> Vec { + context_guard!(self) + .get_deferred_calls_by_slot(slot) + .into_keys() + .collect() + } } diff --git a/massa-execution-worker/src/execution_info.rs b/massa-execution-worker/src/execution_info.rs index aaac0da631d..35994b25a68 100644 --- a/massa-execution-worker/src/execution_info.rs +++ b/massa-execution-worker/src/execution_info.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; +use massa_deferred_calls::DeferredCall; use schnellru::{ByLength, LruMap}; // use massa_execution_exports::Transfer; @@ -40,6 +41,7 @@ pub struct ExecutionInfoForSlot { pub(crate) denunciations: Vec>, pub(crate) operations: Vec, pub(crate) async_messages: Vec>, + pub(crate) deferred_calls_messages: Vec>, /// Deferred credits execution (empty if execution-info feature is NOT enabled) pub deferred_credits_execution: Vec<(Address, Result)>, /// Cancel async message execution (empty if execution-info feature is NOT enabled) @@ -57,6 +59,7 @@ impl ExecutionInfoForSlot { denunciations: Default::default(), operations: Default::default(), async_messages: Default::default(), + deferred_calls_messages: Default::default(), deferred_credits_execution: vec![], cancel_async_message_execution: vec![], auto_sell_execution: vec![], @@ -94,3 +97,25 @@ impl AsyncMessageExecutionResult { } } } + +pub struct DeferredCallExecutionResult { + pub(crate) success: bool, + pub(crate) sender: Address, + pub(crate) target_address: Address, + pub(crate) target_function: String, + pub(crate) coins: Amount, + pub(crate) traces: Option, +} + +impl DeferredCallExecutionResult { + pub fn new(call: &DeferredCall) -> Self { + Self { + success: false, + sender: call.sender_address, + target_address: call.target_address, + target_function: call.target_function.clone(), + coins: call.coins, + traces: None, + } + } +} diff --git a/massa-execution-worker/src/interface_impl.rs b/massa-execution-worker/src/interface_impl.rs index 4dd2f716310..bb93ef98c83 100644 --- a/massa-execution-worker/src/interface_impl.rs +++ b/massa-execution-worker/src/interface_impl.rs @@ -8,10 +8,12 @@ use crate::context::ExecutionContext; use anyhow::{anyhow, bail, Result}; use massa_async_pool::{AsyncMessage, AsyncMessageTrigger}; +use massa_deferred_calls::DeferredCall; use massa_execution_exports::ExecutionConfig; use massa_execution_exports::ExecutionStackElement; use massa_models::bytecode::Bytecode; use massa_models::datastore::get_prefix_bounds; +use massa_models::deferred_calls::DeferredCallId; use massa_models::{ address::{Address, SCAddress, UserAddress}, amount::Amount, @@ -50,6 +52,7 @@ use tracing::warn; test ))] use massa_models::datastore::Datastore; +use massa_models::execution::EventFilter; /// helper for locking the context mutex macro_rules! context_guard { @@ -87,12 +90,14 @@ impl InterfaceImpl { pub fn new_default( sender_addr: Address, operation_datastore: Option, + config: Option, ) -> InterfaceImpl { use massa_db_exports::{MassaDBConfig, MassaDBController}; use massa_db_worker::MassaDB; use massa_final_state::test_exports::get_sample_state; - use massa_ledger_exports::{LedgerEntry, SetUpdateOrDelete}; + use massa_ledger_exports::LedgerEntry; use massa_models::config::{MIP_STORE_STATS_BLOCK_CONSIDERED, THREAD_COUNT}; + use massa_models::types::SetUpdateOrDelete; use massa_module_cache::{config::ModuleCacheConfig, controller::ModuleCache}; use massa_pos_exports::SelectorConfig; use massa_pos_worker::start_selector_worker; @@ -100,7 +105,7 @@ impl InterfaceImpl { use parking_lot::RwLock; use tempfile::TempDir; - let config = ExecutionConfig::default(); + let config = config.unwrap_or_default(); let mip_stats_config = MipStatsConfig { block_count_considered: MIP_STORE_STATS_BLOCK_CONSIDERED, warn_announced_version_ratio: Ratio::new_raw(30, 100), @@ -130,6 +135,7 @@ impl InterfaceImpl { hd_cache_size: config.hd_cache_size, snip_amount: config.snip_amount, max_module_length: config.max_bytecode_size, + condom_limits: config.condom_limits.clone(), }))); // create an empty default store @@ -226,6 +232,29 @@ impl Interface for InterfaceImpl { Ok(()) } + fn increment_recursion_counter(&self) -> Result<()> { + let mut context = context_guard!(self); + + context.recursion_counter += 1; + + if context.recursion_counter > self.config.max_recursive_calls_depth { + bail!("recursion depth limit reached"); + } + + Ok(()) + } + + fn decrement_recursion_counter(&self) -> Result<()> { + let mut context = context_guard!(self); + + match context.recursion_counter.checked_sub(1) { + Some(value) => context.recursion_counter = value, + None => bail!("recursion counter underflow"), + } + + Ok(()) + } + /// Initialize the call when bytecode calls a function from another bytecode /// This function transfers the coins passed as parameter, /// prepares the current execution context by pushing a new element on the top of the call stack, @@ -882,10 +911,7 @@ impl Interface for InterfaceImpl { } // parse the public key - let public_key = libsecp256k1::PublicKey::parse_slice( - public_key_, - Some(libsecp256k1::PublicKeyFormat::Raw), - )?; + let public_key = libsecp256k1::PublicKey::parse_slice(public_key_, None)?; // build the message let prefix = format!("\x19Ethereum Signed Message:\n{}", message_.len()); @@ -898,10 +924,37 @@ impl Interface for InterfaceImpl { // r is the R.x value of the signature's R point (32 bytes) // s is the signature proof for R.x (32 bytes) // v is a recovery parameter used to ease the signature verification (1 byte) - // we ignore the recovery parameter here // see test_evm_verify for an example of its usage + let recovery_id: u8 = libsecp256k1::RecoveryId::parse_rpc(signature_[64])?.into(); + // Note: parse_rpc returns p - 27 and allow for 27, 28, 29, 30 + // restrict to only 27 & 28 (=> 0 & 1) + if recovery_id != 0 && recovery_id != 1 { + // Note: + // The v value in an EVM signature serves as a recovery ID, + // aiding in the recovery of the public key from the signature. + // Typically, v should be either 27 or 28 + // (or sometimes 0 or 1, depending on the implementation). + // Ensuring that v is within the expected range is crucial + // for correctly recovering the public key. + // the Ethereum yellow paper specifies only 27 and 28, requiring additional checks. + return Err(anyhow!( + "invalid recovery id value (v = {recovery_id}) in evm_signature_verify" + )); + } + let signature = libsecp256k1::Signature::parse_standard_slice(&signature_[..64])?; + // Note: + // The s value in an EVM signature should be in the lower half of the elliptic curve + // in order to prevent malleability attacks. + // If s is in the high-order range, it can be converted to its low-order equivalent, + // which should be enforced during signature verification. + if signature.s.is_high() { + return Err(anyhow!( + "High-Order s Value are prohibited in evm_get_pubkey_from_signature" + )); + } + // verify the signature Ok(libsecp256k1::verify(&message, &signature, &public_key)) } @@ -915,13 +968,10 @@ impl Interface for InterfaceImpl { /// Address is the last 20 bytes of the hash of the public key. fn evm_get_address_from_pubkey(&self, public_key_: &[u8]) -> Result> { // parse the public key - let public_key = libsecp256k1::PublicKey::parse_slice( - public_key_, - Some(libsecp256k1::PublicKeyFormat::Raw), - )?; + let public_key = libsecp256k1::PublicKey::parse_slice(public_key_, None)?; // compute the hash of the public key - let hash = sha3::Keccak256::digest(public_key.serialize()); + let hash = sha3::Keccak256::digest(&public_key.serialize()[1..]); // ignore the first 12 bytes of the hash let address = hash[12..].to_vec(); @@ -940,16 +990,33 @@ impl Interface for InterfaceImpl { } // parse the message - let message = libsecp256k1::Message::parse_slice(hash_).unwrap(); + let message = libsecp256k1::Message::parse_slice(hash_)?; // parse the signature as being (r, s, v) use only r and s - let signature = libsecp256k1::Signature::parse_standard_slice(&signature_[..64]).unwrap(); + let signature = libsecp256k1::Signature::parse_standard_slice(&signature_[..64])?; + + // Note: + // See evm_signature_verify explanation + if signature.s.is_high() { + return Err(anyhow!( + "High-Order s Value are prohibited in evm_get_pubkey_from_signature" + )); + } // parse v as a recovery id - let recovery_id = libsecp256k1::RecoveryId::parse_rpc(signature_[64]).unwrap(); + let recovery_id = libsecp256k1::RecoveryId::parse_rpc(signature_[64])?; + + let recovery_id_: u8 = recovery_id.into(); + if recovery_id_ != 0 && recovery_id_ != 1 { + // Note: + // See evm_signature_verify explanation + return Err(anyhow!( + "invalid recovery id value (v = {recovery_id_}) in evm_get_pubkey_from_signature" + )); + } // recover the public key - let recovered = libsecp256k1::recover(&message, &signature, &recovery_id).unwrap(); + let recovered = libsecp256k1::recover(&message, &signature, &recovery_id)?; // return its serialized value Ok(recovered.serialize().to_vec()) @@ -1084,7 +1151,25 @@ impl Interface for InterfaceImpl { }; let mut context = context_guard!(self); + let event = context.event_create(data, false); + let event_filter = EventFilter { + start: None, + end: None, + emitter_address: None, + original_caller_address: None, + original_operation_id: event.context.origin_operation_id, + is_final: None, + is_error: None, + }; + let event_per_op = context + .events + .get_filtered_sc_output_events_iter(&event_filter) + .count(); + if event_per_op >= self.config.max_event_per_operation { + bail!("Too many event for this operation"); + } + context.event_emit(event); Ok(()) } @@ -1101,6 +1186,24 @@ impl Interface for InterfaceImpl { let data_str = String::from_utf8(data.clone()).unwrap_or(format!("{:?}", data)); let mut context = context_guard!(self); let event = context.event_create(data_str, false); + + let event_filter = EventFilter { + start: None, + end: None, + emitter_address: None, + original_caller_address: None, + original_operation_id: event.context.origin_operation_id, + is_final: None, + is_error: None, + }; + let event_per_op = context + .events + .get_filtered_sc_output_events_iter(&event_filter) + .count(); + if event_per_op >= self.config.max_event_per_operation { + bail!("Too many event for this operation"); + } + context.event_emit(event); Ok(()) @@ -1183,6 +1286,11 @@ impl Interface for InterfaceImpl { if validity_end.1 >= self.config.thread_count { bail!("validity end thread exceeds the configuration thread count") } + + if max_gas < self.config.gas_costs.max_instance_cost { + bail!("max gas is lower than the minimum instance cost") + } + let target_addr = Address::from_str(target_address)?; // check that the target address is an SC address @@ -1200,6 +1308,15 @@ impl Interface for InterfaceImpl { let mut execution_context = context_guard!(self); let emission_slot = execution_context.slot; + + if Slot::new(validity_end.0, validity_end.1) < Slot::new(validity_start.0, validity_start.1) + { + bail!("validity end is earlier than the validity start") + } + if Slot::new(validity_end.0, validity_end.1) < emission_slot { + bail!("validity end is earlier than the current slot") + } + let emission_index = execution_context.created_message_index; let sender = execution_context.get_current_address()?; let coins = Amount::from_raw(raw_coins); @@ -1330,6 +1447,147 @@ impl Interface for InterfaceImpl { Ok(blake3::hash(bytes).into()) } + /// Get the number of fees needed to reserve space in the target slot + /// + /// # Arguments + /// * target_slot: tuple containing the period and thread of the target slot + /// * gas_limit: the gas limit for the call + /// + /// # Returns + /// A tuple containing a boolean indicating if the call is possible and the amount of fees needed + fn get_deferred_call_quote( + &self, + target_slot: (u64, u8), + gas_limit: u64, + params_size: u64, + ) -> Result<(bool, u64)> { + // write-lock context + + let context = context_guard!(self); + + let current_slot = context.slot; + + let target_slot = Slot::new(target_slot.0, target_slot.1); + + let gas_request = + gas_limit.saturating_add(self.config.deferred_calls_config.call_cst_gas_cost); + + match context.deferred_calls_compute_call_fee( + target_slot, + gas_request, + current_slot, + params_size, + ) { + Ok(fee) => Ok((true, fee.to_raw())), + Err(_) => Ok((false, 0)), + } + } + + /// Register deferred call + /// + /// # Arguments + /// * target_addr: string representation of the target address + /// * target_func: string representation of the target function + /// * target_slot: tuple containing the period and thread of the target slot + /// * max_gas: the gas limit for the call + /// * coins: the amount of coins to send + /// * params: byte array of the parameters + /// + /// # Returns + /// The id of the call + fn deferred_call_register( + &self, + target_addr: &str, + target_func: &str, + target_slot: (u64, u8), + max_gas: u64, + params: &[u8], + coins: u64, + ) -> Result { + // This function spends coins + deferred_call_quote(target_slot, max_gas).unwrap() from the caller, fails if the balance is insufficient or if the quote would return None. + + let target_addr = Address::from_str(target_addr)?; + + // check that the target address is an SC address + if !matches!(target_addr, Address::SC(..)) { + bail!("target address is not a smart contract address") + } + + // Length verifications + if target_func.len() > self.config.max_function_length as usize { + bail!("Function name is too large"); + } + if params.len() > self.config.max_parameter_length as usize { + bail!("Parameter size is too large"); + } + + // check fee, slot, gas + let (available, fee_raw) = + self.get_deferred_call_quote(target_slot, max_gas, params.len() as u64)?; + if !available { + bail!("The Deferred call cannot be registered. Ensure that the target slot is not before/at the current slot nor too far in the future, and that it has at least max_gas available gas."); + } + let fee = Amount::from_raw(fee_raw); + let coins = Amount::from_raw(coins); + + // write-lock context + let mut context = context_guard!(self); + + // get caller address + let sender_address = context.get_current_address()?; + + // make sender pay coins + fee + // coins + cost for booking the deferred call + context.transfer_coins(Some(sender_address), None, coins.saturating_add(fee), true)?; + + let call = DeferredCall::new( + sender_address, + Slot::new(target_slot.0, target_slot.1), + target_addr, + target_func.to_string(), + params.to_vec(), + coins, + max_gas, + fee, + false, + ); + + let call_id = context.deferred_call_register(call)?; + Ok(call_id.to_string()) + } + + /// Check if an deferred call exists + /// + /// # Arguments + /// * id: the id of the call + /// + /// # Returns + /// true if the call exists, false otherwise + fn deferred_call_exists(&self, id: &str) -> Result { + // write-lock context + let call_id = DeferredCallId::from_str(id)?; + let context = context_guard!(self); + Ok(context.deferred_call_exists(&call_id)) + } + + /// Cancel a deferred call + /// + /// # Arguments + /// * id: the id of the call + fn deferred_call_cancel(&self, id: &str) -> Result<()> { + // Reimburses coins to the sender but not the deferred call fee to avoid spam. Cancelled items are not removed from storage to avoid manipulation, just ignored when it is their turn to be executed. + + let mut context = context_guard!(self); + + // Can only be called by the creator of the deferred call. + let caller = context.get_current_address()?; + + let call_id = DeferredCallId::from_str(id)?; + + context.deferred_call_cancel(&call_id, caller)?; + Ok(()) + } + #[allow(unused_variables)] fn init_call_wasmv1(&self, address: &str, raw_coins: NativeAmount) -> Result> { // get target address @@ -1504,8 +1762,8 @@ impl Interface for InterfaceImpl { fn get_address_category_wasmv1(&self, to_check: &str) -> Result { let addr = Address::from_str(to_check)?; match addr { - Address::User(_) => Ok(AddressCategory::ScAddress), - Address::SC(_) => Ok(AddressCategory::UserAddress), + Address::User(_) => Ok(AddressCategory::UserAddress), + Address::SC(_) => Ok(AddressCategory::ScAddress), #[allow(unreachable_patterns)] _ => Ok(AddressCategory::Unspecified), } @@ -1687,6 +1945,11 @@ impl Interface for InterfaceImpl { } } } + + /// Interface version to sync with the runtime for its versioning + fn get_interface_version(&self) -> Result { + Ok(1) + } } #[cfg(test)] @@ -1699,7 +1962,7 @@ mod tests { #[test] fn test_get_keys() { let sender_addr = Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); - let interface = InterfaceImpl::new_default(sender_addr, None); + let interface = InterfaceImpl::new_default(sender_addr, None, None); interface .set_ds_value_wasmv1(b"k1", b"v1", Some(sender_addr.to_string())) @@ -1728,7 +1991,7 @@ mod tests { operation_datastore.insert(b"k2".to_vec(), b"v2".to_vec()); operation_datastore.insert(b"l3".to_vec(), b"v3".to_vec()); - let interface = InterfaceImpl::new_default(sender_addr, Some(operation_datastore)); + let interface = InterfaceImpl::new_default(sender_addr, Some(operation_datastore), None); let op_keys = interface.get_op_keys_wasmv1(b"k").unwrap(); @@ -1740,7 +2003,7 @@ mod tests { #[test] fn test_native_amount() { let sender_addr = Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); - let interface = InterfaceImpl::new_default(sender_addr, None); + let interface = InterfaceImpl::new_default(sender_addr, None, None); let amount1 = interface.native_amount_from_str_wasmv1("100").unwrap(); let amount2 = interface.native_amount_from_str_wasmv1("100").unwrap(); @@ -1818,7 +2081,7 @@ mod tests { #[test] fn test_base58_check_to_form() { let sender_addr = Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); - let interface = InterfaceImpl::new_default(sender_addr, None); + let interface = InterfaceImpl::new_default(sender_addr, None, None); let data = "helloworld"; let encoded = interface.bytes_to_base58_check_wasmv1(data.as_bytes()); @@ -1829,7 +2092,7 @@ mod tests { #[test] fn test_comparison_function() { let sender_addr = Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); - let interface = InterfaceImpl::new_default(sender_addr, None); + let interface = InterfaceImpl::new_default(sender_addr, None, None); // address let addr1 = diff --git a/massa-execution-worker/src/lib.rs b/massa-execution-worker/src/lib.rs index 0982892a94a..e8c2264b4fa 100644 --- a/massa-execution-worker/src/lib.rs +++ b/massa-execution-worker/src/lib.rs @@ -6,7 +6,7 @@ //! of operations that can contain executable bytecode and managing interactions with the ledger. //! When the worker is launched, a `ExecutionManager` and a `ExecutionController` are returned. //! `ExecutionManager` allows stopping the worker, -//! and `ExecutionController` is the clonable structure through which users interact with the worker. +//! and `ExecutionController` is the cloneable structure through which users interact with the worker. //! //! The worker is fed through the `ExecutionController` with information about blockclique changes and newly finalized blocks //! and will execute the operations in those blocks, as well as pending asynchronous operations on empty slots. @@ -87,6 +87,7 @@ mod interface_impl; mod request_queue; mod slot_sequencer; mod speculative_async_pool; +mod speculative_deferred_calls; mod speculative_executed_denunciations; mod speculative_executed_ops; mod speculative_ledger; diff --git a/massa-execution-worker/src/speculative_async_pool.rs b/massa-execution-worker/src/speculative_async_pool.rs index fd154c89df4..3c1cb5bd9a6 100644 --- a/massa-execution-worker/src/speculative_async_pool.rs +++ b/massa-execution-worker/src/speculative_async_pool.rs @@ -9,8 +9,9 @@ use massa_async_pool::{ AsyncPoolChanges, }; use massa_final_state::FinalStateController; -use massa_ledger_exports::{Applicable, LedgerChanges, SetUpdateOrDelete}; +use massa_ledger_exports::LedgerChanges; use massa_models::slot::Slot; +use massa_models::types::{Applicable, SetUpdateOrDelete}; use parking_lot::RwLock; use std::{ collections::{BTreeMap, HashMap}, @@ -97,7 +98,8 @@ impl SpeculativeAsyncPool { } /// Takes a batch of asynchronous messages to execute, - /// removing them from the speculative asynchronous pool and settling their deletion from it in the changes accumulator. + /// removing them from the speculative asynchronous pool and settling their deletion from it + /// in the changes accumulator. /// /// # Arguments /// * `slot`: slot at which the batch is taken (allows filtering by validity interval) @@ -122,9 +124,14 @@ impl SpeculativeAsyncPool { for (message_id, message_info) in message_infos.iter() { let corrected_max_gas = message_info.max_gas.saturating_add(async_msg_cst_gas_cost); + // Note: SecureShareOperation.get_validity_range(...) returns RangeInclusive + // so to be consistent here, use >= & <= checks if available_gas >= corrected_max_gas - && slot >= message_info.validity_start - && slot < message_info.validity_end + && Self::is_message_ready_to_execute( + &slot, + &message_info.validity_start, + &message_info.validity_end, + ) && message_info.can_be_executed { available_gas -= corrected_max_gas; @@ -158,26 +165,26 @@ impl SpeculativeAsyncPool { ) -> Vec<(AsyncMessageId, AsyncMessage)> { // Update the messages_info: remove messages that should be removed // Filter out all messages for which the validity end is expired. - // Note that the validity_end bound is NOT included in the validity interval of the message. + // Note: that the validity_end bound is included in the validity interval of the message. let mut eliminated_infos = Vec::new(); self.message_infos.retain(|id, info| { - if *slot < info.validity_end { - true - } else { + if Self::is_message_expired(slot, &info.validity_end) { eliminated_infos.push((*id, info.clone())); false + } else { + true } }); let mut eliminated_new_messages = Vec::new(); self.pool_changes.0.retain(|k, v| match v { SetUpdateOrDelete::Set(message) => { - if *slot < message.validity_end { - true - } else { + if Self::is_message_expired(slot, &message.validity_end) { eliminated_new_messages.push((*k, v.clone())); false + } else { + true } } SetUpdateOrDelete::Update(_v) => true, @@ -190,7 +197,7 @@ impl SpeculativeAsyncPool { SetUpdateOrDelete::Delete => None, })); - // Truncate message pool to its max size, removing non-prioritary items + // Truncate message pool to its max size, removing non-priority items let excess_count = self .message_infos .len() @@ -221,9 +228,14 @@ impl SpeculativeAsyncPool { } // Query eliminated messages - let eliminated_msg = + let mut eliminated_msg = self.fetch_msgs(eliminated_infos.iter().map(|(id, _)| id).collect(), true); + eliminated_msg.extend(eliminated_new_messages.iter().filter_map(|(k, v)| match v { + SetUpdateOrDelete::Set(v) => Some((*k, v.clone())), + SetUpdateOrDelete::Update(_v) => None, + SetUpdateOrDelete::Delete => None, + })); eliminated_msg } @@ -276,7 +288,7 @@ impl SpeculativeAsyncPool { } Present(SetUpdateOrDelete::Update(msg_update)) => { current_changes.entry(message_id).and_modify(|e| { - e.apply(msg_update.clone()); + *e = msg_update.clone(); }); return true; } @@ -305,9 +317,83 @@ impl SpeculativeAsyncPool { msgs } + + /// Return true if a message (given its validity end) is expired + /// Must be consistent with is_message_valid + fn is_message_expired(slot: &Slot, message_validity_end: &Slot) -> bool { + // Note: SecureShareOperation.get_validity_range(...) returns RangeInclusive + // (for operation validity) so apply the same rule for message validity + *slot > *message_validity_end + } + + /// Return true if a message (given its validity_start & validity end) is ready to execute + /// Must be consistent with is_message_expired + fn is_message_ready_to_execute( + slot: &Slot, + message_validity_start: &Slot, + message_validity_end: &Slot, + ) -> bool { + // Note: SecureShareOperation.get_validity_range(...) returns RangeInclusive + // (for operation validity) so apply the same rule for message validity + slot >= message_validity_start && slot <= message_validity_end + } } /// Check in the ledger changes if a message trigger has been triggered fn is_triggered(filter: &AsyncMessageTrigger, ledger_changes: &LedgerChanges) -> bool { ledger_changes.has_writes(&filter.address, filter.datastore_key.clone()) } + +#[cfg(test)] +mod tests { + use super::*; + + // Test if is_message_expired & is_message_ready_to_execute are consistent + #[test] + fn test_validity() { + let slot1 = Slot::new(6, 0); + let slot2 = Slot::new(9, 0); + let slot_validity_start = Slot::new(4, 0); + let slot_validity_end = Slot::new(8, 0); + + assert!(!SpeculativeAsyncPool::is_message_expired( + &slot1, + &slot_validity_end + )); + assert!(SpeculativeAsyncPool::is_message_ready_to_execute( + &slot1, + &slot_validity_start, + &slot_validity_end + )); + + assert!(!SpeculativeAsyncPool::is_message_expired( + &slot_validity_start, + &slot_validity_end + )); + assert!(SpeculativeAsyncPool::is_message_ready_to_execute( + &slot_validity_start, + &slot_validity_start, + &slot_validity_end + )); + + assert!(!SpeculativeAsyncPool::is_message_expired( + &slot_validity_end, + &slot_validity_end + )); + assert!(SpeculativeAsyncPool::is_message_ready_to_execute( + &slot_validity_end, + &slot_validity_start, + &slot_validity_end + )); + + assert!(SpeculativeAsyncPool::is_message_expired( + &slot2, + &slot_validity_end + )); + assert!(!SpeculativeAsyncPool::is_message_ready_to_execute( + &slot2, + &slot_validity_start, + &slot_validity_end + )); + } +} diff --git a/massa-execution-worker/src/speculative_deferred_calls.rs b/massa-execution-worker/src/speculative_deferred_calls.rs new file mode 100644 index 00000000000..7bcb3b1bfcb --- /dev/null +++ b/massa-execution-worker/src/speculative_deferred_calls.rs @@ -0,0 +1,727 @@ +//! Speculative async call registry. + +use crate::active_history::ActiveHistory; +use massa_deferred_calls::{ + config::DeferredCallsConfig, registry_changes::DeferredCallRegistryChanges, DeferredCall, + DeferredSlotCalls, +}; +use massa_execution_exports::ExecutionError; +use massa_final_state::FinalStateController; +use massa_models::{ + address::Address, amount::Amount, config::MAX_ASYNC_GAS, deferred_calls::DeferredCallId, + slot::Slot, +}; +use parking_lot::RwLock; +use std::{ + cmp::{max, min}, + sync::Arc, +}; + +const TARGET_BOOKING: u128 = (MAX_ASYNC_GAS / 2) as u128; + +pub(crate) struct SpeculativeDeferredCallRegistry { + final_state: Arc>, + active_history: Arc>, + // current speculative registry changes + deferred_calls_changes: DeferredCallRegistryChanges, + config: DeferredCallsConfig, +} + +impl SpeculativeDeferredCallRegistry { + /// Creates a new `SpeculativeDeferredCallRegistry` + /// + /// # Arguments + pub fn new( + final_state: Arc>, + active_history: Arc>, + config: DeferredCallsConfig, + ) -> Self { + SpeculativeDeferredCallRegistry { + final_state, + active_history, + deferred_calls_changes: Default::default(), + config, + } + } + + /// Takes a snapshot (clone) of the message states + pub fn get_snapshot(&self) -> DeferredCallRegistryChanges { + self.deferred_calls_changes.clone() + } + + /// Resets the `SpeculativeDeferredCallRegistry` to a snapshot (see `get_snapshot` method) + pub fn reset_to_snapshot(&mut self, snapshot: DeferredCallRegistryChanges) { + self.deferred_calls_changes = snapshot; + } + + /// Add a new call to the list of changes of this `SpeculativeDeferredCallRegistry` + pub fn push_new_call(&mut self, id: DeferredCallId, call: DeferredCall) { + self.deferred_calls_changes.set_call(id, call); + } + + pub fn get_total_calls_registered(&self) -> u64 { + if let Some(v) = self.deferred_calls_changes.get_total_calls_registered() { + return v; + } + + { + let history = self.active_history.read(); + for history_item in history.0.iter().rev() { + if let Some(v) = history_item + .state_changes + .deferred_call_changes + .get_total_calls_registered() + { + return v; + } + } + } + + return self + .final_state + .read() + .get_deferred_call_registry() + .get_nb_call_registered(); + } + + pub fn get_effective_total_gas(&self) -> u128 { + // get total gas from current changes + if let Some(v) = self.deferred_calls_changes.get_effective_total_gas() { + return v; + } + + // check in history backwards + { + let history = self.active_history.read(); + for history_item in history.0.iter().rev() { + if let Some(v) = history_item + .state_changes + .deferred_call_changes + .get_effective_total_gas() + { + return v; + } + } + } + + // check in final state + return self + .final_state + .read() + .get_deferred_call_registry() + .get_total_gas(); + } + + pub fn get_effective_slot_gas(&self, slot: &Slot) -> u64 { + // get slot gas from current changes + if let Some(v) = self.deferred_calls_changes.get_effective_slot_gas(slot) { + return v; + } + + // check in history backwards + { + let history = self.active_history.read(); + for history_item in history.0.iter().rev() { + if let Some(v) = history_item + .state_changes + .deferred_call_changes + .get_effective_slot_gas(slot) + { + return v; + } + } + } + + // check in final state + return self + .final_state + .read() + .get_deferred_call_registry() + .get_slot_gas(slot); + } + + pub fn get_slot_base_fee(&self, slot: &Slot) -> Amount { + // get slot base fee from current changes + if let Some(v) = self.deferred_calls_changes.get_slot_base_fee(slot) { + return v; + } + + // check in history backwards + { + let history = self.active_history.read(); + for history_item in history.0.iter().rev() { + if let Some(v) = history_item + .state_changes + .deferred_call_changes + .get_slot_base_fee(slot) + { + return v; + } + } + } + + // check in final state + return self + .final_state + .read() + .get_deferred_call_registry() + .get_slot_base_fee(slot); + } + + /// Consumes and deletes the current slot, prepares a new slot in the future + /// and returns the calls that need to be executed in the current slot + pub fn advance_slot(&mut self, current_slot: Slot) -> DeferredSlotCalls { + // get the state of the current slot + let mut slot_calls = self.get_calls_by_slot(current_slot); + let total_booked_gas_before = self.get_effective_total_gas(); + + // get the previous average booking rate per slot + let avg_booked_gas = + total_booked_gas_before.saturating_div(self.config.max_future_slots as u128); + // select the slot that is newly made available and set its base fee + let new_slot = current_slot + .skip(self.config.max_future_slots, self.config.thread_count) + .expect("could not skip enough slots"); + + let prev_slot = new_slot + .get_prev_slot(self.config.thread_count) + .expect("cannot get prev slot"); + + let prev_slot_base_fee = { + let temp_slot_fee = self.get_slot_base_fee(&prev_slot); + if temp_slot_fee.eq(&Amount::zero()) { + Amount::from_raw(self.config.min_gas_cost) + } else { + temp_slot_fee + } + }; + + let new_slot_base_fee = match avg_booked_gas.cmp(&TARGET_BOOKING) { + // the previous booking rate was exactly the expected one: do not adjust the base fee + std::cmp::Ordering::Equal => prev_slot_base_fee, + // more gas was booked than expected: increase the base fee + std::cmp::Ordering::Greater => { + let gas_used_delta = avg_booked_gas.saturating_sub(TARGET_BOOKING) as u64; + + let raw_v = prev_slot_base_fee.to_raw().saturating_add(max( + gas_used_delta + .saturating_div(self.config.base_fee_max_max_change_denominator as u64), + self.config.min_gas_increment, + )); + + Amount::from_raw(min(1_000_000_000, raw_v)) + } + // less gas was booked than expected: decrease the base fee + std::cmp::Ordering::Less => { + let gas_used_delta = TARGET_BOOKING.saturating_sub(avg_booked_gas) as u64; + + let raw_v = max( + prev_slot_base_fee + .to_raw() + .saturating_sub(gas_used_delta.saturating_div( + self.config.base_fee_max_max_change_denominator as u64, + )), + self.config.min_gas_cost, + ); + + Amount::from_raw(min(1_000_000_000, raw_v)) + } + }; + + self.deferred_calls_changes + .set_slot_base_fee(new_slot, new_slot_base_fee); + + // subtract the current slot gas from the total gas + // cancelled call gas is already decremented from the effective slot gas + let total_gas_after = + total_booked_gas_before.saturating_sub(slot_calls.effective_slot_gas.into()); + if !total_gas_after.eq(&total_booked_gas_before) { + self.deferred_calls_changes + .set_effective_total_gas(total_gas_after); + } + + slot_calls.effective_total_gas = total_gas_after; + + massa_metrics::set_deferred_calls_total_gas(slot_calls.effective_total_gas); + + // delete call in the current slot + let mut nb_call_to_execute = 0; + for (id, call) in &slot_calls.slot_calls { + // cancelled call is already decremented from the total calls registered + if !call.cancelled { + nb_call_to_execute += 1; + } + self.delete_call(id, current_slot); + } + + if nb_call_to_execute > 0 { + let total_calls_registered = self.get_total_calls_registered(); + let new_call_registered = total_calls_registered.saturating_sub(nb_call_to_execute); + self.set_total_calls_registered(new_call_registered); + } + + slot_calls + } + + pub fn get_calls_by_slot(&self, slot: Slot) -> DeferredSlotCalls { + let mut slot_calls: DeferredSlotCalls = self + .final_state + .read() + .get_deferred_call_registry() + .get_slot_calls(slot); + for hist_item in self.active_history.read().0.iter() { + slot_calls.apply_changes(&hist_item.state_changes.deferred_call_changes); + } + slot_calls.apply_changes(&self.deferred_calls_changes); + slot_calls + } + + pub fn get_call(&self, id: &DeferredCallId) -> Option { + let slot = match id.get_slot() { + Ok(slot) => slot, + Err(_) => return None, + }; + + // check from latest to earliest changes + + // check in current changes + if let Some(v) = self.deferred_calls_changes.get_call(&slot, id) { + return Some(v.clone()); + } + + // check history from the most recent to the oldest item + { + let history = self.active_history.read(); + for history_item in history.0.iter().rev() { + if let Some(v) = history_item + .state_changes + .deferred_call_changes + .get_call(&slot, id) + { + return Some(v.clone()); + } + } + } + + // check final state + { + let final_state = self.final_state.read(); + // if let Some(v) = final_state.get_deferred_call_registry().get_call(&slot, id) { + if let Some(v) = final_state.get_deferred_call_registry().get_call(&slot, id) { + return Some(v.clone()); + } + } + + None + } + + pub fn delete_call(&mut self, id: &DeferredCallId, slot: Slot) { + self.deferred_calls_changes.delete_call(slot, id) + } + + /// Cancel a call + /// Returns the sender address and the amount of coins to reimburse them + pub fn cancel_call( + &mut self, + id: &DeferredCallId, + ) -> Result<(Address, Amount), ExecutionError> { + // get call, fail if it does not exist + let Some(mut call) = self.get_call(id) else { + return Err(ExecutionError::DeferredCallsError( + "Call ID does not exist.".into(), + )); + }; + + // check if the call is already cancelled + if call.cancelled { + return Err(ExecutionError::DeferredCallsError( + "Call ID is already cancelled.".into(), + )); + } + + // set call as cancelled + call.cancelled = true; + + // we need to reimburse coins to the sender + let res: (Address, Amount) = (call.sender_address, call.coins); + + // Add a cancellation to the current changes + self.deferred_calls_changes + .set_call(id.clone(), call.clone()); + + let current_gas = self.get_effective_slot_gas(&call.target_slot); + + // set slot gas + // slot_gas = current_gas - (call_gas + call_cst_gas_cost (vm allocation cost)) + self.deferred_calls_changes.set_effective_slot_gas( + call.target_slot, + current_gas.saturating_sub(call.get_effective_gas(self.config.call_cst_gas_cost)), + ); + + let effective_gas_call = call.get_effective_gas(self.config.call_cst_gas_cost) as u128; + // set total gas + self.deferred_calls_changes.set_effective_total_gas( + self.get_effective_total_gas() + .saturating_sub(effective_gas_call), + ); + + let new_total_calls_registered = self.get_total_calls_registered().saturating_sub(1); + self.set_total_calls_registered(new_total_calls_registered); + + Ok(res) + } + + // This function assumes that we have a resource with a total supply `resource_supply`. + // Below a certain target occupancy `target_occupancy` of that resource, the overbooking penalty for using a unit of the resource is zero. + // Above the target, the resource unit cost grows linearly with occupancy. + // The linear fee growth is chosen so that if someone occupies all the resource, they incur a `max_penalty` cost. + fn overbooking_fee( + resource_supply: u128, + target_occupancy: u128, + current_occupancy: u128, + resource_request: u128, + max_penalty: Amount, + ) -> Result { + // linear part of the occupancy before booking the requested amount + let relu_occupancy_before = current_occupancy.saturating_sub(target_occupancy); + + // linear part of the occupancy after booking the requested amount + let relu_occupancy_after = std::cmp::max( + current_occupancy.saturating_add(resource_request), + target_occupancy, + ) + .saturating_sub(target_occupancy); + + // denominator for the linear fee + let denominator = resource_supply.checked_sub(target_occupancy).ok_or( + ExecutionError::DeferredCallsError("Error with denominator on overbooking fee".into()), + )?; + + if denominator.eq(&0) { + // TODO : check if this is correct + return Err(ExecutionError::DeferredCallsError( + "Denominator is zero on overbooking fee".into(), + )); + } + + // compute using the raw fee and u128 to avoid u64 overflows + let raw_max_penalty = max_penalty.to_raw() as u128; + + let raw_fee = (raw_max_penalty.saturating_mul( + (relu_occupancy_after.saturating_mul(relu_occupancy_after)) + .saturating_sub(relu_occupancy_before.saturating_mul(relu_occupancy_before)), + )) + .saturating_div(denominator.saturating_mul(denominator)); + + Ok(Amount::from_raw(min(raw_fee, u64::MAX as u128) as u64)) + } + + /// Compute call fee + pub fn compute_call_fee( + &self, + target_slot: Slot, + max_gas_request: u64, + current_slot: Slot, + params_size: u64, + ) -> Result { + // Check that the slot is not in the past + if target_slot <= current_slot { + return Err(ExecutionError::DeferredCallsError( + "Target slot is in the past.".into(), + )); + } + + // Check that the slot is not in the future + if target_slot + .slots_since(¤t_slot, self.config.thread_count) + .unwrap_or(u64::MAX) + > self.config.max_future_slots + { + // note: the current slot is not counted + return Err(ExecutionError::DeferredCallsError( + "Target slot is too far in the future.".into(), + )); + } + + // Check that the gas is not too high for the target slot + let slot_occupancy = self.get_effective_slot_gas(&target_slot); + if slot_occupancy.saturating_add(max_gas_request) > self.config.max_gas { + return Err(ExecutionError::DeferredCallsError( + "Not enough gas available in the target slot.".into(), + )); + } + + if params_size > self.config.max_parameter_size.into() { + return Err(ExecutionError::DeferredCallsError( + "Parameters size is too big.".into(), + )); + } + + // We perform Dynamic Pricing of slot gas booking using a Proportional-Integral controller (https://en.wikipedia.org/wiki/Proportional–integral–derivative_controller). + // It regulates the average slot async gas usage towards `target_async_gas` by adjusting fees. + + // Constant part of the fee: directly depends on the base async gas cost for the target slot. + // This is the "Integral" part of the Proportional-Integral controller. + // When a new slot `S` is made available for booking, the `S.base_async_gas_cost` is increased or decreased compared to `(S-1).base_async_gas_cost` depending on the average gas usage over the `deferred_call_max_future_slots` slots before `S`. + + // Integral fee + let integral_fee = self + .get_slot_base_fee(&target_slot) + .saturating_mul_u64(max_gas_request); + + // The integral fee is not enough to respond to quick demand surges within the long booking period `deferred_call_max_future_slots`. Proportional regulation is also necessary. + + // A fee that linearly depends on the total load over `deferred_call_max_future_slots` slots but only when the average load is above `target_async_gas` to not penalize normal use. Booking all the gas from all slots within the booking period requires using the whole initial coin supply. + + // Global overbooking fee + let global_occupancy = self.get_effective_total_gas(); + let global_overbooking_fee = Self::overbooking_fee( + (self.config.max_gas as u128).saturating_mul(self.config.max_future_slots as u128), // total available async gas during the booking period + (self.config.max_future_slots as u128).saturating_mul(TARGET_BOOKING), // target a 50% async gas usage over the booking period + global_occupancy, // total amount of async gas currently booked in the booking period + max_gas_request as u128, // amount of gas to book + self.config.global_overbooking_penalty, // fully booking all slots of the booking period requires spending the whole initial supply of coins + )?; + + // Finally, a per-slot proportional fee is also added to prevent attackers from denying significant ranges of consecutive slots within the long booking period. + // Slot overbooking fee + let slot_overbooking_fee = Self::overbooking_fee( + self.config.max_gas as u128, // total available async gas during the target slot + TARGET_BOOKING, // target a 50% async gas usage during the target slot + slot_occupancy as u128, // total amount of async gas currently booked in the target slot + max_gas_request as u128, // amount of gas to book in the target slot + self.config.slot_overbooking_penalty, // total_initial_coin_supply/10000 + )?; + + // Storage cost for the parameters + let storage_cost = DeferredCall::get_storage_cost( + self.config.ledger_cost_per_byte, + params_size, + self.config.max_function_name_length, + ); + + // return the fee + Ok(integral_fee + .saturating_add(global_overbooking_fee) + .saturating_add(slot_overbooking_fee) + .saturating_add(storage_cost)) + } + + /// Register a new call + /// Returns the call id + /// # Arguments + /// * `call` - The call to register + /// * `trail_hash` - The hash of the execution trail hash + pub fn register_call( + &mut self, + call: DeferredCall, + trail_hash: massa_hash::Hash, + ) -> Result { + let mut index = 0; + + if let Some(val) = self + .deferred_calls_changes + .slots_change + .get(&call.target_slot) + { + index += val.calls_len(); + } + + { + // final state + let slots_call = self + .final_state + .read() + .get_deferred_call_registry() + .get_slot_calls(call.target_slot); + index += slots_call.slot_calls.len(); + } + + let id = DeferredCallId::new(0, call.target_slot, index as u64, trail_hash.to_bytes())?; + + self.push_new_call(id.clone(), call.clone()); + + let current_gas = self.get_effective_slot_gas(&call.target_slot); + + // set slot gas for the target slot + // effective_slot_gas = current_gas + (call_gas + call_cst_gas_cost (vm allocation cost)) + self.deferred_calls_changes.set_effective_slot_gas( + call.target_slot, + current_gas.saturating_add(call.get_effective_gas(self.config.call_cst_gas_cost)), + ); + + // set total effective gas + let effective_total_gas = self.get_effective_total_gas(); + let call_effective_gas = call.get_effective_gas(self.config.call_cst_gas_cost) as u128; + self.deferred_calls_changes + .set_effective_total_gas(effective_total_gas.saturating_add(call_effective_gas)); + + // increment total calls registered + let new_total_calls_registered = self.get_total_calls_registered().saturating_add(1); + self.set_total_calls_registered(new_total_calls_registered); + + Ok(id) + } + + /// Take the deferred registry slot changes + pub(crate) fn take(&mut self) -> DeferredCallRegistryChanges { + std::mem::take(&mut self.deferred_calls_changes) + } + + fn set_total_calls_registered(&mut self, nb_calls: u64) { + massa_metrics::set_deferred_calls_registered(nb_calls as usize); + self.deferred_calls_changes + .set_total_calls_registered(nb_calls); + } +} + +#[cfg(test)] +mod tests { + use std::{str::FromStr, sync::Arc}; + + use massa_db_exports::{MassaDBConfig, MassaDBController}; + use massa_db_worker::MassaDB; + use massa_deferred_calls::{config::DeferredCallsConfig, DeferredCallRegistry}; + use massa_final_state::MockFinalStateController; + use massa_models::{amount::Amount, config::THREAD_COUNT, slot::Slot}; + use parking_lot::RwLock; + use tempfile::TempDir; + + use super::SpeculativeDeferredCallRegistry; + + #[test] + fn test_compute_call_fee() { + let disk_ledger = TempDir::new().expect("cannot create temp directory"); + let db_config = MassaDBConfig { + path: disk_ledger.path().to_path_buf(), + max_history_length: 10, + max_final_state_elements_size: 100_000, + max_versioning_elements_size: 100_000, + thread_count: THREAD_COUNT, + max_ledger_backups: 10, + }; + + let db = Arc::new(RwLock::new( + Box::new(MassaDB::new(db_config)) as Box<(dyn MassaDBController + 'static)> + )); + let mock_final_state = Arc::new(RwLock::new(MockFinalStateController::new())); + + let deferred_call_registry = + DeferredCallRegistry::new(db.clone(), DeferredCallsConfig::default()); + + mock_final_state + .write() + .expect_get_deferred_call_registry() + .return_const(deferred_call_registry); + let config = DeferredCallsConfig::default(); + + let mut speculative = SpeculativeDeferredCallRegistry::new( + mock_final_state, + Arc::new(Default::default()), + config, + ); + + let max_period = config.max_future_slots / THREAD_COUNT as u64; + + let slot_too_far = Slot { + period: max_period + 2, + thread: 1, + }; + + let good_slot = Slot { + period: 10, + thread: 1, + }; + + // slot to far in the future + assert!(speculative + .compute_call_fee( + slot_too_far, + 1_000_000, + Slot { + period: 1, + thread: 1, + }, + 1_000 + ) + .is_err()); + + // slot is in the past + assert!(speculative + .compute_call_fee( + Slot { + period: 2, + thread: 1, + }, + 1_000_000, + Slot { + period: 5, + thread: 1, + }, + 1000 + ) + .is_err()); + + // gas too high + speculative + .deferred_calls_changes + .set_effective_slot_gas(good_slot, 999_000_000); + + assert!(speculative + .compute_call_fee( + good_slot, + 1_100_000, + Slot { + period: 1, + thread: 1, + }, + 1000 + ) + .is_err()); + + // params too big + assert!(speculative + .compute_call_fee( + good_slot, + 1_000_000, + Slot { + period: 1, + thread: 1, + }, + 50_000_000 + ) + .is_err()); + + // no params + assert_eq!( + speculative + .compute_call_fee( + good_slot, + 200_000, + Slot { + period: 1, + thread: 1, + }, + 0, + ) + .unwrap(), + Amount::from_str("0.036600079").unwrap() + ); + + // 10Ko params size + assert_eq!( + speculative + .compute_call_fee( + good_slot, + 200_000, + Slot { + period: 1, + thread: 1, + }, + 10_000, + ) + .unwrap(), + Amount::from_str("1.036600079").unwrap() + ); + } +} diff --git a/massa-execution-worker/src/speculative_executed_ops.rs b/massa-execution-worker/src/speculative_executed_ops.rs index f3a44386e69..d3d0d81c0e1 100644 --- a/massa-execution-worker/src/speculative_executed_ops.rs +++ b/massa-execution-worker/src/speculative_executed_ops.rs @@ -57,7 +57,7 @@ impl SpeculativeExecutedOps { /// Checks if an operation was executed previously pub fn is_op_executed(&self, op_id: &OperationId) -> bool { - // check in the curent changes + // check in the current changes if self.executed_ops.contains_key(op_id) { return true; } diff --git a/massa-execution-worker/src/speculative_ledger.rs b/massa-execution-worker/src/speculative_ledger.rs index 16bc6a98053..15f22c2a344 100644 --- a/massa-execution-worker/src/speculative_ledger.rs +++ b/massa-execution-worker/src/speculative_ledger.rs @@ -9,9 +9,10 @@ use crate::active_history::{ActiveHistory, HistorySearchResult}; use massa_execution_exports::ExecutionError; use massa_execution_exports::StorageCostsConstants; use massa_final_state::FinalStateController; -use massa_ledger_exports::{Applicable, LedgerChanges, SetOrDelete, SetUpdateOrDelete}; +use massa_ledger_exports::LedgerChanges; use massa_models::bytecode::Bytecode; use massa_models::datastore::get_prefix_bounds; +use massa_models::types::{Applicable, SetOrDelete, SetUpdateOrDelete}; use massa_models::{address::Address, amount::Amount}; use parking_lot::RwLock; use std::cmp::Ordering; @@ -34,19 +35,6 @@ pub(crate) struct SpeculativeLedger { active_history: Arc>, /// list of ledger changes that were applied to this `SpeculativeLedger` since its creation - #[cfg(all( - not(feature = "gas_calibration"), - not(feature = "benchmarking"), - not(feature = "test-exports"), - not(test) - ))] - added_changes: LedgerChanges, - #[cfg(any( - feature = "gas_calibration", - feature = "benchmarking", - feature = "test-exports", - test - ))] pub added_changes: LedgerChanges, /// max datastore key length @@ -189,6 +177,11 @@ impl SpeculativeLedger { )) })?; changes.set_balance(to_addr, new_balance); + } else if matches!(to_addr, Address::SC(..)) { + return Err(ExecutionError::RuntimeError(format!( + "cannot transfer coins to non-existing smart contract address {}", + to_addr + ))); } else if let Some(remaining_coins) = amount.checked_sub(self.storage_costs_constants.ledger_entry_base_cost) { diff --git a/massa-execution-worker/src/speculative_roll_state.rs b/massa-execution-worker/src/speculative_roll_state.rs index 9b6a8817500..14f7f8b2118 100644 --- a/massa-execution-worker/src/speculative_roll_state.rs +++ b/massa-execution-worker/src/speculative_roll_state.rs @@ -377,7 +377,7 @@ impl SpeculativeRollState { if let Some(v) = self .active_history .read() - .get_adress_deferred_credit_for(addr, slot) + .get_address_deferred_credit_for(addr, slot) { return Some(v); } diff --git a/massa-execution-worker/src/tests/interface.rs b/massa-execution-worker/src/tests/interface.rs index 506ab937f7a..c2ce6d90605 100644 --- a/massa-execution-worker/src/tests/interface.rs +++ b/massa-execution-worker/src/tests/interface.rs @@ -1,19 +1,182 @@ // Copyright (c) 2022 MASSA LABS +use std::str::FromStr; + use hex_literal::hex; +use sha2::Digest; + +use crate::interface_impl::InterfaceImpl; +use massa_execution_exports::ExecutionConfig; use massa_models::address::Address; use massa_sc_runtime::Interface; -use std::str::FromStr; -use crate::interface_impl::InterfaceImpl; #[test] fn test_hash_sha256() { + // test hashing using sha256 algo + let interface = InterfaceImpl::new_default( Address::from_str("AU12cMW9zRKFDS43Z2W88VCmdQFxmHjAo54XvuVV34UzJeXRLXW9M").unwrap(), None, + None, ); let actual_hash = interface.hash_sha256(b"something").unwrap(); let expected_hash = &hex!("3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb")[..]; assert_eq!(actual_hash, expected_hash); } + +#[test] +fn test_evm_signature_verify() { + // Test that we can verify an Ethereum signature + + let interface = InterfaceImpl::new_default( + Address::from_str("AU12cMW9zRKFDS43Z2W88VCmdQFxmHjAo54XvuVV34UzJeXRLXW9M").unwrap(), + None, + None, + ); + + let _address = hex!("807a7bb5193edf9898b9092c1597bb966fe52514"); + let message_ = b"test"; + let signature_ = hex!("d0d05c35080635b5e865006c6c4f5b5d457ec342564d8fc67ce40edc264ccdab3f2f366b5bd1e38582538fed7fa6282148e86af97970a10cb3302896f5d68ef51b"); + let private_key_ = hex!("ed6602758bdd68dc9df67a6936ed69807a74b8cc89bdc18f3939149d02db17f3"); + + // build original public key + let private_key = libsecp256k1::SecretKey::parse_slice(&private_key_).unwrap(); + let public_key = libsecp256k1::PublicKey::from_secret_key(&private_key); + + let result = interface.evm_signature_verify(message_, &signature_, &public_key.serialize()); + assert!(result.is_ok()); + + // Invalid v + { + let mut signature_2_ = signature_; + signature_2_[64] ^= 1; + let result = + interface.evm_signature_verify(message_, &signature_2_, &public_key.serialize()); + assert!(result.is_err()); + } +} + +#[test] +fn test_evm_get_pubkey_from_signature() { + // Test that we can retrieve the public key from an Ethereum signature + + let interface = InterfaceImpl::new_default( + Address::from_str("AU12cMW9zRKFDS43Z2W88VCmdQFxmHjAo54XvuVV34UzJeXRLXW9M").unwrap(), + None, + None, + ); + + // let _address = hex!("807a7bb5193edf9898b9092c1597bb966fe52514"); + let message_ = b"test"; + let signature_ = hex!("d0d05c35080635b5e865006c6c4f5b5d457ec342564d8fc67ce40edc264ccdab3f2f366b5bd1e38582538fed7fa6282148e86af97970a10cb3302896f5d68ef51b"); + let private_key_ = hex!("ed6602758bdd68dc9df67a6936ed69807a74b8cc89bdc18f3939149d02db17f3"); + + // build original public key + let private_key = libsecp256k1::SecretKey::parse_slice(&private_key_).unwrap(); + let public_key = libsecp256k1::PublicKey::from_secret_key(&private_key); + + // build the hash + let prefix = format!("\x19Ethereum Signed Message:\n{}", message_.len()); + let to_hash = [prefix.as_bytes(), message_].concat(); + let full_hash = sha3::Keccak256::digest(to_hash); + + let result = interface.evm_get_pubkey_from_signature(&full_hash, &signature_); + assert!(result.is_ok()); + assert_eq!(public_key.serialize(), result.unwrap().as_ref()); + + // Invalid s - expect failure + { + let mut signature_2 = + libsecp256k1::Signature::parse_standard_slice(&signature_[..64]).unwrap(); + signature_2.s = -signature_2.s; + assert!(signature_2.s.is_high()); + let result = interface.evm_get_pubkey_from_signature(&full_hash, &signature_2.serialize()); + assert!(result.is_err()); + } + + // Invalid v - expect failure + { + let mut signature_2_ = signature_; + signature_2_[64] ^= 1; + let result = interface.evm_get_pubkey_from_signature(&full_hash, &signature_2_); + assert!(result.is_err()); + } +} + +#[test] +fn test_evm_address() { + // Test that we can retrieve an Ethereum address from an Ethereum public key (from a private key) + + let interface = InterfaceImpl::new_default( + Address::from_str("AU12cMW9zRKFDS43Z2W88VCmdQFxmHjAo54XvuVV34UzJeXRLXW9M").unwrap(), + None, + None, + ); + + let _address = hex!("807a7bb5193edf9898b9092c1597bb966fe52514"); + // let message_ = b"test"; + // let signature_ = hex!("d0d05c35080635b5e865006c6c4f5b5d457ec342564d8fc67ce40edc264ccdab3f2f366b5bd1e38582538fed7fa6282148e86af97970a10cb3302896f5d68ef51b"); + let private_key_ = hex!("ed6602758bdd68dc9df67a6936ed69807a74b8cc89bdc18f3939149d02db17f3"); + + // build original public key + let private_key = libsecp256k1::SecretKey::parse_slice(&private_key_).unwrap(); + + let public_key = libsecp256k1::PublicKey::from_secret_key(&private_key); + let res = interface + .evm_get_address_from_pubkey(&public_key.serialize()) + .unwrap(); + // println!("***result: {:?}", res.to_vec()); + // println!("***add: {:?}", _address); + assert_eq!(res, _address); +} + +#[test] +fn test_emit_event() { + // emit 2 events and check that the 2nd event is rejected (because the limit is reached) + + let config = ExecutionConfig { + max_event_per_operation: 1, + ..Default::default() + }; + + let interface = InterfaceImpl::new_default( + Address::from_str("AU12cMW9zRKFDS43Z2W88VCmdQFxmHjAo54XvuVV34UzJeXRLXW9M").unwrap(), + None, + Some(config), + ); + + let res = interface.generate_event("foo".to_string()); + assert!(res.is_ok()); + let res_2 = interface.generate_event("foo".to_string()); + assert!(res_2.is_err()); + println!("res_2: {:?}", res_2); + if let Err(e) = res_2 { + assert!(e.to_string().contains("Too many event for this operation")); + } +} + +#[test] +fn test_emit_event_too_large() { + // emit 2 events and check that the 2nd event is rejected (because the msg is too large) + + let config = ExecutionConfig { + max_event_size: 10, + ..Default::default() + }; + + let interface = InterfaceImpl::new_default( + Address::from_str("AU12cMW9zRKFDS43Z2W88VCmdQFxmHjAo54XvuVV34UzJeXRLXW9M").unwrap(), + None, + Some(config.clone()), + ); + + let res = interface.generate_event("a".repeat(config.max_event_size).to_string()); + assert!(res.is_ok()); + let res_2 = interface.generate_event("b".repeat(config.max_event_size + 1).to_string()); + assert!(res_2.is_err()); + println!("res_2: {:?}", res_2); + if let Err(e) = res_2 { + assert!(e.to_string().contains("Event data size is too large")); + } +} diff --git a/massa-execution-worker/src/tests/scenarios_mandatories.rs b/massa-execution-worker/src/tests/scenarios_mandatories.rs index a04a9d305ae..e1708ae5bd8 100644 --- a/massa-execution-worker/src/tests/scenarios_mandatories.rs +++ b/massa-execution-worker/src/tests/scenarios_mandatories.rs @@ -2,6 +2,10 @@ use massa_async_pool::{AsyncMessage, AsyncPool, AsyncPoolChanges, AsyncPoolConfig}; use massa_db_exports::{DBBatch, ShareableMassaDBController}; +use massa_deferred_calls::config::DeferredCallsConfig; +use massa_deferred_calls::registry_changes::DeferredCallRegistryChanges; +use massa_deferred_calls::slot_changes::DeferredRegistrySlotChanges; +use massa_deferred_calls::{DeferredCall, DeferredCallRegistry}; use massa_executed_ops::{ExecutedDenunciations, ExecutedDenunciationsConfig}; use massa_execution_exports::{ ExecutionConfig, ExecutionQueryRequest, ExecutionQueryRequestItem, ExecutionStackElement, @@ -10,15 +14,15 @@ use massa_execution_exports::{ use massa_final_state::test_exports::get_initials; use massa_final_state::MockFinalStateController; use massa_hash::Hash; -use massa_ledger_exports::{ - LedgerEntryUpdate, MockLedgerControllerWrapper, SetOrKeep, SetUpdateOrDelete, -}; +use massa_ledger_exports::{LedgerEntryUpdate, MockLedgerControllerWrapper}; use massa_models::bytecode::Bytecode; use massa_models::config::{ - CHAINID, ENDORSEMENT_COUNT, LEDGER_ENTRY_DATASTORE_BASE_SIZE, THREAD_COUNT, + CHAINID, ENDORSEMENT_COUNT, GENESIS_KEY, LEDGER_ENTRY_DATASTORE_BASE_SIZE, THREAD_COUNT, }; +use massa_models::deferred_calls::DeferredCallId; use massa_models::prehash::PreHashMap; use massa_models::test_exports::gen_endorsements_for_denunciation; +use massa_models::types::{SetOrDelete, SetOrKeep, SetUpdateOrDelete}; use massa_models::{address::Address, amount::Amount, slot::Slot}; use massa_models::{ denunciation::Denunciation, @@ -58,7 +62,9 @@ use std::io::Cursor; const TEST_SK_1: &str = "S18r2i8oJJyhF7Kprx98zwxAc3W4szf7RKuVMX6JydZz8zSxHeC"; const TEST_SK_2: &str = "S1FpYC4ugG9ivZZbLVrTwWtF9diSRiAwwrVX5Gx1ANSRLfouUjq"; const TEST_SK_3: &str = "S1LgXhWLEgAgCX3nm6y8PVPzpybmsYpi6yg6ZySwu5Z4ERnD7Bu"; +const BLOCK_CREDIT_PART_COUNT: u64 = 3 * (1 + ENDORSEMENT_COUNT as u64); +#[allow(clippy::too_many_arguments)] fn final_state_boilerplate( mock_final_state: &mut Arc>, db: ShareableMassaDBController, @@ -67,6 +73,7 @@ fn final_state_boilerplate( saved_bytecode: Option>>>, custom_async_pool: Option, custom_pos_state: Option, + custom_deferred_call_registry: Option, ) { mock_final_state .write() @@ -140,6 +147,14 @@ fn final_state_boilerplate( }, db.clone(), )); + + let deferred_call_registry = custom_deferred_call_registry + .unwrap_or_else(|| DeferredCallRegistry::new(db.clone(), DeferredCallsConfig::default())); + + mock_final_state + .write() + .expect_get_deferred_call_registry() + .return_const(deferred_call_registry); } fn expect_finalize_deploy_and_call_blocks( @@ -207,6 +222,7 @@ fn test_execution_shutdown() { None, None, None, + None, ); ExecutionTestUniverse::new(foreign_controllers, ExecutionConfig::default()); } @@ -223,6 +239,7 @@ fn test_sending_command() { None, None, None, + None, ); let universe = ExecutionTestUniverse::new(foreign_controllers, ExecutionConfig::default()); universe.module_controller.update_blockclique_status( @@ -268,6 +285,7 @@ fn test_readonly_execution() { None, None, None, + None, ); let universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); @@ -296,8 +314,8 @@ fn test_readonly_execution() { assert_eq!( res.out.state_changes.ledger_changes.0.get(&addr).unwrap(), &SetUpdateOrDelete::Update(LedgerEntryUpdate { - balance: massa_ledger_exports::SetOrKeep::Set(Amount::from_str("60").unwrap()), - bytecode: massa_ledger_exports::SetOrKeep::Keep, + balance: massa_models::types::SetOrKeep::Set(Amount::from_str("60").unwrap()), + bytecode: massa_models::types::SetOrKeep::Keep, datastore: BTreeMap::new() }) ); @@ -342,8 +360,8 @@ fn test_readonly_execution() { assert_eq!( res2.out.state_changes.ledger_changes.0.get(&addr).unwrap(), &SetUpdateOrDelete::Update(LedgerEntryUpdate { - balance: massa_ledger_exports::SetOrKeep::Set(Amount::from_str("50").unwrap()), - bytecode: massa_ledger_exports::SetOrKeep::Keep, + balance: massa_models::types::SetOrKeep::Set(Amount::from_str("50").unwrap()), + bytecode: massa_models::types::SetOrKeep::Keep, datastore: BTreeMap::new() }) ); @@ -396,6 +414,7 @@ fn test_nested_call_gas_usage() { Some(saved_bytecode), None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); @@ -448,6 +467,191 @@ fn test_nested_call_gas_usage() { ); } +/// Test the recursion depth limit in nested calls using call SC operation +/// +/// We call a smart contract that has a nested function call, while setting the max_recursive_calls_depth to 0. +/// We expect the execution of the smart contract call to fail with a message that the recursion depth limit was reached. +#[test] +fn test_nested_call_recursion_limit_reached() { + // setup the period duration + let exec_cfg = ExecutionConfig { + max_recursive_calls_depth: 0, // This limit will be reached + ..Default::default() + }; + + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + selector_boilerplate(&mut foreign_controllers.selector_controller); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .times(1) + .returning(move |_| true); + }); + let saved_bytecode = expect_finalize_deploy_and_call_blocks( + Slot::new(1, 0), + Some(Slot::new(1, 1)), + finalized_waitpoint.get_trigger_handle(), + &mut foreign_controllers.final_state, + ); + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + None, + ); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/nested_call.wasm"), + include_bytes!("./wasm/test.wasm"), + ); + finalized_waitpoint.wait(); + let address = universe.get_address_sc_deployed(Slot::new(1, 0)); + + // Call the function test of the smart contract + let operation = ExecutionTestUniverse::create_call_sc_operation( + &KeyPair::from_str(TEST_SK_2).unwrap(), + 10000000, + Amount::from_str("0").unwrap(), + Amount::from_str("0").unwrap(), + Address::from_str(&address).unwrap(), + String::from("test"), + address.as_bytes().to_vec(), + ) + .unwrap(); + universe.call_sc_block( + &KeyPair::from_str(TEST_SK_2).unwrap(), + Slot::new(1, 1), + operation, + ); + finalized_waitpoint.wait(); + + // Get the events of the smart contract execution. We expect the call to have failed, so we check for the error message. + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 1)), + ..Default::default() + }); + assert!(events.len() >= 2); + //println!("events: {:?}", events); + assert!(events[1].data.contains("recursion depth limit reached")); +} + +/// Test the recursion depth limit in nested calls using call SC operation +/// +/// We call a smart contract that has a nested function call, while setting the max_recursive_calls_depth to 2. +/// We expect the execution of the smart contract call to succeed as the recursion depth limit was not reached. +#[test] +fn test_nested_call_recursion_limit_not_reached() { + // setup the period duration + let exec_cfg = ExecutionConfig { + max_recursive_calls_depth: 2, // This limit will not be reached + ..Default::default() + }; + + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + selector_boilerplate(&mut foreign_controllers.selector_controller); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .times(1) + .returning(move |_| true); + }); + let saved_bytecode = expect_finalize_deploy_and_call_blocks( + Slot::new(1, 0), + Some(Slot::new(1, 1)), + finalized_waitpoint.get_trigger_handle(), + &mut foreign_controllers.final_state, + ); + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + None, + ); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/nested_call.wasm"), + include_bytes!("./wasm/test.wasm"), + ); + finalized_waitpoint.wait(); + let address = universe.get_address_sc_deployed(Slot::new(1, 0)); + + // Call the function test of the smart contract + let operation = ExecutionTestUniverse::create_call_sc_operation( + &KeyPair::from_str(TEST_SK_2).unwrap(), + 10000000, + Amount::from_str("0").unwrap(), + Amount::from_str("0").unwrap(), + Address::from_str(&address).unwrap(), + String::from("test"), + address.as_bytes().to_vec(), + ) + .unwrap(); + universe.call_sc_block( + &KeyPair::from_str(TEST_SK_2).unwrap(), + Slot::new(1, 1), + operation, + ); + finalized_waitpoint.wait(); + + // Get the events. We expect the call to have succeeded, so we check for the length of the events. + // The smart contract emits 4 events in total, (to check gas usage), so we expect at least 4 events, + // and none of them should contain the error message. + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 1)), + ..Default::default() + }); + assert!(events.len() >= 4); + for event in events.iter() { + assert!(!event.data.contains("recursion depth limit reached")); + } +} + /// Test the ABI get call coins /// /// Deploy an SC with a method `test` that generate an event saying how many coins he received @@ -492,6 +696,7 @@ fn test_get_call_coins() { Some(saved_bytecode), None, None, + None, ); foreign_controllers .final_state @@ -662,7 +867,7 @@ fn send_and_receive_async_message() { println!("changes: {:?}", changes.async_pool_changes.0); assert_eq!( changes.async_pool_changes.0.first_key_value().unwrap().1, - &massa_ledger_exports::SetUpdateOrDelete::Set(message_cloned.clone()) + &massa_models::types::SetUpdateOrDelete::Set(message_cloned.clone()) ); assert_eq!( changes.async_pool_changes.0.first_key_value().unwrap().0, @@ -711,7 +916,7 @@ fn send_and_receive_async_message() { }, 0, ), - massa_ledger_exports::SetUpdateOrDelete::Set(message), + massa_models::types::SetUpdateOrDelete::Set(message), ); let mut db_batch = DBBatch::default(); async_pool.apply_changes_to_batch(&AsyncPoolChanges(changes), &mut db_batch); @@ -727,6 +932,7 @@ fn send_and_receive_async_message() { Some(saved_bytecode), Some(async_pool), None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); @@ -743,7 +949,7 @@ fn send_and_receive_async_message() { let block = ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 1), vec![], vec![], vec![]); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); // retrieve events emitted by smart contracts let events = universe @@ -759,7 +965,7 @@ fn send_and_receive_async_message() { } #[test] -fn cancel_async_message() { +fn send_and_receive_async_message_expired() { let exec_cfg = ExecutionConfig::default(); let finalized_waitpoint = WaitPoint::new(); let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); @@ -771,152 +977,1288 @@ fn cancel_async_message() { .expect_get_producer() .returning(move |_| { Ok(Address::from_public_key( - &KeyPair::from_str(TEST_SK_2).unwrap().get_public_key(), + &KeyPair::from_str(TEST_SK_1).unwrap().get_public_key(), )) }); }); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .returning(move |_| true); + }); let saved_bytecode = Arc::new(RwLock::new(None)); - let saved_bytecode_edit = saved_bytecode.clone(); let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); - let sender_addr = - Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi").unwrap(); - let message = AsyncMessage { - emission_slot: Slot { - period: 1, - thread: 0, - }, - emission_index: 0, - sender: sender_addr, - destination: Address::from_str("AU12mzL2UWroPV7zzHpwHnnF74op9Gtw7H55fAmXMnCuVZTFSjZCA") - .unwrap(), - function: String::from("receive"), - max_gas: 3000000, - fee: Amount::from_raw(1), - coins: Amount::from_raw(100), - validity_start: Slot { + + // Expected message from SC: send_message.ts (see massa unit tests src repo) + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + //println!("changes S (1 0): {:?}", changes); + assert_eq!( + changes.async_pool_changes, + AsyncPoolChanges(BTreeMap::new()) + ); + finalized_waitpoint_trigger_handle.trigger(); + }); + + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + None, + ); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/send_message_expired.wasm"), + include_bytes!("./wasm/receive_message.wasm"), + ); + println!("waiting for finalized"); + finalized_waitpoint.wait(); + + // retrieve events emitted by smart contracts + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 0)), + end: Some(Slot::new(1, 1)), + ..Default::default() + }); + // match the events + assert!(events.len() == 1, "One event was expected"); + assert!(events[0] + .data + .contains("validity end is earlier than the validity start")); +} + +#[test] +fn send_and_receive_async_message_expired_2() { + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + selector_boilerplate(&mut foreign_controllers.selector_controller); + foreign_controllers + .selector_controller + .set_expectations(|selector_controller| { + selector_controller + .expect_get_producer() + .returning(move |_| { + Ok(Address::from_public_key( + &KeyPair::from_str(TEST_SK_1).unwrap().get_public_key(), + )) + }); + }); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .returning(move |_| true); + }); + let saved_bytecode = Arc::new(RwLock::new(None)); + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + + // Expected message from SC: send_message.ts (see massa unit tests src repo) + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + assert_eq!( + changes.async_pool_changes, + AsyncPoolChanges(BTreeMap::new()) + ); + finalized_waitpoint_trigger_handle.trigger(); + }); + + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + None, + ); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/send_message_expired_2.wasm"), + include_bytes!("./wasm/receive_message.wasm"), + ); + println!("waiting for finalized"); + finalized_waitpoint.wait(); + + // retrieve events emitted by smart contracts + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 0)), + end: Some(Slot::new(1, 1)), + ..Default::default() + }); + // match the events + assert!(events.len() == 1, "One event was expected"); + assert!(events[0] + .data + .contains("validity end is earlier than the current slot")); +} + +#[test] +fn send_and_receive_async_message_without_init_gas() { + let mut exec_cfg = ExecutionConfig::default(); + exec_cfg.gas_costs.max_instance_cost = 4000000; + + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + selector_boilerplate(&mut foreign_controllers.selector_controller); + foreign_controllers + .selector_controller + .set_expectations(|selector_controller| { + selector_controller + .expect_get_producer() + .returning(move |_| { + Ok(Address::from_public_key( + &KeyPair::from_str(TEST_SK_1).unwrap().get_public_key(), + )) + }); + }); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .returning(move |_| true); + }); + let saved_bytecode = Arc::new(RwLock::new(None)); + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + + // Expected message from SC: send_message.ts (see massa unit tests src repo) + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + assert_eq!( + changes.async_pool_changes, + AsyncPoolChanges(BTreeMap::new()) + ); + finalized_waitpoint_trigger_handle.trigger(); + }); + + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + None, + ); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/send_message.wasm"), + include_bytes!("./wasm/receive_message.wasm"), + ); + println!("waiting for finalized"); + finalized_waitpoint.wait(); + + // retrieve events emitted by smart contracts + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 0)), + end: Some(Slot::new(1, 1)), + ..Default::default() + }); + // match the events + assert!(events.len() == 1, "One event was expected"); + assert!(events[0] + .data + .contains("max gas is lower than the minimum instance cost")); +} + +#[test] +fn cancel_async_message() { + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + selector_boilerplate(&mut foreign_controllers.selector_controller); + foreign_controllers + .selector_controller + .set_expectations(|selector_controller| { + selector_controller + .expect_get_producer() + .returning(move |_| { + Ok(Address::from_public_key( + &KeyPair::from_str(TEST_SK_2).unwrap().get_public_key(), + )) + }); + }); + + let saved_bytecode = Arc::new(RwLock::new(None)); + let saved_bytecode_edit = saved_bytecode.clone(); + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + let sender_addr = + Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi").unwrap(); + let message = AsyncMessage { + emission_slot: Slot { + period: 1, + thread: 0, + }, + emission_index: 0, + sender: sender_addr, + destination: Address::from_str("AU12mzL2UWroPV7zzHpwHnnF74op9Gtw7H55fAmXMnCuVZTFSjZCA") + .unwrap(), + function: String::from("receive"), + max_gas: 3000000, + fee: Amount::from_raw(1), + coins: Amount::from_raw(100), + validity_start: Slot { + period: 1, + thread: 1, + }, + validity_end: Slot { + period: 20, + thread: 20, + }, + function_params: vec![42, 42, 42, 42], + trigger: None, + can_be_executed: true, + }; + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + { + let mut saved_bytecode = saved_bytecode_edit.write(); + *saved_bytecode = Some(changes.ledger_changes.get_bytecode_updates()[0].clone()); + } + assert_eq!( + changes.ledger_changes.0.get(&sender_addr).unwrap(), + &SetUpdateOrDelete::Update(LedgerEntryUpdate { + balance: SetOrKeep::Set(Amount::from_str("90.298635211").unwrap()), + bytecode: massa_models::types::SetOrKeep::Keep, + datastore: BTreeMap::new() + }) + ); + + finalized_waitpoint_trigger_handle.trigger(); + }); + + let finalized_waitpoint_trigger_handle2 = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 1)), predicate::always()) + .returning(move |_, changes| { + match changes.ledger_changes.0.get(&sender_addr).unwrap() { + // at slot (1,1) msg was canceled so sender has received the coins (0.0000001) + // sender has received the coins (0.0000001) + SetUpdateOrDelete::Update(change_sender_update) => { + assert_eq!( + change_sender_update.balance, + SetOrKeep::Set(Amount::from_str("100.0000001").unwrap()) + ); + } + _ => panic!("wrong change type"), + } + + match changes.async_pool_changes.0.first_key_value().unwrap().1 { + SetUpdateOrDelete::Delete => { + // msg was deleted + } + _ => panic!("wrong change type"), + } + + finalized_waitpoint_trigger_handle2.trigger(); + }); + + let mut async_pool = AsyncPool::new(AsyncPoolConfig::default(), foreign_controllers.db.clone()); + let mut changes = BTreeMap::default(); + changes.insert( + ( + Reverse(Ratio::new(1, 100000)), + Slot { + period: 1, + thread: 0, + }, + 0, + ), + massa_models::types::SetUpdateOrDelete::Set(message), + ); + let mut db_batch = DBBatch::default(); + async_pool.apply_changes_to_batch(&AsyncPoolChanges(changes), &mut db_batch); + foreign_controllers + .db + .write() + .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 0))); + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + Some(async_pool), + None, + None, + ); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/send_message.wasm"), + include_bytes!("./wasm/receive_message.wasm"), + ); + finalized_waitpoint.wait(); + + let keypair = KeyPair::from_str(TEST_SK_2).unwrap(); + let block = + ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 1), vec![], vec![], vec![]); + + universe.send_and_finalize(&keypair, block, None); + finalized_waitpoint.wait(); + + // Sleep to wait (1,1) candidate slot to be executed. We don't have a mock to waitpoint on or empty block + std::thread::sleep(Duration::from_millis(exec_cfg.t0.as_millis())); + // retrieve events emitted by smart contracts + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 1)), + end: Some(Slot::new(20, 1)), + ..Default::default() + }); + assert!(events[0].data.contains(" is not a smart contract address")); +} + +#[test] +fn deferred_calls() { + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + selector_boilerplate(&mut foreign_controllers.selector_controller); + // TODO: add some context for this override + foreign_controllers + .selector_controller + .set_expectations(|selector_controller| { + selector_controller + .expect_get_producer() + .returning(move |_| { + Ok(Address::from_public_key( + &KeyPair::from_str(TEST_SK_2).unwrap().get_public_key(), + )) + }); + }); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .returning(move |_| true); + }); + let saved_bytecode = Arc::new(RwLock::new(None)); + let saved_bytecode_edit = saved_bytecode.clone(); + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + + let destination = match *CHAINID { + 77 => Address::from_str("AS12jc7fTsSKwQ9hSk97C3iMNgNT1XrrD6MjSJRJZ4NE53YgQ4kFV").unwrap(), + 77658366 => { + Address::from_str("AS12DSPbsNvvdP1ScCivmKpbQfcJJ3tCQFkNb8ewkRuNjsgoL2AeQ").unwrap() + } + 77658377 => { + Address::from_str("AS127QtY6Hzm6BnJc9wqCBfPNvEH9fKer3LiMNNQmcX3MzLwCL6G6").unwrap() + } + _ => panic!("CHAINID not supported"), + }; + + let target_slot = Slot { + period: 1, + thread: 1, + }; + + let call = DeferredCall { + sender_address: Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi") + .unwrap(), + target_slot, + target_address: destination, + target_function: "receive".to_string(), + parameters: vec![42, 42, 42, 42], + coins: Amount::from_raw(100), + max_gas: 2_300_000, + fee: Amount::from_raw(1), + cancelled: false, + }; + + let call2 = DeferredCall { + sender_address: Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi") + .unwrap(), + target_slot: Slot { + period: 8, + thread: 1, + }, + target_address: destination, + target_function: "tata".to_string(), + parameters: vec![42, 42, 42, 42], + coins: Amount::from_raw(100), + max_gas: 700_000, + fee: Amount::from_raw(1), + cancelled: false, + }; + + let call_id = + DeferredCallId::new(0, target_slot, 0, "trail_hash".to_string().as_bytes()).unwrap(); + + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + { + let mut saved_bytecode = saved_bytecode_edit.write(); + *saved_bytecode = Some(changes.ledger_changes.get_bytecode_updates()[0].clone()); + } + + println!("changes: {:?}", changes.deferred_call_changes.slots_change); + assert_eq!(changes.deferred_call_changes.slots_change.len(), 1); + finalized_waitpoint_trigger_handle.trigger(); + }); + + let finalized_waitpoint_trigger_handle2 = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 1)), predicate::always()) + .returning(move |_, changes| { + match changes.ledger_changes.0.get(&destination).unwrap() { + // sc has received the coins (0.0000001) + SetUpdateOrDelete::Update(change_sc_update) => { + assert_eq!( + change_sc_update.balance, + SetOrKeep::Set(Amount::from_str("100.0000001").unwrap()) + ); + } + _ => panic!("wrong change type"), + } + + assert_eq!(changes.deferred_call_changes.slots_change.len(), 2); + let (_slot, slot_change) = changes + .deferred_call_changes + .slots_change + .first_key_value() + .unwrap(); + + let (_id, set_delete) = slot_change.calls.first_key_value().unwrap(); + // call was executed and then deleted + assert_eq!(set_delete, &SetOrDelete::Delete); + + // // total gas was set to 700_000 (call2.max_gas) + assert_eq!( + changes.deferred_call_changes.effective_total_gas, + SetOrKeep::Set(700_000) + ); + finalized_waitpoint_trigger_handle2.trigger(); + }); + + let registry = DeferredCallRegistry::new( + foreign_controllers.db.clone(), + DeferredCallsConfig::default(), + ); + + let mut defer_reg_slot_changes = DeferredRegistrySlotChanges { + calls: BTreeMap::new(), + effective_slot_gas: massa_deferred_calls::DeferredRegistryGasChange::Set(call.max_gas), + base_fee: massa_deferred_calls::DeferredRegistryBaseFeeChange::Keep, + }; + defer_reg_slot_changes.set_call(call_id.clone(), call.clone()); + + let call_id2 = DeferredCallId::new( + 0, + Slot { + period: 8, + thread: 1, + }, + 0, + "trail_hash".to_string().as_bytes(), + ) + .unwrap(); + + let mut defer_reg_slot_changes2 = defer_reg_slot_changes.clone(); + defer_reg_slot_changes2.set_effective_slot_gas(call2.max_gas); + defer_reg_slot_changes2.set_call(call_id2, call2.clone()); + + let mut slot_changes = BTreeMap::default(); + slot_changes.insert(target_slot, defer_reg_slot_changes); + + slot_changes.insert( + Slot { + period: 8, + thread: 1, + }, + defer_reg_slot_changes2, + ); + + let mut db_batch = DBBatch::default(); + + registry.apply_changes_to_batch( + DeferredCallRegistryChanges { + slots_change: slot_changes, + effective_total_gas: SetOrKeep::Set(call.max_gas.saturating_add(call2.max_gas).into()), + total_calls_registered: SetOrKeep::Set(2), + }, + &mut db_batch, + ); + + foreign_controllers + .db + .write() + .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 0))); + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + Some(registry), + ); + + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); + + // load bytecodes + universe.deploy_bytecode_block( + &KeyPair::from_str(TEST_SK_1).unwrap(), + Slot::new(1, 0), + include_bytes!("./wasm/send_message.wasm"), + include_bytes!("./wasm/receive_message.wasm"), + ); + finalized_waitpoint.wait(); + + let keypair = KeyPair::from_str(TEST_SK_2).unwrap(); + let block = + ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 1), vec![], vec![], vec![]); + + universe.send_and_finalize(&keypair, block, None); + finalized_waitpoint.wait(); + // retrieve events emitted by smart contracts + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 1)), + end: Some(Slot::new(20, 1)), + ..Default::default() + }); + + // match the events + assert!(events.len() == 1, "One event was expected"); + assert_eq!(events[0].data, "message correctly received: 42,42,42,42"); +} + +#[test] +fn deferred_call_register() { + // setup the period duration + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + let keypair = KeyPair::from_str(TEST_SK_1).unwrap(); + let keypair2 = KeyPair::from_str(TEST_SK_3).unwrap(); + let saved_bytecode = Arc::new(RwLock::new(None)); + + let db_lock = foreign_controllers.db.clone(); + + let sender_addr = + Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi").unwrap(); + + let sender_addr_clone = sender_addr; + + dbg!(Address::from_public_key(&keypair2.get_public_key()).to_string()); + + selector_boilerplate(&mut foreign_controllers.selector_controller); + + foreign_controllers + .selector_controller + .set_expectations(|selector_controller| { + selector_controller + .expect_get_producer() + .returning(move |_| { + Ok(Address::from_public_key( + &KeyPair::from_str(TEST_SK_2).unwrap().get_public_key(), + )) + }); + }); + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + }); + + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + // assert sender was debited ( -10 coins) and -5.2866 for fees + match changes.ledger_changes.0.get(&sender_addr_clone).unwrap() { + SetUpdateOrDelete::Update(change_sc_update) => { + assert_eq!( + change_sc_update.balance, + SetOrKeep::Set(Amount::from_str("75.361635312").unwrap()) + ); + } + _ => panic!("wrong change type"), + }; + + { + // manually write the deferred call to the db + // then in the next slot (1,1) we will find and execute it + let reg = + DeferredCallRegistry::new(db_lock.clone(), DeferredCallsConfig::default()); + let mut batch = DBBatch::default(); + reg.apply_changes_to_batch(changes.deferred_call_changes.clone(), &mut batch); + db_lock + .write() + .write_batch(batch, DBBatch::default(), Some(Slot::new(1, 0))); + } + + let slot_changes = changes + .deferred_call_changes + .slots_change + .get(&Slot::new(1, 1)) + .unwrap(); + let _call = slot_changes.calls.first_key_value().unwrap().1; + + // assert total gas was set to 1050000 = (750_000 + 300_000) = (allocated gas + call gas) + assert_eq!( + changes.deferred_call_changes.effective_total_gas, + SetOrKeep::Set(1050000) + ); + + //gas was set to 1050000 = (750_000 + 300_000) = (allocated gas + call gas) + assert_eq!(slot_changes.get_effective_slot_gas().unwrap(), 1050000); + + finalized_waitpoint_trigger_handle.trigger(); + }); + + let finalized_waitpoint_trigger_handle2 = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 1)), predicate::always()) + .returning(move |_, changes| { + match changes + .ledger_changes + .0 + .get( + &Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi") + .unwrap(), + ) + .unwrap() + { + SetUpdateOrDelete::Update(change_sc_update) => { + assert_eq!( + change_sc_update.balance, + SetOrKeep::Set(Amount::from_str("110.1111").unwrap()) + ); + } + _ => panic!("wrong change type"), + } + + assert_eq!(changes.deferred_call_changes.slots_change.len(), 2); + let (_slot, slot_change) = changes + .deferred_call_changes + .slots_change + .first_key_value() + .unwrap(); + + let (_id, set_delete) = slot_change.calls.first_key_value().unwrap(); + + // call was executed and then deleted + assert_eq!(set_delete, &SetOrDelete::Delete); + + // assert total gas was set to 0 + assert_eq!( + changes.deferred_call_changes.effective_total_gas, + SetOrKeep::Set(0) + ); + finalized_waitpoint_trigger_handle2.trigger(); + }); + + let registry = DeferredCallRegistry::new( + foreign_controllers.db.clone(), + DeferredCallsConfig::default(), + ); + + let mut defer_reg_slot_changes = DeferredRegistrySlotChanges { + calls: BTreeMap::new(), + effective_slot_gas: massa_deferred_calls::DeferredRegistryGasChange::Keep, + base_fee: massa_deferred_calls::DeferredRegistryBaseFeeChange::Keep, + }; + + defer_reg_slot_changes.set_base_fee(Amount::from_str("0.000005").unwrap()); + + let mut slot_changes = BTreeMap::default(); + slot_changes.insert( + Slot { + period: 1, + thread: 1, + }, + defer_reg_slot_changes, + ); + + let mut db_batch = DBBatch::default(); + + registry.apply_changes_to_batch( + DeferredCallRegistryChanges { + slots_change: slot_changes, + effective_total_gas: SetOrKeep::Keep, + total_calls_registered: SetOrKeep::Set(0), + }, + &mut db_batch, + ); + + foreign_controllers + .db + .write() + .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 0))); + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + Some(DeferredCallRegistry::new( + foreign_controllers.db.clone(), + DeferredCallsConfig::default(), + )), + ); + + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); + + // abi call to register a deferred call + universe.deploy_bytecode_block( + &keypair, + Slot::new(1, 0), + include_bytes!("./wasm/deferred_call_register.wasm"), + //unused + include_bytes!("./wasm/use_builtins.wasm"), + ); + finalized_waitpoint.wait(); + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter::default()); + + assert_eq!(events[0].data, "Deferred call registered"); + + // call id in the register event + let callid_event: String = events[1].data.clone(); + + let _call_id = DeferredCallId::from_str(&callid_event).unwrap(); + + let keypair = KeyPair::from_str(TEST_SK_2).unwrap(); + let block = + ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 1), vec![], vec![], vec![]); + + universe.send_and_finalize(&keypair, block, None); + // match the events + finalized_waitpoint.wait(); +} + +#[test] +fn deferred_call_register_fail() { + // setup the period duration + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + let saved_bytecode = Arc::new(RwLock::new(None)); + let target_slot = Slot { + period: 1, + thread: 10, + }; + + selector_boilerplate(&mut foreign_controllers.selector_controller); + + foreign_controllers + .selector_controller + .set_expectations(|selector_controller| { + selector_controller + .expect_get_producer() + .returning(move |_| { + Ok(Address::from_public_key( + &KeyPair::from_str(TEST_SK_2).unwrap().get_public_key(), + )) + }); + }); + + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + assert!(changes.deferred_call_changes.effective_total_gas == SetOrKeep::Keep); + finalized_waitpoint_trigger_handle.trigger(); + }); + + let finalized_waitpoint_trigger_handle2 = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 1)), predicate::always()) + .returning(move |_, changes| { + assert_eq!(changes.deferred_call_changes.slots_change.len(), 1); + // deferred call was not register + assert!(changes.deferred_call_changes.effective_total_gas == SetOrKeep::Keep); + + finalized_waitpoint_trigger_handle2.trigger(); + }); + + let call = DeferredCall { + sender_address: Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi") + .unwrap(), + target_slot, + target_address: Address::from_str("AS12jc7fTsSKwQ9hSk97C3iMNgNT1XrrD6MjSJRJZ4NE53YgQ4kFV") + .unwrap(), + target_function: "toto".to_string(), + parameters: vec![42, 42, 42, 42], + coins: Amount::from_raw(100), + max_gas: 500, + fee: Amount::from_raw(1), + cancelled: false, + }; + + let call_id = + DeferredCallId::new(0, target_slot, 0, "trail_hash".to_string().as_bytes()).unwrap(); + let registry = DeferredCallRegistry::new( + foreign_controllers.db.clone(), + DeferredCallsConfig::default(), + ); + + let mut defer_reg_slot_changes = DeferredRegistrySlotChanges { + calls: BTreeMap::new(), + effective_slot_gas: massa_deferred_calls::DeferredRegistryGasChange::Set(500), + base_fee: massa_deferred_calls::DeferredRegistryBaseFeeChange::Keep, + }; + + defer_reg_slot_changes.set_call(call_id.clone(), call.clone()); + + let mut slot_changes = BTreeMap::default(); + slot_changes.insert(target_slot, defer_reg_slot_changes); + + let mut db_batch = DBBatch::default(); + + registry.apply_changes_to_batch( + DeferredCallRegistryChanges { + slots_change: slot_changes, + effective_total_gas: SetOrKeep::Set(2000), + total_calls_registered: SetOrKeep::Set(1), + }, + &mut db_batch, + ); + + foreign_controllers + .db + .write() + .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 0))); + + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + Some(registry), + ); + + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); + + let keypair = KeyPair::from_str(TEST_SK_2).unwrap(); + let block = + ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 0), vec![], vec![], vec![]); + + universe.send_and_finalize(&keypair, block, None); + + finalized_waitpoint.wait(); + + // abi call to register a deferred call + // the call want to book max_async_gas 1_000_000_000 so it fail because we already have a call at this slot with 500 gas + universe.deploy_bytecode_block( + &keypair, + Slot::new(1, 1), + include_bytes!("./wasm/deferred_call_register_fail.wasm"), + //unused + include_bytes!("./wasm/use_builtins.wasm"), + ); + finalized_waitpoint.wait(); + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + start: Some(Slot::new(1, 1)), + end: Some(Slot::new(20, 1)), + ..Default::default() + }); + + let ev = events[1].clone(); + assert!(ev.context.is_error); + assert!(ev.data.contains("The Deferred call cannot be registered. Ensure that the target slot is not before/at the current slot nor too far in the future, and that it has at least max_gas available gas")); + + // // update base fee at slot 1,10 + // defer_reg_slot_changes.set_base_fee(Amount::from_str("0.0005").unwrap()); + + // slot_changes.insert(target_slot, defer_reg_slot_changes); + + // let mut db_batch = DBBatch::default(); + + // // reset total slot gas + // registry.apply_changes_to_batch( + // DeferredRegistryChanges { + // slots_change: slot_changes, + // total_gas: SetOrKeep::Set(0), + // }, + // &mut db_batch, + // ); + + // foreign_controllers + // .db + // .write() + // .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 1))); + + // universe.deploy_bytecode_block( + // &keypair, + // Slot::new(1, 2), + // include_bytes!("./wasm/deferred_call_register_fail.wasm"), + // //unused + // include_bytes!("./wasm/use_builtins.wasm"), + // ); + + // let events = universe + // .module_controller + // .get_filtered_sc_output_event(EventFilter { + // start: Some(Slot::new(1, 1)), + // end: Some(Slot::new(20, 1)), + // ..Default::default() + // }); + + // dbg!(&events); + + // let ev = events[1].clone(); +} + +#[test] +fn deferred_call_exists() { + // setup the period duration + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + let target_slot = Slot { + period: 10, + thread: 1, + }; + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |_| Some(Amount::from_str("100").unwrap())); + + ledger_controller + .expect_entry_exists() + .times(2) + .returning(move |_| false); + + ledger_controller + .expect_entry_exists() + .times(1) + .returning(move |_| true); + }); + + selector_boilerplate(&mut foreign_controllers.selector_controller); + + let saved_bytecode = expect_finalize_deploy_and_call_blocks( + Slot::new(1, 1), + Some(Slot::new(1, 2)), + finalized_waitpoint.get_trigger_handle(), + &mut foreign_controllers.final_state, + ); + + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); + foreign_controllers + .final_state + .write() + .expect_finalize() + .times(1) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) + .returning(move |_, changes| { + assert!(changes.deferred_call_changes.effective_total_gas == SetOrKeep::Keep); + finalized_waitpoint_trigger_handle.trigger(); + }); + + let call = DeferredCall { + sender_address: Address::from_str("AU1TyzwHarZMQSVJgxku8co7xjrRLnH74nFbNpoqNd98YhJkWgi") + .unwrap(), + target_slot, + target_address: Address::from_str("AS12jc7fTsSKwQ9hSk97C3iMNgNT1XrrD6MjSJRJZ4NE53YgQ4kFV") + .unwrap(), + target_function: "toto".to_string(), + parameters: vec![42, 42, 42, 42], + coins: Amount::from_raw(100), + max_gas: 1000000, + fee: Amount::from_raw(1), + cancelled: false, + }; + + let call_id = + DeferredCallId::new(0, target_slot, 0, "trail_hash".to_string().as_bytes()).unwrap(); + let registry = DeferredCallRegistry::new( + foreign_controllers.db.clone(), + DeferredCallsConfig::default(), + ); + + let mut defer_reg_slot_changes = DeferredRegistrySlotChanges { + calls: BTreeMap::new(), + effective_slot_gas: massa_deferred_calls::DeferredRegistryGasChange::Set(500), + base_fee: massa_deferred_calls::DeferredRegistryBaseFeeChange::Keep, + }; + + defer_reg_slot_changes.set_call(call_id.clone(), call.clone()); + + let mut slot_changes = BTreeMap::default(); + slot_changes.insert(target_slot, defer_reg_slot_changes); + + let mut db_batch = DBBatch::default(); + + registry.apply_changes_to_batch( + DeferredCallRegistryChanges { + slots_change: slot_changes, + effective_total_gas: SetOrKeep::Set(2000), + total_calls_registered: SetOrKeep::Set(1), + }, + &mut db_batch, + ); + + foreign_controllers + .db + .write() + .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 0))); + + final_state_boilerplate( + &mut foreign_controllers.final_state, + foreign_controllers.db.clone(), + &foreign_controllers.selector_controller, + &mut foreign_controllers.ledger_controller, + Some(saved_bytecode), + None, + None, + Some(registry), + ); + + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); + + let keypair = KeyPair::from_str(TEST_SK_2).unwrap(); + let block = + ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 0), vec![], vec![], vec![]); + + universe.send_and_finalize(&keypair, block, None); + + finalized_waitpoint.wait(); + + // block 1,1 + universe.deploy_bytecode_block( + &keypair, + Slot::new(1, 1), + include_bytes!("./wasm/deferred_call_exists.wasm"), + include_bytes!("./wasm/deferred_call_exists.wasm"), + ); + finalized_waitpoint.wait(); + let address_sc = universe.get_address_sc_deployed(Slot::new(1, 1)); + + // block 1,2 + let operation = ExecutionTestUniverse::create_call_sc_operation( + &KeyPair::from_str(TEST_SK_3).unwrap(), + 10000000, + Amount::from_str("0.01").unwrap(), + Amount::from_str("20").unwrap(), + Address::from_str(&address_sc).unwrap(), + String::from("exists"), + call_id.to_string().as_bytes().to_vec(), + ) + .unwrap(); + + universe.call_sc_block( + &KeyPair::from_str(TEST_SK_3).unwrap(), + Slot { period: 1, - thread: 1, - }, - validity_end: Slot { - period: 20, - thread: 20, + thread: 2, }, - function_params: vec![42, 42, 42, 42], - trigger: None, - can_be_executed: true, - }; - foreign_controllers - .final_state - .write() - .expect_finalize() - .times(1) - .with(predicate::eq(Slot::new(1, 0)), predicate::always()) - .returning(move |_, changes| { - { - let mut saved_bytecode = saved_bytecode_edit.write(); - *saved_bytecode = Some(changes.ledger_changes.get_bytecode_updates()[0].clone()); - } - assert_eq!( - changes.ledger_changes.0.get(&sender_addr).unwrap(), - &SetUpdateOrDelete::Update(LedgerEntryUpdate { - balance: massa_ledger_exports::SetOrKeep::Set( - Amount::from_str("100.670399899").unwrap() - ), - bytecode: massa_ledger_exports::SetOrKeep::Keep, - datastore: BTreeMap::new() - }) - ); - - finalized_waitpoint_trigger_handle.trigger(); + operation, + ); + finalized_waitpoint.wait(); + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + emitter_address: Some(Address::from_str(&address_sc).unwrap()), + ..Default::default() }); - let finalized_waitpoint_trigger_handle2 = finalized_waitpoint.get_trigger_handle(); + assert_eq!(events[1].data, "true"); +} + +#[test] +fn deferred_call_quote() { + // setup the period duration + let exec_cfg = ExecutionConfig::default(); + let finalized_waitpoint = WaitPoint::new(); + let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + + selector_boilerplate(&mut foreign_controllers.selector_controller); + + let saved_bytecode = expect_finalize_deploy_and_call_blocks( + Slot::new(1, 1), + None, + finalized_waitpoint.get_trigger_handle(), + &mut foreign_controllers.final_state, + ); + + let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); foreign_controllers .final_state .write() .expect_finalize() .times(1) - .with(predicate::eq(Slot::new(1, 1)), predicate::always()) + .with(predicate::eq(Slot::new(1, 0)), predicate::always()) .returning(move |_, changes| { - match changes.ledger_changes.0.get(&sender_addr).unwrap() { - // at slot (1,1) msg was canceled so sender has received the coins (0.0000001) - // sender has received the coins (0.0000001) - SetUpdateOrDelete::Update(change_sender_update) => { - assert_eq!( - change_sender_update.balance, - SetOrKeep::Set(Amount::from_str("100.0000001").unwrap()) - ); - } - _ => panic!("wrong change type"), - } - - match changes.async_pool_changes.0.first_key_value().unwrap().1 { - SetUpdateOrDelete::Delete => { - // msg was deleted - } - _ => panic!("wrong change type"), - } - - finalized_waitpoint_trigger_handle2.trigger(); + assert!(changes.deferred_call_changes.effective_total_gas == SetOrKeep::Keep); + finalized_waitpoint_trigger_handle.trigger(); }); - let mut async_pool = AsyncPool::new(AsyncPoolConfig::default(), foreign_controllers.db.clone()); - let mut changes = BTreeMap::default(); - changes.insert( - ( - Reverse(Ratio::new(1, 100000)), - Slot { - period: 1, - thread: 0, - }, - 0, - ), - massa_ledger_exports::SetUpdateOrDelete::Set(message), - ); - let mut db_batch = DBBatch::default(); - async_pool.apply_changes_to_batch(&AsyncPoolChanges(changes), &mut db_batch); - foreign_controllers - .db - .write() - .write_batch(db_batch, DBBatch::default(), Some(Slot::new(1, 0))); final_state_boilerplate( &mut foreign_controllers.final_state, foreign_controllers.db.clone(), &foreign_controllers.selector_controller, &mut foreign_controllers.ledger_controller, Some(saved_bytecode), - Some(async_pool), + None, + None, None, ); - let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); - // load bytecodes - universe.deploy_bytecode_block( - &KeyPair::from_str(TEST_SK_1).unwrap(), - Slot::new(1, 0), - include_bytes!("./wasm/send_message.wasm"), - include_bytes!("./wasm/receive_message.wasm"), - ); - finalized_waitpoint.wait(); + let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); let keypair = KeyPair::from_str(TEST_SK_2).unwrap(); let block = - ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 1), vec![], vec![], vec![]); + ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 0), vec![], vec![], vec![]); + + universe.send_and_finalize(&keypair, block, None); - universe.send_and_finalize(&keypair, block); finalized_waitpoint.wait(); - // Sleep to wait (1,1) candidate slot to be executed. We don't have a mock to waitpoint on or empty block - std::thread::sleep(Duration::from_millis(exec_cfg.t0.as_millis())); - // retrieve events emitted by smart contracts + // block 1,1 + universe.deploy_bytecode_block( + &keypair, + Slot::new(1, 1), + include_bytes!("./wasm/deferred_call_quote.wasm"), + include_bytes!("./wasm/deferred_call_quote.wasm"), + ); + finalized_waitpoint.wait(); let events = universe .module_controller - .get_filtered_sc_output_event(EventFilter { - start: Some(Slot::new(1, 1)), - end: Some(Slot::new(20, 1)), - ..Default::default() - }); - assert!(events[0].data.contains(" is not a smart contract address")); + .get_filtered_sc_output_event(EventFilter::default()); + + assert_eq!(events[0].data, "136600000"); } /// Context @@ -957,6 +2299,7 @@ fn local_execution() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); // load bytecodes @@ -1010,11 +2353,11 @@ fn local_execution() { /// Functional test for sc deployment utility functions, `functionExists` and `callerHasWriteAccess` /// /// 1. a block is created with one ExecuteSC operation containing -/// a deployment sc as bytecode to execute and a deployed sc as an op datatsore entry +/// a deployment sc as bytecode to execute and a deployed sc as an op datastore entry /// 2. store and set the block as final /// 3. wait for execution /// 4. retrieve events emitted by the initial an sub functions -/// 5. match events to make sure that `functionExists` and `callerHasWriteAccess` had the expected behaviour +/// 5. match events to make sure that `functionExists` and `callerHasWriteAccess` had the expected behavior #[test] fn sc_deployment() { // setup the period duration @@ -1036,6 +2379,7 @@ fn sc_deployment() { Some(saved_bytecode), None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); // load bytecodes @@ -1120,6 +2464,7 @@ fn send_and_receive_async_message_with_trigger() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); // load bytecode @@ -1155,7 +2500,7 @@ fn send_and_receive_async_message_with_trigger() { vec![], ); universe.storage.store_block(block.clone()); - universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block); + universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block, None); finalized_waitpoint.wait(); // retrieve events emitted by smart contracts let events = universe @@ -1221,10 +2566,43 @@ fn send_and_receive_transaction() { // setup the period duration let exec_cfg = ExecutionConfig::default(); let mut foreign_controllers = ExecutionForeignControllers::new_with_mocks(); + + // Set various addresses to test the execution's behavior + let existing_user_recipient_address = + Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); + let non_existing_user_recipient_address = + Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); + let existing_sc_recipient_address = + Address::from_str("AS1Bc3kZ6LhPLJvXV4vcVJLFRExRFbkPWD7rCg9aAdQ1NGzRwgnu").unwrap(); + let non_existing_sc_recipient_address = + Address::from_str("AS1aEhosr1ebJJZ7cEMpSVKbY6xp1p4DdXabGb8fdkKKJ6WphGnR").unwrap(); + let non_existing_recipient_addresses = [ + non_existing_user_recipient_address, + non_existing_sc_recipient_address, + ]; + let recipient_addresses = vec![ + existing_user_recipient_address, + existing_sc_recipient_address, + non_existing_user_recipient_address, + non_existing_sc_recipient_address, + ]; + + foreign_controllers + .ledger_controller + .set_expectations(|ledger_controller| { + ledger_controller + .expect_get_balance() + .returning(move |address| { + if non_existing_recipient_addresses.contains(address) { + None + } else { + Some(Amount::from_str("100").unwrap()) + } + }); + }); let finalized_waitpoint = WaitPoint::new(); let finalized_waitpoint_trigger_handle = finalized_waitpoint.get_trigger_handle(); - let recipient_address = - Address::from_public_key(&KeyPair::generate(0).unwrap().get_public_key()); + selector_boilerplate(&mut foreign_controllers.selector_controller); final_state_boilerplate( &mut foreign_controllers.final_state, @@ -1234,6 +2612,7 @@ fn send_and_receive_transaction() { None, None, None, + None, ); foreign_controllers .final_state @@ -1242,14 +2621,54 @@ fn send_and_receive_transaction() { .times(1) .with(predicate::eq(Slot::new(1, 0)), predicate::always()) .returning(move |_, changes| { - // 190 because 100 in the get_balance in the `final_state_boilerplate` and 90 from the transfer. + // 110 because 100 in the get_balance in the `final_state_boilerplate` and 10 from the transfer. assert_eq!( changes .ledger_changes - .get_balance_or_else(&recipient_address, || None), - Some(Amount::from_str("190").unwrap()) + .get_balance_or_else(&existing_user_recipient_address, || None), + Some(Amount::from_str("110").unwrap()) ); - // 1.02 for the block rewards + // 9.999 because -0.001 for address creation and 10 from the transfer. + assert_eq!( + changes + .ledger_changes + .get_balance_or_else(&non_existing_user_recipient_address, || None), + Some(Amount::from_str("9.999").unwrap()) + ); + // 110 because 100 in the get_balance in the `final_state_boilerplate` and 10 from the transfer. + assert_eq!( + changes + .ledger_changes + .get_balance_or_else(&existing_sc_recipient_address, || None), + Some(Amount::from_str("110").unwrap()) + ); + // Cannot transfer coins to a non-existing smart contract + assert_eq!( + changes + .ledger_changes + .get_balance_or_else(&non_existing_sc_recipient_address, || None), + None + ); + // block rewards computation + let total_rewards = exec_cfg + .block_reward + .saturating_add(Amount::from_str("20").unwrap()); // add 20 MAS for fees + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); + // 100 initial balance, + block rewards - transferred amount (3*10) - fees (4*5) + let sender_expected_balance = Amount::from_str("100") + .unwrap() + .saturating_add(rewards_for_block_creator) + .saturating_sub(Amount::from_str("30").unwrap()) + .saturating_sub(Amount::from_str("20").unwrap()); + assert_eq!( changes.ledger_changes.get_balance_or_else( &Address::from_public_key( @@ -1257,42 +2676,56 @@ fn send_and_receive_transaction() { ), || None ), - Some( - exec_cfg - .block_reward - .saturating_add(Amount::from_str("10").unwrap()) // add 10 fee - ) + Some(sender_expected_balance) ); finalized_waitpoint_trigger_handle.trigger(); }); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); - // create the operation - let operation = Operation::new_verifiable( - Operation { - fee: Amount::from_str("10").unwrap(), - expire_period: 10, - op: OperationType::Transaction { - recipient_address, - amount: Amount::from_str("90").unwrap(), + // create the operations + let mut operation_vec = Vec::new(); + for recipient_address in recipient_addresses { + let operation = Operation::new_verifiable( + Operation { + fee: Amount::from_str("5").unwrap(), + expire_period: 10, + op: OperationType::Transaction { + recipient_address, + amount: Amount::from_str("10").unwrap(), + }, }, - }, - OperationSerializer::new(), - &KeyPair::from_str(TEST_SK_1).unwrap(), - *CHAINID, - ) - .unwrap(); + OperationSerializer::new(), + &KeyPair::from_str(TEST_SK_1).unwrap(), + *CHAINID, + ) + .unwrap(); + operation_vec.push(operation.clone()); + } + // create the block containing the transaction operation - universe.storage.store_operations(vec![operation.clone()]); + universe.storage.store_operations(operation_vec.clone()); let block = ExecutionTestUniverse::create_block( &KeyPair::from_str(TEST_SK_1).unwrap(), Slot::new(1, 0), - vec![operation], + operation_vec, vec![], vec![], ); // store the block in storage - universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block); + universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block, None); finalized_waitpoint.wait(); + + let events = universe + .module_controller + .get_filtered_sc_output_event(EventFilter { + is_error: Some(true), + ..Default::default() + }); + // match the events + println!("{:?}", events); + assert!(events.len() == 1, "1 event was expected"); + assert!(events[0] + .data + .contains("cannot transfer coins to non-existing smart contract address")); } #[test] @@ -1313,6 +2746,7 @@ fn roll_buy() { None, None, None, + None, ); foreign_controllers .final_state @@ -1326,12 +2760,22 @@ fn roll_buy() { assert_eq!(changes.pos_changes.roll_changes.get(&address), Some(&101)); // address has 100 coins before buying roll - // -> (100 (balance) - 100 (roll price)) + 1.02 (block reward) + // -> (100 (balance) - 100 (roll price)) + 1.02 / 17 * 3 (block reward) + let total_rewards = exec_cfg.block_reward; + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); assert_eq!( changes.ledger_changes.0.get(&address).unwrap(), &SetUpdateOrDelete::Update(LedgerEntryUpdate { - balance: massa_ledger_exports::SetOrKeep::Set(exec_cfg.block_reward), - bytecode: massa_ledger_exports::SetOrKeep::Keep, + balance: SetOrKeep::Set(rewards_for_block_creator), + bytecode: SetOrKeep::Keep, datastore: BTreeMap::new() }) ); @@ -1361,7 +2805,7 @@ fn roll_buy() { vec![], ); // set our block as a final block so the purchase is processed - universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block); + universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block, None); finalized_waitpoint.wait(); } @@ -1413,6 +2857,7 @@ fn roll_sell() { None, None, Some(pos_final_state), + None, ); foreign_controllers .final_state @@ -1425,6 +2870,17 @@ fn roll_sell() { .ledger_changes .get_balance_or_else(&address, || None) .unwrap(); + // block rewards computation + let total_rewards = exec_cfg.block_reward; + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); assert_eq!( amount, // 100 from the boilerplate @@ -1433,7 +2889,7 @@ fn roll_sell() { // + deferred credits set above .saturating_add(initial_deferred_credits) // + block rewards - .saturating_add(exec_cfg.block_reward) + .saturating_add(rewards_for_block_creator) ); let deferred_credits = changes .pos_changes @@ -1490,7 +2946,7 @@ fn roll_sell() { vec![], ); // set our block as a final block so the purchase is processed - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); } @@ -1572,6 +3028,7 @@ fn auto_sell_on_missed_blocks() { None, None, Some(pos_final_state.clone()), + None, ); foreign_controllers @@ -1606,10 +3063,10 @@ fn auto_sell_on_missed_blocks() { let block = ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 0), vec![], vec![], vec![]); // set our block as a final block so the purchase is processed - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); let block = ExecutionTestUniverse::create_block(&keypair, Slot::new(1, 1), vec![], vec![], vec![]); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); } @@ -1671,13 +3128,25 @@ fn roll_slash() { .ledger_changes .get_balance_or_else(&address, || None) .unwrap(); - // 100 base + reward of the 3 slash (50%) + block reward + + // block rewards computation + let total_rewards = exec_cfg + .block_reward + .saturating_add(Amount::from_str("150").unwrap()); //reward of the 3 slash (50%) + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); assert_eq!( balance, Amount::from_mantissa_scale(100, 0) .unwrap() - .saturating_add(exec_cfg.block_reward) - .saturating_add(Amount::from_mantissa_scale(150, 0).unwrap()) + .saturating_add(rewards_for_block_creator) ); waitpoint_trigger_handle.trigger() }); @@ -1689,6 +3158,7 @@ fn roll_slash() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); @@ -1727,7 +3197,7 @@ fn roll_slash() { vec![], vec![denunciation, denunciation_2], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); waitpoint.wait(); } @@ -1789,13 +3259,24 @@ fn roll_slash_2() { .ledger_changes .get_balance_or_else(&address, || None) .unwrap(); - // 100 base + reward of the 4 slash (50%) + block reward + // block rewards computation + let total_rewards = exec_cfg + .block_reward + .saturating_add(Amount::from_str("200").unwrap()); //reward of the 4 slash (50%) + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); assert_eq!( balance, Amount::from_mantissa_scale(100, 0) .unwrap() - .saturating_add(exec_cfg.block_reward) - .saturating_add(Amount::from_mantissa_scale(200, 0).unwrap()) + .saturating_add(rewards_for_block_creator) ); waitpoint_trigger_handle.trigger() }); @@ -1807,6 +3288,7 @@ fn roll_slash_2() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); @@ -1845,7 +3327,7 @@ fn roll_slash_2() { vec![], vec![denunciation, denunciation_2], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); waitpoint.wait(); } @@ -1870,6 +3352,7 @@ fn sc_execution_error() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); // load bytecode @@ -1887,7 +3370,7 @@ fn sc_execution_error() { vec![], vec![], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); // retrieve the event emitted by the execution error let events = universe @@ -1928,6 +3411,7 @@ fn sc_datastore() { Some(saved_bytecode), None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); // load bytecode @@ -1946,7 +3430,7 @@ fn sc_datastore() { vec![], vec![], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); // retrieve the event emitted by the execution error let events = universe @@ -1982,6 +3466,7 @@ fn set_bytecode_error() { Some(saved_bytecode), None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); // load bytecodes @@ -2002,7 +3487,7 @@ fn set_bytecode_error() { vec![], vec![], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); // retrieve the event emitted by the execution error let events = universe @@ -2061,6 +3546,7 @@ fn datastore_manipulations() { None, None, None, + None, ); foreign_controllers .final_state @@ -2080,15 +3566,26 @@ fn datastore_manipulations() { .ledger_changes .get_balance_or_else(&addr, || None) .unwrap(); + // block rewards computation + let total_rewards = exec_cfg + .block_reward + .saturating_add(Amount::from_str("10").unwrap()); // 10 MAS for fees + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); assert_eq!( amount, // Base from the boilerplate Amount::from_str("100") .unwrap() .saturating_sub(Amount::const_init(10, 0)) - .saturating_add(exec_cfg.block_reward) - // Gas fee - .saturating_add(Amount::from_str("10").unwrap()) + .saturating_add(rewards_for_block_creator) // Storage cost base .saturating_sub( exec_cfg @@ -2134,7 +3631,7 @@ fn datastore_manipulations() { vec![], vec![], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); let events = universe @@ -2240,6 +3737,7 @@ fn events_from_switching_blockclique() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); @@ -2291,6 +3789,7 @@ fn not_enough_instance_gas() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); @@ -2324,7 +3823,7 @@ fn not_enough_instance_gas() { ); // store the block in storage universe.storage.store_block(block.clone()); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); // assert events let events = universe @@ -2357,6 +3856,7 @@ fn sc_builtins() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); universe.deploy_bytecode_block( @@ -2404,6 +3904,7 @@ fn validate_address() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); universe.deploy_bytecode_block( @@ -2455,6 +3956,7 @@ fn test_rewards() { None, None, None, + None, ); foreign_controllers .final_state @@ -2463,19 +3965,29 @@ fn test_rewards() { .times(1) .with(predicate::eq(Slot::new(1, 0)), predicate::always()) .returning(move |_, changes| { - let block_credit_part = exec_cfg - .block_reward - .checked_div_u64(3 * (1 + (ENDORSEMENT_COUNT as u64))) - .expect("critical: block_credits checked_div factor is 0") - .saturating_mul_u64(2); - let first_block_reward = exec_cfg.block_reward.saturating_sub(block_credit_part); + let block_credits = exec_cfg.block_reward; + let block_credit_part = block_credits + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: block_credits checked_div factor is 0"); + let remainder = block_credits + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: block_credits checked_rem factor is 0"); + + let first_block_reward_for_block_creator = block_credit_part + .saturating_mul_u64(3) + .saturating_add(remainder) // base reward + .saturating_add(block_credit_part.saturating_mul_u64(2)); // 2 endorsements included + let first_block_reward_for_endorsement_producer_address = + block_credit_part.saturating_mul_u64(2); // produced 2 endorsements that were included in the block + assert_eq!( changes .ledger_changes .get_balance_or_else(&keypair_address, || None), // Reward + 100 base from boilerplate Some( - first_block_reward.saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) + first_block_reward_for_block_creator + .saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) ) ); @@ -2485,7 +3997,8 @@ fn test_rewards() { .get_balance_or_else(&endorsement_producer_address, || None), // Reward + 100 base from boilerplate Some( - block_credit_part.saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) + first_block_reward_for_endorsement_producer_address + .saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) ) ); finalized_waitpoint_trigger_handle.trigger(); @@ -2498,27 +4011,28 @@ fn test_rewards() { .times(1) .with(predicate::eq(Slot::new(1, 1)), predicate::always()) .returning(move |_, changes| { - let block_credit_part_parent_in_thread = exec_cfg - .block_reward - .checked_div_u64(3 * (1 + (ENDORSEMENT_COUNT as u64))) - .expect("critical: block_credits checked_div factor is 0") - .saturating_mul_u64(ENDORSEMENT_COUNT as u64); - let block_credit_part_endorsement_producer = exec_cfg - .block_reward - .checked_div_u64(3 * (1 + (ENDORSEMENT_COUNT as u64))) - .expect("critical: block_credits checked_div factor is 0") - .saturating_mul_u64(ENDORSEMENT_COUNT as u64); - let creator_block_reward = exec_cfg - .block_reward - .saturating_sub(block_credit_part_endorsement_producer) - .saturating_sub(block_credit_part_parent_in_thread); + let block_credits = exec_cfg.block_reward; + let block_credit_part = block_credits + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: block_credits checked_div factor is 0"); + let remainder = block_credits + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: block_credits checked_rem factor is 0"); + + let second_block_reward_for_block_creator = block_credit_part + .saturating_mul_u64(3) + .saturating_add(remainder) // base reward + .saturating_add(block_credit_part.saturating_mul_u64(ENDORSEMENT_COUNT as u64)); // ENDORSEMENT_COUNT endorsements included + let second_block_reward_for_endorsement_producer_address = + block_credit_part.saturating_mul_u64(ENDORSEMENT_COUNT as u64); // produced ENDORSEMENT_COUNT endorsements that were included in the block + assert_eq!( changes .ledger_changes .get_balance_or_else(&keypair2_address, || None), // Reward + 100 base from boilerplate Some( - creator_block_reward + second_block_reward_for_block_creator .saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) ) ); @@ -2529,21 +4043,11 @@ fn test_rewards() { .get_balance_or_else(&endorsement_producer_address, || None), // Reward + 100 base from boilerplate Some( - block_credit_part_endorsement_producer + second_block_reward_for_endorsement_producer_address .saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) ) ); - assert_eq!( - changes - .ledger_changes - .get_balance_or_else(&keypair_address, || None), - // Reward + 100 base from boilerplate - Some( - block_credit_part_parent_in_thread - .saturating_add(Amount::from_mantissa_scale(100, 0).unwrap()) - ) - ); finalized_waitpoint_trigger_handle_2.trigger(); }); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg.clone()); @@ -2556,11 +4060,6 @@ fn test_rewards() { &endorsement_producer, Slot::new(1, 0), )); - } else { - endorsements.push(ExecutionTestUniverse::create_endorsement( - &keypair, - Slot::new(1, 0), - )); } } let block = ExecutionTestUniverse::create_block( @@ -2570,7 +4069,7 @@ fn test_rewards() { endorsements, vec![], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, Some(GENESIS_KEY.clone())); finalized_waitpoint.wait(); // Second block @@ -2584,7 +4083,7 @@ fn test_rewards() { ], vec![], ); - universe.send_and_finalize(&keypair, block); + universe.send_and_finalize(&keypair, block, None); finalized_waitpoint.wait(); } @@ -2610,6 +4109,7 @@ fn chain_id() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); universe.deploy_bytecode_block( @@ -2654,6 +4154,7 @@ fn execution_trace() { None, None, None, + None, ); let mut universe = ExecutionTestUniverse::new(foreign_controllers, exec_cfg); @@ -2696,7 +4197,7 @@ fn execution_trace() { .collect(); assert_eq!(traces_1.len(), 1); // Only one op - assert_eq!(traces_1.first().unwrap().1.len(), 1); // Only one generate_event + assert_eq!(traces_1.first().unwrap().1.len(), 2); assert_eq!( traces_1.first().unwrap().1.first().unwrap().name, abi_name_1 @@ -2739,7 +4240,7 @@ fn execution_trace() { SCRuntimeAbiTraceValue { name: "to_address".to_string(), value: SCRuntimeAbiTraceType::String( - "AU12E6N5BFAdC2wyiBV6VJjqkWhpz1kLVp2XpbRdSnL1mKjCWT6oR".to_string() + "AU12o4xrpyL6mobLpuoJevPRbHXnJJRUJC5FyDwjQdhuxcPoTwz3h".to_string() ), }, SCRuntimeAbiTraceValue { @@ -2776,6 +4277,7 @@ fn execution_trace_nested() { None, None, None, + None, ); // let rt = tokio::runtime::Runtime::new().unwrap(); @@ -2846,15 +4348,22 @@ fn execution_trace_nested() { .cloned() .collect(); - // println!("params: {:?}", sub_call.first().unwrap().parameters); + println!("params: {:?}", sub_call.first().unwrap().parameters); + + let from_addr = match *CHAINID { + 77 => "AS1aEhosr1ebJJZ7cEMpSVKbY6xp1p4DdXabGb8fdkKKJ6WphGnR".to_string(), + 77658377 => "AS1Bc3kZ6LhPLJvXV4vcVJLFRExRFbkPWD7rCg9aAdQ1NGzRwgnu".to_string(), + _ => { + panic!("Invalid chain id for this test"); + } + }; + assert_eq!( sub_call.first().unwrap().parameters, vec![ SCRuntimeAbiTraceValue { name: "from_address".to_string(), - value: SCRuntimeAbiTraceType::String( - "AS1aEhosr1ebJJZ7cEMpSVKbY6xp1p4DdXabGb8fdkKKJ6WphGnR".to_string() - ) + value: SCRuntimeAbiTraceType::String(from_addr) }, SCRuntimeAbiTraceValue { name: "to_address".to_string(), @@ -2891,6 +4400,7 @@ fn test_dump_block() { None, None, None, + None, ); foreign_controllers .final_state @@ -2907,6 +4417,18 @@ fn test_dump_block() { Some(Amount::from_str("190").unwrap()) ); // 1.02 for the block rewards + let total_rewards = exec_cfg + .block_reward + .saturating_add(Amount::from_str("10").unwrap()); // add 10 MAS for fees + let rewards_for_block_creator = total_rewards + .checked_div_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_div factor is 0") + .saturating_mul_u64(3) + .saturating_add( + total_rewards + .checked_rem_u64(BLOCK_CREDIT_PART_COUNT) + .expect("critical: total_rewards checked_rem factor is 0"), + ); assert_eq!( changes.ledger_changes.get_balance_or_else( &Address::from_public_key( @@ -2914,11 +4436,7 @@ fn test_dump_block() { ), || None ), - Some( - exec_cfg - .block_reward - .saturating_add(Amount::from_str("10").unwrap()) // add 10 fee - ) + Some(rewards_for_block_creator) ); finalized_waitpoint_trigger_handle.trigger(); }); @@ -2949,13 +4467,13 @@ fn test_dump_block() { vec![], ); // store the block in storage - universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block); + universe.send_and_finalize(&KeyPair::from_str(TEST_SK_1).unwrap(), block, None); finalized_waitpoint.wait(); std::thread::sleep(Duration::from_secs(1)); // if the the storage backend for the dump-block feature is a rocksdb, this - // is mandatory (the db must be closed before we can reopen it to ckeck the + // is mandatory (the db must be closed before we can reopen it to check the // data) drop(universe); diff --git a/massa-execution-worker/src/tests/tests_active_history.rs b/massa-execution-worker/src/tests/tests_active_history.rs index 9d384b422fb..67838ce5809 100644 --- a/massa-execution-worker/src/tests/tests_active_history.rs +++ b/massa-execution-worker/src/tests/tests_active_history.rs @@ -43,6 +43,7 @@ fn test_active_history_deferred_credits() { state_changes: StateChanges { ledger_changes: Default::default(), async_pool_changes: Default::default(), + deferred_call_changes: Default::default(), pos_changes: PoSChanges { seed_bits: Default::default(), roll_changes: Default::default(), @@ -66,7 +67,7 @@ fn test_active_history_deferred_credits() { let active_history = ActiveHistory(VecDeque::from([exec_output_1])); assert_eq!( - active_history.get_adress_deferred_credit_for(&addr1, &slot2), + active_history.get_address_deferred_credit_for(&addr1, &slot2), Some(amount_a1_s2) ); diff --git a/massa-execution-worker/src/tests/universe.rs b/massa-execution-worker/src/tests/universe.rs index 0866d91931a..49f7ea2873f 100644 --- a/massa-execution-worker/src/tests/universe.rs +++ b/massa-execution-worker/src/tests/universe.rs @@ -4,6 +4,7 @@ use std::{ sync::Arc, }; +use crate::start_execution_worker; #[cfg(all(feature = "file_storage_backend", not(feature = "db_storage_backend")))] use crate::storage_backend::FileStorageBackend; #[cfg(feature = "db_storage_backend")] @@ -11,6 +12,9 @@ use crate::storage_backend::RocksDBStorageBackend; use cfg_if::cfg_if; use massa_db_exports::{MassaDBConfig, MassaDBController, ShareableMassaDBController}; use massa_db_worker::MassaDB; +use massa_event_cache::MockEventCacheControllerWrapper; +#[cfg(feature = "execution-trace")] +use massa_execution_exports::types_trace_info::SlotAbiCallStack; use massa_execution_exports::{ ExecutionBlockMetadata, ExecutionChannels, ExecutionConfig, ExecutionController, ExecutionError, ExecutionManager, SlotExecutionOutput, @@ -18,7 +22,8 @@ use massa_execution_exports::{ use massa_final_state::{FinalStateController, MockFinalStateController}; use massa_ledger_exports::MockLedgerControllerWrapper; use massa_metrics::MassaMetrics; -use massa_models::config::CHAINID; +use massa_models::config::{CHAINID, GENESIS_KEY}; +use massa_models::output_event::SCOutputEvent; use massa_models::{ address::Address, amount::Amount, @@ -43,16 +48,12 @@ use parking_lot::RwLock; use tempfile::TempDir; use tokio::sync::broadcast; -use crate::start_execution_worker; - -#[cfg(feature = "execution-trace")] -use massa_execution_exports::types_trace_info::SlotAbiCallStack; - pub struct ExecutionForeignControllers { pub selector_controller: Box, pub final_state: Arc>, pub ledger_controller: MockLedgerControllerWrapper, pub db: ShareableMassaDBController, + pub event_cache_controller: Box, } impl ExecutionForeignControllers { @@ -70,11 +71,85 @@ impl ExecutionForeignControllers { let db = Arc::new(RwLock::new( Box::new(MassaDB::new(db_config)) as Box<(dyn MassaDBController + 'static)> )); + + let mut event_cache_controller = MockEventCacheControllerWrapper::new(); + let events = Arc::new(std::sync::Mutex::new(Vec::new())); + let events_clone_1 = events.clone(); + let events_clone_2 = events.clone(); + + event_cache_controller.set_expectations(|controller| { + controller + .expect_save_events() + .withf(move |new_events| { + // Save events in memory + events_clone_1 + .lock() + .unwrap() + .extend(new_events.iter().cloned()); + true + }) + .return_const(()); + controller + .expect_get_filtered_sc_output_events() + .returning(move |filter| { + let events_ = events_clone_2.lock().unwrap().clone(); + events_ + .into_iter() + .filter(|evt| { + if let Some(start) = filter.start { + if evt.context.slot < start { + return false; + } + } + if let Some(end) = filter.end { + if evt.context.slot >= end { + return false; + } + } + if let Some(is_final) = filter.is_final { + if evt.context.is_final != is_final { + return false; + } + } + if let Some(is_error) = filter.is_error { + if evt.context.is_error != is_error { + return false; + } + } + + match ( + filter.original_caller_address, + evt.context.call_stack.front(), + ) { + (Some(addr1), Some(addr2)) if addr1 != *addr2 => return false, + (Some(_), None) => return false, + _ => (), + } + match (filter.emitter_address, evt.context.call_stack.back()) { + (Some(addr1), Some(addr2)) if addr1 != *addr2 => return false, + (Some(_), None) => return false, + _ => (), + } + match ( + filter.original_operation_id, + evt.context.origin_operation_id, + ) { + (Some(addr1), Some(addr2)) if addr1 != addr2 => return false, + (Some(_), None) => return false, + _ => (), + } + + true + }) + .collect::>() + }); + }); Self { selector_controller: Box::new(MockSelectorControllerWrapper::new()), ledger_controller: MockLedgerControllerWrapper::new(), final_state: Arc::new(RwLock::new(MockFinalStateController::new())), db, + event_cache_controller: Box::new(event_cache_controller), } } } @@ -141,6 +216,7 @@ impl TestUniverse for ExecutionTestUniverse { std::time::Duration::from_secs(5), ) .0, + controllers.event_cache_controller, #[cfg(feature = "dump-block")] block_storage_backend.clone(), ); @@ -249,10 +325,15 @@ impl ExecutionTestUniverse { ExecutionTestUniverse::create_block(keypair, slot, vec![operation], vec![], vec![]); // set our block as a final block so the message is sent - self.send_and_finalize(keypair, block); + self.send_and_finalize(keypair, block, None); } - pub fn send_and_finalize(&mut self, keypair: &KeyPair, block: SecureShareBlock) { + pub fn send_and_finalize( + &mut self, + keypair: &KeyPair, + block: SecureShareBlock, + same_thread_parent_creator_keypair: Option, + ) { // store the block in storage self.storage.store_block(block.clone()); let mut finalized_blocks: HashMap = Default::default(); @@ -262,7 +343,9 @@ impl ExecutionTestUniverse { block.id, ExecutionBlockMetadata { same_thread_parent_creator: Some(Address::from_public_key( - &keypair.get_public_key(), + &same_thread_parent_creator_keypair + .unwrap_or_else(|| keypair.clone()) + .get_public_key(), )), storage: Some(self.storage.clone()), }, @@ -325,7 +408,7 @@ fn init_execution_worker( storage: &Storage, execution_controller: Box, ) { - let genesis_keypair = KeyPair::generate(0).unwrap(); + let genesis_keypair: KeyPair = GENESIS_KEY.clone(); let genesis_addr = Address::from_public_key(&genesis_keypair.get_public_key()); let mut finalized_blocks: HashMap = HashMap::new(); let mut block_metadata: PreHashMap = PreHashMap::default(); diff --git a/massa-execution-worker/src/tests/wasm/deferred_call_exists.wasm b/massa-execution-worker/src/tests/wasm/deferred_call_exists.wasm new file mode 100644 index 00000000000..eb6ce3d0076 Binary files /dev/null and b/massa-execution-worker/src/tests/wasm/deferred_call_exists.wasm differ diff --git a/massa-execution-worker/src/tests/wasm/deferred_call_quote.wasm b/massa-execution-worker/src/tests/wasm/deferred_call_quote.wasm new file mode 100644 index 00000000000..5db659087ea Binary files /dev/null and b/massa-execution-worker/src/tests/wasm/deferred_call_quote.wasm differ diff --git a/massa-execution-worker/src/tests/wasm/deferred_call_register.wasm b/massa-execution-worker/src/tests/wasm/deferred_call_register.wasm new file mode 100644 index 00000000000..523f6a86a5b Binary files /dev/null and b/massa-execution-worker/src/tests/wasm/deferred_call_register.wasm differ diff --git a/massa-execution-worker/src/tests/wasm/deferred_call_register_fail.wasm b/massa-execution-worker/src/tests/wasm/deferred_call_register_fail.wasm new file mode 100644 index 00000000000..c8189911c8c Binary files /dev/null and b/massa-execution-worker/src/tests/wasm/deferred_call_register_fail.wasm differ diff --git a/massa-execution-worker/src/tests/wasm/execution_trace.wasm b/massa-execution-worker/src/tests/wasm/execution_trace.wasm index f07935f26ce..803ce350bcb 100644 Binary files a/massa-execution-worker/src/tests/wasm/execution_trace.wasm and b/massa-execution-worker/src/tests/wasm/execution_trace.wasm differ diff --git a/massa-execution-worker/src/tests/wasm/send_message_expired.wasm b/massa-execution-worker/src/tests/wasm/send_message_expired.wasm new file mode 100644 index 00000000000..edfcc3c351f Binary files /dev/null and b/massa-execution-worker/src/tests/wasm/send_message_expired.wasm differ diff --git a/massa-execution-worker/src/tests/wasm/send_message_expired_2.wasm b/massa-execution-worker/src/tests/wasm/send_message_expired_2.wasm new file mode 100644 index 00000000000..6ada5cee98c Binary files /dev/null and b/massa-execution-worker/src/tests/wasm/send_message_expired_2.wasm differ diff --git a/massa-execution-worker/src/worker.rs b/massa-execution-worker/src/worker.rs index 5fee6e65eac..c7bc715ed28 100644 --- a/massa-execution-worker/src/worker.rs +++ b/massa-execution-worker/src/worker.rs @@ -13,6 +13,7 @@ use crate::{ request_queue::RequestQueue, slot_sequencer::SlotSequencer, }; +use massa_event_cache::controller::EventCacheController; use massa_execution_exports::{ ExecutionBlockMetadata, ExecutionChannels, ExecutionConfig, ExecutionController, ExecutionError, ExecutionManager, ReadOnlyExecutionOutput, ReadOnlyExecutionRequest, @@ -258,6 +259,7 @@ pub fn start_execution_worker( channels: ExecutionChannels, wallet: Arc>, massa_metrics: MassaMetrics, + event_cache: Box, #[cfg(feature = "dump-block")] block_storage_backend: Arc>, ) -> (Box, Box) { if config.hd_cache_size < config.snip_amount { @@ -273,6 +275,7 @@ pub fn start_execution_worker( channels, wallet, massa_metrics, + event_cache, #[cfg(feature = "dump-block")] block_storage_backend, ))); diff --git a/massa-factory-exports/Cargo.toml b/massa-factory-exports/Cargo.toml index c78da522637..0ae6b9664fd 100644 --- a/massa-factory-exports/Cargo.toml +++ b/massa-factory-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_factory_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-factory-worker/Cargo.toml b/massa-factory-worker/Cargo.toml index 17de465a586..bafe75cf9e6 100644 --- a/massa-factory-worker/Cargo.toml +++ b/massa-factory-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_factory_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-factory-worker/src/block_factory.rs b/massa-factory-worker/src/block_factory.rs index b972b29a983..8c6fdfd5ab3 100644 --- a/massa-factory-worker/src/block_factory.rs +++ b/massa-factory-worker/src/block_factory.rs @@ -183,7 +183,7 @@ impl BlockFactoryWorker { .channels .pool .get_block_endorsements(&same_thread_parent_id, &slot); - //TODO: Do we want ot populate only with endorsement id in the future ? + //TODO: Do we want to populate only with endorsement id in the future ? let endorsements: Vec = { let endo_read = endo_storage.read_endorsements(); endorsements_ids diff --git a/massa-final-state/Cargo.toml b/massa-final-state/Cargo.toml index edd968cc685..7e8b1f6ef8e 100644 --- a/massa-final-state/Cargo.toml +++ b/massa-final-state/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_final_state" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" @@ -42,6 +42,7 @@ massa-proto-rs = { workspace = true, "features" = ["tonic"] } massa_versioning = { workspace = true } massa_time = { workspace = true } massa_hash = { workspace = true } +massa_deferred_calls = { workspace = true } serde_json = { workspace = true, optional = true } parking_lot = { workspace = true, "features" = [ diff --git a/massa-final-state/src/config.rs b/massa-final-state/src/config.rs index 49396734a91..0895734a439 100644 --- a/massa-final-state/src/config.rs +++ b/massa-final-state/src/config.rs @@ -3,6 +3,7 @@ //! This file defines a configuration structure containing all settings for final state management use massa_async_pool::AsyncPoolConfig; +use massa_deferred_calls::config::DeferredCallsConfig; use massa_executed_ops::{ExecutedDenunciationsConfig, ExecutedOpsConfig}; use massa_ledger_exports::LedgerConfig; use massa_pos_exports::PoSConfig; @@ -16,6 +17,8 @@ pub struct FinalStateConfig { pub ledger_config: LedgerConfig, /// asynchronous pool configuration pub async_pool_config: AsyncPoolConfig, + /// config for deferred calls + pub deferred_calls_config: DeferredCallsConfig, /// proof-of-stake configuration pub pos_config: PoSConfig, /// executed operations configuration diff --git a/massa-final-state/src/controller_trait.rs b/massa-final-state/src/controller_trait.rs index fd32c400695..52506c90454 100644 --- a/massa-final-state/src/controller_trait.rs +++ b/massa-final-state/src/controller_trait.rs @@ -1,5 +1,6 @@ use massa_async_pool::AsyncPool; use massa_db_exports::{DBBatch, ShareableMassaDBController}; +use massa_deferred_calls::DeferredCallRegistry; use massa_executed_ops::ExecutedDenunciations; use massa_hash::Hash; use massa_ledger_exports::LedgerController; @@ -90,4 +91,7 @@ pub trait FinalStateController: Send + Sync { /// Get mutable reference to MIP Store fn get_mip_store_mut(&mut self) -> &mut MipStore; + + /// Get deferred call registry + fn get_deferred_call_registry(&self) -> &DeferredCallRegistry; } diff --git a/massa-final-state/src/final_state.rs b/massa-final-state/src/final_state.rs index 0396a663764..faf31b34953 100644 --- a/massa-final-state/src/final_state.rs +++ b/massa-final-state/src/final_state.rs @@ -16,14 +16,15 @@ use massa_db_exports::{ EXECUTED_OPS_PREFIX, LEDGER_PREFIX, MIP_STORE_PREFIX, STATE_CF, }; use massa_db_exports::{EXECUTION_TRAIL_HASH_PREFIX, MIP_STORE_STATS_PREFIX, VERSIONING_CF}; +use massa_deferred_calls::DeferredCallRegistry; use massa_executed_ops::ExecutedDenunciations; use massa_executed_ops::ExecutedOps; use massa_hash::Hash; use massa_ledger_exports::LedgerController; -use massa_ledger_exports::SetOrKeep; use massa_models::operation::OperationId; use massa_models::slot::Slot; use massa_models::timeslots::get_block_slot_timestamp; +use massa_models::types::SetOrKeep; use massa_pos_exports::{PoSFinalState, SelectorController}; use massa_versioning::versioning::MipStore; use tracing::{debug, info, warn}; @@ -36,6 +37,8 @@ pub struct FinalState { pub ledger: Box, /// asynchronous pool containing messages sorted by priority and their data pub async_pool: AsyncPool, + /// deferred calls + pub deferred_call_registry: DeferredCallRegistry, /// proof of stake state containing cycle history and deferred credits pub pos_state: PoSFinalState, /// executed operations @@ -106,9 +109,13 @@ impl FinalState { let executed_denunciations = ExecutedDenunciations::new(config.executed_denunciations_config.clone(), db.clone()); + let deferred_call_registry = + DeferredCallRegistry::new(db.clone(), config.deferred_calls_config); + let mut final_state = FinalState { ledger, async_pool, + deferred_call_registry, pos_state, config, executed_ops, @@ -454,6 +461,9 @@ impl FinalState { self.executed_ops .apply_changes_to_batch(changes.executed_ops_changes, slot, &mut db_batch); + self.deferred_call_registry + .apply_changes_to_batch(changes.deferred_call_changes, &mut db_batch); + self.executed_denunciations.apply_changes_to_batch( changes.executed_denunciations_changes, slot, @@ -917,6 +927,10 @@ impl FinalStateController for FinalState { fn get_mip_store(&self) -> &MipStore { &self.mip_store } + + fn get_deferred_call_registry(&self) -> &DeferredCallRegistry { + &self.deferred_call_registry + } } #[cfg(test)] @@ -926,6 +940,7 @@ mod test { use std::str::FromStr; use std::sync::Arc; + use massa_deferred_calls::config::DeferredCallsConfig; use num::rational::Ratio; use parking_lot::RwLock; use tempfile::tempdir; @@ -935,11 +950,12 @@ mod test { use massa_db_worker::MassaDB; use massa_executed_ops::{ExecutedDenunciationsConfig, ExecutedOpsConfig}; use massa_hash::Hash; - use massa_ledger_exports::{LedgerChanges, LedgerConfig, LedgerEntryUpdate, SetUpdateOrDelete}; + use massa_ledger_exports::{LedgerChanges, LedgerConfig, LedgerEntryUpdate}; use massa_ledger_worker::FinalLedger; use massa_models::address::Address; use massa_models::amount::Amount; use massa_models::bytecode::Bytecode; + use massa_models::types::SetUpdateOrDelete; use massa_models::config::{ DENUNCIATION_EXPIRE_PERIODS, ENDORSEMENT_COUNT, KEEP_EXECUTED_HISTORY_EXTRA_PERIODS, @@ -995,9 +1011,11 @@ mod test { keep_executed_history_extra_periods: KEEP_EXECUTED_HISTORY_EXTRA_PERIODS, }; + let deferred_calls_config = DeferredCallsConfig::default(); let final_state_config = FinalStateConfig { ledger_config: ledger_config.clone(), async_pool_config, + deferred_calls_config, pos_config, executed_ops_config, executed_denunciations_config, diff --git a/massa-final-state/src/mapping_grpc.rs b/massa-final-state/src/mapping_grpc.rs index 1f42bf4f402..5c45b997634 100644 --- a/massa-final-state/src/mapping_grpc.rs +++ b/massa-final-state/src/mapping_grpc.rs @@ -2,7 +2,7 @@ use crate::StateChanges; use massa_async_pool::AsyncMessageId; -use massa_ledger_exports::{SetOrKeep, SetUpdateOrDelete}; +use massa_models::types::{SetOrKeep, SetUpdateOrDelete}; use massa_proto_rs::massa::model::v1 as grpc_model; impl From for grpc_model::StateChanges { diff --git a/massa-final-state/src/state_changes.rs b/massa-final-state/src/state_changes.rs index 85dd5b9b3df..ce1b02746a0 100644 --- a/massa-final-state/src/state_changes.rs +++ b/massa-final-state/src/state_changes.rs @@ -5,16 +5,21 @@ use massa_async_pool::{ AsyncPoolChanges, AsyncPoolChangesDeserializer, AsyncPoolChangesSerializer, }; +use massa_deferred_calls::{ + config::DeferredCallsConfig, + registry_changes::{ + DeferredCallRegistryChanges, DeferredRegistryChangesDeserializer, + DeferredRegistryChangesSerializer, + }, +}; use massa_executed_ops::{ ExecutedDenunciationsChanges, ExecutedDenunciationsChangesDeserializer, ExecutedDenunciationsChangesSerializer, ExecutedOpsChanges, ExecutedOpsChangesDeserializer, ExecutedOpsChangesSerializer, }; use massa_hash::{HashDeserializer, HashSerializer}; -use massa_ledger_exports::{ - LedgerChanges, LedgerChangesDeserializer, LedgerChangesSerializer, SetOrKeep, - SetOrKeepDeserializer, SetOrKeepSerializer, -}; +use massa_ledger_exports::{LedgerChanges, LedgerChangesDeserializer, LedgerChangesSerializer}; +use massa_models::types::{SetOrKeep, SetOrKeepDeserializer, SetOrKeepSerializer}; use massa_pos_exports::{PoSChanges, PoSChangesDeserializer, PoSChangesSerializer}; use massa_serialization::{Deserializer, SerializeError, Serializer}; use nom::{ @@ -31,6 +36,8 @@ pub struct StateChanges { pub ledger_changes: LedgerChanges, /// asynchronous pool changes pub async_pool_changes: AsyncPoolChanges, + /// deferred call changes + pub deferred_call_changes: DeferredCallRegistryChanges, /// roll state changes pub pos_changes: PoSChanges, /// executed operations changes @@ -45,6 +52,7 @@ pub struct StateChanges { pub struct StateChangesSerializer { ledger_changes_serializer: LedgerChangesSerializer, async_pool_changes_serializer: AsyncPoolChangesSerializer, + deferred_call_changes_serializer: DeferredRegistryChangesSerializer, pos_changes_serializer: PoSChangesSerializer, ops_changes_serializer: ExecutedOpsChangesSerializer, de_changes_serializer: ExecutedDenunciationsChangesSerializer, @@ -63,6 +71,7 @@ impl StateChangesSerializer { Self { ledger_changes_serializer: LedgerChangesSerializer::new(), async_pool_changes_serializer: AsyncPoolChangesSerializer::new(), + deferred_call_changes_serializer: DeferredRegistryChangesSerializer::new(), pos_changes_serializer: PoSChangesSerializer::new(), ops_changes_serializer: ExecutedOpsChangesSerializer::new(), de_changes_serializer: ExecutedDenunciationsChangesSerializer::new(), @@ -77,6 +86,8 @@ impl Serializer for StateChangesSerializer { .serialize(&value.ledger_changes, buffer)?; self.async_pool_changes_serializer .serialize(&value.async_pool_changes, buffer)?; + self.deferred_call_changes_serializer + .serialize(&value.deferred_call_changes, buffer)?; self.pos_changes_serializer .serialize(&value.pos_changes, buffer)?; self.ops_changes_serializer @@ -93,6 +104,7 @@ impl Serializer for StateChangesSerializer { pub struct StateChangesDeserializer { ledger_changes_deserializer: LedgerChangesDeserializer, async_pool_changes_deserializer: AsyncPoolChangesDeserializer, + deferred_call_changes_deserializer: DeferredRegistryChangesDeserializer, pos_changes_deserializer: PoSChangesDeserializer, ops_changes_deserializer: ExecutedOpsChangesDeserializer, de_changes_deserializer: ExecutedDenunciationsChangesDeserializer, @@ -118,6 +130,7 @@ impl StateChangesDeserializer { max_ops_changes_length: u64, endorsement_count: u32, max_de_changes_length: u64, + deferred_calls_config: DeferredCallsConfig, ) -> Self { Self { ledger_changes_deserializer: LedgerChangesDeserializer::new( @@ -133,6 +146,10 @@ impl StateChangesDeserializer { max_function_params_length, max_datastore_key_length as u32, ), + // todo max gas + deferred_call_changes_deserializer: DeferredRegistryChangesDeserializer::new( + deferred_calls_config, + ), pos_changes_deserializer: PoSChangesDeserializer::new( thread_count, max_rolls_length, @@ -169,6 +186,9 @@ impl Deserializer for StateChangesDeserializer { context("Failed async_pool_changes deserialization", |input| { self.async_pool_changes_deserializer.deserialize(input) }), + context("Failed deferred_call_changes deserialization", |input| { + self.deferred_call_changes_deserializer.deserialize(input) + }), context("Failed roll_state_changes deserialization", |input| { self.pos_changes_deserializer.deserialize(input) }), @@ -191,6 +211,7 @@ impl Deserializer for StateChangesDeserializer { |( ledger_changes, async_pool_changes, + deferred_call_changes, pos_changes, executed_ops_changes, executed_denunciations_changes, @@ -198,6 +219,7 @@ impl Deserializer for StateChangesDeserializer { )| StateChanges { ledger_changes, async_pool_changes, + deferred_call_changes, pos_changes, executed_ops_changes, executed_denunciations_changes, @@ -211,12 +233,15 @@ impl Deserializer for StateChangesDeserializer { impl StateChanges { /// extends the current `StateChanges` with another one pub fn apply(&mut self, changes: StateChanges) { - use massa_ledger_exports::Applicable; + // TODO deferred_call_changes ? + use massa_models::types::Applicable; self.ledger_changes.apply(changes.ledger_changes); self.async_pool_changes.apply(changes.async_pool_changes); self.pos_changes.extend(changes.pos_changes); self.executed_ops_changes .extend(changes.executed_ops_changes); + self.executed_denunciations_changes + .extend(changes.executed_denunciations_changes); self.execution_trail_hash_change .apply(changes.execution_trail_hash_change); } @@ -228,11 +253,13 @@ mod test { use std::str::FromStr; use massa_async_pool::AsyncMessage; - use massa_ledger_exports::{LedgerEntryUpdate, SetUpdateOrDelete}; + use massa_deferred_calls::config::DeferredCallsConfig; + use massa_ledger_exports::LedgerEntryUpdate; use massa_models::address::Address; use massa_models::amount::Amount; use massa_models::bytecode::Bytecode; use massa_models::slot::Slot; + use massa_models::types::SetUpdateOrDelete; use massa_serialization::DeserializeError; use massa_models::config::{ @@ -317,6 +344,7 @@ mod test { MAX_EXECUTED_OPS_CHANGES_LENGTH, ENDORSEMENT_COUNT, MAX_DENUNCIATION_CHANGES_LENGTH, + DeferredCallsConfig::default(), ) .deserialize::(&serialized) .unwrap(); @@ -353,7 +381,7 @@ mod test { let mut datastore = BTreeMap::new(); datastore.insert( b"hello".to_vec(), - massa_ledger_exports::SetOrDelete::Set(b"world".to_vec()), + massa_models::types::SetOrDelete::Set(b"world".to_vec()), ); let ledger_entry = LedgerEntryUpdate { balance: SetOrKeep::Set(amount), diff --git a/massa-final-state/src/test_exports/config.rs b/massa-final-state/src/test_exports/config.rs index 5321441ef96..068db07a3ad 100644 --- a/massa-final-state/src/test_exports/config.rs +++ b/massa-final-state/src/test_exports/config.rs @@ -4,11 +4,13 @@ use std::path::PathBuf; +use massa_deferred_calls::config::DeferredCallsConfig; use num::rational::Ratio; use crate::{FinalState, FinalStateConfig}; use massa_async_pool::{AsyncPool, AsyncPoolConfig}; use massa_db_exports::ShareableMassaDBController; +use massa_deferred_calls::DeferredCallRegistry; use massa_executed_ops::{ ExecutedDenunciations, ExecutedDenunciationsConfig, ExecutedOps, ExecutedOpsConfig, }; @@ -34,6 +36,10 @@ impl FinalState { FinalState { ledger: Box::new(FinalLedger::new(config.ledger_config.clone(), db.clone())), async_pool: AsyncPool::new(config.async_pool_config.clone(), db.clone()), + deferred_call_registry: DeferredCallRegistry::new( + db.clone(), + config.deferred_calls_config, + ), pos_state, executed_ops: ExecutedOps::new(config.executed_ops_config.clone(), db.clone()), executed_denunciations: ExecutedDenunciations::new( @@ -62,6 +68,7 @@ impl Default for FinalStateConfig { FinalStateConfig { ledger_config: LedgerConfig::default(), async_pool_config: AsyncPoolConfig::default(), + deferred_calls_config: DeferredCallsConfig::default(), executed_ops_config: ExecutedOpsConfig { thread_count: THREAD_COUNT, keep_executed_history_extra_periods: KEEP_EXECUTED_HISTORY_EXTRA_PERIODS, diff --git a/massa-final-state/src/test_exports/mock.rs b/massa-final-state/src/test_exports/mock.rs index 741a2f27589..349820152e8 100644 --- a/massa-final-state/src/test_exports/mock.rs +++ b/massa-final-state/src/test_exports/mock.rs @@ -9,10 +9,12 @@ use std::{ sync::Arc, }; +use crate::{controller_trait::FinalStateController, FinalState, FinalStateConfig}; use massa_async_pool::AsyncPool; use massa_db_exports::{ DBBatch, MassaIteratorMode, ShareableMassaDBController, METADATA_CF, STATE_CF, STATE_HASH_KEY, }; +use massa_deferred_calls::{config::DeferredCallsConfig, DeferredCallRegistry}; use massa_executed_ops::{ExecutedDenunciations, ExecutedOps}; use massa_ledger_exports::{LedgerConfig, LedgerController, LedgerEntry, LedgerError}; use massa_ledger_worker::FinalLedger; @@ -27,14 +29,13 @@ use massa_versioning::versioning::MipStore; use parking_lot::RwLock; use tempfile::NamedTempFile; -use crate::{controller_trait::FinalStateController, FinalState, FinalStateConfig}; - #[allow(clippy::too_many_arguments)] /// Create a `FinalState` from pre-set values pub fn create_final_state( config: FinalStateConfig, ledger: Box, async_pool: AsyncPool, + deferred_call_registry: DeferredCallRegistry, pos_state: PoSFinalState, executed_ops: ExecutedOps, executed_denunciations: ExecutedDenunciations, @@ -45,6 +46,7 @@ pub fn create_final_state( config, ledger, async_pool, + deferred_call_registry, pos_state, executed_ops, executed_denunciations, @@ -202,6 +204,7 @@ pub fn get_sample_state( t0: T0, genesis_timestamp: *GENESIS_TIMESTAMP, ledger_backup_periods_interval: 10, + deferred_calls_config: DeferredCallsConfig::default(), }; let mut final_state = if last_start_period > 0 { diff --git a/massa-final-state/src/tests/scenarios.rs b/massa-final-state/src/tests/scenarios.rs index 0d862e30dd9..35b3ca8b53d 100644 --- a/massa-final-state/src/tests/scenarios.rs +++ b/massa-final-state/src/tests/scenarios.rs @@ -8,10 +8,9 @@ use crate::{ use massa_async_pool::{AsyncMessage, AsyncPoolChanges, AsyncPoolConfig}; use massa_db_exports::{DBBatch, MassaDBConfig, MassaDBController}; use massa_db_worker::MassaDB; +use massa_deferred_calls::config::DeferredCallsConfig; use massa_executed_ops::{ExecutedDenunciationsConfig, ExecutedOpsConfig}; -use massa_ledger_exports::{ - LedgerChanges, LedgerConfig, LedgerEntryUpdate, SetOrKeep, SetUpdateOrDelete, -}; +use massa_ledger_exports::{LedgerChanges, LedgerConfig, LedgerEntryUpdate}; use massa_ledger_worker::FinalLedger; use massa_models::address::Address; use massa_models::amount::Amount; @@ -22,6 +21,7 @@ use massa_models::config::{ MAX_DEFERRED_CREDITS_LENGTH, MAX_DENUNCIATIONS_PER_BLOCK_HEADER, MAX_FUNCTION_NAME_LENGTH, MAX_PARAMETERS_SIZE, MAX_PRODUCTION_STATS_LENGTH, MAX_ROLLS_COUNT_LENGTH, POS_SAVED_CYCLES, T0, }; +use massa_models::types::{SetOrKeep, SetUpdateOrDelete}; use massa_models::{config::MAX_DATASTORE_VALUE_LENGTH, slot::Slot}; use massa_pos_exports::{PoSConfig, SelectorConfig}; use massa_pos_worker::start_selector_worker; @@ -94,6 +94,7 @@ fn create_final_state(temp_dir: &TempDir, reset_final_state: bool) -> Arc grpc.grpc_config.max_query_items_per_request { - return Err(GrpcError::InvalidArgument(format!("too many query items received. Only a maximum of {} operations are accepted per request", grpc.grpc_config.max_operation_ids_per_request))); + return Err(GrpcError::InvalidArgument(format!("too many query items received. Only a maximum of {} operations are accepted per request", grpc.grpc_config.max_query_items_per_request))); } let response = grpc diff --git a/massa-grpc/src/server.rs b/massa-grpc/src/server.rs index 5b0b5efcd2c..ac357397f19 100644 --- a/massa-grpc/src/server.rs +++ b/massa-grpc/src/server.rs @@ -12,8 +12,7 @@ use std::sync::{Arc, Condvar, Mutex}; use crate::config::{GrpcConfig, ServiceName}; use crate::error::GrpcError; use futures_util::FutureExt; -use hyper::service::Service; -use hyper::{Body, Method, Request, Response}; +use hyper::{Method, Request, Response}; use massa_consensus_exports::{ConsensusBroadcasts, ConsensusController}; use massa_execution_exports::{ExecutionChannels, ExecutionController}; use massa_pool_exports::{PoolBroadcasts, PoolController}; @@ -31,7 +30,7 @@ use massa_wallet::Wallet; use tokio::sync::oneshot; use tonic::body::BoxBody; use tonic::codegen::CompressionEncoding; -use tonic::transport::NamedService; +use tonic::server::NamedService; use tonic::transport::{Certificate, Identity, ServerTlsConfig}; use tonic_health::server::HealthReporter; use tonic_web::GrpcWebLayer; @@ -173,7 +172,7 @@ async fn massa_service_status(mut reporter: HealthReporter) { // Configure and start the gRPC API with the given service async fn serve(service: S, config: &GrpcConfig) -> Result where - S: Service, Response = Response, Error = Infallible> + S: tower_service::Service, Response = Response, Error = Infallible> + NamedService + Clone + Send @@ -206,7 +205,7 @@ where } let cert = std::fs::read_to_string(config.server_certificate_path.clone()) - .expect("error, failed to read server certificat"); + .expect("error, failed to read server certificate"); let key = std::fs::read_to_string(config.server_private_key_path.clone()) .expect("error, failed to read server private key"); @@ -241,7 +240,8 @@ where }; let reflection_service = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(file_descriptor_set) - .build()?; + //.build()?; + .build_v1()?; Some(reflection_service) } else { @@ -324,7 +324,7 @@ fn generate_self_signed_certificates(config: &GrpcConfig) { gen_signed_cert(&ca_cert, config.subject_alt_names.clone()) .expect("error, failed to generate cert"); std::fs::write(config.client_certificate_path.clone(), client_cert_pem) - .expect("error, failed to write client certificat"); + .expect("error, failed to write client certificate"); std::fs::write( config.client_private_key_path.clone(), client_private_key_pem, @@ -333,13 +333,13 @@ fn generate_self_signed_certificates(config: &GrpcConfig) { } std::fs::write(config.certificate_authority_root_path.clone(), ca_cert_pem) - .expect("error, failed to write certificat authority root"); + .expect("error, failed to write certificate authority root"); let (cert_pem, server_private_key_pem) = gen_signed_cert(&ca_cert, config.subject_alt_names.clone()) - .expect("error, failed to generate server certificat"); + .expect("error, failed to generate server certificate"); std::fs::write(config.server_certificate_path.clone(), cert_pem) - .expect("error, failed to write server certificat"); + .expect("error, failed to write server certificate"); std::fs::write( config.server_private_key_path.clone(), server_private_key_pem, diff --git a/massa-grpc/src/stream/mod.rs b/massa-grpc/src/stream/mod.rs index 8c212996e6d..d300120b8b6 100644 --- a/massa-grpc/src/stream/mod.rs +++ b/massa-grpc/src/stream/mod.rs @@ -20,5 +20,5 @@ pub mod send_blocks; pub mod send_endorsements; /// send operations pub mod send_operations; -/// subscribe tx througput +/// subscribe tx throughput pub mod tx_throughput; diff --git a/massa-grpc/src/tests/stream.rs b/massa-grpc/src/tests/stream.rs index 286900bcf54..c6a6286b0dd 100644 --- a/massa-grpc/src/tests/stream.rs +++ b/massa-grpc/src/tests/stream.rs @@ -42,14 +42,14 @@ async fn transactions_throughput_stream() { let mut exec_ctrl = Box::new(MockExecutionController::new()); exec_ctrl.expect_get_stats().returning(|| { let now = MassaTime::now(); - let futur = MassaTime::from_millis( + let future_ = MassaTime::from_millis( now.as_millis() .add(Duration::from_secs(30).as_millis() as u64), ); ExecutionStats { time_window_start: now, - time_window_end: futur, + time_window_end: future_, final_block_count: 10, final_executed_operations_count: 2000, active_cursor: massa_models::slot::Slot { @@ -69,14 +69,14 @@ async fn transactions_throughput_stream() { let mut exec_ctrl = Box::new(MockExecutionController::new()); exec_ctrl.expect_get_stats().returning(|| { let now = MassaTime::now(); - let futur = MassaTime::from_millis( + let future_ = MassaTime::from_millis( now.as_millis() .add(Duration::from_secs(30).as_millis() as u64), ); ExecutionStats { time_window_start: now, - time_window_end: futur, + time_window_end: future_, final_block_count: 10, final_executed_operations_count: 2000, active_cursor: massa_models::slot::Slot { diff --git a/massa-hash/Cargo.toml b/massa-hash/Cargo.toml index d0d9398f53e..e5a2e7d8d37 100644 --- a/massa-hash/Cargo.toml +++ b/massa-hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_hash" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-ledger-exports/Cargo.toml b/massa-ledger-exports/Cargo.toml index 3e863c329e2..664b9bae28a 100644 --- a/massa-ledger-exports/Cargo.toml +++ b/massa-ledger-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_ledger_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-ledger-exports/src/ledger_changes.rs b/massa-ledger-exports/src/ledger_changes.rs index 790c4f91122..829d205e949 100644 --- a/massa-ledger-exports/src/ledger_changes.rs +++ b/massa-ledger-exports/src/ledger_changes.rs @@ -3,16 +3,16 @@ //! This file provides structures representing changes to ledger entries use crate::ledger_entry::{LedgerEntry, LedgerEntryDeserializer, LedgerEntrySerializer}; -use crate::types::{ - Applicable, SetOrDelete, SetOrDeleteDeserializer, SetOrDeleteSerializer, SetOrKeep, - SetOrKeepDeserializer, SetOrKeepSerializer, SetUpdateOrDelete, SetUpdateOrDeleteDeserializer, - SetUpdateOrDeleteSerializer, -}; use massa_models::address::{Address, AddressDeserializer, AddressSerializer}; use massa_models::amount::{Amount, AmountDeserializer, AmountSerializer}; use massa_models::bytecode::{Bytecode, BytecodeDeserializer, BytecodeSerializer}; use massa_models::prehash::PreHashMap; use massa_models::serialization::{VecU8Deserializer, VecU8Serializer}; +use massa_models::types::{ + Applicable, SetOrDelete, SetOrDeleteDeserializer, SetOrDeleteSerializer, SetOrKeep, + SetOrKeepDeserializer, SetOrKeepSerializer, SetUpdateOrDelete, SetUpdateOrDeleteDeserializer, + SetUpdateOrDeleteSerializer, +}; use massa_serialization::{ Deserializer, SerializeError, Serializer, U64VarIntDeserializer, U64VarIntSerializer, }; @@ -66,8 +66,9 @@ impl Serializer, SetOrDelete>>> for DatastoreUpdateSeri /// ## Example /// ```rust /// use std::collections::BTreeMap; - /// use massa_ledger_exports::{DatastoreUpdateSerializer, SetOrDelete}; + /// use massa_ledger_exports::{DatastoreUpdateSerializer}; /// use massa_serialization::Serializer; + /// use massa_models::types::SetOrDelete; /// /// let serializer = DatastoreUpdateSerializer::new(); /// let mut buffer = Vec::new(); @@ -131,8 +132,9 @@ impl Deserializer, SetOrDelete>>> for DatastoreUpdateDe /// ## Example /// ```rust /// use std::collections::BTreeMap; - /// use massa_ledger_exports::{DatastoreUpdateDeserializer, DatastoreUpdateSerializer, SetOrDelete}; + /// use massa_ledger_exports::{DatastoreUpdateDeserializer, DatastoreUpdateSerializer}; /// use massa_serialization::{Serializer, Deserializer, DeserializeError}; + /// use massa_models::types::SetOrDelete; /// /// let serializer = DatastoreUpdateSerializer::new(); /// let deserializer = DatastoreUpdateDeserializer::new(255, 255, 255); @@ -203,7 +205,8 @@ impl Serializer for LedgerEntryUpdateSerializer { /// use massa_models::{prehash::PreHashMap, address::Address, amount::Amount, bytecode::Bytecode}; /// use std::str::FromStr; /// use std::collections::BTreeMap; - /// use massa_ledger_exports::{SetOrDelete, SetOrKeep, LedgerEntryUpdate, LedgerEntryUpdateSerializer}; + /// use massa_models::types::{SetOrDelete, SetOrKeep}; + /// use massa_ledger_exports::{LedgerEntryUpdate, LedgerEntryUpdateSerializer}; /// /// let key = "hello world".as_bytes().to_vec(); /// let mut datastore = BTreeMap::default(); @@ -270,8 +273,9 @@ impl Deserializer for LedgerEntryUpdateDeserializer { /// use massa_serialization::{Deserializer, Serializer, DeserializeError}; /// use massa_models::{prehash::PreHashMap, address::Address, amount::Amount, bytecode::Bytecode}; /// use std::str::FromStr; + /// use massa_models::types::{SetOrDelete, SetOrKeep}; /// use std::collections::BTreeMap; - /// use massa_ledger_exports::{SetOrDelete, SetOrKeep, LedgerEntryUpdate, LedgerEntryUpdateSerializer, LedgerEntryUpdateDeserializer}; + /// use massa_ledger_exports::{LedgerEntryUpdate, LedgerEntryUpdateSerializer, LedgerEntryUpdateDeserializer}; /// /// let key = "hello world".as_bytes().to_vec(); /// let mut datastore = BTreeMap::default(); @@ -369,8 +373,9 @@ impl Serializer for LedgerChangesSerializer { /// ## Example /// ``` /// use massa_serialization::Serializer; - /// use massa_ledger_exports::{LedgerEntry, SetUpdateOrDelete, LedgerChanges, LedgerChangesSerializer}; + /// use massa_ledger_exports::{LedgerEntry, LedgerChanges, LedgerChangesSerializer}; /// use std::str::FromStr; + /// use massa_models::types::{SetUpdateOrDelete}; /// use std::collections::BTreeMap; /// use massa_models::{amount::Amount, address::Address, bytecode::Bytecode}; /// @@ -451,9 +456,10 @@ impl Deserializer for LedgerChangesDeserializer { /// ## Example /// ``` /// use massa_serialization::{Deserializer, Serializer, DeserializeError}; - /// use massa_ledger_exports::{LedgerEntry, SetUpdateOrDelete, LedgerChanges, LedgerChangesSerializer, LedgerChangesDeserializer}; + /// use massa_ledger_exports::{LedgerEntry, LedgerChanges, LedgerChangesSerializer, LedgerChangesDeserializer}; /// use std::str::FromStr; /// use std::collections::BTreeMap; + /// use massa_models::types::{SetUpdateOrDelete}; /// use massa_models::{amount::Amount, address::Address, bytecode::Bytecode}; /// /// let key = "hello world".as_bytes().to_vec(); diff --git a/massa-ledger-exports/src/ledger_entry.rs b/massa-ledger-exports/src/ledger_entry.rs index abac9332072..52f4064d5e0 100644 --- a/massa-ledger-exports/src/ledger_entry.rs +++ b/massa-ledger-exports/src/ledger_entry.rs @@ -3,10 +3,10 @@ //! This file defines the structure representing an entry in the `FinalLedger` use crate::ledger_changes::LedgerEntryUpdate; -use crate::types::{Applicable, SetOrDelete}; use massa_models::amount::{Amount, AmountDeserializer, AmountSerializer}; use massa_models::bytecode::{Bytecode, BytecodeDeserializer, BytecodeSerializer}; use massa_models::datastore::{Datastore, DatastoreDeserializer, DatastoreSerializer}; +use massa_models::types::{Applicable, SetOrDelete}; use massa_serialization::{Deserializer, SerializeError, Serializer}; use nom::error::{context, ContextError, ParseError}; use nom::sequence::tuple; diff --git a/massa-ledger-exports/src/lib.rs b/massa-ledger-exports/src/lib.rs index 9a2dadd65eb..67a099a9c5e 100644 --- a/massa-ledger-exports/src/lib.rs +++ b/massa-ledger-exports/src/lib.rs @@ -9,7 +9,6 @@ mod key; mod ledger_changes; mod ledger_entry; mod mapping_grpc; -mod types; pub use config::LedgerConfig; pub use controller::LedgerController; @@ -24,10 +23,6 @@ pub use ledger_changes::{ LedgerEntryUpdateDeserializer, LedgerEntryUpdateSerializer, }; pub use ledger_entry::{LedgerEntry, LedgerEntryDeserializer, LedgerEntrySerializer}; -pub use types::{ - Applicable, SetOrDelete, SetOrKeep, SetOrKeepDeserializer, SetOrKeepSerializer, - SetUpdateOrDelete, SetUpdateOrDeleteDeserializer, SetUpdateOrDeleteSerializer, -}; #[cfg(feature = "test-exports")] pub mod test_exports; diff --git a/massa-ledger-exports/src/mapping_grpc.rs b/massa-ledger-exports/src/mapping_grpc.rs index 04aa055dd51..48b9f015dfd 100644 --- a/massa-ledger-exports/src/mapping_grpc.rs +++ b/massa-ledger-exports/src/mapping_grpc.rs @@ -1,6 +1,8 @@ // Copyright (c) 2023 MASSA LABS -use crate::{LedgerEntry, LedgerEntryUpdate, SetOrDelete, SetOrKeep}; +use crate::{LedgerEntry, LedgerEntryUpdate}; + +use massa_models::types::{SetOrDelete, SetOrKeep}; use massa_proto_rs::massa::model::v1 as grpc_model; impl From for grpc_model::LedgerEntry { diff --git a/massa-ledger-worker/Cargo.toml b/massa-ledger-worker/Cargo.toml index 2e8523d0afe..dfc7934061c 100644 --- a/massa-ledger-worker/Cargo.toml +++ b/massa-ledger-worker/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "massa_ledger_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" [features] -test-exports = ["tempfile", "massa_models/test-exports", "massa_ledger_exports/test-exports", "massa_db_worker", "parking_lot"] +test-exports = ["tempfile", "massa_models/test-exports", "massa_ledger_exports/test-exports", "massa_db_worker"] [dependencies] serde_json = {workspace = true} # BOM UPGRADE Revert to "1.0" if problem @@ -15,7 +15,7 @@ massa_models = {workspace = true} massa_serialization = {workspace = true} massa_db_exports = {workspace = true} massa_db_worker = {workspace = true, "optional" = true} -parking_lot = {workspace = true, "features" = ["deadlock_detection"], "optional" = true} +parking_lot = {workspace = true, "features" = ["deadlock_detection"]} [dev-dependencies] massa_signature = {workspace = true} diff --git a/massa-ledger-worker/src/ledger_db.rs b/massa-ledger-worker/src/ledger_db.rs index 56886211cb3..a2e517d6f05 100644 --- a/massa-ledger-worker/src/ledger_db.rs +++ b/massa-ledger-worker/src/ledger_db.rs @@ -3,19 +3,21 @@ //! Module to interact with the disk ledger use massa_db_exports::{ - DBBatch, MassaDirection, MassaIteratorMode, ShareableMassaDBController, CRUD_ERROR, - KEY_SER_ERROR, LEDGER_PREFIX, STATE_CF, + DBBatch, MassaDBController, MassaDirection, MassaIteratorMode, ShareableMassaDBController, + CRUD_ERROR, KEY_SER_ERROR, LEDGER_PREFIX, STATE_CF, }; use massa_ledger_exports::*; use massa_models::amount::AmountDeserializer; use massa_models::bytecode::BytecodeDeserializer; use massa_models::datastore::get_prefix_bounds; +use massa_models::types::{SetOrDelete, SetOrKeep, SetUpdateOrDelete}; use massa_models::{ address::Address, amount::AmountSerializer, bytecode::BytecodeSerializer, slot::Slot, }; use massa_serialization::{ DeserializeError, Deserializer, Serializer, U64VarIntDeserializer, U64VarIntSerializer, }; +use parking_lot::{lock_api::RwLockReadGuard, RawRwLock}; use std::collections::{BTreeSet, HashMap}; use std::fmt::Debug; @@ -286,6 +288,9 @@ impl LedgerDB { fn put_entry(&self, addr: &Address, ledger_entry: LedgerEntry, batch: &mut DBBatch) { let db = self.db.read(); + // Ensures any potential previous entry is fully deleted. + delete_datastore_entries(addr, &db, batch); + // Version //TODO: Get version number from parameters let mut bytes_version = Vec::new(); @@ -456,18 +461,29 @@ impl LedgerDB { .expect(KEY_SER_ERROR); db.delete_key(batch, serialized_key); - // datastore - let key_prefix = datastore_prefix_from_address(addr, &[]); + delete_datastore_entries(addr, &db, batch); + } +} - for (serialized_key, _) in db - .iterator_cf( - STATE_CF, - MassaIteratorMode::From(&key_prefix, MassaDirection::Forward), - ) - .take_while(|(key, _)| key <= &end_prefix(&key_prefix).unwrap()) - { - db.delete_key(batch, serialized_key.to_vec()); - } +// Helper function to delete all datastore entries for a given address +// Needs to be called in put entry and delete entry +// Note: This function takes a lock on the DB to avoid multiple reads. +fn delete_datastore_entries( + addr: &Address, + db: &RwLockReadGuard>, + batch: &mut std::collections::BTreeMap, Option>>, +) { + // datastore + let key_prefix = datastore_prefix_from_address(addr, &[]); + + for (serialized_key, _) in db + .iterator_cf( + STATE_CF, + MassaIteratorMode::From(&key_prefix, MassaDirection::Forward), + ) + .take_while(|(key, _)| key < &end_prefix(&key_prefix).unwrap()) + { + db.delete_key(batch, serialized_key.to_vec()); } } @@ -560,7 +576,8 @@ mod tests { use massa_db_exports::{MassaDBConfig, MassaDBController, STATE_HASH_INITIAL_BYTES}; use massa_db_worker::MassaDB; use massa_hash::HashXof; - use massa_ledger_exports::{LedgerEntry, LedgerEntryUpdate, SetOrKeep}; + use massa_ledger_exports::{LedgerEntry, LedgerEntryUpdate}; + use massa_models::types::SetOrKeep; use massa_models::{ address::Address, amount::{Amount, AmountDeserializer}, diff --git a/massa-logging/Cargo.toml b/massa-logging/Cargo.toml index 69ed1b0b0ee..448e3795beb 100644 --- a/massa-logging/Cargo.toml +++ b/massa-logging/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_logging" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-metrics/Cargo.toml b/massa-metrics/Cargo.toml index 1b2a9a1c080..96373eb4cbf 100644 --- a/massa-metrics/Cargo.toml +++ b/massa-metrics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_metrics" -version = "2.4.0" +version = "2.5.0" edition = "2021" [features] @@ -9,7 +9,8 @@ sandbox = [] [dependencies] prometheus = {workspace = true, "features" = ["process"]} -hyper = {workspace = true, "features" = ["server", "http1"]} # BOM UPGRADE Revert to {"version": "0.14.26", "features": ["server", "tcp", "http1"]} if problem +# hyper = {workspace = true, "features" = ["server", "http1"]} # BOM UPGRADE Revert to {"version": "0.14.26", "features": ["server", "tcp", "http1"]} if problem +hyper = {"version" = "0.14.26", "features" = ["server", "tcp", "http1"]} tokio = {workspace = true, "features" = ["full"]} # BOM UPGRADE Revert to {"version": "1.28.0", "features": ["full"]} if problem lazy_static = {workspace = true} tracing = {workspace = true} diff --git a/massa-metrics/src/lib.rs b/massa-metrics/src/lib.rs index fb591833d1e..98da5ace180 100644 --- a/massa-metrics/src/lib.rs +++ b/massa-metrics/src/lib.rs @@ -30,6 +30,33 @@ lazy_static! { register_int_gauge!("blocks_storage_counter", "blocks storage counter len").unwrap(); static ref ENDORSEMENTS_COUNTER: IntGauge = register_int_gauge!("endorsements_storage_counter", "endorsements storage counter len").unwrap(); + + static ref DEFERRED_CALL_REGISTERED: IntGauge = register_int_gauge!( + "deferred_calls_registered", "number of deferred calls registered" ).unwrap(); + + static ref DEFERRED_CALLS_TOTAL_GAS: IntGauge = register_int_gauge!( + "deferred_calls_total_gas", "total gas used by deferred calls" ).unwrap(); + +} + +pub fn dec_deferred_calls_registered() { + DEFERRED_CALL_REGISTERED.dec(); +} + +pub fn inc_deferred_calls_registered() { + DEFERRED_CALL_REGISTERED.inc(); +} + +pub fn set_deferred_calls_registered(val: usize) { + DEFERRED_CALL_REGISTERED.set(val as i64); +} + +pub fn set_deferred_calls_total_gas(val: u128) { + DEFERRED_CALLS_TOTAL_GAS.set(val as i64); +} + +pub fn get_deferred_calls_registered() -> i64 { + DEFERRED_CALL_REGISTERED.get() } pub fn set_blocks_counter(val: usize) { @@ -172,7 +199,14 @@ pub struct MassaMetrics { // peer bandwidth (bytes sent, bytes received) peers_bandwidth: Arc>>, + // network versions votes + network_versions_votes: Arc>>, + pub tick_delay: Duration, + + // deferred calls metrics + deferred_calls_executed: IntCounter, + deferred_calls_failed: IntCounter, } impl MassaMetrics { @@ -199,6 +233,8 @@ impl MassaMetrics { consensus_vec.push(gauge); } + set_deferred_calls_registered(0); + // set available processors let process_available_processors = IntGauge::new("process_available_processors", "number of processors") @@ -406,6 +442,15 @@ impl MassaMetrics { ) .unwrap(); + let deferred_calls_executed = IntCounter::new( + "deferred_calls_executed", + "number of deferred calls executed", + ) + .unwrap(); + + let deferred_calls_failed = + IntCounter::new("deferred_calls_failed", "number of deferred calls failed").unwrap(); + let mut stopper = MetricsStopper::default(); if enabled { @@ -458,6 +503,8 @@ impl MassaMetrics { let _ = prometheus::register(Box::new(current_time_period.clone())); let _ = prometheus::register(Box::new(current_time_thread.clone())); let _ = prometheus::register(Box::new(block_slot_delay.clone())); + let _ = prometheus::register(Box::new(deferred_calls_executed.clone())); + let _ = prometheus::register(Box::new(deferred_calls_failed.clone())); stopper = server::bind_metrics(addr); } @@ -513,7 +560,10 @@ impl MassaMetrics { final_cursor_thread, final_cursor_period, peers_bandwidth: Arc::new(RwLock::new(HashMap::new())), + network_versions_votes: Arc::new(RwLock::new(HashMap::new())), tick_delay, + deferred_calls_executed, + deferred_calls_failed, }, stopper, ) @@ -702,6 +752,49 @@ impl MassaMetrics { self.block_slot_delay.observe(delay); } + pub fn inc_deferred_calls_executed(&self) { + self.deferred_calls_executed.inc(); + } + + pub fn inc_deferred_calls_failed(&self) { + self.deferred_calls_failed.inc(); + } + + // Update the network version vote metrics + pub fn update_network_version_vote(&self, data: HashMap) { + if self.enabled { + let mut write = self.network_versions_votes.write().unwrap(); + + { + let missing_version = write + .keys() + .filter(|key| !data.contains_key(key)) + .cloned() + .collect::>(); + + for key in missing_version { + if let Some(counter) = write.remove(&key) { + if let Err(e) = prometheus::unregister(Box::new(counter)) { + warn!("Failed to unregister network_version_vote_{} : {}", key, e); + } + } + } + } + + for (version, count) in data.into_iter() { + if let Some(actual_counter) = write.get_mut(&version) { + actual_counter.set(count as i64); + } else { + let label = format!("network_version_votes_{}", version); + let counter = IntGauge::new(label, "vote counter for network version").unwrap(); + counter.set(count as i64); + let _ = prometheus::register(Box::new(counter.clone())); + write.insert(version, counter); + } + } + } + } + /// Update the bandwidth metrics for all peers /// HashMap pub fn update_peers_tx_rx(&self, data: HashMap) { diff --git a/massa-metrics/src/server.rs b/massa-metrics/src/server.rs index 0bf27215ff8..09b48d716d4 100644 --- a/massa-metrics/src/server.rs +++ b/massa-metrics/src/server.rs @@ -1,11 +1,7 @@ use std::net::SocketAddr; -use hyper::{ - body::Body, - header::CONTENT_TYPE, - service::{make_service_fn, service_fn}, - Request, Response, -}; +use hyper::service::make_service_fn; +use hyper::{body::Body, header::CONTENT_TYPE, service::service_fn, Request, Response}; use prometheus::{Encoder, TextEncoder}; use tracing::{error, info}; diff --git a/massa-models/Cargo.toml b/massa-models/Cargo.toml index 5c3ba74a705..696634ea8f6 100644 --- a/massa-models/Cargo.toml +++ b/massa-models/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_models" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-models/src/block.rs b/massa-models/src/block.rs index 74a536fe4c1..836f61f406f 100644 --- a/massa-models/src/block.rs +++ b/massa-models/src/block.rs @@ -62,7 +62,7 @@ pub struct FilledBlock { pub operations: Vec<(OperationId, Option)>, } -/// Block with assosciated meta-data and interfaces allowing trust of data in untrusted network +/// Block with associated meta-data and interfaces allowing trust of data in untrusted network pub type SecureShareBlock = SecureShare; impl SecureShareContent for Block { diff --git a/massa-models/src/block_id.rs b/massa-models/src/block_id.rs index 3bf85225033..ff5689df05d 100644 --- a/massa-models/src/block_id.rs +++ b/massa-models/src/block_id.rs @@ -285,7 +285,7 @@ mod test { #[test] fn test_block_id_errors() { - let actual_error = BlockId::from_str("SomeUnvalidBlockId") + let actual_error = BlockId::from_str("SomeInvalidBlockId") .unwrap_err() .to_string(); let expected_error = "block id parsing error".to_string(); diff --git a/massa-models/src/config/constants.rs b/massa-models/src/config/constants.rs index 76aa82c1e18..7d284a5b0a8 100644 --- a/massa-models/src/config/constants.rs +++ b/massa-models/src/config/constants.rs @@ -70,9 +70,9 @@ lazy_static::lazy_static! { /// node version pub static ref VERSION: Version = { if cfg!(feature = "sandbox") { - "SAND.2.4" + "SAND.2.5" } else { - "MAIN.2.4" + "MAIN.2.5" } .parse() .unwrap() @@ -196,11 +196,60 @@ pub const MAX_DATASTORE_ENTRY_COUNT: u64 = u64::MAX; /// Maximum number of key/values in the datastore of a `ExecuteSC` operation pub const MAX_OPERATION_DATASTORE_ENTRY_COUNT: u64 = 128; /// Maximum length function name in call SC -pub const MAX_FUNCTION_NAME_LENGTH: u16 = u16::MAX; +pub const MAX_FUNCTION_NAME_LENGTH: u16 = 255; /// Maximum size of parameters in call SC pub const MAX_PARAMETERS_SIZE: u32 = 10_000_000; /// Maximum length of `rng_seed` in thread cycle pub const MAX_RNG_SEED_LENGTH: u32 = PERIODS_PER_CYCLE.saturating_mul(THREAD_COUNT as u64) as u32; + +/// CondomMiddleware limits +/// see test_condom_middleware_calibrate in massa-sc-runtime for origin of +/// values. +/// Maximum number of function defined in a smart contract +const MAX_RUNTIME_MODULE_DEFINED_FUNCTIONS: usize = 512; +/// Maximum number of function used by a smart contract +pub const MAX_RUNTIME_MODULE_FUNCTIONS: usize = + MAX_RUNTIME_MODULE_DEFINED_FUNCTIONS + MAX_RUNTIME_MODULE_FUNCTION_IMPORTS; +/// Maximum number of arguments to a function +const MAX_RUNTIME_MODULE_FUNCTION_ARGS: usize = 64; +/// Maximum number of value a function can return +const MAX_RUNTIME_MODULE_FUNCTION_RETURN_VALUES: usize = 8; +/// Maximum signature length (total number of arguments and return values) for a +/// function of a smart contract module +pub const MAX_RUNTIME_MODULE_SIGNATURE_LEN: usize = + MAX_RUNTIME_MODULE_FUNCTION_ARGS + MAX_RUNTIME_MODULE_FUNCTION_RETURN_VALUES; +/// Maximum length for the name of a function defined in a smart contract +pub const MAX_RUNTIME_MODULE_FUNCTION_NAME_LEN: usize = 256; +/// Maximum length for the name of a smart contract +pub const MAX_RUNTIME_MODULE_NAME_LEN: usize = 256; +/// Maximum number of custom section data +pub const MAX_RUNTIME_MODULE_CUSTOM_SECTION_LEN: usize = 1; +/// Maximum length for the custom section data +pub const MAX_RUNTIME_MODULE_CUSTOM_SECTION_DATA_LEN: usize = 1_000_000; +/// Maximum number of functions a module can import +const MAX_RUNTIME_MODULE_FUNCTION_IMPORTS: usize = 256; +/// Maximum number of memory a module can import +const MAX_RUNTIME_MODULE_MEMORY_IMPORTS: usize = 1; +/// Maximum number of elements a module can import +pub const MAX_RUNTIME_MODULE_IMPORTS: usize = + MAX_RUNTIME_MODULE_FUNCTION_IMPORTS + MAX_RUNTIME_MODULE_MEMORY_IMPORTS; +/// Maximum number of table initializer in a smart contract +pub const MAX_RUNTIME_MODULE_TABLE_INITIALIZER: usize = MAX_RUNTIME_MODULE_DEFINED_FUNCTIONS; +/// Maximum number of passive element in a smart contract +pub const MAX_RUNTIME_MODULE_PASSIVE_ELEMENT: usize = MAX_RUNTIME_MODULE_DEFINED_FUNCTIONS; +/// Maximum number of passive data in a smart contract +pub const MAX_RUNTIME_MODULE_PASSIVE_DATA: usize = 512; +/// Maximum number of global initializer in a smart contract +pub const MAX_RUNTIME_MODULE_GLOBAL_INITIALIZER: usize = 512; +/// Maximum number of table in a smart contract +pub const MAX_RUNTIME_MODULE_TABLE: usize = 16; +/// Maximum number of memories in a smart contract +/// - only 1 supported so far (cf specification) +pub const MAX_RUNTIME_MODULE_MEMORIES: usize = 1; +/// Maximum number of exports for a smart contract module (function and globals) +pub const MAX_RUNTIME_MODULE_EXPORTS: usize = + MAX_RUNTIME_MODULE_DEFINED_FUNCTIONS + MAX_RUNTIME_MODULE_GLOBAL_INITIALIZER; + // *********************** // Bootstrap constants // @@ -260,7 +309,11 @@ pub const ASYNC_MSG_CST_GAS_COST: u64 = 750_000; /// Gas used by a base operation (transaction, roll buy, roll sell) pub const BASE_OPERATION_GAS_COST: u64 = 800_000; // approx MAX_GAS_PER_BLOCK / MAX_OPERATIONS_PER_BLOCK /// Maximum event size in bytes -pub const MAX_EVENT_DATA_SIZE: usize = 50_000; +pub const MAX_EVENT_DATA_SIZE: usize = 512; +/// Maximum event number that can be emitted for an operation +pub const MAX_EVENT_PER_OPERATION: usize = 25; +/// Maximum number of recursion for calls +pub const MAX_RECURSIVE_CALLS_DEPTH: u16 = 25; // // Constants used in network @@ -338,6 +391,26 @@ pub const MAX_DENUNCIATIONS_PER_BLOCK_HEADER: u32 = 128; pub const ROLL_COUNT_TO_SLASH_ON_DENUNCIATION: u64 = 1; /// Maximum size of executed denunciations pub const MAX_DENUNCIATION_CHANGES_LENGTH: u64 = 1_000; +/// Maximum size of deferred call pool changes +// TODO define this value +// TODO: set to a reasonable value max pool changes +pub const DEFERRED_CALL_MAX_POOL_CHANGES: u64 = 100_000; +/// Maximum size of deferred call future slots (1 week) +pub const DEFERRED_CALL_MAX_FUTURE_SLOTS: u64 = 1209600; +/// maximum gas for deferred call +pub const DEFERRED_CALL_MAX_ASYNC_GAS: u64 = MAX_ASYNC_GAS; +/// max change denominator +pub const DEFERRED_CALL_BASE_FEE_MAX_CHANGE_DENOMINATOR: usize = 1250; +/// deferred call min gas increment (1 nanomassa) +pub const DEFERRED_CALL_MIN_GAS_INCREMENT: u64 = 1; +/// deferred call min gas cost (10 nanomassa) +pub const DEFERRED_CALL_MIN_GAS_COST: u64 = 10; +/// deferred call global overbooking penalty +pub const DEFERRED_CALL_GLOBAL_OVERBOOKING_PENALTY: Amount = Amount::from_raw(1_000_000_000); +/// deferred call slot overbooking penalty +pub const DEFERRED_CALL_SLOT_OVERBOOKING_PENALTY: Amount = Amount::from_raw(1_000_000_000 / 10_000); +/// deferred call call gas cost +pub const DEFERRED_CALL_CST_GAS_COST: u64 = 750_000; // Some checks at compile time that should not be ignored! #[allow(clippy::assertions_on_constants)] diff --git a/massa-models/src/deferred_calls.rs b/massa-models/src/deferred_calls.rs new file mode 100644 index 00000000000..c9c0a29536b --- /dev/null +++ b/massa-models/src/deferred_calls.rs @@ -0,0 +1,328 @@ +#![allow(unused_macros)] + +use std::{fmt::Debug, ops::Bound, str::FromStr}; + +use massa_serialization::{ + DeserializeError, Deserializer, SerializeError, Serializer, U64VarIntDeserializer, + U64VarIntSerializer, +}; +use nom::{ + error::{ContextError, ParseError}, + IResult, +}; +use transition::Versioned; + +use crate::{ + config::THREAD_COUNT, + error::ModelsError, + serialization::{VecU8Deserializer, VecU8Serializer}, + slot::{Slot, SlotDeserializer, SlotSerializer}, +}; + +const DEFERRED_CALL_ID_PREFIX: &str = "D"; + +#[allow(missing_docs)] +#[transition::versioned(versions("0"))] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct DeferredCallId(Vec); + +/// Serializer for `DeferredCallId` +#[derive(Default, Clone)] +pub struct DeferredCallIdSerializer { + bytes_serializer: VecU8Serializer, +} + +impl DeferredCallIdSerializer { + /// Serializes an `DeferredCallId` into a `Vec` + pub fn new() -> Self { + Self { + bytes_serializer: VecU8Serializer::new(), + } + } +} + +impl Serializer for DeferredCallIdSerializer { + fn serialize( + &self, + value: &DeferredCallId, + buffer: &mut Vec, + ) -> Result<(), SerializeError> { + match value { + DeferredCallId::DeferredCallIdV0(id) => { + self.bytes_serializer.serialize(&id.0, buffer)?; + } + } + Ok(()) + } +} + +/// Deserializer for `DeferredCallId` +#[derive(Clone)] +pub struct DeferredCallIdDeserializer { + bytes_deserializer: VecU8Deserializer, +} + +impl DeferredCallIdDeserializer { + /// Deserializes a `Vec` into an `DeferredCallId` + pub fn new() -> Self { + Self { + bytes_deserializer: VecU8Deserializer::new( + std::ops::Bound::Included(0), + std::ops::Bound::Included(128), + ), + } + } +} + +impl Default for DeferredCallIdDeserializer { + fn default() -> Self { + Self::new() + } +} + +impl Deserializer for DeferredCallIdDeserializer { + fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>( + &self, + buffer: &'a [u8], + ) -> IResult<&'a [u8], DeferredCallId, E> { + let (rest, bytes) = self.bytes_deserializer.deserialize(buffer)?; + Ok(( + rest, + DeferredCallId::DeferredCallIdV0(DeferredCallIdV0(bytes)), + )) + } +} + +impl FromStr for DeferredCallId { + type Err = ModelsError; + + fn from_str(s: &str) -> Result { + if !s.starts_with(DEFERRED_CALL_ID_PREFIX) { + return Err(ModelsError::DeserializeError(format!( + "Invalid prefix for DeferredCallId: {}", + s + ))); + } + let s = &s[DEFERRED_CALL_ID_PREFIX.len()..]; + let bytes = bs58::decode(s).with_check(None).into_vec().map_err(|_| { + ModelsError::DeserializeError(format!( + "Invalid base58 string for DeferredCallId: {}", + s + )) + })?; + DeferredCallId::from_bytes(&bytes) + } +} + +impl std::fmt::Display for DeferredCallId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}{}", + DEFERRED_CALL_ID_PREFIX, + bs58::encode(self.as_bytes()).with_check().into_string() + ) + } +} + +impl ::serde::Serialize for DeferredCallId { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + if serializer.is_human_readable() { + serializer.collect_str(&self.to_string()) + } else { + serializer.serialize_bytes(self.as_bytes()) + } + } +} + +impl<'de> ::serde::Deserialize<'de> for DeferredCallId { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct DeferredCallIdVisitor; + + impl<'de> ::serde::de::Visitor<'de> for DeferredCallIdVisitor { + type Value = DeferredCallId; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("ASC + base58::encode(bytes)") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(v_str) = std::str::from_utf8(v) { + DeferredCallId::from_str(v_str).map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + DeferredCallId::from_str(v).map_err(E::custom) + } + } + d.deserialize_str(DeferredCallIdVisitor) + } else { + struct BytesVisitor; + + impl<'de> ::serde::de::Visitor<'de> for BytesVisitor { + type Value = DeferredCallId; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("[u64varint-of-addr-variant][u64varint-of-version][bytes]") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + DeferredCallId::from_bytes(v).map_err(E::custom) + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + +impl DeferredCallId { + /// Return the slot of the `DeferredCallId` + pub fn get_slot(&self) -> Result { + let version_deserializer = U64VarIntDeserializer::new( + std::ops::Bound::Included(0), + std::ops::Bound::Included(u64::MAX), + ); + + let slot_deser = SlotDeserializer::new( + (Bound::Included(0), Bound::Included(u64::MAX)), + (Bound::Included(0), Bound::Excluded(THREAD_COUNT)), + ); + + let (rest, _version) = version_deserializer + .deserialize::(self.as_bytes()) + .map_err(|_e| ModelsError::DeferredCallIdParseError)?; + let (_rest, slot) = slot_deser + .deserialize::(rest) + .map_err(|_e| ModelsError::DeferredCallIdParseError)?; + Ok(slot) + } + + /// Create a new `DeferredCallId` + pub fn new( + version: u64, + target_slot: Slot, + index: u64, + trail_hash: &[u8], + ) -> Result { + let mut id: Vec = Vec::new(); + match version { + 0 => { + let version_serializer = U64VarIntSerializer::new(); + let slot_serializer = SlotSerializer::new(); + version_serializer.serialize(&version, &mut id)?; + slot_serializer.serialize(&target_slot, &mut id)?; + id.extend(index.to_be_bytes()); + id.extend(trail_hash); + Ok(DeferredCallId::DeferredCallIdV0(DeferredCallIdV0(id))) + } + _ => Err(ModelsError::InvalidVersionError(format!( + "Invalid version to create an DeferredCallId: {}", + version + ))), + } + } + + /// Return the version of the `DeferredCallId` as bytes + pub fn as_bytes(&self) -> &[u8] { + match self { + DeferredCallId::DeferredCallIdV0(block_id) => block_id.as_bytes(), + } + } + + /// Create an `DeferredCallId` from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.is_empty() { + return Err(ModelsError::SerializeError("Empty bytes".to_string())); + } + let version = U64VarIntDeserializer::new( + std::ops::Bound::Included(0), + std::ops::Bound::Included(u64::MAX), + ); + let (_, version) = version.deserialize(bytes)?; + match version { + 0 => { + let id = DeferredCallIdV0::from_bytes(bytes)?; + Ok(DeferredCallId::DeferredCallIdV0(id)) + } + _ => Err(ModelsError::InvalidVersionError(format!( + "Invalid version to create an DeferredCallId: {}", + version + ))), + } + } +} + +#[transition::impl_version(versions("0"))] +impl DeferredCallId { + /// Return the version of the `DeferredCallId` as bytes + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Create an `DeferredCallId` from bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + Ok(DeferredCallId(bytes.to_vec())) + } +} + +#[cfg(test)] +mod tests { + use massa_serialization::DeserializeError; + + use super::*; + use crate::slot::Slot; + + #[test] + fn test_deferred_call_id_ser_deser() { + let slot = Slot::new(1, 2); + let index = 3; + let trail_hash = [4, 5, 6]; + let id = DeferredCallId::new(0, slot, index, &trail_hash).unwrap(); + let serializer = DeferredCallIdSerializer::new(); + let mut buffer = Vec::new(); + serializer.serialize(&id, &mut buffer).unwrap(); + let deserializer = DeferredCallIdDeserializer::new(); + let (rest, deserialized_id) = deserializer + .deserialize::(&buffer) + .unwrap(); + assert_eq!(deserialized_id, id); + assert!(rest.is_empty()); + } + + #[test] + fn test_deferred_call_id_from_str() { + let slot = Slot::new(1, 2); + let index = 3; + let trail_hash = [4, 5, 6]; + let id = DeferredCallId::new(0, slot, index, &trail_hash).unwrap(); + let id_str = id.to_string(); + let deserialized_id = DeferredCallId::from_str(&id_str).unwrap(); + assert_eq!(deserialized_id, id); + } + + #[test] + fn test_get_slot() { + let slot = Slot::new(1, 2); + let index = 3; + let trail_hash = [4, 5, 6]; + let id = DeferredCallId::new(0, slot, index, &trail_hash).unwrap(); + assert_eq!(id.get_slot().unwrap(), slot); + } +} diff --git a/massa-models/src/endorsement.rs b/massa-models/src/endorsement.rs index 69d0840a202..1f748b8acbb 100644 --- a/massa-models/src/endorsement.rs +++ b/massa-models/src/endorsement.rs @@ -242,7 +242,7 @@ impl SecureShareEndorsement { return Err(e.into()); } if self.content.slot.thread >= crate::config::THREAD_COUNT { - Err("Endorsement slot on non-existant thread".into()) + Err("Endorsement slot on non-existent thread".into()) } else if self.content.index >= crate::config::ENDORSEMENT_COUNT { Err("Endorsement index out of range".into()) } else { @@ -660,7 +660,7 @@ mod tests { #[test] #[serial] fn test_endorsement_id_errors() { - let actual_error = EndorsementId::from_str("SomeUnvalidEndorsementId") + let actual_error = EndorsementId::from_str("SomeInvalidEndorsementId") .unwrap_err() .to_string(); let expected_error = "endorsement id parsing error".to_string(); diff --git a/massa-models/src/error.rs b/massa-models/src/error.rs index ba4a6b8b069..213672c9ba5 100644 --- a/massa-models/src/error.rs +++ b/massa-models/src/error.rs @@ -62,6 +62,8 @@ pub enum ModelsError { OutdatedBootstrapCursor, /// Error raised {0} ErrorRaised(String), + /// deferred call id parsing error + DeferredCallIdParseError, } impl From>> for ModelsError { diff --git a/massa-models/src/lib.rs b/massa-models/src/lib.rs index ba80ecf5b4d..da5bcf81293 100644 --- a/massa-models/src/lib.rs +++ b/massa-models/src/lib.rs @@ -28,6 +28,8 @@ pub mod composite; pub mod config; /// datastore serialization / deserialization pub mod datastore; +/// deferred call id +pub mod deferred_calls; /// denunciation pub mod denunciation; /// endorsements @@ -62,6 +64,8 @@ pub mod stats; pub mod streaming_step; /// management of the relation between time and slots pub mod timeslots; +/// types +pub mod types; /// versions pub mod version; diff --git a/massa-models/src/output_event.rs b/massa-models/src/output_event.rs index 924b3927bdb..50ea0eb54d3 100644 --- a/massa-models/src/output_event.rs +++ b/massa-models/src/output_event.rs @@ -1,8 +1,9 @@ use crate::{address::Address, block_id::BlockId, operation::OperationId, slot::Slot}; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; use std::{collections::VecDeque, fmt::Display}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] /// By product of a byte code execution pub struct SCOutputEvent { /// context generated by the execution context @@ -11,6 +12,19 @@ pub struct SCOutputEvent { pub data: String, } +impl PartialOrd for SCOutputEvent { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SCOutputEvent { + fn cmp(&self, other: &Self) -> Ordering { + (self.context.slot, self.context.index_in_slot) + .cmp(&(other.context.slot, other.context.index_in_slot)) + } +} + impl Display for SCOutputEvent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "Context: {}", self.context)?; @@ -19,7 +33,7 @@ impl Display for SCOutputEvent { } /// Context of the event (not generated by the user) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct EventExecutionContext { /// when was it generated pub slot: Slot, diff --git a/massa-models/src/secure_share.rs b/massa-models/src/secure_share.rs index 2cf591102ba..440c360883b 100644 --- a/massa-models/src/secure_share.rs +++ b/massa-models/src/secure_share.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; /// Packages type T such that it can be securely sent and received in a trust-free network /// -/// If the internal content is mutated, then it must be re-wrapped, as the assosciated +/// If the internal content is mutated, then it must be re-wrapped, as the associated /// signature, serialized data, etc. would no longer be in sync #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SecureShare @@ -23,9 +23,9 @@ where T: Display + SecureShareContent, ID: Id, { - /// Reference contents. Not required for the the security protocols. + /// Reference contents. Not required for the security protocols. /// - /// Use the Lightweight equivilant structures when you need verifiable + /// Use the Lightweight equivalent structures when you need verifiable /// serialized data, but do not need to read the values directly (such as when sending) pub content: T, #[serde(skip)] diff --git a/massa-models/src/slot.rs b/massa-models/src/slot.rs index 65b1015a611..c5de3031d82 100644 --- a/massa-models/src/slot.rs +++ b/massa-models/src/slot.rs @@ -325,6 +325,33 @@ impl Slot { .ok_or(ModelsError::PeriodOverflowError)? .saturating_sub(s.thread as u64)) } + + /// Returns the n-th slot after the current one + /// + /// ## Example + /// ```rust + /// # use massa_models::slot::Slot; + /// let slot = Slot::new(10,3); + /// assert_eq!(slot.skip(62, 32).unwrap(), Slot::new(12, 1)); + /// ``` + pub fn skip(&self, n: u64, thread_count: u8) -> Result { + let mut res_period = self + .period + .checked_add(n / (thread_count as u64)) + .ok_or(ModelsError::PeriodOverflowError)?; + let mut res_thread = (self.thread as u64) + .checked_add(n % (thread_count as u64)) + .ok_or(ModelsError::ThreadOverflowError)?; + + if res_thread >= thread_count as u64 { + res_period = res_period + .checked_add(1) + .ok_or(ModelsError::PeriodOverflowError)?; + res_thread -= thread_count as u64; + } + + Ok(Slot::new(res_period, res_thread as u8)) + } } /// When an address is drawn to create an endorsement it is selected for a specific index diff --git a/massa-ledger-exports/src/types.rs b/massa-models/src/types.rs similarity index 99% rename from massa-ledger-exports/src/types.rs rename to massa-models/src/types.rs index 38749edd91f..3f4485552a3 100644 --- a/massa-ledger-exports/src/types.rs +++ b/massa-models/src/types.rs @@ -1,6 +1,6 @@ // Copyright (c) 2022 MASSA LABS -//! Provides various tools to manipulate ledger entries and changes happening on them. +#![allow(missing_docs)] use massa_serialization::{Deserializer, SerializeError, Serializer}; use nom::bytes::complete::take; diff --git a/massa-module-cache/Cargo.toml b/massa-module-cache/Cargo.toml index 0f208b6646f..83a02d52330 100644 --- a/massa-module-cache/Cargo.toml +++ b/massa-module-cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_module_cache" -version = "2.4.0" +version = "2.5.0" edition = "2021" [features] @@ -8,7 +8,6 @@ test-exports = ["massa-sc-runtime/testing"] [dependencies] schnellru = {workspace = true} -serial_test = {workspace = true} rand = {workspace = true} # BOM UPGRADE Revert to "0.8.5" if problem num_enum = {workspace = true} nom = {workspace = true} @@ -24,3 +23,4 @@ massa-sc-runtime = {workspace = true, "features" = ["testing"]} [dev-dependencies] tempfile = {workspace = true} # BOM UPGRADE Revert to "3.3" if problem +serial_test = {workspace = true} diff --git a/massa-module-cache/src/config.rs b/massa-module-cache/src/config.rs index 0d202e7d418..48f3c9b82d3 100644 --- a/massa-module-cache/src/config.rs +++ b/massa-module-cache/src/config.rs @@ -1,4 +1,4 @@ -use massa_sc_runtime::GasCosts; +use massa_sc_runtime::{CondomLimits, GasCosts}; use std::path::PathBuf; pub struct ModuleCacheConfig { @@ -16,4 +16,6 @@ pub struct ModuleCacheConfig { pub snip_amount: usize, /// Maximum length of a module pub max_module_length: u64, + /// Runtime condom middleware limits + pub condom_limits: CondomLimits, } diff --git a/massa-module-cache/src/controller.rs b/massa-module-cache/src/controller.rs index 1362970e1a6..18d12ea3935 100644 --- a/massa-module-cache/src/controller.rs +++ b/massa-module-cache/src/controller.rs @@ -41,7 +41,12 @@ impl ModuleCache { /// Internal function to compile and build `ModuleInfo` fn compile_cached(&mut self, bytecode: &[u8], hash: Hash) -> ModuleInfo { - match RuntimeModule::new(bytecode, self.cfg.gas_costs.clone(), Compiler::CL) { + match RuntimeModule::new( + bytecode, + self.cfg.gas_costs.clone(), + Compiler::CL, + self.cfg.condom_limits.clone(), + ) { Ok(module) => { debug!("compilation of module {} succeeded", hash); ModuleInfo::Module(module) @@ -57,7 +62,11 @@ impl ModuleCache { /// Save a new or an already existing module in the cache pub fn save_module(&mut self, bytecode: &[u8]) { let hash = Hash::compute_from(bytecode); - if let Some(hd_module_info) = self.hd_cache.get(hash, self.cfg.gas_costs.clone()) { + if let Some(hd_module_info) = self.hd_cache.get( + hash, + self.cfg.gas_costs.clone(), + self.cfg.condom_limits.clone(), + ) { debug!("save_module: {} present in hd", hash); self.lru_cache.insert(hash, hd_module_info); } else if let Some(lru_module_info) = self.lru_cache.get(hash) { @@ -110,7 +119,11 @@ impl ModuleCache { if let Some(lru_module_info) = self.lru_cache.get(hash) { debug!("load_module: {} present in lru", hash); lru_module_info - } else if let Some(hd_module_info) = self.hd_cache.get(hash, self.cfg.gas_costs.clone()) { + } else if let Some(hd_module_info) = self.hd_cache.get( + hash, + self.cfg.gas_costs.clone(), + self.cfg.condom_limits.clone(), + ) { debug!("load_module: {} missing in lru but present in hd", hash); self.lru_cache.insert(hash, hd_module_info.clone()); hd_module_info @@ -181,7 +194,12 @@ impl ModuleCache { "Provided gas {} is lower than the base instance creation gas cost {}", limit, self.cfg.gas_costs.max_instance_cost )))?; - let module = RuntimeModule::new(bytecode, self.cfg.gas_costs.clone(), Compiler::SP)?; + let module = RuntimeModule::new( + bytecode, + self.cfg.gas_costs.clone(), + Compiler::SP, + self.cfg.condom_limits.clone(), + )?; Ok(module) } } diff --git a/massa-module-cache/src/hd_cache.rs b/massa-module-cache/src/hd_cache.rs index 5eb59cad25d..b5a8ed91ff0 100644 --- a/massa-module-cache/src/hd_cache.rs +++ b/massa-module-cache/src/hd_cache.rs @@ -2,7 +2,7 @@ use crate::types::{ ModuleInfo, ModuleMetadata, ModuleMetadataDeserializer, ModuleMetadataSerializer, }; use massa_hash::Hash; -use massa_sc_runtime::{GasCosts, RuntimeModule}; +use massa_sc_runtime::{CondomLimits, GasCosts, RuntimeModule}; use massa_serialization::{DeserializeError, Deserializer, Serializer}; use rand::RngCore; use rocksdb::{Direction, IteratorMode, WriteBatch, DB}; @@ -137,7 +137,12 @@ impl HDCache { } /// Retrieve a module - pub fn get(&self, hash: Hash, gas_costs: GasCosts) -> Option { + pub fn get( + &self, + hash: Hash, + gas_costs: GasCosts, + condom_limits: CondomLimits, + ) -> Option { let mut iterator = self .db .iterator(IteratorMode::From(&module_key!(hash), Direction::Forward)); @@ -153,9 +158,13 @@ impl HDCache { if let ModuleMetadata::Invalid(err_msg) = metadata { return Some(ModuleInfo::Invalid(err_msg)); } - let module = - RuntimeModule::deserialize(&ser_module, gas_costs.max_instance_cost, gas_costs) - .expect(MOD_DESER_ERROR); + let module = RuntimeModule::deserialize( + &ser_module, + gas_costs.max_instance_cost, + gas_costs, + condom_limits, + ) + .expect(MOD_DESER_ERROR); let result = match metadata { ModuleMetadata::Invalid(err_msg) => ModuleInfo::Invalid(err_msg), ModuleMetadata::NotExecuted => ModuleInfo::Module(module), @@ -233,7 +242,13 @@ mod tests { 0x70, 0x30, ]; ModuleInfo::Module( - RuntimeModule::new(&bytecode, GasCosts::default(), Compiler::CL).unwrap(), + RuntimeModule::new( + &bytecode, + GasCosts::default(), + Compiler::CL, + CondomLimits::default(), + ) + .unwrap(), ) } @@ -251,18 +266,23 @@ mod tests { let init_cost = 100; let gas_costs = GasCosts::default(); + let condom_limits = CondomLimits::default(); cache.insert(hash, module); - let cached_module_v1 = cache.get(hash, gas_costs.clone()).unwrap(); + let cached_module_v1 = cache + .get(hash, gas_costs.clone(), condom_limits.clone()) + .unwrap(); assert!(matches!(cached_module_v1, ModuleInfo::Module(_))); cache.set_init_cost(hash, init_cost); - let cached_module_v2 = cache.get(hash, gas_costs.clone()).unwrap(); + let cached_module_v2 = cache + .get(hash, gas_costs.clone(), condom_limits.clone()) + .unwrap(); assert!(matches!(cached_module_v2, ModuleInfo::ModuleAndDelta(_))); let err_msg = "test_error".to_string(); cache.set_invalid(hash, err_msg.clone()); - let cached_module_v3 = cache.get(hash, gas_costs).unwrap(); + let cached_module_v3 = cache.get(hash, gas_costs, condom_limits.clone()).unwrap(); let ModuleInfo::Invalid(res_err) = cached_module_v3 else { panic!("expected ModuleInfo::Invalid"); }; @@ -299,6 +319,7 @@ mod tests { let module = make_default_module_info(); let gas_costs = GasCosts::default(); + let condom_limits = CondomLimits::default(); for count in 0..cache.max_entry_count { let key = Hash::compute_from(count.to_string().as_bytes()); @@ -309,7 +330,7 @@ mod tests { let mut rbytes = [0u8; 16]; thread_rng().fill_bytes(&mut rbytes); let get_key = Hash::compute_from(&rbytes); - let cached_module = cache.get(get_key, gas_costs.clone()); + let cached_module = cache.get(get_key, gas_costs.clone(), condom_limits.clone()); assert!(cached_module.is_none()); } } diff --git a/massa-node/Cargo.toml b/massa-node/Cargo.toml index 9c706672f6b..dc7083f961b 100644 --- a/massa-node/Cargo.toml +++ b/massa-node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa-node" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" @@ -35,9 +35,7 @@ dump-block = [ ] db_storage_backend = [] file_storage_backend = [] -execution-info = [ - "execution-trace" -] +execution-info = ["execution-trace"] [dependencies] crossbeam-channel = { workspace = true } # BOM UPGRADE Revert to "0.5.6" if problem @@ -64,6 +62,7 @@ massa_bootstrap = { workspace = true } massa_channel = { workspace = true } massa_consensus_exports = { workspace = true } massa_consensus_worker = { workspace = true } +massa_deferred_calls = { workspace = true } massa_executed_ops = { workspace = true } massa_execution_exports = { workspace = true } massa_execution_worker = { workspace = true } @@ -89,3 +88,4 @@ massa_versioning = { workspace = true } massa_signature = { workspace = true } massa_db_exports = { workspace = true } massa_db_worker = { workspace = true } +massa_event_cache = { workspace = true } diff --git a/massa-node/base_config/bootstrap_whitelist.json b/massa-node/base_config/bootstrap_whitelist.json deleted file mode 100644 index 5793b574453..00000000000 --- a/massa-node/base_config/bootstrap_whitelist.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - "149.202.86.103", - "149.202.89.125", - "158.69.120.215", - "158.69.23.120", - "198.27.74.5", - "51.75.60.228", - "2001:41d0:1004:67::", - "2001:41d0:a:7f7d::", - "2001:41d0:602:21e4::" -] \ No newline at end of file diff --git a/massa-node/base_config/config.toml b/massa-node/base_config/config.toml index 798875dd871..d3cc6a726ea 100644 --- a/massa-node/base_config/config.toml +++ b/massa-node/base_config/config.toml @@ -221,6 +221,14 @@ broadcast_slot_execution_traces_channel_capacity = 5000 # Max slots execution traces to keep in cache execution_traces_limit = 320 + # path to the event cache storage + event_cache_path = "storage/event_cache/rocks_db" + # maximum number of entries we want to keep in the Event cache (~ 10 Gb) + event_cache_size = 20071520 + # amount of entries removed when `event_cache_size` is reached + event_snip_amount = 10 + # maximum number of events return by a query + max_event_per_query = 7000 [ledger] # path to the initial ledger @@ -273,7 +281,7 @@ message_timeout = 5000 # timeout after which a peer tester will consider the peer unreachable tester_timeout = 10000 - # timeout after whick we consider a node does not have the block we asked for + # timeout after which we consider a node does not have the block we asked for ask_block_timeout = 10000 # Max known blocks we keep during their propagation max_blocks_kept_for_propagation = 300 @@ -391,7 +399,7 @@ max_clock_delta = 5000 # [server] data is cached for cache duration milliseconds cache_duration = 15000 - # max number of simulataneous bootstraps for server + # max number of simultaneous bootstraps for server max_simultaneous_bootstraps = 2 # max size of recently bootstrapped IP cache ip_list_max_size = 10000 @@ -441,4 +449,4 @@ [block_dump] block_dump_folder_path = "dump/blocks" # max number of blocks to keep in the dump folder - max_blocks = 2048000 \ No newline at end of file + max_blocks = 2048000 diff --git a/massa-node/base_config/gas_costs/abi_gas_costs.json b/massa-node/base_config/gas_costs/abi_gas_costs.json index 66ad4901324..5ed2283dd5e 100644 --- a/massa-node/base_config/gas_costs/abi_gas_costs.json +++ b/massa-node/base_config/gas_costs/abi_gas_costs.json @@ -12,6 +12,10 @@ "assembly_script_date_now": 71, "assembly_script_delete_data": 196, "assembly_script_delete_data_for": 220, + "assembly_script_deferred_call_cancel": 833, + "assembly_script_deferred_call_exists": 1316, + "assembly_script_get_deferred_call_quote": 244, + "assembly_script_deferred_call_register": 530, "assembly_script_function_exists": 575, "assembly_script_generate_event": 172, "assembly_script_get_balance": 149, @@ -125,5 +129,14 @@ "abi_unsafe_random": 402, "abi_verify_signature": 1192, "abi_chain_id": 301, - "launch_wasmv1": 18641 + "abi_deferred_call_cancel": 750, + "abi_deferred_call_exists": 443, + "abi_deferred_call_register": 745, + "abi_get_deferred_call_quote": 416, + "assembly_script_console_log": 171, + "assembly_script_console_info": 171, + "assembly_script_console_debug": 171, + "assembly_script_console_warn": 171, + "assembly_script_console_error": 171, + "assembly_script_trace": 171 } \ No newline at end of file diff --git a/massa-node/base_config/gas_costs/wasm_gas_costs.json b/massa-node/base_config/gas_costs/wasm_gas_costs.json deleted file mode 100644 index bac1b461b78..00000000000 --- a/massa-node/base_config/gas_costs/wasm_gas_costs.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Wasm:Drop": 0, - "Wasm:GlobalGet": 27, - "Wasm:GlobalSet": 75, - "Wasm:I32Add": 38, - "Wasm:I32Const": 11, - "Wasm:I32DivS": 6, - "Wasm:I32Mul": 58, - "Wasm:I32Sub": 0, - "Wasm:If": 44, - "Wasm:LocalGet": 0, - "Wasm:LocalSet": 0 -} \ No newline at end of file diff --git a/massa-node/base_config/openrpc.json b/massa-node/base_config/openrpc.json index c846f2e9f0a..b6d2b090e6c 100644 --- a/massa-node/base_config/openrpc.json +++ b/massa-node/base_config/openrpc.json @@ -2,7 +2,7 @@ "openrpc": "1.2.4", "info": { "title": "Massa OpenRPC Specification", - "version": "MAIN.2.4", + "version": "MAIN.2.5", "description": "Massa OpenRPC Specification document. Find more information on https://docs.massa.net/docs/build/api/jsonrpc", "termsOfService": "https://open-rpc.org", "contact": { @@ -485,6 +485,105 @@ "summary": "Summary of the current state", "description": "Summary of the current state: time, last final blocks (hash, thread, slot, timestamp), clique count, connected nodes count." }, + { + "tags": [ + { + "name": "public", + "description": "Massa public api" + } + ], + "params": [ + { + "name": "req", + "description": "Deferred calls quote request", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeferredCallsQuoteRequest" + } + }, + "required": true + } + ], + "result": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeferredCallsQuoteResponse" + } + }, + "name": "DeferredCallsQuoteResponse" + }, + "name": "get_deferred_call_quote", + "summary": "Get deferred call quote", + "description": "Returns if slot is available and the price to book the requested gas." + }, + { + "tags": [ + { + "name": "public", + "description": "Massa public api" + } + ], + "params": [ + { + "name": "arg", + "description": "Deferred calls ids", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": true + } + ], + "result": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeferredCallResponse" + } + }, + "name": "DeferredCallResponse" + }, + "name": "get_deferred_call_info", + "summary": "Get deferred calls information", + "description": "Returns information about deferred calls." + }, + { + "tags": [ + { + "name": "public", + "description": "Massa public api" + } + ], + "params": [ + { + "name": "slots", + "description": "Deferred calls ids", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Slot" + } + }, + "required": true + } + ], + "result": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeferredCallsSlotResponse" + } + }, + "name": "DeferredCallResponse" + }, + "name": "get_deferred_call_ids_by_slot", + "summary": "List deferred call ids by slot", + "description": "Returns deferred calls list for given slots." + }, { "tags": [ { @@ -1909,6 +2008,123 @@ }, "additionalProperties": false }, + "DeferredCall": { + "type": "object", + "required": [ + "sender_address", + "target_slot", + "target_address", + "target_function", + "parameters", + "coins", + "max_gas", + "fee", + "cancelled" + ], + "properties": { + "sender_address": { + "$ref": "#/components/schemas/Address" + }, + "target_slot": { + "$ref": "#/components/schemas/Slot" + }, + "target_address": { + "$ref": "#/components/schemas/Address" + }, + "target_function": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "type": "number" + } + }, + "coins": { + "type": "number" + }, + "max_gas": { + "type": "number" + }, + "fee": { + "type": "number" + }, + "cancelled": { + "type": "boolean" + } + } + }, + "DeferredCallsQuoteRequest": { + "type": "object", + "required": [ + "target_slot", + "max_gas_request", + "params_size" + ], + "properties": { + "target_slot": { + "$ref": "#/components/schemas/Slot" + }, + "max_gas_request": { + "type": "number" + }, + "params_size": { + "type": "number" + } + } + }, + "DeferredCallsQuoteResponse": { + "type": "object", + "required": [ + "target_slot", + "max_gas_request", + "available", + "price" + ], + "properties": { + "target_slot": { + "$ref": "#/components/schemas/Slot" + }, + "max_gas_request": { + "type": "number" + }, + "available": { + "type": "boolean" + }, + "price": { + "type": "number" + } + } + }, + "DeferredCallResponse": { + "type": "object", + "required": [ + "call_id", + "call" + ], + "properties": { + "call_id": { + "type": "string" + }, + "call": { + "$ref": "#/components/schemas/DeferredCall" + } + } + }, + "DeferredCallsSlotResponse": { + "type": "object", + "properties": { + "slot": { + "$ref": "#/components/schemas/Slot" + }, + "call_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "Denunciation": { "oneOf": [ { @@ -3215,6 +3431,7 @@ "required": [ "async_pool_changes", "executed_ops_changes", + "deferred_call_changes", "ledger_changes", "pos_changes", "executed_denunciations_changes", @@ -3241,6 +3458,10 @@ "description": "executed operations changes", "type": "object" }, + "deferred_call_changes": { + "description": "deferred call changes", + "type": "object" + }, "executed_denunciations_changes": { "description": "executed denunciation changes", "type": "object" diff --git a/massa-node/src/main.rs b/massa-node/src/main.rs index 1709fd5c684..5a4de192928 100644 --- a/massa-node/src/main.rs +++ b/massa-node/src/main.rs @@ -31,9 +31,11 @@ use massa_consensus_exports::{ use massa_consensus_worker::start_consensus_worker; use massa_db_exports::{MassaDBConfig, MassaDBController}; use massa_db_worker::MassaDB; +use massa_deferred_calls::config::DeferredCallsConfig; use massa_executed_ops::{ExecutedDenunciationsConfig, ExecutedOpsConfig}; use massa_execution_exports::{ - ExecutionChannels, ExecutionConfig, ExecutionManager, GasCosts, StorageCostsConstants, + CondomLimits, ExecutionChannels, ExecutionConfig, ExecutionManager, GasCosts, + StorageCostsConstants, }; use massa_execution_worker::start_execution_worker; #[cfg(all( @@ -58,21 +60,21 @@ use massa_models::address::Address; use massa_models::amount::Amount; use massa_models::config::constants::{ ASYNC_MSG_CST_GAS_COST, BLOCK_REWARD, BOOTSTRAP_RANDOMNESS_SIZE_BYTES, CHANNEL_SIZE, - CONSENSUS_BOOTSTRAP_PART_SIZE, DELTA_F0, DENUNCIATION_EXPIRE_PERIODS, ENDORSEMENT_COUNT, - END_TIMESTAMP, GENESIS_KEY, GENESIS_TIMESTAMP, INITIAL_DRAW_SEED, LEDGER_COST_PER_BYTE, - LEDGER_ENTRY_BASE_COST, LEDGER_ENTRY_DATASTORE_BASE_SIZE, MAX_ADVERTISE_LENGTH, MAX_ASYNC_GAS, - MAX_ASYNC_POOL_LENGTH, MAX_BLOCK_SIZE, MAX_BOOTSTRAP_BLOCKS, MAX_BOOTSTRAP_ERROR_LENGTH, - MAX_BYTECODE_LENGTH, MAX_CONSENSUS_BLOCKS_IDS, MAX_DATASTORE_ENTRY_COUNT, - MAX_DATASTORE_KEY_LENGTH, MAX_DATASTORE_VALUE_LENGTH, MAX_DEFERRED_CREDITS_LENGTH, - MAX_DENUNCIATIONS_PER_BLOCK_HEADER, MAX_DENUNCIATION_CHANGES_LENGTH, - MAX_ENDORSEMENTS_PER_MESSAGE, MAX_EXECUTED_OPS_CHANGES_LENGTH, MAX_EXECUTED_OPS_LENGTH, - MAX_FUNCTION_NAME_LENGTH, MAX_GAS_PER_BLOCK, MAX_LEDGER_CHANGES_COUNT, MAX_LISTENERS_PER_PEER, - MAX_OPERATIONS_PER_BLOCK, MAX_OPERATIONS_PER_MESSAGE, MAX_OPERATION_DATASTORE_ENTRY_COUNT, - MAX_OPERATION_DATASTORE_KEY_LENGTH, MAX_OPERATION_DATASTORE_VALUE_LENGTH, - MAX_OPERATION_STORAGE_TIME, MAX_PARAMETERS_SIZE, MAX_PEERS_IN_ANNOUNCEMENT_LIST, - MAX_PRODUCTION_STATS_LENGTH, MAX_ROLLS_COUNT_LENGTH, MAX_SIZE_CHANNEL_COMMANDS_CONNECTIVITY, - MAX_SIZE_CHANNEL_COMMANDS_PEERS, MAX_SIZE_CHANNEL_COMMANDS_PEER_TESTERS, - MAX_SIZE_CHANNEL_COMMANDS_PROPAGATION_BLOCKS, + CONSENSUS_BOOTSTRAP_PART_SIZE, DEFERRED_CALL_MAX_FUTURE_SLOTS, DELTA_F0, + DENUNCIATION_EXPIRE_PERIODS, ENDORSEMENT_COUNT, END_TIMESTAMP, GENESIS_KEY, GENESIS_TIMESTAMP, + INITIAL_DRAW_SEED, LEDGER_COST_PER_BYTE, LEDGER_ENTRY_BASE_COST, + LEDGER_ENTRY_DATASTORE_BASE_SIZE, MAX_ADVERTISE_LENGTH, MAX_ASYNC_GAS, MAX_ASYNC_POOL_LENGTH, + MAX_BLOCK_SIZE, MAX_BOOTSTRAP_BLOCKS, MAX_BOOTSTRAP_ERROR_LENGTH, MAX_BYTECODE_LENGTH, + MAX_CONSENSUS_BLOCKS_IDS, MAX_DATASTORE_ENTRY_COUNT, MAX_DATASTORE_KEY_LENGTH, + MAX_DATASTORE_VALUE_LENGTH, MAX_DEFERRED_CREDITS_LENGTH, MAX_DENUNCIATIONS_PER_BLOCK_HEADER, + MAX_DENUNCIATION_CHANGES_LENGTH, MAX_ENDORSEMENTS_PER_MESSAGE, MAX_EXECUTED_OPS_CHANGES_LENGTH, + MAX_EXECUTED_OPS_LENGTH, MAX_FUNCTION_NAME_LENGTH, MAX_GAS_PER_BLOCK, MAX_LEDGER_CHANGES_COUNT, + MAX_LISTENERS_PER_PEER, MAX_OPERATIONS_PER_BLOCK, MAX_OPERATIONS_PER_MESSAGE, + MAX_OPERATION_DATASTORE_ENTRY_COUNT, MAX_OPERATION_DATASTORE_KEY_LENGTH, + MAX_OPERATION_DATASTORE_VALUE_LENGTH, MAX_OPERATION_STORAGE_TIME, MAX_PARAMETERS_SIZE, + MAX_PEERS_IN_ANNOUNCEMENT_LIST, MAX_PRODUCTION_STATS_LENGTH, MAX_ROLLS_COUNT_LENGTH, + MAX_SIZE_CHANNEL_COMMANDS_CONNECTIVITY, MAX_SIZE_CHANNEL_COMMANDS_PEERS, + MAX_SIZE_CHANNEL_COMMANDS_PEER_TESTERS, MAX_SIZE_CHANNEL_COMMANDS_PROPAGATION_BLOCKS, MAX_SIZE_CHANNEL_COMMANDS_PROPAGATION_ENDORSEMENTS, MAX_SIZE_CHANNEL_COMMANDS_PROPAGATION_OPERATIONS, MAX_SIZE_CHANNEL_COMMANDS_RETRIEVAL_BLOCKS, MAX_SIZE_CHANNEL_COMMANDS_RETRIEVAL_ENDORSEMENTS, @@ -85,9 +87,19 @@ use massa_models::config::constants::{ VERSION, }; use massa_models::config::{ - BASE_OPERATION_GAS_COST, CHAINID, KEEP_EXECUTED_HISTORY_EXTRA_PERIODS, - MAX_BOOTSTRAP_FINAL_STATE_PARTS_SIZE, MAX_BOOTSTRAP_VERSIONING_ELEMENTS_SIZE, - MAX_EVENT_DATA_SIZE, MAX_MESSAGE_SIZE, POOL_CONTROLLER_DENUNCIATIONS_CHANNEL_SIZE, + BASE_OPERATION_GAS_COST, CHAINID, DEFERRED_CALL_BASE_FEE_MAX_CHANGE_DENOMINATOR, + DEFERRED_CALL_CST_GAS_COST, DEFERRED_CALL_GLOBAL_OVERBOOKING_PENALTY, + DEFERRED_CALL_MAX_ASYNC_GAS, DEFERRED_CALL_MAX_POOL_CHANGES, DEFERRED_CALL_MIN_GAS_COST, + DEFERRED_CALL_MIN_GAS_INCREMENT, DEFERRED_CALL_SLOT_OVERBOOKING_PENALTY, + KEEP_EXECUTED_HISTORY_EXTRA_PERIODS, MAX_BOOTSTRAP_FINAL_STATE_PARTS_SIZE, + MAX_BOOTSTRAP_VERSIONING_ELEMENTS_SIZE, MAX_EVENT_DATA_SIZE, MAX_EVENT_PER_OPERATION, + MAX_MESSAGE_SIZE, MAX_RECURSIVE_CALLS_DEPTH, MAX_RUNTIME_MODULE_CUSTOM_SECTION_DATA_LEN, + MAX_RUNTIME_MODULE_CUSTOM_SECTION_LEN, MAX_RUNTIME_MODULE_EXPORTS, + MAX_RUNTIME_MODULE_FUNCTIONS, MAX_RUNTIME_MODULE_FUNCTION_NAME_LEN, + MAX_RUNTIME_MODULE_GLOBAL_INITIALIZER, MAX_RUNTIME_MODULE_IMPORTS, MAX_RUNTIME_MODULE_MEMORIES, + MAX_RUNTIME_MODULE_NAME_LEN, MAX_RUNTIME_MODULE_PASSIVE_DATA, + MAX_RUNTIME_MODULE_PASSIVE_ELEMENT, MAX_RUNTIME_MODULE_SIGNATURE_LEN, MAX_RUNTIME_MODULE_TABLE, + MAX_RUNTIME_MODULE_TABLE_INITIALIZER, POOL_CONTROLLER_DENUNCIATIONS_CHANNEL_SIZE, POOL_CONTROLLER_ENDORSEMENTS_CHANNEL_SIZE, POOL_CONTROLLER_OPERATIONS_CHANNEL_SIZE, }; use massa_models::slot::Slot; @@ -115,6 +127,8 @@ use std::sync::{Condvar, Mutex}; use std::time::Duration; use std::{path::Path, process, sync::Arc}; +use massa_event_cache::config::EventCacheConfig; +use massa_event_cache::worker::{start_event_cache_writer_worker, EventCacheManager}; use survey::MassaSurveyStopper; use tokio::sync::broadcast; use tracing::{debug, error, info, warn}; @@ -138,6 +152,7 @@ async fn launch( Box, Box, Box, + Box, StopHandle, StopHandle, StopHandle, @@ -190,9 +205,25 @@ async fn launch( endorsement_count: ENDORSEMENT_COUNT, keep_executed_history_extra_periods: KEEP_EXECUTED_HISTORY_EXTRA_PERIODS, }; + let deferred_calls_config = DeferredCallsConfig { + thread_count: THREAD_COUNT, + max_function_name_length: MAX_FUNCTION_NAME_LENGTH, + max_parameter_size: MAX_PARAMETERS_SIZE, + max_pool_changes: DEFERRED_CALL_MAX_POOL_CHANGES, + max_gas: DEFERRED_CALL_MAX_ASYNC_GAS, + max_future_slots: DEFERRED_CALL_MAX_FUTURE_SLOTS, + base_fee_max_max_change_denominator: DEFERRED_CALL_BASE_FEE_MAX_CHANGE_DENOMINATOR, + min_gas_increment: DEFERRED_CALL_MIN_GAS_INCREMENT, + min_gas_cost: DEFERRED_CALL_MIN_GAS_COST, + global_overbooking_penalty: DEFERRED_CALL_GLOBAL_OVERBOOKING_PENALTY, + slot_overbooking_penalty: DEFERRED_CALL_SLOT_OVERBOOKING_PENALTY, + call_cst_gas_cost: DEFERRED_CALL_CST_GAS_COST, + ledger_cost_per_byte: LEDGER_COST_PER_BYTE, + }; let final_state_config = FinalStateConfig { ledger_config: ledger_config.clone(), async_pool_config, + deferred_calls_config, pos_config, executed_ops_config, executed_denunciations_config, @@ -445,6 +476,25 @@ async fn launch( } } + // Event cache thread + let event_cache_config = EventCacheConfig { + event_cache_path: SETTINGS.execution.event_cache_path.clone(), + max_event_cache_length: SETTINGS.execution.event_cache_size, + snip_amount: SETTINGS.execution.event_snip_amount, + max_event_data_length: MAX_EVENT_DATA_SIZE as u64, + thread_count: THREAD_COUNT, + // Note: SCOutputEvent call stack comes from the execution module, and we assume + // this should return a limited call stack length + // The value remains for future use & limitations + max_call_stack_length: u16::MAX, + + max_events_per_operation: MAX_EVENT_PER_OPERATION as u64, + max_operations_per_block: MAX_OPERATIONS_PER_BLOCK as u64, + max_events_per_query: SETTINGS.execution.max_event_per_query, + }; + let (event_cache_manager, event_cache_controller) = + start_event_cache_writer_worker(event_cache_config); + // Storage costs constants let storage_costs_constants = StorageCostsConstants { ledger_cost_per_byte: LEDGER_COST_PER_BYTE, @@ -455,11 +505,27 @@ async fn launch( }; // gas costs - let gas_costs = GasCosts::new( - SETTINGS.execution.abi_gas_costs_file.clone(), - SETTINGS.execution.wasm_gas_costs_file.clone(), - ) - .expect("Failed to load gas costs"); + let gas_costs = GasCosts::new(SETTINGS.execution.abi_gas_costs_file.clone()) + .expect("Failed to load gas costs"); + + // Limits imposed to wasm files so the compilation phase is smooth + let condom_limits = CondomLimits { + max_exports: Some(MAX_RUNTIME_MODULE_EXPORTS), + max_functions: Some(MAX_RUNTIME_MODULE_FUNCTIONS), + max_signature_len: Some(MAX_RUNTIME_MODULE_SIGNATURE_LEN), + max_name_len: Some(MAX_RUNTIME_MODULE_NAME_LEN), + max_imports_len: Some(MAX_RUNTIME_MODULE_IMPORTS), + max_table_initializers_len: Some(MAX_RUNTIME_MODULE_TABLE_INITIALIZER), + max_passive_elements_len: Some(MAX_RUNTIME_MODULE_PASSIVE_ELEMENT), + max_passive_data_len: Some(MAX_RUNTIME_MODULE_PASSIVE_DATA), + max_global_initializers_len: Some(MAX_RUNTIME_MODULE_GLOBAL_INITIALIZER), + max_function_names_len: Some(MAX_RUNTIME_MODULE_FUNCTION_NAME_LEN), + max_tables_count: Some(MAX_RUNTIME_MODULE_TABLE), + max_memories_len: Some(MAX_RUNTIME_MODULE_MEMORIES), + max_globals_len: Some(MAX_RUNTIME_MODULE_GLOBAL_INITIALIZER), + max_custom_sections_len: Some(MAX_RUNTIME_MODULE_CUSTOM_SECTION_LEN), + max_custom_sections_data_len: Some(MAX_RUNTIME_MODULE_CUSTOM_SECTION_DATA_LEN), + }; let block_dump_folder_path = SETTINGS.block_dump.block_dump_folder_path.clone(); if !block_dump_folder_path.exists() { @@ -518,6 +584,13 @@ async fn launch( .broadcast_slot_execution_traces_channel_capacity, max_execution_traces_slot_limit: SETTINGS.execution.execution_traces_limit, block_dump_folder_path, + max_recursive_calls_depth: MAX_RECURSIVE_CALLS_DEPTH, + condom_limits, + deferred_calls_config, + max_event_per_operation: MAX_EVENT_PER_OPERATION, + event_cache_path: SETTINGS.execution.event_cache_path.clone(), + event_cache_size: SETTINGS.execution.event_cache_size, + event_snip_amount: SETTINGS.execution.event_snip_amount, }; let execution_channels = ExecutionChannels { @@ -556,6 +629,7 @@ async fn launch( execution_channels.clone(), node_wallet.clone(), massa_metrics.clone(), + event_cache_controller, #[cfg(feature = "dump-block")] block_storage_backend.clone(), ); @@ -879,6 +953,7 @@ async fn launch( chain_id: *CHAINID, deferred_credits_delta: SETTINGS.api.deferred_credits_delta, minimal_fees: SETTINGS.pool.minimal_fees, + deferred_calls_config, }; // spawn Massa API @@ -1051,6 +1126,7 @@ async fn launch( api_config.periods_per_cycle, api_config.last_start_period, ), + mip_store, ); #[cfg(feature = "deadlock_detection")] @@ -1091,6 +1167,7 @@ async fn launch( pool_manager, protocol_manager, factory_manager, + event_cache_manager, api_private_handle, api_public_handle, api_handle, @@ -1186,6 +1263,7 @@ struct Managers { pool_manager: Box, protocol_manager: Box, factory_manager: Box, + event_cache_manager: Box, } #[allow(clippy::too_many_arguments)] @@ -1199,6 +1277,7 @@ async fn stop( mut pool_manager, mut protocol_manager, mut factory_manager, + mut event_cache_manager, }: Managers, api_private_handle: StopHandle, api_public_handle: StopHandle, @@ -1270,6 +1349,8 @@ async fn stop( //let protocol_pool_event_receiver = pool_manager.stop().await.expect("pool shutdown failed"); // note that FinalLedger gets destroyed as soon as its Arc count goes to zero + + event_cache_manager.stop(); } #[derive(Parser)] @@ -1401,7 +1482,7 @@ async fn run(args: Args) -> anyhow::Result<()> { *sig_int_toggled_clone .0 .lock() - .expect("double-lock on interupt bool in ctrl-c handler") = true; + .expect("double-lock on interrupt bool in ctrl-c handler") = true; sig_int_toggled_clone.1.notify_all(); }) .expect("Error setting Ctrl-C handler"); @@ -1419,6 +1500,7 @@ async fn run(args: Args) -> anyhow::Result<()> { pool_manager, protocol_manager, factory_manager, + event_cache_manager, api_private_handle, api_public_handle, api_handle, @@ -1453,11 +1535,11 @@ async fn run(args: Args) -> anyhow::Result<()> { let int_sig = sig_int_toggled .0 .lock() - .expect("double-lock() on interupted signal mutex"); + .expect("double-lock() on interrupted signal mutex"); let wake = sig_int_toggled .1 .wait_timeout(int_sig, Duration::from_millis(100)) - .expect("interupt signal mutex poisoned"); + .expect("interrupt signal mutex poisoned"); if *wake.0 { info!("interrupt signal received"); break false; @@ -1485,6 +1567,7 @@ async fn run(args: Args) -> anyhow::Result<()> { pool_manager, protocol_manager, factory_manager, + event_cache_manager, }, api_private_handle, api_public_handle, diff --git a/massa-node/src/settings.rs b/massa-node/src/settings.rs index 4d1d443dd39..6ebf309a34a 100644 --- a/massa-node/src/settings.rs +++ b/massa-node/src/settings.rs @@ -27,7 +27,6 @@ pub struct ExecutionSettings { pub stats_time_window_duration: MassaTime, pub max_read_only_gas: u64, pub abi_gas_costs_file: PathBuf, - pub wasm_gas_costs_file: PathBuf, pub hd_cache_path: PathBuf, pub lru_cache_size: u32, pub hd_cache_size: usize, @@ -37,6 +36,10 @@ pub struct ExecutionSettings { /// slot execution traces channel capacity pub broadcast_slot_execution_traces_channel_capacity: usize, pub execution_traces_limit: usize, + pub event_cache_path: PathBuf, + pub event_cache_size: usize, + pub event_snip_amount: usize, + pub max_event_per_query: usize, } #[derive(Clone, Debug, Deserialize)] diff --git a/massa-node/src/survey.rs b/massa-node/src/survey.rs index 26018f3cda8..a4dc7c5accd 100644 --- a/massa-node/src/survey.rs +++ b/massa-node/src/survey.rs @@ -8,6 +8,7 @@ use massa_metrics::MassaMetrics; use massa_models::{address::Address, slot::Slot, timeslots::get_latest_block_slot_at_timestamp}; use massa_pool_exports::PoolController; use massa_time::MassaTime; +use massa_versioning::versioning::MipStore; use tracing::info; // use std::time::Duration; use tracing::warn; @@ -45,6 +46,7 @@ impl MassaSurvey { pool_controller: Box, massa_metrics: MassaMetrics, config: (u8, MassaTime, MassaTime, u64, u64), + mip_store: MipStore, ) -> MassaSurveyStopper { if massa_metrics.is_enabled() { #[cfg(all(not(feature = "sandbox"), not(test)))] @@ -130,6 +132,11 @@ impl MassaSurvey { .get(); massa_metrics.set_available_processors(count); } + + { + let network_stats= mip_store.0.read().get_network_versions_stats(); + massa_metrics.update_network_version_vote(network_stats); + } } } }) { diff --git a/massa-pool-exports/Cargo.toml b/massa-pool-exports/Cargo.toml index 0ff4d672f7a..a810458be90 100644 --- a/massa-pool-exports/Cargo.toml +++ b/massa-pool-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_pool_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-pool-worker/Cargo.toml b/massa-pool-worker/Cargo.toml index 16f3551e673..9cd1e3a811e 100644 --- a/massa-pool-worker/Cargo.toml +++ b/massa-pool-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_pool_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-pool-worker/src/denunciation_pool.rs b/massa-pool-worker/src/denunciation_pool.rs index 3e6286ae561..e3b3272e1da 100644 --- a/massa-pool-worker/src/denunciation_pool.rs +++ b/massa-pool-worker/src/denunciation_pool.rs @@ -48,11 +48,10 @@ impl DenunciationPool { pub fn _contains(&self, denunciation: &Denunciation) -> bool { self.denunciations_cache .iter() - .find(|(_, de_st)| match *de_st { + .any(|(_, de_st)| match de_st { DenunciationStatus::Accumulating(_) => false, DenunciationStatus::DenunciationEmitted(de) => de == denunciation, }) - .is_some() } /// Add a denunciation precursor to the pool - can lead to a Denunciation creation diff --git a/massa-pos-exports/Cargo.toml b/massa-pos-exports/Cargo.toml index da929d17c0c..65555df332c 100644 --- a/massa-pos-exports/Cargo.toml +++ b/massa-pos-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_pos_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-pos-worker/Cargo.toml b/massa-pos-worker/Cargo.toml index 0c9300c0e49..4bc90200817 100644 --- a/massa-pos-worker/Cargo.toml +++ b/massa-pos-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_pos_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-protocol-exports/Cargo.toml b/massa-protocol-exports/Cargo.toml index 68b4ddbf288..fec7930d927 100644 --- a/massa-protocol-exports/Cargo.toml +++ b/massa-protocol-exports/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_protocol_exports" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-protocol-worker/Cargo.toml b/massa-protocol-worker/Cargo.toml index 1a94058414b..26f8bde02f1 100644 --- a/massa-protocol-worker/Cargo.toml +++ b/massa-protocol-worker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_protocol_worker" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-protocol-worker/src/handlers/block_handler/retrieval.rs b/massa-protocol-worker/src/handlers/block_handler/retrieval.rs index 64f19961501..bbcd6caeacf 100644 --- a/massa-protocol-worker/src/handlers/block_handler/retrieval.rs +++ b/massa-protocol-worker/src/handlers/block_handler/retrieval.rs @@ -409,7 +409,7 @@ impl RetrievalThread { BlockInfoReply::Operations(operations) => { // Send operations to pool, // before performing the below checks, - // and wait for them to have been procesed(i.e. added to storage). + // and wait for them to have been processed (i.e. added to storage). self.on_block_full_operations_received(from_peer_id, block_id, operations); } BlockInfoReply::NotFound => { diff --git a/massa-protocol-worker/src/handlers/peer_handler/models.rs b/massa-protocol-worker/src/handlers/peer_handler/models.rs index 7e3c05f44c8..645868a766a 100644 --- a/massa-protocol-worker/src/handlers/peer_handler/models.rs +++ b/massa-protocol-worker/src/handlers/peer_handler/models.rs @@ -68,7 +68,7 @@ impl Ord for ConnectionMetadata { // Time since last failed peer test, more recent = less priority let test_failure_check = match (self.last_test_failure, other.last_test_failure) { - (Some(st), Some(ot)) => Some(st.cmp(&ot)), + (Some(st), Some(other_)) => Some(st.cmp(&other_)), (Some(_), None) => Some(Ordering::Greater), (None, Some(_)) => Some(Ordering::Less), (None, None) => None, @@ -79,7 +79,7 @@ impl Ord for ConnectionMetadata { // Time since last succeeded peer test, more recent = more priority let test_success_check = match (self.last_test_success, other.last_test_success) { - (Some(st), Some(ot)) => Some(st.cmp(&ot).reverse()), + (Some(st), Some(other_)) => Some(st.cmp(&other_).reverse()), (Some(_), None) => Some(Ordering::Less), (None, Some(_)) => Some(Ordering::Greater), (None, None) => None, diff --git a/massa-sdk/Cargo.toml b/massa-sdk/Cargo.toml index f2974a9d8e5..96a14a5a339 100644 --- a/massa-sdk/Cargo.toml +++ b/massa-sdk/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "massa_sdk" -version = "2.4.0" +version = "2.5.0" edition = "2021" [dependencies] jsonrpsee = {workspace = true, "features" = ["client"]} -jsonrpsee-http-client = {workspace = true, "features" = ["webpki-tls"]} -jsonrpsee-ws-client = {workspace = true, "features" = ["webpki-tls"]} +jsonrpsee-http-client = {workspace = true} +jsonrpsee-ws-client = {workspace = true} http = {workspace = true} tonic = {workspace = true, "features" = ["gzip"]} # BOM UPGRADE Revert to {"version": "0.9.1", "features": ["gzip"]} if problem thiserror = {workspace = true} diff --git a/massa-sdk/src/lib.rs b/massa-sdk/src/lib.rs index 357968cd77c..3837676c3f5 100644 --- a/massa-sdk/src/lib.rs +++ b/massa-sdk/src/lib.rs @@ -575,7 +575,7 @@ impl RpcClientV2 { /// New produced blocks pub async fn subscribe_new_blocks( &self, - ) -> Result, jsonrpsee::core::Error> { + ) -> Result, jsonrpsee::core::client::Error> { if let Some(client) = self.ws_client.as_ref() { client .subscribe( @@ -592,7 +592,8 @@ impl RpcClientV2 { /// New produced blocks headers pub async fn subscribe_new_blocks_headers( &self, - ) -> Result>, jsonrpsee::core::Error> { + ) -> Result>, jsonrpsee::core::client::Error> + { if let Some(client) = self.ws_client.as_ref() { client .subscribe( @@ -609,7 +610,7 @@ impl RpcClientV2 { /// New produced blocks with operations content. pub async fn subscribe_new_filled_blocks( &self, - ) -> Result, jsonrpsee::core::Error> { + ) -> Result, jsonrpsee::core::client::Error> { if let Some(client) = self.ws_client.as_ref() { client .subscribe( @@ -626,7 +627,7 @@ impl RpcClientV2 { /// New produced operations. pub async fn subscribe_new_operations( &self, - ) -> Result, jsonrpsee::core::Error> { + ) -> Result, jsonrpsee::core::client::error::Error> { if let Some(client) = self.ws_client.as_ref() { client .subscribe( @@ -642,18 +643,22 @@ impl RpcClientV2 { } fn http_client_from_url(url: &str, http_config: &HttpConfig) -> HttpClient { - let mut builder = HttpClientBuilder::default() + let builder = HttpClientBuilder::default() .max_request_size(http_config.client_config.max_request_body_size) .request_timeout(http_config.client_config.request_timeout.to_duration()) .max_concurrent_requests(http_config.client_config.max_concurrent_requests) .id_format(get_id_kind(http_config.client_config.id_kind.as_str())) .set_headers(get_headers(&http_config.client_config.headers)); + // Note: use_*_rustls() are not available anymore + // keep the config for compatibility reason but this will be unused + /* match http_config.client_config.certificate_store.as_str() { "Native" => builder = builder.use_native_rustls(), "WebPki" => builder = builder.use_webpki_rustls(), _ => {} } + */ builder .build(url) @@ -664,7 +669,7 @@ async fn ws_client_from_url(url: &str, ws_config: &WsConfig) -> WsClient where WsClient: SubscriptionClientT, { - let mut builder = WsClientBuilder::default() + let builder = WsClientBuilder::default() .max_request_size(ws_config.client_config.max_request_body_size) .request_timeout(ws_config.client_config.request_timeout.to_duration()) .max_concurrent_requests(ws_config.client_config.max_concurrent_requests) @@ -673,11 +678,15 @@ where .max_buffer_capacity_per_subscription(ws_config.max_notifs_per_subscription) .max_redirections(ws_config.max_redirections); + // Note: use_*_rustls() are not available anymore + // keep the config for compatibility reason but this will be unused + /* match ws_config.client_config.certificate_store.as_str() { "Native" => builder = builder.use_native_rustls(), "WebPki" => builder = builder.use_webpki_rustls(), _ => {} } + */ builder .build(url) diff --git a/massa-serialization/Cargo.toml b/massa-serialization/Cargo.toml index b30a4f70fd7..3a3f152bb70 100644 --- a/massa-serialization/Cargo.toml +++ b/massa-serialization/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_serialization" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-serialization/src/lib.rs b/massa-serialization/src/lib.rs index 79d75060879..5d06985663c 100644 --- a/massa-serialization/src/lib.rs +++ b/massa-serialization/src/lib.rs @@ -252,7 +252,8 @@ macro_rules! gen_varint { gen_varint! { u16, U16VarIntSerializer, u16_buffer, U16VarIntDeserializer, "`u16`"; u32, U32VarIntSerializer, u32_buffer, U32VarIntDeserializer, "`u32`"; -u64, U64VarIntSerializer, u64_buffer, U64VarIntDeserializer, "`u64`" +u64, U64VarIntSerializer, u64_buffer, U64VarIntDeserializer, "`u64`"; +u128, U128VarIntSerializer, u128_buffer, U128VarIntDeserializer, "`u128`" } #[derive(Clone)] @@ -486,7 +487,7 @@ mod tests { use num::rational::Ratio; use paste::paste; - // This macro creates a suite of tests for all types of numbers declared as parameters. Ths list of the + // This macro creates a suite of tests for all types of numbers declared as parameters. This list of the // tests for each type : // - Test with a normal case that everything works // - Test with a normal case but a more bigger number that everything works diff --git a/massa-signature/Cargo.toml b/massa-signature/Cargo.toml index 0f95e57e029..8db584f1bb3 100644 --- a/massa-signature/Cargo.toml +++ b/massa-signature/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_signature" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-storage/Cargo.toml b/massa-storage/Cargo.toml index c8917c92bb7..b2bef5d7ed6 100644 --- a/massa-storage/Cargo.toml +++ b/massa-storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_storage" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-storage/src/lib.rs b/massa-storage/src/lib.rs index 62744fb121f..ca4cd256beb 100644 --- a/massa-storage/src/lib.rs +++ b/massa-storage/src/lib.rs @@ -1,7 +1,7 @@ //! Copyright (c) 2022 MASSA LABS //! //! This crate is used to store shared objects (blocks, operations...) across different modules. -//! The clonable `Storage` structure has thread-safe shared access to the stored objects. +//! The cloneable `Storage` structure has thread-safe shared access to the stored objects. //! //! The `Storage` structure also has lists of object references held by the current instance of `Storage`. //! When no instance of `Storage` claims a reference to a given object anymore, that object is automatically removed from storage. @@ -131,7 +131,7 @@ impl Storage { /// Efficiently extends the current Storage by consuming the refs of another storage. pub fn extend(&mut self, mut other: Storage) { - // Take ownership ot `other`'s references. + // Take ownership of `other`'s references. // Objects owned by both require a counter decrement and are handled when `other` is dropped. other .local_used_ops diff --git a/massa-test-framework/Cargo.toml b/massa-test-framework/Cargo.toml index d71e995f7de..52c60fcaa6d 100644 --- a/massa-test-framework/Cargo.toml +++ b/massa-test-framework/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_test_framework" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-time/Cargo.toml b/massa-time/Cargo.toml index 374097892bb..706fc254966 100644 --- a/massa-time/Cargo.toml +++ b/massa-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_time" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-versioning/Cargo.toml b/massa-versioning/Cargo.toml index a530e2583b1..3a267a50f47 100644 --- a/massa-versioning/Cargo.toml +++ b/massa-versioning/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_versioning" -version = "2.4.0" +version = "2.5.0" authors = ["Massa Labs "] edition = "2021" diff --git a/massa-versioning/src/versioning.rs b/massa-versioning/src/versioning.rs index 3ad72745524..6eaaa6cf67b 100644 --- a/massa-versioning/src/versioning.rs +++ b/massa-versioning/src/versioning.rs @@ -1310,6 +1310,12 @@ impl MipStoreRaw { Ok(()) } + // get network versions stats + // + pub fn get_network_versions_stats(&self) -> HashMap { + self.stats.network_version_counters.clone() + } + // Final state pub fn is_key_value_valid(&self, serialized_key: &[u8], serialized_value: &[u8]) -> bool { self._is_key_value_valid(serialized_key, serialized_value) diff --git a/massa-versioning/src/versioning_ser_der.rs b/massa-versioning/src/versioning_ser_der.rs index 463adb7c7a3..f7f73e01825 100644 --- a/massa-versioning/src/versioning_ser_der.rs +++ b/massa-versioning/src/versioning_ser_der.rs @@ -668,7 +668,7 @@ impl Deserializer for MipStoreStatsDeserializer { )) })?; - let (rem3, latest_annoucements_) = context( + let (rem3, latest_announcements_) = context( "Failed MipStoreStats latest announcements der", length_count( context("Failed latest announcements count der", |input| { @@ -714,7 +714,7 @@ impl Deserializer for MipStoreStatsDeserializer { rem4, MipStoreStats { config: self.config.clone(), - latest_announcements: latest_annoucements_.into_iter().collect(), + latest_announcements: latest_announcements_.into_iter().collect(), network_version_counters: network_version_counters.into_iter().collect(), }, )) diff --git a/massa-wallet/Cargo.toml b/massa-wallet/Cargo.toml index 3e41697bed0..60d252db0df 100644 --- a/massa-wallet/Cargo.toml +++ b/massa-wallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "massa_wallet" -version = "2.4.0" +version = "2.5.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/massa-xtask/Cargo.toml b/massa-xtask/Cargo.toml index faa40f53332..f4f32126d65 100644 --- a/massa-xtask/Cargo.toml +++ b/massa-xtask/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "massa_xtask" -version = "2.4.0" +version = "2.5.0" edition = "2021" [dependencies] +# update_package_versions dependencies massa_models = {workspace = true} toml_edit = {workspace = true} # BOM UPGRADE Revert to "0.19.8" if problem walkdir = {workspace = true} +# check_gas_costs dependencies +massa-sc-runtime = {workspace = true, features = ["gas_calibration"]} diff --git a/massa-xtask/src/check_gas_cost_definitions.rs b/massa-xtask/src/check_gas_cost_definitions.rs new file mode 100644 index 00000000000..2976aed9dd4 --- /dev/null +++ b/massa-xtask/src/check_gas_cost_definitions.rs @@ -0,0 +1,55 @@ +use massa_sc_runtime::GasCosts; +use std::collections::HashSet; + +pub(crate) fn check_gas_cost_definitions() -> Result<(), String> { + // Check gas cost definition between: + // massa-node/base_config/gas_costs/abi_gas_costs.json + // massa-sc-runtime GasCosts::default() + + let gas_costs = GasCosts::default(); + let gas_costs_abi_defined = gas_costs + .get_abi_costs() + .keys() + .cloned() + .collect::>(); + + let massa_node_gas_costs = GasCosts::new( + // SETTINGS.execution.abi_gas_costs_file.clone(), + // SETTINGS.execution.wasm_gas_costs_file.clone(), + "massa-node/base_config/gas_costs/abi_gas_costs.json".into(), + ) + .expect("Failed to load gas costs"); + + let massa_node_gas_costs_abi_defined = massa_node_gas_costs + .get_abi_costs() + .keys() + .cloned() + .collect::>(); + + let mut found_diff = false; + let diff_1 = gas_costs_abi_defined.difference(&massa_node_gas_costs_abi_defined); + for x1 in diff_1 { + println!("Found in default() but not in json: {x1}"); + found_diff = true; + } + + let diff_2 = massa_node_gas_costs_abi_defined.difference(&gas_costs_abi_defined); + let exclude_list = HashSet::from([ + "cl_compilation", + "launch", + "sp_compilation", + "launch_wasmv1", + "max_instance", + ]); + for x2 in diff_2 { + if !exclude_list.contains(x2.as_str()) { + println!("Found in json but not in default(): {x2}"); + } + } + + if found_diff { + Err("Found gas costs definition differences".to_string()) + } else { + Ok(()) + } +} diff --git a/massa-xtask/src/main.rs b/massa-xtask/src/main.rs index a962ef40764..df8aacee12e 100644 --- a/massa-xtask/src/main.rs +++ b/massa-xtask/src/main.rs @@ -1,4 +1,7 @@ +mod check_gas_cost_definitions; mod update_package_versions; + +use crate::check_gas_cost_definitions::check_gas_cost_definitions; use crate::update_package_versions::update_package_versions; use std::env; @@ -10,6 +13,7 @@ fn main() { match task.as_deref() { // We can add more tasks here Some("update_package_versions") => update_package_versions(), + Some("check_gas_cost_definitions") => check_gas_cost_definitions().unwrap(), _ => panic!("Unknown task"), } } diff --git a/tools/setup_test.rs b/tools/setup_test.rs index 3bf11fefd7e..7d17ae45d1e 100644 --- a/tools/setup_test.rs +++ b/tools/setup_test.rs @@ -80,7 +80,7 @@ fn download_src() -> Result { if Path::new(&extract_folder).exists() { println!( - "Please remove the folder: {} before runnning this script", + "Please remove the folder: {} before running this script", extract_folder ); std::process::exit(1);