diff --git a/.drone.yml b/.drone.yml index df3e313fc7..d66e3de853 100644 --- a/.drone.yml +++ b/.drone.yml @@ -53,10 +53,10 @@ steps: - cp /usr/local/bin/mina cli/bin/ - name: build - image: rust:1.80-bullseye + image: rust:1.83-bullseye commands: - apt-get update && apt-get install -y libssl-dev libjemalloc-dev jq protobuf-compiler - - rustup update 1.80 && rustup default 1.80 + - rustup update 1.83 && rustup default 1.83 - rustup component add rustfmt # just to be sure it builds without errors - cargo build diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b3b835dfd8..2e728f7d8f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -43,6 +43,81 @@ jobs: cd ledger cargo test --release -- -Z unstable-options --report-time + ledger-32x9-tests: + runs-on: ubuntu-20.04 + steps: + - name: Git checkout + uses: actions/checkout@v4 + - name: Setup build dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler + - name: Setup Rust + run: | + # Nightly to be able to use `--report-time` below + rustup install nightly + rustup override set nightly + - name: Download circuits files + run: | + git clone --depth 1 https://github.com/openmina/circuit-blobs.git + ln -s -b $PWD/circuit-blobs/* ledger/ + - name: Enable 32x9 fields implementation + run: | + cargo install sd + sd '^mina-curves.*$' '' ./Cargo.toml + sd '^ark-ff = \{ version .*$' '' ./Cargo.toml + sd -F '# UNCOMMENTED_IN_CI ' '' ./Cargo.toml + cat ./Cargo.toml + - name: Build ledger tests + run: | + cd ledger + cargo build --release --tests + - name: Run ledger tests + run: | + cd ledger + cargo test --release -- -Z unstable-options --report-time + + vrf-tests: + runs-on: ubuntu-20.04 + steps: + - name: Git checkout + uses: actions/checkout@v4 + - name: Setup build dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler + - name: Setup Rust + run: | + # Nightly to be able to use `--report-time` below + rustup install nightly + rustup override set nightly + - name: Build vrf tests + run: | + cd vrf + cargo build --release --tests + - name: Run vrf tests + run: | + cd vrf + cargo test --release -- -Z unstable-options --report-time + + tx-fuzzer-check: + runs-on: ubuntu-20.04 + steps: + - name: Git checkout + uses: actions/checkout@v4 + - name: Setup build dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler + - name: Setup Rust + run: | + rustup install nightly + rustup override set nightly + - name: Check for compilation errors in transaction fuzzer + run: | + cd tools/fuzzing + cargo check + p2p-tests: runs-on: ubuntu-20.04 steps: @@ -56,7 +131,7 @@ jobs: - name: Setup Rust run: | - rustup default 1.80 + rustup default 1.83 rustup component add rustfmt - name: Setup Rust Cache @@ -82,7 +157,7 @@ jobs: - name: Setup Rust run: | - rustup default 1.80 + rustup default 1.83 rustup component add rustfmt - name: Setup Rust Cache @@ -142,7 +217,7 @@ jobs: - name: Setup Rust run: | - rustup default 1.80 + rustup default 1.83 rustup component add rustfmt - name: Setup Rust Cache @@ -178,7 +253,7 @@ jobs: - name: Setup Rust run: | - rustup default 1.80 + rustup default 1.83 rustup component add rustfmt - name: Setup Rust Cache @@ -259,15 +334,15 @@ jobs: # TODO: query cluster for actual addresses, or specify then on deployment env: PEERS: | - /ip4/135.181.217.23/tcp/31881/p2p/12D3KooWS4TMSjrAS4Cj31PgjZ9KgeHh5goLP65M5GSriF28d7Jx - /ip4/135.181.217.23/tcp/30386/p2p/12D3KooWK92cYz26JqBE9vM9s9Jd9pJKcNxLd7VVRupU7YG5NupU - /ip4/135.181.217.23/tcp/32272/p2p/12D3KooWSU1DYZYVA7wAYLvLsH6yVS8oV1sMJUcC7VtUxCgtsWkJ - /ip4/135.181.217.23/tcp/32218/p2p/12D3KooWM5m9QqHpDkPJi54GPP6rGFpzo7E274husrModPSLQ7tn - /ip4/135.181.217.23/tcp/30798/p2p/12D3KooWCk2QSmQH2XbtpDXiSPUq6wb2LB2JaExHRXkJggmEfN4J - /ip4/135.181.217.23/tcp/31631/p2p/12D3KooWQ1642Dzm57Kr8tmTwS9NRFaJPy4ysaQ2ne3ZYwQn5qCk - /ip4/135.181.217.23/tcp/30196/p2p/12D3KooWHK67syE2LeTz5EnNqCe5ZFf9SoZRFN4AdHVZsL31WkMn - /ip4/135.181.217.23/tcp/30790/p2p/12D3KooWDwxrG5u12FzXAFyK7vd8aHnEQf4dwoboBJ72FUS179xK - /ip4/135.181.217.23/tcp/30070/p2p/12D3KooWEowA3VakSddUjZuBTK3HJhNM7sRqwWDbqtPtwymAMCcy + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40101/p2p/12D3KooWNGY3guz8pYHrVEqs8se4MSnnmpgguyQYDazMbVCyrMnS + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40102/p2p/12D3KooWSqZ4qtysb8Du4yVpcc5SYc3gsRuNqgMomggw6hekATWg + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40103/p2p/12D3KooWSHiGavQpamDPEc6rPaqT4PoS1Lr9aDfrfg5dKM2V6x3H + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40104/p2p/12D3KooWA3yPrTaLXsggVSCG4mr7c33YNdz5DSs87LszRUVt9vLT + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40105/p2p/12D3KooWCLcUWCdU4VstETztxE3feQyS57dVDdzBhmkj5tiCaha8 + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40106/p2p/12D3KooWNZWqEoCuhMrc9tTMxtEsfxmeFhjh2agUcmzJFNKxQnNA + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40107/p2p/12D3KooWAMSP94SM3icSeAXeBmPUuZ5JvwrZ5w87fpRHVeJkdboe + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40108/p2p/12D3KooWL5gPf5CrARVPhBi6KsDHmB1gsJKZ4vWrcLweWyMjpB5e + /dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40109/p2p/12D3KooWT1nNJLGE8jWcshPSq3FmSXmmNn2MzfmvJcWYZ1HrtHnZ outputs: peers: ${{ steps.peers.outputs.peers }} steps: @@ -296,7 +371,7 @@ jobs: # TODO: remove when replayer supports identify KEEP_CONNECTION_WITH_UNKNOWN_STREAM: true OPENMINA_SCENARIO_SEEDS: ${{ needs.k8s-peers.outputs.peers }} - REPLAYER_MULTIADDR: "/dns4/1.k8.openmina.com/tcp/31968/p2p/12D3KooWPayQEdprqY2m3biReUUybA5LoULpJE7YWu6wetEKKELv" + REPLAYER_MULTIADDR: "/dns4/primary-tcp-proxy.hz.minaprotocol.network/tcp/40110/p2p/12D3KooWPayQEdprqY2m3biReUUybA5LoULpJE7YWu6wetEKKELv" BPF_ALIAS: /coda/0.0.1/29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6-0.0.0.0 strategy: matrix: @@ -333,7 +408,7 @@ jobs: with: pattern: tests* merge-multiple: true - + - name: Download tests uses: actions/download-artifact@v4 with: @@ -399,7 +474,7 @@ jobs: env: PEERS_LIST: ${{ needs.k8s-peers.outputs.peers }} PEER_LIST_FILE: peer-list.txt - WORK_DIR: data + OPENMINA_HOME: data BPF_ALIAS: /coda/0.0.1/29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6-0.0.0.0 services: @@ -444,7 +519,7 @@ jobs: env: OPENMINA_COMMAND: openmina NO_PEER_DISCOVERY: "true" - OUT_PATH: ${{ env.WORK_DIR }}/logs/bootstrap_output + OUT_PATH: ${{ env.OPENMINA_HOME }}/logs/bootstrap_output RECORD: state-with-input-actions run: | mkdir -p $OUT_PATH @@ -459,14 +534,14 @@ jobs: uses: actions/upload-artifact@v4 with: name: bootstrap-logs - path: ${{ env.WORK_DIR }}/logs/* + path: ${{ env.OPENMINA_HOME }}/logs/* if: ${{ failure() }} - name: Upload record uses: actions/upload-artifact@v4 with: name: bootstrap-record - path: ${{ env.WORK_DIR }}/recorder/* + path: ${{ env.OPENMINA_HOME }}/recorder/* if: ${{ failure() }} - name: Archive network debugger database diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 39bda33630..a45a0d70d4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: sudo apt install -y protobuf-compiler - uses: actions-rs/toolchain@v1 with: - toolchain: 1.81 + toolchain: 1.83 components: rustfmt, clippy default: true - uses: actions-rs/cargo@v1 diff --git a/.gitignore b/.gitignore index 053699847a..a8e606eb62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /node/testing/res/ +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb675141a..6ff4a9cf2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.12.0] - 2024-12-04 + +### Fixed + +- Properly handle time in cases in which the system goes to sleep. +- Various corner cases in block proof production. +- Improved ledgers sync during bootstrap (be smarter about which peers to query). +- **P2P**: More efficient memory usage when managing the p2p state. +- **P2P**: Lower outgoing traffic by being more conservative about what is broadcasted to each peer. +- **P2P**: Better handling of disconnections. +- **VRF**: Correctly handle cases in which the delegator table is empty. +- **Webnode**: Peer connection handling improvements. + +### Changed + +- **Webnode**: Reduced startup time by loading prover indexes in parallel to the bootstrap process. +- **Webnode**: Faster field operations (faster hashing and proving). +- Improved hashing performance by caching and reusing common prefixes. +- Added more pre-validation checks for blocks received from the network. + +### Added + +- **Webnode**: Transaction propagation +- **P2P**: Support for specifying the external ip to be advertised. + ## [0.11.0] - 2024-10-31 ### Added @@ -305,24 +330,25 @@ First public release. - Alpha version of the node which can connect and syncup to the berkeleynet network, and keep applying new blocks to maintain consensus state and ledger up to date. - Web-based frontend for the node. -[Unreleased]: https://github.com/openmina/openmina/compare/v0.11.0...develop -[0.11.0]: https://github.com/openmina/openmina/releases/tag/v0.10.3...v0.11.0 -[0.10.3]: https://github.com/openmina/openmina/releases/tag/v0.10.0...v0.10.3 -[0.10.0]: https://github.com/openmina/openmina/releases/tag/v0.9.0...v0.10.0 -[0.9.0]: https://github.com/openmina/openmina/releases/tag/v0.8.14...v0.9.0 -[0.8.14]: https://github.com/openmina/openmina/releases/tag/v0.8.13...v0.8.14 -[0.8.13]: https://github.com/openmina/openmina/releases/tag/v0.8.3...v0.8.13 -[0.8.3]: https://github.com/openmina/openmina/releases/tag/v0.8.2...v0.8.3 -[0.8.2]: https://github.com/openmina/openmina/releases/tag/v0.8.1...v0.8.2 -[0.8.1]: https://github.com/openmina/openmina/releases/tag/v0.8.0...v0.8.1 -[0.8.0]: https://github.com/openmina/openmina/releases/tag/v0.7.0...v0.8.0 -[0.7.0]: https://github.com/openmina/openmina/releases/tag/v0.6.0...v0.7.0 -[0.6.0]: https://github.com/openmina/openmina/releases/tag/v0.5.1...v0.6.0 -[0.5.1]: https://github.com/openmina/openmina/releases/tag/v0.5.0...v0.5.1 -[0.5.0]: https://github.com/openmina/openmina/releases/tag/v0.4.0...v0.5.0 -[0.4.0]: https://github.com/openmina/openmina/releases/tag/v0.3.0...v0.4.0 -[0.3.1]: https://github.com/openmina/openmina/releases/tag/v0.3.0...v0.3.1 -[0.3.0]: https://github.com/openmina/openmina/releases/tag/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/openmina/openmina/releases/tag/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/openmina/openmina/releases/tag/v0.0.1...v0.1.0 +[Unreleased]: https://github.com/openmina/openmina/compare/v0.12.0...develop +[0.12.0]: https://github.com/openmina/openmina/compare/v0.11.0...v0.12.0 +[0.11.0]: https://github.com/openmina/openmina/compare/v0.10.3...v0.11.0 +[0.10.3]: https://github.com/openmina/openmina/compare/v0.10.0...v0.10.3 +[0.10.0]: https://github.com/openmina/openmina/compare/v0.9.0...v0.10.0 +[0.9.0]: https://github.com/openmina/openmina/compare/v0.8.14...v0.9.0 +[0.8.14]: https://github.com/openmina/openmina/compare/v0.8.13...v0.8.14 +[0.8.13]: https://github.com/openmina/openmina/compare/v0.8.3...v0.8.13 +[0.8.3]: https://github.com/openmina/openmina/compare/v0.8.2...v0.8.3 +[0.8.2]: https://github.com/openmina/openmina/compare/v0.8.1...v0.8.2 +[0.8.1]: https://github.com/openmina/openmina/compare/v0.8.0...v0.8.1 +[0.8.0]: https://github.com/openmina/openmina/compare/v0.7.0...v0.8.0 +[0.7.0]: https://github.com/openmina/openmina/compare/v0.6.0...v0.7.0 +[0.6.0]: https://github.com/openmina/openmina/compare/v0.5.1...v0.6.0 +[0.5.1]: https://github.com/openmina/openmina/compare/v0.5.0...v0.5.1 +[0.5.0]: https://github.com/openmina/openmina/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/openmina/openmina/compare/v0.3.0...v0.4.0 +[0.3.1]: https://github.com/openmina/openmina/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/openmina/openmina/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/openmina/openmina/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/openmina/openmina/compare/v0.0.1...v0.1.0 [0.0.1]: https://github.com/openmina/openmina/releases/tag/v0.0.1 diff --git a/Cargo.lock b/Cargo.lock index 7350cf9a1f..a8bb229c4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,7 +242,7 @@ dependencies = [ [[package]] name = "ark-ec" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "ark-ff", "ark-serialize 0.3.0", @@ -256,7 +256,7 @@ dependencies = [ [[package]] name = "ark-ff" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "ark-ff-asm", "ark-ff-macros", @@ -274,7 +274,7 @@ dependencies = [ [[package]] name = "ark-ff-asm" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "quote", "syn 1.0.109", @@ -283,7 +283,7 @@ dependencies = [ [[package]] name = "ark-ff-macros" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "num-bigint", "num-traits", @@ -294,7 +294,7 @@ dependencies = [ [[package]] name = "ark-poly" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "ark-ff", "ark-serialize 0.3.0", @@ -307,7 +307,7 @@ dependencies = [ [[package]] name = "ark-serialize" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "ark-serialize-derive", "ark-std 0.3.0", @@ -328,7 +328,7 @@ dependencies = [ [[package]] name = "ark-serialize-derive" version = "0.3.0" -source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" +source = "git+https://github.com/openmina/algebra?rev=d0343f5#d0343f56c517675d2c340d066727d470ce22d552" dependencies = [ "proc-macro2", "quote", @@ -535,7 +535,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http", + "http 0.2.9", "log", "url", ] @@ -571,18 +571,19 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-util", "itoa", "matchit", "memchr", @@ -594,28 +595,33 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", "tower-service", + "tracing", ] [[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.1.0", + "http-body 1.0.1", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1036,7 +1042,7 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "cli" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "bytes", @@ -2387,7 +2393,7 @@ dependencies = [ [[package]] name = "groupmap" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -2405,7 +2411,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.9", "indexmap 2.0.2", "slab", "tokio", @@ -2415,7 +2421,7 @@ dependencies = [ [[package]] name = "hash-tool" -version = "0.11.0" +version = "0.12.0" dependencies = [ "bs58 0.5.0", "hex", @@ -2474,7 +2480,7 @@ dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http", + "http 0.2.9", "httpdate", "mime", "sha1", @@ -2486,7 +2492,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.9", ] [[package]] @@ -2606,6 +2612,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -2613,15 +2630,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" @@ -2652,8 +2692,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -2665,6 +2705,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -2672,12 +2731,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.27", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", + "pin-project-lite", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -2756,8 +2830,8 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http", - "hyper", + "http 0.2.9", + "hyper 0.14.27", "log", "rand", "tokio", @@ -2828,7 +2902,7 @@ dependencies = [ [[package]] name = "internal-tracing" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" [[package]] name = "io-lifetimes" @@ -2991,7 +3065,7 @@ dependencies = [ [[package]] name = "kimchi" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -3041,7 +3115,7 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "ledger-tool" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "mina-curves", @@ -3367,7 +3441,7 @@ dependencies = [ [[package]] name = "libp2p-rpc-behaviour" -version = "0.11.0" +version = "0.12.0" dependencies = [ "libp2p", "log", @@ -3655,7 +3729,7 @@ dependencies = [ [[package]] name = "mina-curves" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -3664,7 +3738,7 @@ dependencies = [ [[package]] name = "mina-hasher" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ff", "bitvec", @@ -3693,11 +3767,11 @@ dependencies = [ "lazy_static", "mina-curves", "mina-hasher", - "mina-poseidon", "mina-signer", "num-bigint", "o1-utils", "openmina-macros", + "poseidon", "rsexp", "rsexp-derive", "serde", @@ -3717,7 +3791,7 @@ dependencies = [ [[package]] name = "mina-poseidon" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -3734,7 +3808,7 @@ dependencies = [ [[package]] name = "mina-signer" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -3752,7 +3826,7 @@ dependencies = [ [[package]] name = "mina-transport" -version = "0.11.0" +version = "0.12.0" dependencies = [ "blake2", "hex", @@ -3763,7 +3837,7 @@ dependencies = [ [[package]] name = "mina-tree" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "ark-ec", @@ -3781,7 +3855,6 @@ dependencies = [ "getrandom", "hex", "itertools 0.10.5", - "js-sys", "juniper", "kimchi", "lazy_static", @@ -3798,6 +3871,7 @@ dependencies = [ "openmina-core", "openmina-macros", "poly-commitment", + "poseidon", "postcard", "rand", "rand_pcg", @@ -3857,7 +3931,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 0.2.9", "httparse", "log", "memchr", @@ -4103,7 +4177,7 @@ dependencies = [ [[package]] name = "node" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "ark-ff", @@ -4119,6 +4193,7 @@ dependencies = [ "openmina-core", "openmina-node-account", "p2p", + "poseidon", "postcard", "rand", "redux", @@ -4352,7 +4427,7 @@ dependencies = [ [[package]] name = "o1-utils" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -4461,7 +4536,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openmina-bootstrap-sandbox" -version = "0.11.0" +version = "0.12.0" dependencies = [ "base64 0.21.7", "binprot", @@ -4486,12 +4561,17 @@ dependencies = [ [[package]] name = "openmina-core" -version = "0.11.0" +version = "0.12.0" dependencies = [ + "argon2", "ark-ff", + "base64 0.22.0", "binprot", "binprot_derive", + "bs58 0.4.0", + "crypto_secretbox", "hex", + "js-sys", "lazy_static", "md5", "mina-hasher", @@ -4500,22 +4580,25 @@ dependencies = [ "once_cell", "openmina-fuzzer", "openmina-macros", + "poseidon", "redux", "serde", "serde_json", "sha2 0.10.8", "slab", + "thiserror", "time", "tokio", "tracing", "wasm-bindgen", "wasm-bindgen-futures", "wasm_thread", + "web-sys", ] [[package]] name = "openmina-fuzzer" -version = "0.11.0" +version = "0.12.0" dependencies = [ "lazy_static", "rand", @@ -4526,7 +4609,7 @@ dependencies = [ [[package]] name = "openmina-gossipsub-sandbox" -version = "0.11.0" +version = "0.12.0" dependencies = [ "bs58 0.5.0", "env_logger", @@ -4540,7 +4623,7 @@ dependencies = [ [[package]] name = "openmina-macros" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "openmina-core", @@ -4553,13 +4636,10 @@ dependencies = [ [[package]] name = "openmina-node-account" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", - "argon2", - "base64 0.22.0", "bs58 0.4.0", - "crypto_secretbox", "hex", "lazy_static", "mina-hasher", @@ -4574,7 +4654,7 @@ dependencies = [ [[package]] name = "openmina-node-common" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ark-ff", "gloo-timers", @@ -4605,7 +4685,7 @@ dependencies = [ [[package]] name = "openmina-node-invariants" -version = "0.11.0" +version = "0.12.0" dependencies = [ "documented", "lazy_static", @@ -4619,7 +4699,7 @@ dependencies = [ [[package]] name = "openmina-node-native" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "bs58 0.4.0", @@ -4656,10 +4736,12 @@ dependencies = [ [[package]] name = "openmina-node-testing" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "axum", + "base64 0.22.0", + "bs58 0.4.0", "clap 4.5.20", "console", "ctrlc", @@ -4700,12 +4782,13 @@ dependencies = [ [[package]] name = "openmina-node-web" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "bytes", "console_error_panic_hook", "derive_more", + "gloo-utils", "jsonpath-rust", "libp2p-identity", "mina-p2p-messages", @@ -4726,7 +4809,7 @@ dependencies = [ [[package]] name = "openmina-producer-dashboard" -version = "0.11.0" +version = "0.12.0" dependencies = [ "bincode", "clap 4.5.20", @@ -4818,10 +4901,11 @@ dependencies = [ [[package]] name = "p2p" -version = "0.11.0" +version = "0.12.0" dependencies = [ "aes-gcm 0.10.3", "anyhow", + "base64 0.22.0", "binprot", "binprot_derive", "bitflags 2.4.1", @@ -4841,7 +4925,6 @@ dependencies = [ "gloo-utils", "hex", "hkdf", - "hyper", "js-sys", "libc", "libp2p-identity", @@ -4883,7 +4966,7 @@ dependencies = [ [[package]] name = "p2p-testing" -version = "0.11.0" +version = "0.12.0" dependencies = [ "derive_more", "futures", @@ -5203,7 +5286,7 @@ dependencies = [ [[package]] name = "poly-commitment" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ec", "ark-ff", @@ -5277,6 +5360,15 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +[[package]] +name = "poseidon" +version = "0.12.0" +dependencies = [ + "ark-ff", + "mina-curves", + "once_cell", +] + [[package]] name = "postcard" version = "1.0.10" @@ -5686,7 +5778,7 @@ dependencies = [ [[package]] name = "redux" version = "0.1.0" -source = "git+https://github.com/openmina/redux-rs.git?rev=bf0726e5#bf0726e596a456f2204c5138528ac7a64695e3b4" +source = "git+https://github.com/openmina/redux-rs.git?rev=ab14890c#ab14890c68fa478ccfec9d499a76d25f20759619" dependencies = [ "enum_dispatch", "linkme", @@ -5741,7 +5833,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "replay_dynamic_effects" -version = "0.11.0" +version = "0.12.0" dependencies = [ "node", "openmina-node-invariants", @@ -5760,9 +5852,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-tls", "ipnet", "js-sys", @@ -5776,7 +5868,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -6124,7 +6216,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "salsa-simple" -version = "0.11.0" +version = "0.12.0" dependencies = [ "generic-array", "hex", @@ -6532,7 +6624,7 @@ dependencies = [ [[package]] name = "snark" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ark-ec", "ark-ff", @@ -6552,6 +6644,7 @@ dependencies = [ "once_cell", "openmina-core", "poly-commitment", + "poseidon", "rand", "rayon", "redux", @@ -6993,6 +7086,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "synstructure" version = "0.12.6" @@ -7290,27 +7389,34 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ "bitflags 2.4.1", "bytes", - "futures-core", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" @@ -7418,7 +7524,7 @@ dependencies = [ [[package]] name = "transaction_fuzzer" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ark-ec", "ark-ff", @@ -7441,6 +7547,7 @@ dependencies = [ "object 0.36.5", "once_cell", "openmina-core", + "poseidon", "rand", "ring_buffer", "rsprocmaps", @@ -7512,7 +7619,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.9", "httparse", "log", "rand", @@ -7551,7 +7658,7 @@ dependencies = [ [[package]] name = "turshi" version = "0.1.0" -source = "git+https://github.com/openmina/proof-systems?rev=2fdb5a7#2fdb5a75c61e2ac1db452bd813aa12fd9fb77527" +source = "git+https://github.com/openmina/proof-systems?rev=c478b19#c478b197ddb7fcefee87c4cfdc097a217a855086" dependencies = [ "ark-ff", "hex", @@ -7785,7 +7892,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "vrf" -version = "0.11.0" +version = "0.12.0" dependencies = [ "anyhow", "ark-ec", @@ -7802,6 +7909,7 @@ dependencies = [ "num", "o1-utils", "openmina-node-account", + "poseidon", "rand", "redux", "serde", @@ -7844,8 +7952,8 @@ dependencies = [ "futures-channel", "futures-util", "headers", - "http", - "hyper", + "http 0.2.9", + "hyper 0.14.27", "log", "mime", "mime_guess", diff --git a/Cargo.toml b/Cargo.toml index 763bd8b627..7c6b6a2118 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "mina-p2p-messages", "ledger", + "poseidon", "tools/transport", "tools/bootstrap-sandbox", @@ -35,20 +36,29 @@ members = [ resolver = "2" +[workspace.lints.clippy] +#unwrap_used = "warn" +arithmetic_side_effects = "warn" +indexing_slicing = "warn" + [workspace.dependencies] mina-p2p-messages = { path = "mina-p2p-messages" } +poseidon = { path = "poseidon" } ledger = { path = "ledger", package = "mina-tree" } -mina-hasher = { git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7" } -mina-signer = { git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7" } -mina-curves = { git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7" } -o1-utils = { git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7" } -kimchi = { git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7" } -mina-poseidon = {git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7"} -poly-commitment = {git = "https://github.com/openmina/proof-systems", rev = "2fdb5a7"} + +mina-hasher = { git = "https://github.com/openmina/proof-systems", rev = "c478b19" } +mina-signer = { git = "https://github.com/openmina/proof-systems", rev = "c478b19" } +mina-curves = { git = "https://github.com/openmina/proof-systems", rev = "c478b19" } +# UNCOMMENTED_IN_CI mina-curves = { git = "https://github.com/openmina/proof-systems", rev = "c478b19", features = [ "32x9" ] } +o1-utils = { git = "https://github.com/openmina/proof-systems", rev = "c478b19" } +kimchi = { git = "https://github.com/openmina/proof-systems", rev = "c478b19" } +mina-poseidon = {git = "https://github.com/openmina/proof-systems", rev = "c478b19" } +poly-commitment = {git = "https://github.com/openmina/proof-systems", rev = "c478b19" } + libp2p = { git = "https://github.com/openmina/rust-libp2p", rev = "5c44c7d9", default-features = false } vrf = { path = "vrf" } openmina-node-account = { path = "node/account" } -redux = { git = "https://github.com/openmina/redux-rs.git", rev = "bf0726e5", features = ["serde"] } +redux = { git = "https://github.com/openmina/redux-rs.git", rev = "ab14890c", features = ["serde"] } serde = "1.0.190" serde_json = "1.0.107" serde_with = { version = "3.7.0", features = ["hex"] } @@ -56,6 +66,9 @@ linkme = "0.3.22" static_assertions = "1.1.0" juniper = { version = "0.16" } +ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +# UNCOMMENTED_IN_CI ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std", "32x9" ] } + [profile.fuzz] inherits = "release" @@ -69,10 +82,10 @@ incremental = false codegen-units = 1 [patch.crates-io] -ark-ff = { git = "https://github.com/openmina/algebra", rev = "33a1de2" } # branch: fix-openmina -ark-ec = { git = "https://github.com/openmina/algebra", rev = "33a1de2" } # branch: fix-openmina -ark-poly = { git = "https://github.com/openmina/algebra", rev = "33a1de2" } # branch: fix-openmina -ark-serialize = { git = "https://github.com/openmina/algebra", rev = "33a1de2" } # branch: fix-openmina +ark-ff = { git = "https://github.com/openmina/algebra", rev = "d0343f5" } # branch: fix-openmina-webnode +ark-ec = { git = "https://github.com/openmina/algebra", rev = "d0343f5" } # branch: fix-openmina-webnode +ark-poly = { git = "https://github.com/openmina/algebra", rev = "d0343f5" } # branch: fix-openmina-webnode +ark-serialize = { git = "https://github.com/openmina/algebra", rev = "d0343f5" } # branch: fix-openmina-webnode [profile.test.package."*"] opt-level = 3 diff --git a/Dockerfile b/Dockerfile index 441ca69069..fb375a0697 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM rust:buster AS build RUN apt-get update && apt-get install -y protobuf-compiler && apt-get clean -RUN rustup default 1.80 && rustup component add rustfmt +RUN rustup default 1.83 && rustup component add rustfmt WORKDIR /openmina COPY . . RUN cargo build --release --package=cli --bin=openmina diff --git a/README.md b/README.md index 6972671913..0dcc46dfbd 100644 --- a/README.md +++ b/README.md @@ -1,264 +1,91 @@ -# The Open Mina Node +
+ + + + The Open Mina Node is a fast and secure implementation of the Mina protocol in Rust. + -## With the Rust-based Open Mina node, you can produce, validate and apply blocks +![Beta][beta-badge] [![release-badge]][release-link] [![Changelog][changelog-badge]][changelog] [![Apache licensed]][Apache link] -[![Openmina Daily](https://github.com/openmina/openmina/actions/workflows/daily.yaml/badge.svg)](https://github.com/openmina/openmina/actions/workflows/daily.yaml) [![Changelog][changelog-badge]][changelog] [![release-badge]][release-link] [![Apache licensed]][Apache link] +_The **Open Mina Node** is a fast and secure implementation of the Mina protocol in **Rust**._ +_Currently in **public beta**, join our [Discord community](https://discord.com/channels/484437221055922177/1290662938734231552) to help test future releases._ -## Run the Block Producer +
-Once you have completed the [pre-requisites](./docs/docker-installation.md) for your operating system, follow these steps: +--- -### Setup Option 1: Download Docker Compose Files from the Release +## Getting Started -1. **Download the Docker Compose files:** +### Building from Source - - Go to the [Releases page](https://github.com/openmina/openmina/releases) of this repository. - - Download the latest `openmina-vX.Y.Z-docker-compose.zip` (or `.tar.gz`) file corresponding to the release version (available since v0.8.0). +- [Rust Node](/docs/building-from-source-guide.md#how-to-build-and-launch-a-node-from-source) and [Dashboards](./docs/building-from-source-guide.md#how-to-launch-the-ui) -2. **Extract the files:** +### Run Node on Devnet via Docker - - Unzip or untar the downloaded file: - ```bash - unzip openmina-vX.Y.Z-docker-compose.zip - ``` - or - ```bash - tar -xzvf openmina-vX.Y.Z-docker-compose.tar.gz - ``` - - Replace `vX.Y.Z` with the actual release version you downloaded. +- [Non-Block Producing Node](/docs/alpha-testing-guide.md) Connect to peers and sync a node on the devnet; no devnet stake needed. +- [Block Producing Node](/docs/block-producer-guide.md) Produce blocks on the devnet; sufficient devnet stake needed. +- [Local Block Production Demo](/docs/local-demo-guide.md) Produce blocks on a custom local chain without devnet stake. -3. **Navigate to the extracted directory:** - ```bash - cd openmina-vX.Y.Z-docker-compose - ``` +Block production Node UI -### Setup Option 2: Clone the Repository +--- -1. **Clone this repository:** +## Release Process - ```bash - git clone https://github.com/openmina/openmina.git - ``` +**This project is in beta**. We maintain a monthly release cycle, providing [updates every month](https://github.com/openmina/openmina/releases). -2. **Navigate to the repository:** - ```bash - cd openmina - ``` -### Launch -**Run the following command to start the demo:** +## Core Features -```bash -docker compose -f docker-compose.local.producers.yml up --pull always --force-recreate -``` - -And finally: - -**Open your browser and visit [http://localhost:8070](http://localhost:8070)** - -You should see the following screen: - -![producer-demo](https://github.com/user-attachments/assets/f0ccc36e-0ee8-4284-a8d7-de0f9a3397d6) - -## Description - -The Open Mina Node is a Mina node written completely in Rust and capable of verifying blocks of transactions, producing blocks and generating SNARKs. - -In the design of the Open Mina node, we are utilizing much of the same logic as in the Mina Web Node. The key difference is that unlike the Web Node, which is an in-browser node with limited resources, the Open Mina node is able to perform resource-intensive tasks such as SNARK proof generation. - -## Overview of the Node’s current functionalities - -| Current functionalities | In Development | Future Plans | -| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | --------------------------------------------------- | -| ☑ **Produce and prove blocks** (with and without transactions). | ☐ Receiving and broadcasting transactions from/into the transaction pool. | ☐ Direct transfer of MINA funds using Webnode | -| ☑ **Produce SNARK proofs** for transactions. | ☐ A block replayer that uses data from the archive nodes | ☐ O1JS support for Webnode. | -| ☑ **Connect to the network** and sync up to the best tip block | | | -| ☑ **Validate and apply new blocks** and transactions to update consensus and ledger state. | | | -| ☑ **Broadcast messages**: blocks, SNARK pool | | | - -Please note that receiving and broadcasting transactions from/into the transaction pool is already possible, but is still an early alpha version and needs more work. - -## Updates to the Front End - -We've added two new pages to the node's front end: - -### Mempool - -![image](https://github.com/user-attachments/assets/a66b993d-5a9f-42a7-a946-f19f6e18e6ab) - -Shows a list of the transactions from the pool and a side panel detail. - -### Benchmarks - -![image](https://github.com/user-attachments/assets/5aa9f0b8-2f53-4c2e-8b60-ed2ccaa7335b) - -The benchmarks page helps us to send transactions. The transactions are signed in the front end by the Mina signer. -Every user can send transactions and they can see in the mempool whether the transactions were sent by their node. - -## Launch the block producer demo - -Run the Open Mina block producer node by following this [guide](https://github.com/openmina/openmina/blob/main/docs/producer-demo.md). - -## How to launch the node (with Docker compose): - -From the directory containing the Docker Compose files (either the root of the cloned repository or the directory where the released Docker Compose files were extracted): - -``` -docker compose up --pull always -``` - -Then visit http://localhost:8070 in your browser. - -![image](https://github.com/user-attachments/assets/b8e10a12-ec96-44a9-951a-ef0c1b291428) - -By default, `docker compose up` will use the latest node and frontend images available (tagged with `latest`), but specific versions can be selected by using the `OPENMINA_TAG` and `OPENMINA_FRONTEND_TAG` variables. - -## How to launch the node (without Docker compose): - -This installation guide has been tested on Debian and Ubuntu and should work on most distributions of Linux. - -**Pre-requisites:** - -Ubuntu or Debian-based Linux distribution with the following packages installed: - -- `curl` -- `git` -- `libssl-dev` -- `pkg-config` -- `protobuf-compiler` -- `build-essential` - -Example (debian-based): - -```sh -# Either using "sudo" or as the "root" user -sudo apt install curl git libssl-dev pkg-config protobuf-compiler build-essential -``` - -Example (macOS): - -If you have not yet installed homebrew: - -```sh -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` - -```sh -brew install curl git openssl pkg-config protobuf gcc make -``` - -**Steps (for Debian-based Linux distros and macOS):** - -Open up the command line and enter the following: - -And then: - -```sh -# Install rustup and set the default Rust toolchain to 1.80 (newer versions work too) -curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.80 -# Setup the current shell with rustup -source "$HOME/.cargo/env" -# Clone the openmina repository -git clone https://github.com/openmina/openmina.git -cd openmina/ -# Build and run the node -cargo run --release -p cli node -``` - -## How to launch the UI: - -## Prerequisites - -### 1. Node.js v20.11.1 - -#### MacOS - -```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew install node@20.11.1 -``` - -#### Linux - -```bash -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash -source ~/.bashrc -nvm install 20.11.1 -``` - -#### Windows - -Download [Node.js v20.11.1](https://nodejs.org/) from the official website, open the installer and follow the prompts to complete the installation. - -### 2. Angular CLI v16.2.0 - -```bash -npm install -g @angular/cli@16.2.0 -``` - -### 3. Installation - -Open a terminal and navigate to this project's root directory - -```bash -cd PROJECT_LOCATION/openmina/frontend -``` - -Install the dependencies - -```bash -npm install -``` - -## Run the application - -```bash -npm start -``` +- **Mina Network**: Connect to peers, sync up, broadcast messages +- **Block Production**: Produces, validates, and applies blocks according to Mina's consensus. +- **SNARK Generation**: Produce SNARK proofs for transactions +- **Debugging**: A block replayer that uses data from the archive nodes ## Repository Structure -- [core/](core) - Provides basic types needed to be shared across different - components of the node. +- [core/](core) - Provides basic types needed to be shared across different components of the node. - [ledger/](ledger) - Mina ledger implementation in Rust. - [snark/](snark) - Snark/Proof verification. - [p2p/](p2p) - P2p implementation for OpenMina node. - [node/](node) - Combines all the business logic of the node. - - [native/](node/native) - OS specific pieces of the node, which is - used to run the node natively (Linux/Mac/Windows). + - [native/](node/native) - OS specific pieces of the node, which is used to run the node natively (Linux/Mac/Windows). - [testing/](node/testing) - Testing framework for OpenMina node. - [cli/](cli) - OpenMina cli. - [frontend/](frontend) - OpenMina frontend. -[Details regarding architecture](ARCHITECTURE.md) - ## The Open Mina Documentation +### What is Open Mina? + - [Why we are developing Open Mina](docs/why-openmina.md) -- What is Open Mina? - - [Openmina Node](#the-open-mina-node) - - [The Mina Web Node](https://github.com/openmina/webnode/blob/main/README.md) -- Core components - - [P2P communication](https://github.com/openmina/openmina/blob/documentation/docs/p2p_service.md) - - [GossipSub](https://github.com/openmina/mina-wiki/blob/3ea9041e52fb2e606918f6c60bd3a32b8652f016/p2p/mina-gossip.md) - - [Scan state](docs/scan-state.md) - - [SNARKs](docs/snark-work.md) -- Developer tools - - [Debugger](https://github.com/openmina/mina-network-debugger/blob/main/README.md) - - [Front End](https://github.com/openmina/mina-frontend/blob/main/README.md) - - [Dashboard](https://github.com/openmina/mina-frontend/blob/main/docs/MetricsTracing.md#Dashboard) -- [Testing](docs/testing/testing.md) -- How to run - - [Launch Openmina node](#how-to-launch-without-docker-compose) - - [Launch Node with UI](#how-to-launch-with-docker-compose) - - [Debugger](https://github.com/openmina/mina-network-debugger?tab=readme-ov-file#Preparing-for-build) - - [Web Node](https://github.com/openmina/webnode/blob/main/README.md#try-out-the-mina-web-node) -- External links - - [Medium](https://medium.com/openmina) - - [Twitter](https://twitter.com/viable_systems) + +### Core components + +- [P2P communication](https://github.com/openmina/openmina/blob/documentation/docs/p2p_service.md) + - [GossipSub](https://github.com/openmina/mina-wiki/blob/3ea9041e52fb2e606918f6c60bd3a32b8652f016/p2p/mina-gossip.md) +- [Scan state](docs/scan-state.md) +- [SNARKs](docs/snark-work.md) + +### Developer tools + +- [Front End](./docs/building-from-source-guide.md#how-to-launch-the-ui) + +### Testing Framework for Mina + +- [Full Testing Documentation](docs/testing/testing.md) + +### How to run + +- [Non-Block Producing Node](./docs/alpha-testing-guide.md) +- [Block Producing Node](./docs/block-producer-guide.md) +- [Local Block Production Demo](./docs/local-demo-guide.md) [changelog]: ./CHANGELOG.md +[beta-badge]: https://img.shields.io/badge/status-beta-yellow [changelog-badge]: https://img.shields.io/badge/changelog-Changelog-%23E05735 [release-badge]: https://img.shields.io/github/v/release/openmina/openmina [release-link]: https://github.com/openmina/openmina/releases/latest diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4583f8783e..9acf6026fc 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cli" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/cli/replay_dynamic_effects/Cargo.toml b/cli/replay_dynamic_effects/Cargo.toml index 99df2ad7df..2f9d41f8e3 100644 --- a/cli/replay_dynamic_effects/Cargo.toml +++ b/cli/replay_dynamic_effects/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "replay_dynamic_effects" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/cli/replay_dynamic_effects/src/lib.rs b/cli/replay_dynamic_effects/src/lib.rs index 29b7bcc3b4..f788b95c75 100644 --- a/cli/replay_dynamic_effects/src/lib.rs +++ b/cli/replay_dynamic_effects/src/lib.rs @@ -1,5 +1,5 @@ use ::node::{ActionWithMeta, Store}; -use openmina_node_invariants::{InvariantResult, Invariants}; +use openmina_node_invariants::{InvariantIgnoreReason, InvariantResult, Invariants}; use openmina_node_native::NodeService; pub mod ret { @@ -33,6 +33,8 @@ extern "C" fn replay_dynamic_effects( ) -> u8 { for (invariant, res) in Invariants::check_all(store, action) { match res { + InvariantResult::Ignored(InvariantIgnoreReason::GlobalInvariantNotInTestingCluster) => { + } InvariantResult::Violation(violation) => { eprintln!( "Invariant({}) violated! violation: {violation}", diff --git a/cli/src/commands/node/mod.rs b/cli/src/commands/node/mod.rs index fde5d76d9a..1f837271ce 100644 --- a/cli/src/commands/node/mod.rs +++ b/cli/src/commands/node/mod.rs @@ -169,9 +169,9 @@ impl Node { // warning, this overrides `OPENMINA_P2P_SEC_KEY` if let (Some(key_file), Some(password)) = (&self.libp2p_keypair, &self.libp2p_password) { - match AccountSecretKey::from_encrypted_file(key_file, password) { + match SecretKey::from_encrypted_file(key_file, password) { Ok(sk) => { - node_builder.p2p_sec_key(SecretKey::from_bytes(sk.to_bytes())); + node_builder.p2p_sec_key(sk.clone()); node::core::info!( node::core::log::system_time(); summary = "read sercret key from file", @@ -226,10 +226,12 @@ impl Node { if let Some(producer_key_path) = self.producer_key { let password = &self.producer_key_password; - node::core::info!(node::core::log::system_time(); summary = "loading provers index"); - let provers = BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); - node::core::info!(node::core::log::system_time(); summary = "loaded provers index"); - node_builder.block_producer_from_file(provers, producer_key_path, password)?; + openmina_core::thread::spawn(|| { + node::core::info!(node::core::log::system_time(); summary = "loading provers index"); + BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); + node::core::info!(node::core::log::system_time(); summary = "loaded provers index"); + }); + node_builder.block_producer_from_file(producer_key_path, password, None)?; if let Some(pub_key) = self.coinbase_receiver { node_builder diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000..c592911cd6 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +allow-unwrap-in-tests=true \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index acc29c0767..7d1af1ad45 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-core" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -8,6 +8,7 @@ license = "Apache-2.0" lazy_static = "1.4.0" once_cell = "1" serde = { version = "1.0.147", features = ["rc"] } +serde_json = "1.0" slab = { version = "0.4.7", features = ["serde"] } tracing = { version = "0.1", features = ["std"] } sha2 = "0.10.6" @@ -20,11 +21,17 @@ md5 = "0.7.0" multihash = { version = "0.18.1", features = ["blake2b"] } openmina-macros = { path = "../macros" } openmina-fuzzer = { path = "../fuzzer", optional = true } +argon2 = { version = "0.5.3", features = ["std"] } +crypto_secretbox = { version = "0.1.1", features = ["std"] } +base64 = "0.22" +bs58 = "0.4.0" +thiserror = "1.0.37" mina-hasher = { workspace = true } mina-p2p-messages = { workspace = true } +poseidon = { workspace = true } hex = "0.4.3" -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +ark-ff = { workspace = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] redux = { workspace = true, features=["serializable_callbacks"] } @@ -33,6 +40,8 @@ redux = { workspace = true, features=["serializable_callbacks"] } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" wasm_thread = { version = "0.3", features = [ "es_modules" ] } +js-sys = "0.3" +web-sys = { version = "0.3", features = ["Window", "Response"] } [dev-dependencies] serde_json = { version = "1" } diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 5429dbf07d..bd149eba74 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -7,7 +7,7 @@ use time::{macros::format_description, OffsetDateTime}; use crate::constants::constraint_constants; pub use crate::constants::{ - checkpoint_window_size_in_slots, grace_period_end, slots_per_window, CHECKPOINTS_PER_YEAR, + checkpoint_window_size_in_slots, slots_per_window, CHECKPOINTS_PER_YEAR, }; // TODO get constants from elsewhere @@ -39,21 +39,21 @@ pub fn is_short_range_fork(a: &MinaConsensusState, b: &MinaConsensusState) -> bo if s1.epoch_count.as_u32() == s2.epoch_count.as_u32() + 1 && s2_epoch_slot >= slots_per_epoch * 2 / 3 { - crate::log::debug!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("s2 is 1 epoch behind and not in seed update range: {} vs {}", s1.staking_epoch_data.lock_checkpoint, s2.next_epoch_data.lock_checkpoint)); + crate::log::trace!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("s2 is 1 epoch behind and not in seed update range: {} vs {}", s1.staking_epoch_data.lock_checkpoint, s2.next_epoch_data.lock_checkpoint)); // S1 is one epoch ahead of S2 and S2 is not in the seed update range s1.staking_epoch_data.lock_checkpoint == s2.next_epoch_data.lock_checkpoint } else { - crate::log::debug!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("chains are from different epochs")); + crate::log::trace!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("chains are from different epochs")); false } }; - crate::log::debug!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("epoch count: {} vs {}", a.epoch_count.as_u32(), b.epoch_count.as_u32())); + crate::log::trace!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("epoch count: {} vs {}", a.epoch_count.as_u32(), b.epoch_count.as_u32())); if a.epoch_count == b.epoch_count { let a_prev_lock_checkpoint = &a.staking_epoch_data.lock_checkpoint; let b_prev_lock_checkpoint = &b.staking_epoch_data.lock_checkpoint; // Simple case: blocks have same previous epoch, so compare previous epochs' lock_checkpoints - crate::log::debug!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("checkpoints: {} vs {}", a_prev_lock_checkpoint, b_prev_lock_checkpoint)); + crate::log::trace!(crate::log::system_time(); kind = "is_short_range_fork", msg = format!("checkpoints: {} vs {}", a_prev_lock_checkpoint, b_prev_lock_checkpoint)); a_prev_lock_checkpoint == b_prev_lock_checkpoint } else { // Check for previous epoch case using both orientations @@ -75,7 +75,9 @@ pub fn relative_min_window_density(b1: &MinaConsensusState, b2: &MinaConsensusSt let projected_window = { // Compute shift count - let shift_count = (max_slot - global_slot(b1) - 1).clamp(0, SUB_WINDOWS_PER_WINDOW); + let shift_count = max_slot + .saturating_sub(global_slot(b1) + 1) + .min(SUB_WINDOWS_PER_WINDOW); // Initialize projected window let mut projected_window = b1 diff --git a/core/src/constants.rs b/core/src/constants.rs index 7d99d64772..070adfbddc 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -107,19 +107,6 @@ pub fn checkpoint_window_size_in_slots() -> u32 { size_in_slots as u32 } -pub fn grace_period_end(constants: &v2::MinaBaseProtocolConstantsCheckedValueStableV1) -> u32 { - let slots = { - const NUM_DAYS: u64 = 3; - let n_days_ms = days_to_ms(NUM_DAYS); - let n_days = n_days_ms / constraint_constants().block_window_duration_ms; - (n_days as u32).min(constants.slots_per_epoch.as_u32()) - }; - match constraint_constants().fork.as_ref() { - None => slots, - Some(fork) => slots + fork.global_slot_since_genesis, - } -} - pub const DEFAULT_GENESIS_TIMESTAMP_MILLISECONDS: u64 = 1707157200000; pub const PROTOCOL_TRANSACTION_VERSION: u8 = 3; diff --git a/core/src/distributed_pool.rs b/core/src/distributed_pool.rs new file mode 100644 index 0000000000..7346caa20a --- /dev/null +++ b/core/src/distributed_pool.rs @@ -0,0 +1,219 @@ +use std::cmp::Ord; +use std::collections::BTreeMap; +use std::ops::RangeBounds; + +use crate::bug_condition; + +#[derive(Clone)] +pub struct DistributedPool { + counter: u64, + list: BTreeMap, + by_key: BTreeMap, +} + +impl Default for DistributedPool { + fn default() -> Self { + Self { + counter: 0, + list: Default::default(), + by_key: Default::default(), + } + } +} + +impl DistributedPool +where + State: AsRef, + Key: Ord + Clone, +{ + pub fn is_empty(&self) -> bool { + self.list.is_empty() + } + + pub fn len(&self) -> usize { + self.list.len() + } + + pub fn contains(&self, key: &Key) -> bool { + self.by_key + .get(key) + .map_or(false, |i| self.list.contains_key(i)) + } + + pub fn get(&self, key: &Key) -> Option<&State> { + self.by_key.get(key).and_then(|i| self.list.get(i)) + } + + fn get_mut(&mut self, key: &Key) -> Option<&mut State> { + self.by_key.get(key).and_then(|i| self.list.get_mut(i)) + } + + pub fn range(&self, range: R) -> impl '_ + DoubleEndedIterator + where + R: RangeBounds, + { + self.list.range(range).map(|(k, v)| (*k, v)) + } + + pub fn last_index(&self) -> u64 { + self.list.last_key_value().map_or(0, |(k, _)| *k) + } + + pub fn insert(&mut self, state: State) { + let key = state.as_ref().clone(); + self.list.insert(self.counter, state); + self.by_key.insert(key, self.counter); + self.counter = self.counter.saturating_add(1); + } + + pub fn update(&mut self, key: &Key, f: F) -> Option + where + F: FnOnce(&mut State) -> R, + { + let mut state = self.remove(key)?; + let res = f(&mut state); + self.insert(state); + Some(res) + } + + /// Don't use if the change needs to be synced with other peers. + pub fn silent_update(&mut self, key: &Key, f: F) -> Option + where + F: FnOnce(&mut State) -> R, + { + self.get_mut(key).map(f) + } + + pub fn remove(&mut self, key: &Key) -> Option { + let index = self.by_key.remove(key)?; + self.list.remove(&index) + } + + pub fn retain(&mut self, mut f: F) + where + F: FnMut(&Key, &State) -> bool, + { + self.retain_and_update(|key, state| f(key, state)) + } + + pub fn retain_and_update(&mut self, mut f: F) + where + F: FnMut(&Key, &mut State) -> bool, + { + let list = &mut self.list; + self.by_key.retain(|key, index| { + let Some(v) = list.get_mut(index) else { + bug_condition!("Pool: key found in the index, but the item not found"); + return false; + }; + if f(key, v) { + return true; + } + list.remove(index); + false + }); + } + + pub fn states(&self) -> impl Iterator { + self.list.values() + } +} + +impl DistributedPool +where + State: AsRef, + Key: Ord + Clone, +{ + pub fn next_messages_to_send( + &self, + (index, limit): (u64, u8), + extract_message: F, + ) -> (Vec, u64, u64) + where + F: Fn(&State) -> Option, + { + if limit == 0 { + let index = index.saturating_sub(1); + return (vec![], index, index); + } + + self.range(index..) + .try_fold( + (vec![], None), + |(mut list, mut first_index), (index, job)| { + if let Some(data) = extract_message(job) { + let first_index = *first_index.get_or_insert(index); + list.push(data); + if list.len() >= limit as usize { + return Err((list, first_index, index)); + } + } + + Ok((list, first_index)) + }, + ) + // Loop iterated on whole list. + .map(|(list, first_index)| (list, first_index.unwrap_or(index), self.last_index())) + // Loop preemptively ended. + .unwrap_or_else(|v| v) + } +} + +mod ser { + use super::*; + use serde::ser::SerializeStruct; + use serde::{Deserialize, Serialize}; + + #[derive(Deserialize)] + struct Pool { + counter: u64, + list: BTreeMap, + } + + impl Serialize for super::DistributedPool + where + State: Serialize, + Key: Ord, + { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut s = serializer.serialize_struct("Pool", 2)?; + s.serialize_field("counter", &self.counter)?; + s.serialize_field("list", &self.list)?; + s.end() + } + } + impl<'de, State, Key> Deserialize<'de> for super::DistributedPool + where + State: Deserialize<'de> + AsRef, + Key: Ord + Clone, + { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let v = Pool::::deserialize(deserializer)?; + let by_key = v + .list + .iter() + .map(|(k, v)| (v.as_ref().clone(), *k)) + .collect(); + Ok(Self { + counter: v.counter, + list: v.list, + by_key, + }) + } + } +} + +impl std::fmt::Debug for DistributedPool { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Pool") + .field("counter", &self.counter) + .field("len", &self.list.len()) + .finish() + } +} diff --git a/core/src/encrypted_key.rs b/core/src/encrypted_key.rs new file mode 100644 index 0000000000..1d5b894b79 --- /dev/null +++ b/core/src/encrypted_key.rs @@ -0,0 +1,154 @@ +use std::fs; +use std::path::Path; + +use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; +use base64::Engine; +use crypto_secretbox::aead::{Aead, OsRng}; +use crypto_secretbox::{AeadCore, KeyInit, XSalsa20Poly1305}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +struct Base58String(String); + +impl Base58String { + pub fn new(raw: &[u8], version: u8) -> Self { + Base58String(bs58::encode(raw).with_check_version(version).into_string()) + } + + pub fn try_decode(&self, version: u8) -> Result, EncryptionError> { + let decoded = bs58::decode(&self.0).with_check(Some(version)).into_vec()?; + Ok(decoded[1..].to_vec()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum EncryptionError { + #[error(transparent)] + SecretBox(#[from] crypto_secretbox::aead::Error), + #[error(transparent)] + ArgonError(#[from] argon2::Error), + #[error(transparent)] + PasswordHash(#[from] argon2::password_hash::Error), + #[error(transparent)] + Base58DecodeError(#[from] bs58::decode::Error), + #[error(transparent)] + CipherKeyInvalidLength(#[from] crypto_secretbox::cipher::InvalidLength), + #[error("Password hash missing after hash_password")] + HashMissing, + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + #[error("Other: {0}")] + Other(String), +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct EncryptedSecretKeyFile { + box_primitive: String, + pw_primitive: String, + nonce: Base58String, + pwsalt: Base58String, + pwdiff: (u32, u32), + ciphertext: Base58String, +} + +impl EncryptedSecretKeyFile { + pub fn new(path: impl AsRef) -> Result { + let file = fs::File::open(path)?; + Ok(serde_json::from_reader(file)?) + } +} + +fn setup_argon(pwdiff: (u32, u32)) -> Result, EncryptionError> { + let params = argon2::Params::new( + pwdiff.0 / 1024, + pwdiff.1, + argon2::Params::DEFAULT_P_COST, + None, + )?; + + Ok(Argon2::new( + argon2::Algorithm::Argon2i, + Default::default(), + params, + )) +} + +pub trait EncryptedSecretKey { + const ENCRYPTION_DATA_VERSION_BYTE: u8 = 2; + const SECRET_KEY_PREFIX_BYTE: u8 = 1; + + // Based on the ocaml implementation + const BOX_PRIMITIVE: &'static str = "xsalsa20poly1305"; + const PW_PRIMITIVE: &'static str = "argon2i"; + // Note: Only used for enryption, for decryption use the pwdiff from the file + const PW_DIFF: (u32, u32) = (134217728, 6); + + fn try_decrypt( + encrypted: &EncryptedSecretKeyFile, + password: &str, + ) -> Result, EncryptionError> { + // prepare inputs to cipher + let password = password.as_bytes(); + let pwsalt = encrypted + .pwsalt + .try_decode(Self::ENCRYPTION_DATA_VERSION_BYTE)?; + let nonce = encrypted + .nonce + .try_decode(Self::ENCRYPTION_DATA_VERSION_BYTE)?; + let ciphertext = encrypted + .ciphertext + .try_decode(Self::ENCRYPTION_DATA_VERSION_BYTE)?; + + // The argon crate's SaltString can only be built from base64 string, ocaml node encodes the salt in base58 + // So we decoded it from base58 first, then convert to base64 and lastly to SaltString + let pwsalt_encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(pwsalt); + let salt = SaltString::from_b64(&pwsalt_encoded)?; + + let argon2 = setup_argon(encrypted.pwdiff)?; + let password_hash = argon2 + .hash_password(password, &salt)? + .hash + .ok_or(EncryptionError::HashMissing)?; + let password_bytes = password_hash.as_bytes(); + + // decrypt cipher + let cipher = XSalsa20Poly1305::new_from_slice(password_bytes)?; + let decrypted = cipher.decrypt(nonce.as_slice().into(), ciphertext.as_ref())?; + + // strip the prefix and create keypair + Ok(decrypted) + } + fn try_encrypt(key: &[u8], password: &str) -> Result { + let argon2 = setup_argon(Self::PW_DIFF)?; + + // add the prefix byt to the key + let mut key_prefixed = vec![Self::SECRET_KEY_PREFIX_BYTE]; + key_prefixed.extend(key); + + let salt = SaltString::generate(&mut OsRng); + let password_hash = argon2 + .hash_password(password.as_bytes(), &salt)? + .hash + .ok_or(EncryptionError::HashMissing)?; + + let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); + let cipher = XSalsa20Poly1305::new_from_slice(password_hash.as_bytes())?; + + let ciphertext = cipher.encrypt(&nonce, key_prefixed.as_slice())?; + + // Same reason as in decrypt, we ned to decode the SaltString from base64 then encode it to base58 bellow + let mut salt_bytes = [0; 32]; + let salt_portion = salt.decode_b64(&mut salt_bytes)?; + + Ok(EncryptedSecretKeyFile { + box_primitive: Self::BOX_PRIMITIVE.to_string(), + pw_primitive: Self::PW_PRIMITIVE.to_string(), + nonce: Base58String::new(&nonce, Self::ENCRYPTION_DATA_VERSION_BYTE), + pwsalt: Base58String::new(salt_portion, Self::ENCRYPTION_DATA_VERSION_BYTE), + pwdiff: (argon2.params().m_cost() * 1024, argon2.params().t_cost()), + ciphertext: Base58String::new(&ciphertext, Self::ENCRYPTION_DATA_VERSION_BYTE), + }) + } +} diff --git a/core/src/http.rs b/core/src/http.rs new file mode 100644 index 0000000000..830776cfb4 --- /dev/null +++ b/core/src/http.rs @@ -0,0 +1,49 @@ +#[cfg(target_family = "wasm")] +mod http { + use crate::thread; + use wasm_bindgen::prelude::*; + + fn to_io_err(err: JsValue) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::Other, format!("{err:?}")) + } + + async fn _get_bytes(url: String) -> std::io::Result> { + use wasm_bindgen_futures::JsFuture; + use web_sys::Response; + + // let window = js_sys::global().dyn_into::().unwrap(); + let window = web_sys::window().unwrap(); + + let resp_value = JsFuture::from(window.fetch_with_str(&url)) + .await + .map_err(to_io_err)?; + + assert!(resp_value.is_instance_of::()); + let resp: Response = resp_value.dyn_into().unwrap(); + let js = JsFuture::from(resp.array_buffer().map_err(to_io_err)?) + .await + .map_err(to_io_err)?; + Ok(js_sys::Uint8Array::new(&js).to_vec()) + } + + pub async fn get_bytes(url: &str) -> std::io::Result> { + let url = url.to_owned(); + if thread::is_web_worker_thread() { + thread::run_async_fn_in_main_thread(move || _get_bytes(url)).await.expect("failed to run task in the main thread! Maybe main thread crashed or not initialized?") + } else { + _get_bytes(url).await + } + } + + pub fn get_bytes_blocking(url: &str) -> std::io::Result> { + let url = url.to_owned(); + if thread::is_web_worker_thread() { + thread::run_async_fn_in_main_thread_blocking(move || _get_bytes(url)).expect("failed to run task in the main thread! Maybe main thread crashed or not initialized?") + } else { + panic!("can't do blocking requests from main browser thread"); + } + } +} + +#[cfg(target_family = "wasm")] +pub use http::{get_bytes, get_bytes_blocking}; diff --git a/core/src/invariants.rs b/core/src/invariants.rs index 3ab2861eee..727a11bc63 100644 --- a/core/src/invariants.rs +++ b/core/src/invariants.rs @@ -1,7 +1,22 @@ use std::any::Any; pub trait InvariantService: redux::Service { + type ClusterInvariantsState<'a>: 'a + std::ops::DerefMut + where + Self: 'a; + + fn node_id(&self) -> usize { + 0 + } + fn invariants_state(&mut self) -> &mut InvariantsState; + + fn cluster_invariants_state<'a>(&'a mut self) -> Option> + where + Self: 'a, + { + None + } } #[derive(Default)] diff --git a/core/src/lib.rs b/core/src/lib.rs index 6301073699..42755a1cab 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,7 +1,12 @@ +pub mod distributed_pool; pub mod invariants; pub mod log; pub mod requests; +// TODO(binier): refactor +#[cfg(target_family = "wasm")] +pub mod http; + pub mod channels; pub mod thread; @@ -23,6 +28,9 @@ pub use network::NetworkConfig; mod chain_id; pub use chain_id::*; +pub mod encrypted_key; +pub use encrypted_key::*; + mod work_dir { use once_cell::sync::OnceCell; use std::path::PathBuf; diff --git a/core/src/log.rs b/core/src/log.rs index b1883e5f06..34a9182a5d 100644 --- a/core/src/log.rs +++ b/core/src/log.rs @@ -168,7 +168,7 @@ macro_rules! bug_condition { .unwrap_or(false) { panic!($($arg)*) } else { - $crate::log::inner::error!($($arg)*) + $crate::log::inner::error!("BUG CONDITION: {}", format!($($arg)*)) } }}; } diff --git a/core/src/network.rs b/core/src/network.rs index 6b84f4cbe4..53d670e0de 100644 --- a/core/src/network.rs +++ b/core/src/network.rs @@ -1,4 +1,8 @@ use once_cell::sync::OnceCell; +use poseidon::hash::{ + legacy, + params::{CODA_SIGNATURE, MAINNET_ZKAPP_BODY, MINA_SIGNATURE_MAINNET, TESTNET_ZKAPP_BODY}, +}; use crate::constants::ConstraintConstants; @@ -16,8 +20,9 @@ pub enum NetworkId { pub struct NetworkConfig { pub name: &'static str, pub network_id: NetworkId, - pub signature_prefix: &'static str, - pub account_update_hash_param: &'static str, + pub signature_prefix: &'static poseidon::hash::LazyParam, + pub legacy_signature_prefix: &'static poseidon::hash::LazyParam, + pub account_update_hash_param: &'static poseidon::hash::LazyParam, pub constraint_system_digests: &'static [[u8; 16]; 3], pub default_peers: Vec<&'static str>, pub circuits_config: &'static CircuitsConfig, @@ -76,8 +81,9 @@ impl NetworkConfig { Self { name: mainnet::NAME, network_id: mainnet::NETWORK_ID, - signature_prefix: mainnet::SIGNATURE_PREFIX, - account_update_hash_param: mainnet::ACCOUNT_UPDATE_HASH_PARAM, + signature_prefix: &MINA_SIGNATURE_MAINNET, + legacy_signature_prefix: &legacy::params::MINA_SIGNATURE_MAINNET, + account_update_hash_param: &MAINNET_ZKAPP_BODY, constraint_system_digests: &mainnet::CONSTRAINT_SYSTEM_DIGESTS, default_peers: mainnet::default_peers(), circuits_config: &mainnet::CIRCUITS_CONFIG, @@ -89,8 +95,9 @@ impl NetworkConfig { Self { name: devnet::NAME, network_id: devnet::NETWORK_ID, - signature_prefix: devnet::SIGNATURE_PREFIX, - account_update_hash_param: devnet::ACCOUNT_UPDATE_HASH_PARAM, + signature_prefix: &CODA_SIGNATURE, + legacy_signature_prefix: &legacy::params::CODA_SIGNATURE, + account_update_hash_param: &TESTNET_ZKAPP_BODY, constraint_system_digests: &devnet::CONSTRAINT_SYSTEM_DIGESTS, default_peers: devnet::default_peers(), circuits_config: &devnet::CIRCUITS_CONFIG, diff --git a/core/src/snark/snark_cmp.rs b/core/src/snark/snark_cmp.rs index 20e9147b0b..b354e817a9 100644 --- a/core/src/snark/snark_cmp.rs +++ b/core/src/snark/snark_cmp.rs @@ -11,13 +11,13 @@ pub struct SnarkCmp<'a> { pub prover: &'a NonZeroCurvePoint, } -impl<'a> SnarkCmp<'a> { +impl SnarkCmp<'_> { pub fn tie_breaker_hash(&self) -> [u8; 32] { super::tie_breaker_hash(&self.job_id, self.prover) } } -impl<'a> Ord for SnarkCmp<'a> { +impl Ord for SnarkCmp<'_> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.job_id .cmp(&other.job_id) @@ -26,7 +26,7 @@ impl<'a> Ord for SnarkCmp<'a> { } } -impl<'a> PartialOrd for SnarkCmp<'a> { +impl PartialOrd for SnarkCmp<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } diff --git a/core/src/thread.rs b/core/src/thread.rs index daabd4fc67..7752288db6 100644 --- a/core/src/thread.rs +++ b/core/src/thread.rs @@ -73,6 +73,24 @@ mod main_thread { })); rx.await.ok() } + + pub fn run_async_fn_in_main_thread_blocking(f: F) -> Option + where + T: 'static + Send, + FU: Future, + F: 'static + Send + FnOnce() -> FU, + { + let sender = MAIN_THREAD_TASK_SENDER + .get() + .expect("main thread not initialized"); + let (tx, rx) = oneshot::channel(); + let _ = sender.send(Box::pin(async move { + wasm_bindgen_futures::spawn_local(async move { + let _ = tx.send(f().await); + }) + })); + rx.blocking_recv().ok() + } } #[cfg(target_family = "wasm")] pub use main_thread::*; diff --git a/core/src/transaction/mod.rs b/core/src/transaction/mod.rs index 6378f1a6b8..4a4a9e5938 100644 --- a/core/src/transaction/mod.rs +++ b/core/src/transaction/mod.rs @@ -1,4 +1,7 @@ mod transaction_info; pub use transaction_info::TransactionInfo; +mod transaction_with_hash; +pub use transaction_with_hash::*; + pub use mina_p2p_messages::v2::{MinaBaseUserCommandStableV2 as Transaction, TransactionHash}; diff --git a/core/src/transaction/transaction_with_hash.rs b/core/src/transaction/transaction_with_hash.rs new file mode 100644 index 0000000000..e62cf6ae04 --- /dev/null +++ b/core/src/transaction/transaction_with_hash.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; + +use super::{Transaction, TransactionHash}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TransactionWithHash = Transaction> { + hash: TransactionHash, + body: T, +} + +impl> TransactionWithHash { + pub fn try_new(body: T) -> std::io::Result { + Ok(Self { + hash: body.as_ref().hash()?, + body, + }) + } + + pub fn hash(&self) -> &TransactionHash { + &self.hash + } + + pub fn body(&self) -> &Transaction { + self.body.as_ref() + } + + pub fn into_body(self) -> T { + self.body + } +} diff --git a/docker-compose.block-producer.yml b/docker-compose.block-producer.yml index 352606c559..a738413167 100644 --- a/docker-compose.block-producer.yml +++ b/docker-compose.block-producer.yml @@ -2,16 +2,17 @@ services: openmina-node: image: openmina/openmina:${OPENMINA_TAG:-latest} entrypoint: > - sh -c "openmina node --producer-key /root/.openmina/producer-key $${COINBASE_RECEIVER:+--coinbase-receiver $$COINBASE_RECEIVER} $${OPENMINA_LIBP2P_EXTERNAL_IP:+--libp2p-external-ip $$OPENMINA_LIBP2P_EXTERNAL_IP}" + sh -c "openmina node --producer-key /root/.openmina/producer-key $${COINBASE_RECEIVER:+--coinbase-receiver $$COINBASE_RECEIVER} $${OPENMINA_LIBP2P_EXTERNAL_IP:+--libp2p-external-ip $$OPENMINA_LIBP2P_EXTERNAL_IP} $${OPENMINA_LIBP2P_PORT:+--libp2p-port $$OPENMINA_LIBP2P_PORT}" ports: - "3000:3000" - - "8302:8302" + - "${OPENMINA_LIBP2P_PORT:-8302}:${OPENMINA_LIBP2P_PORT:-8302}" volumes: - ./openmina-workdir:/root/.openmina:rw environment: MINA_PRIVKEY_PASS: "${MINA_PRIVKEY_PASS:-}" COINBASE_RECEIVER: "${COINBASE_RECEIVER:-}" OPENMINA_LIBP2P_EXTERNAL_IP: "${OPENMINA_LIBP2P_EXTERNAL_IP}" + OPENMINA_LIBP2P_PORT: "${OPENMINA_LIBP2P_PORT}" frontend: image: openmina/frontend:${OPENMINA_FRONTEND_TAG:-latest} diff --git a/docker-compose.local.producers.yml b/docker-compose.local.producers.yml index 588aee6825..fd57492317 100644 --- a/docker-compose.local.producers.yml +++ b/docker-compose.local.producers.yml @@ -1,7 +1,7 @@ services: local-producer-cluster: container_name: local-producer-cluster - image: openmina/openmina:0.11.0 + image: openmina/openmina:0.12.0 environment: - RUST_BACKTRACE=1 entrypoint: ["openmina-node-testing", "scenarios-generate", "--name", "simulation-small-forever-real-time"] @@ -12,7 +12,7 @@ services: frontend: container_name: frontend - image: openmina/frontend:0.11.0 + image: openmina/frontend:0.12.0 environment: OPENMINA_FRONTEND_ENVIRONMENT: block-producers ports: diff --git a/docker-compose.yml b/docker-compose.yml index 32ba5ea634..b313c0245c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,14 +2,15 @@ services: openmina-node: image: openmina/openmina:${OPENMINA_TAG:-latest} entrypoint: > - sh -c "openmina node $${OPENMINA_LIBP2P_EXTERNAL_IP:+--libp2p-external-ip $$OPENMINA_LIBP2P_EXTERNAL_IP}" + sh -c "openmina node $${OPENMINA_LIBP2P_EXTERNAL_IP:+--libp2p-external-ip $$OPENMINA_LIBP2P_EXTERNAL_IP} $${OPENMINA_LIBP2P_PORT:+--libp2p-port $$OPENMINA_LIBP2P_PORT}" volumes: - ./openmina-workdir:/root/.openmina:rw ports: - "3000:3000" - - "8302:8302" + - "${OPENMINA_LIBP2P_PORT:-8302}:${OPENMINA_LIBP2P_PORT:-8302}" environment: OPENMINA_LIBP2P_EXTERNAL_IP: "${OPENMINA_LIBP2P_EXTERNAL_IP}" + OPENMINA_LIBP2P_PORT: "${OPENMINA_LIBP2P_PORT}" frontend: image: openmina/frontend:${OPENMINA_FRONTEND_TAG:-latest} diff --git a/docker/producer-dashboard/Dockerfile b/docker/producer-dashboard/Dockerfile index e9970ff97e..c6ae4a58f0 100644 --- a/docker/producer-dashboard/Dockerfile +++ b/docker/producer-dashboard/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.80 AS app-builder +FROM rust:1.83 AS app-builder WORKDIR /usr/src/openmina-producer-dashboard @@ -77,10 +77,10 @@ RUN eval $(opam config env) && make build_all_sigs FROM ubuntu:latest RUN apt-get update && apt-get install -y libpq5 libjemalloc2 - + COPY --from=app-builder /usr/local/cargo/bin/openmina-producer-dashboard /usr/local/bin/openmina-producer-dashboard COPY --from=mina-builder /go/mina/src/app/libp2p_helper/result/bin/libp2p_helper /usr/local/bin/coda-libp2p_helper COPY --from=mina-builder /go/mina/_build/default/src/app/cli/src/mina_testnet_signatures.exe /usr/local/bin/mina # TODO: replace -ENTRYPOINT [ "openmina-producer-dashboard" ] \ No newline at end of file +ENTRYPOINT [ "openmina-producer-dashboard" ] diff --git a/docs/alpha-testing-guide.md b/docs/alpha-testing-guide.md index 76c182248d..adaf7a9b9f 100644 --- a/docs/alpha-testing-guide.md +++ b/docs/alpha-testing-guide.md @@ -1,4 +1,4 @@ -# Test Alpha Rust Node on Devnet +# Run Non-Block Producing Node on Devnet This guide will walk you through running the **Alpha Rust Node** on Devnet using Docker. Follow these steps to set up the node and [Provide Feedback](#4-provide-feedback) on this Alpha release. @@ -22,6 +22,12 @@ Ensure you have **Docker** installed: cd openmina-vX.Y.Z-docker-compose ``` + Additional optional parameters: + + `OPENMINA_LIBP2P_EXTERNAL_IP` Sets your node’s external IP address to help other nodes find it. + + `OPENMINA_LIBP2P_PORT` Sets the port for Libp2p communication. + 3. **Start the Node on Devnet and Save Logs**: Start the node and save the logs for later analysis: diff --git a/docs/assets/NodeUI.png b/docs/assets/NodeUI.png new file mode 100644 index 0000000000..1c8024777f Binary files /dev/null and b/docs/assets/NodeUI.png differ diff --git a/docs/assets/OpenMinaGH_Dark.svg b/docs/assets/OpenMinaGH_Dark.svg new file mode 100644 index 0000000000..18d979943b --- /dev/null +++ b/docs/assets/OpenMinaGH_Dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/OpenMinaGH_Light.svg b/docs/assets/OpenMinaGH_Light.svg new file mode 100644 index 0000000000..197a1e2a13 --- /dev/null +++ b/docs/assets/OpenMinaGH_Light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/block-producer-guide.md b/docs/block-producer-guide.md index 515c8bb793..166c34faac 100644 --- a/docs/block-producer-guide.md +++ b/docs/block-producer-guide.md @@ -1,4 +1,4 @@ -# Rust Node Block Production Testing on Devnet +# Run Block Producing Node on Devnet This guide is intended for setting up block producer nodes on **Mina Devnet** only. Do not use this guide for Mina Mainnet until necessary security audits are complete. @@ -43,6 +43,12 @@ Ensure Docker and Docker Compose are installed on your system - [Docker Installa docker compose -f docker-compose.block-producer.yml up -d --pull always ``` + Optional parameters: + + `OPENMINA_LIBP2P_EXTERNAL_IP` Sets your node’s external IP address to help other nodes find it. + + `OPENMINA_LIBP2P_PORT` Sets the port for Libp2p communication. + 4. **Go to Dashboard** Visit [http://localhost:8070](http://localhost:8070) to [monitor sync](http://localhost:8070/dashboard) and [block production](http://localhost:8070/block-production). diff --git a/docs/building-from-source-guide.md b/docs/building-from-source-guide.md new file mode 100644 index 0000000000..dd749d8417 --- /dev/null +++ b/docs/building-from-source-guide.md @@ -0,0 +1,102 @@ +# How to build and launch a node from source + +This installation guide has been tested on Debian and Ubuntu and should work on most distributions of Linux. + +## Prerequisites + +Ubuntu or Debian-based Linux distribution with the following packages installed: + +- `curl` +- `git` +- `libssl-dev` +- `pkg-config` +- `protobuf-compiler` +- `build-essential` + +Example (debian-based): + +```sh +# Either using "sudo" or as the "root" user +sudo apt install curl git libssl-dev pkg-config protobuf-compiler build-essential +``` + +Example (macOS): + +If you have not yet installed homebrew: + +```sh +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +```sh +brew install curl git openssl pkg-config protobuf gcc make +``` + +## Steps (for Debian-based Linux distros and macOS): + +Open up the command line and enter the following: + +And then: + +```sh +# Install rustup and set the default Rust toolchain to 1.80 (newer versions work too) +curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.80 +# Setup the current shell with rustup +source "$HOME/.cargo/env" +# Clone the openmina repository +git clone https://github.com/openmina/openmina.git +cd openmina/ +# Build and run the node +cargo run --release -p cli node +``` + +# How to launch the UI: + +## Prerequisites + +### 1. Node.js v20.11.1 + +#### MacOS + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +brew install node@20.11.1 +``` + +#### Linux + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash +source ~/.bashrc +nvm install 20.11.1 +``` + +#### Windows + +Download [Node.js v20.11.1](https://nodejs.org/) from the official website, open the installer and follow the prompts to complete the installation. + +### 2. Angular CLI v16.2.0 + +```bash +npm install -g @angular/cli@16.2.0 +``` + +### 3. Installation + +Open a terminal and navigate to this project's root directory + +```bash +cd PROJECT_LOCATION/openmina/frontend +``` + +Install the dependencies + +```bash +npm install +``` + +## Run the application + +```bash +npm start +``` diff --git a/docs/local-demo-guide.md b/docs/local-demo-guide.md new file mode 100644 index 0000000000..487c0646b9 --- /dev/null +++ b/docs/local-demo-guide.md @@ -0,0 +1,56 @@ +# Run Block Producers on local network + +Once you have completed the [pre-requisites](./docs/docker-installation.md) for your operating system, follow these steps: + +## Setup Option 1: Download Docker Compose Files from the Release + +1. **Download the Docker Compose files:** + + - Go to the [Releases page](https://github.com/openmina/openmina/releases) of this repository. + - Download the latest `openmina-vX.Y.Z-docker-compose.zip` (or `.tar.gz`) file corresponding to the release version (available since v0.8.0). + +2. **Extract the files:** + + - Unzip or untar the downloaded file: + ```bash + unzip openmina-vX.Y.Z-docker-compose.zip + ``` + or + ```bash + tar -xzvf openmina-vX.Y.Z-docker-compose.tar.gz + ``` + - Replace `vX.Y.Z` with the actual release version you downloaded. + +3. **Navigate to the extracted directory:** + ```bash + cd openmina-vX.Y.Z-docker-compose + ``` + +## Setup Option 2: Clone the Repository + +1. **Clone this repository:** + + ```bash + git clone https://github.com/openmina/openmina.git + ``` + +2. **Navigate to the repository:** + ```bash + cd openmina + ``` + +## Launch + +**Run the following command to start the demo:** + +```bash +docker compose -f docker-compose.local.producers.yml up --pull always --force-recreate +``` + +And finally: + +**Open your browser and visit [http://localhost:8070](http://localhost:8070)** + +You should see the following screen: + +![producer-demo](https://github.com/user-attachments/assets/f0ccc36e-0ee8-4284-a8d7-de0f9a3397d6) diff --git a/frontend/.gitignore b/frontend/.gitignore index e10321af5a..bf0f3a86c7 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -44,3 +44,11 @@ Thumbs.db # Open Mina /src/assets/webnode/pkg /src/assets/webnode/circuit-blobs + +# Sentry Config File +.sentryclirc + +# Firebase +.firebase +*-debug.log +.runtimeconfig.json diff --git a/frontend/Dockerfile b/frontend/Dockerfile index e993ee8550..ad489347c7 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -9,13 +9,14 @@ RUN npm prune --production RUN echo "---------- USING APACHE ----------" + FROM httpd:2.4 RUN apt-get update && \ apt-get install -y curl && \ rm -rf /var/lib/apt/lists/* -COPY --from=BUILD_IMAGE /app/dist/frontend /usr/local/apache2/htdocs/ +COPY --from=BUILD_IMAGE /app/dist/frontend/browser /usr/local/apache2/htdocs/ COPY --from=BUILD_IMAGE /app/httpd.conf /usr/local/apache2/conf/httpd.conf COPY docker/startup.sh /usr/local/bin/startup.sh @@ -23,4 +24,4 @@ RUN chmod +x /usr/local/bin/startup.sh ENTRYPOINT ["/usr/local/bin/startup.sh"] -CMD ["httpd-foreground"] \ No newline at end of file +CMD ["httpd-foreground"] diff --git a/frontend/README.md b/frontend/README.md index ded497ce4c..7bff8ff863 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,13 +4,13 @@ This is a simple Angular application that will help you to see the behaviour of ## Prerequisites -### 1. Node.js v20.11.1 +### 1. Node.js v23.1.0 #### MacOS ```bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew install node@20.11.1 +brew install node@23.1.0 ``` #### Linux @@ -18,17 +18,17 @@ brew install node@20.11.1 ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash source ~/.bashrc -nvm install 20.11.1 +nvm install 23.1.0 ``` #### Windows -Download [Node.js v20.11.1](https://nodejs.org/) from the official website, open the installer and follow the prompts to complete the installation. +Download [Node.js v23.1.0](https://nodejs.org/) from the official website, open the installer and follow the prompts to complete the installation. -### 2. Angular CLI v16.2.0 +### 2. Angular CLI v17.3.0 ```bash -npm install -g @angular/cli@16.2.0 +npm install -g @angular/cli@17.3.0 ``` ### 3. Installation diff --git a/frontend/angular.json b/frontend/angular.json index 2fc3226724..d5c75245ff 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -34,7 +34,7 @@ "customWebpackConfig": { "path": "./webpack.config.js" }, - "outputPath": "dist/frontend", + "outputPath": "dist/frontend/browser", "index": "src/index.html", "main": "src/main.ts", "polyfills": [ @@ -46,6 +46,7 @@ "assets": [ "src/assets" ], + "sourceMap": true, "styles": [ "src/styles.scss" ], @@ -134,7 +135,6 @@ ], "inlineStyleLanguage": "scss", "assets": [ - "src/favicon.ico", "src/assets" ], "styles": [ @@ -142,6 +142,93 @@ ], "scripts": [] } + }, + "server": { + "builder": "@angular-devkit/build-angular:server", + "options": { + "outputPath": "dist/frontend/server", + "main": "server.ts", + "tsConfig": "tsconfig.server.json", + "stylePreprocessorOptions": { + "includePaths": [ + "src/assets", + "src/assets/styles", + "src/assets/styles/utilities", + "src/assets/images" + ] + }, + "inlineStyleLanguage": "scss" + }, + "configurations": { + "production": { + "outputHashing": "media", + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ] + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "sourceMap": true, + "extractLicenses": false, + "vendorChunk": true + }, + "local": { + "outputHashing": "media", + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.local.ts" + } + ] + }, + "producer": { + "outputHashing": "media", + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.producer.ts" + } + ] + } + }, + "defaultConfiguration": "production" + }, + "serve-ssr": { + "builder": "@angular-devkit/build-angular:ssr-dev-server", + "configurations": { + "development": { + "browserTarget": "frontend:build:development", + "serverTarget": "frontend:server:development" + }, + "production": { + "browserTarget": "frontend:build:production", + "serverTarget": "frontend:server:production" + } + }, + "defaultConfiguration": "development" + }, + "prerender": { + "builder": "@angular-devkit/build-angular:prerender", + "options": { + "routes": [ + "/" + ] + }, + "configurations": { + "production": { + "browserTarget": "frontend:build:production", + "serverTarget": "frontend:server:production" + }, + "development": { + "browserTarget": "frontend:build:development", + "serverTarget": "frontend:server:development" + } + }, + "defaultConfiguration": "production" } } } diff --git a/frontend/cypress/e2e/dashboard/ledgers.cy.ts b/frontend/cypress/e2e/dashboard/ledgers.cy.ts index 21c8b8c663..22ecbdd6bd 100644 --- a/frontend/cypress/e2e/dashboard/ledgers.cy.ts +++ b/frontend/cypress/e2e/dashboard/ledgers.cy.ts @@ -88,7 +88,7 @@ describe('DASHBOARD LEDGERS', () => { cy.get('mina-dashboard-ledger > div:first-child > div.h-minus-xl > .group:nth-child(3) div.primary') .then((el: any) => { let progress; - progress = state.rpcStats.rootLedger?.fetched / state.rpcStats.rootLedger?.estimation * 100 || 0; + progress = state.rpcStats.snarkedRootLedger?.fetched / state.rpcStats.snarkedRootLedger?.estimation * 100 || 0; progress = Math.round(progress); if (state.nodes[0].ledgers.rootSnarked.state === 'success') { diff --git a/frontend/docker/startup.sh b/frontend/docker/startup.sh index 7f167362f2..3ce73c91a2 100644 --- a/frontend/docker/startup.sh +++ b/frontend/docker/startup.sh @@ -27,6 +27,8 @@ download_circuit_files() { CIRCUITS_VERSION="3.0.1devnet" DEVNET_CIRCUIT_FILES=( + "block_verifier_index.postcard" + "transaction_verifier_index.postcard" "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_gates.json" "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_internal_vars.bin" "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_rows_rev.bin" @@ -77,15 +79,15 @@ download_wasm_files() { echo "Error: OPENMINA_WASM_VERSION is not set. Exiting." exit 1 fi - + WASM_URL="$OPENMINA_BASE_URL/openmina/releases/download/$OPENMINA_WASM_VERSION/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" TARGET_DIR="/usr/local/apache2/htdocs/assets/webnode/pkg" - + mkdir -p "$TARGET_DIR" echo "Downloading WASM files from $WASM_URL..." curl -s -L --retry 3 --retry-delay 5 -o "/tmp/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" "$WASM_URL" - + if [[ $? -ne 0 ]]; then echo "Failed to download the WASM file after 3 attempts, exiting." exit 1 @@ -93,19 +95,95 @@ download_wasm_files() { echo "WASM file downloaded successfully. Extracting to $TARGET_DIR..." tar -xzf "/tmp/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" -C "$TARGET_DIR" - + # Check if the extraction was successful if [[ $? -ne 0 ]]; then echo "Failed to extract the WASM file, exiting." exit 1 else echo "WASM files extracted successfully to $TARGET_DIR" + + # Inject caching logic into openmina_node_web.js + OPENMINA_JS="$TARGET_DIR/openmina_node_web.js" + INDEX_HTML="/usr/local/apache2/htdocs/index.html" + inject_caching_logic "$OPENMINA_JS" "$INDEX_HTML" fi fi rm "/tmp/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" } +get_short_sha1() { + local file="$1" + if [ -z "$file" ]; then + echo "Usage: get_short_sha1 filename" >&2 + return 1 + fi + if [ ! -f "$file" ]; then + echo "Error: File not found: $file" >&2 + return 1 + fi + + if command -v sha1sum >/dev/null 2>&1; then + sha1sum "$file" | awk '{ print substr($1,1,8) }' + elif command -v openssl >/dev/null 2>&1; then + openssl sha1 "$file" | awk '{ print substr($2,1,8) }' + else + echo "Error: Neither sha1sum nor openssl is available for hashing." >&2 + return 1 + fi +} + +inject_caching_logic() { + local js_file="$1" + local index_html="$2" + + # Check if JavaScript file exists + if [ ! -f "$js_file" ]; then + echo "Warning: $js_file not found. Caching logic not injected." + return 0 + fi + + echo "Injecting caching logic into $js_file" + + # Define target files + local js_target_file="${TARGET_DIR}/openmina_node_web.js" + local wasm_target_file="${TARGET_DIR}/openmina_node_web_bg.wasm" + + # Generate checksum hashes + local js_file_hash + js_file_hash=$(get_short_sha1 "$js_target_file") || { echo "Failed to get hash for $js_target_file"; return 1; } + + local wasm_file_hash + wasm_file_hash=$(get_short_sha1 "$wasm_target_file") || { echo "Failed to get hash for $wasm_target_file"; return 1; } + + # Check if hashed files already exist to prevent multiple injections + local js_new_file="${TARGET_DIR}/openmina_node_web.${js_file_hash}.js" + local wasm_new_file="${TARGET_DIR}/openmina_node_web_bg.${wasm_file_hash}.wasm" + + if [[ -f "$js_new_file" ]] && [[ -f "$wasm_new_file" ]]; then + echo "Hashed files already exist. Skipping caching logic injection." + return 0 + fi + + # Replace openmina_node_web_bg.wasm with openmina_node_web_bg..wasm in JS file + sed -i "s/openmina_node_web_bg\.wasm/openmina_node_web_bg.${wasm_file_hash}.wasm/g" "$js_file" || { echo "Failed to update wasm filename in $js_file"; return 1; } + + # Add cache headers to fetch calls in JS file + sed -i 's/module_or_path = fetch(module_or_path);/module_or_path = fetch(module_or_path, { cache: "force-cache", headers: { "Cache-Control": "max-age=31536000, immutable" } });/' "$js_file" || { echo "Failed to inject cache headers into $js_file"; return 1; } + + # Rename wasm file with hash + mv "$wasm_target_file" "$wasm_new_file" || { echo "Failed to rename $wasm_target_file to $wasm_new_file"; return 1; } + + # Rename JS file with hash + mv "$js_target_file" "$js_new_file" || { echo "Failed to rename $js_target_file to $js_new_file"; return 1; } + + # Replace JS filename in index.html + sed -i "s/openmina_node_web\.js/openmina_node_web.${js_file_hash}.js/g" "$index_html" || { echo "Failed to update JS filename in $index_html"; return 1; } + + echo "Successfully injected caching logic into $js_file" +} + if [ -n "$OPENMINA_FRONTEND_ENVIRONMENT" ]; then echo "Using environment: $OPENMINA_FRONTEND_ENVIRONMENT" cp -f /usr/local/apache2/htdocs/assets/environments/"$OPENMINA_FRONTEND_ENVIRONMENT".js \ diff --git a/frontend/firebase.json b/frontend/firebase.json new file mode 100644 index 0000000000..314966c493 --- /dev/null +++ b/frontend/firebase.json @@ -0,0 +1,58 @@ +{ + "hosting": { + "public": "dist/frontend/browser", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ], + "headers": [ + { + "source": "/service-worker.js", + "headers": [ + { + "key": "Cache-Control", + "value": "no-cache" + } + ] + }, + { + "source": "/**", + "headers": [ + { + "key": "Cross-Origin-Opener-Policy", + "value": "same-origin" + }, + { + "key": "Cross-Origin-Embedder-Policy", + "value": "require-corp" + } + ] + }, + { + "source": "**/*.@(js|wasm|html)", + "headers": [ + { + "key": "Cache-Control", + "value": "no-cache, no-store, must-revalidate" + } + ] + }, + { + "source": "/", + "headers": [ + { + "key": "Cache-Control", + "value": "no-cache, no-store, must-revalidate" + } + ] + } + ] + } +} diff --git a/frontend/httpd.conf b/frontend/httpd.conf index f82a328e46..c333bb343c 100644 --- a/frontend/httpd.conf +++ b/frontend/httpd.conf @@ -1,68 +1,8 @@ -# -# This is the main Apache HTTP server configuration file. It contains the -# configuration directives that give the server its instructions. -# See for detailed information. -# In particular, see -# -# for a discussion of each configuration directive. -# -# Do NOT simply read the instructions in here without understanding -# what they do. They're here only as hints or reminders. If you are unsure -# consult the online docs. You have been warned. -# -# Configuration and logfile names: If the filenames you specify for many -# of the server's control files begin with "/" (or "drive:/" for Win32), the -# server will use that explicit path. If the filenames do *not* begin -# with "/", the value of ServerRoot is prepended -- so "logs/access_log" -# with ServerRoot set to "/usr/local/apache2" will be interpreted by the -# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" -# will be interpreted as '/logs/access_log'. -# -# ServerRoot: The top of the directory tree under which the server's -# configuration, error, and log files are kept. -# -# Do not add a slash at the end of the directory path. If you point -# ServerRoot at a non-local disk, be sure to specify a local disk on the -# Mutex directive, if file-based mutexes are used. If you wish to share the -# same ServerRoot for multiple httpd daemons, you will need to change at -# least PidFile. -# ServerRoot "/usr/local/apache2" -# -# Mutex: Allows you to set the mutex mechanism and mutex file directory -# for individual mutexes, or change the global defaults -# -# Uncomment and change the directory if mutexes are file-based and the default -# mutex file directory is not on a local disk or is not appropriate for some -# other reason. -# -# Mutex default:logs - -# -# Listen: Allows you to bind Apache to specific IP addresses and/or -# ports, instead of the default. See also the -# directive. -# -# Change this to Listen on specific IP addresses as shown below to -# prevent Apache from glomming onto all bound IP addresses. -# -#Listen 12.34.56.78:80 Listen 80 -# -# Dynamic Shared Object (DSO) Support -# -# To be able to use the functionality of a module which was built as a DSO you -# have to place corresponding `LoadModule' lines at this location so the -# directives contained in it are actually available _before_ they are used. -# Statically compiled modules (those listed by `httpd -l') do not need -# to be loaded here. -# -# Example: -# LoadModule foo_module modules/mod_foo.so -# LoadModule mpm_event_module modules/mod_mpm_event.so #LoadModule mpm_prefork_module modules/mod_mpm_prefork.so #LoadModule mpm_worker_module modules/mod_mpm_worker.so @@ -83,55 +23,18 @@ LoadModule authz_core_module modules/mod_authz_core.so #LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so LoadModule access_compat_module modules/mod_access_compat.so LoadModule auth_basic_module modules/mod_auth_basic.so -#LoadModule auth_form_module modules/mod_auth_form.so -#LoadModule auth_digest_module modules/mod_auth_digest.so -#LoadModule allowmethods_module modules/mod_allowmethods.so -#LoadModule isapi_module modules/mod_isapi.so -#LoadModule file_cache_module modules/mod_file_cache.so -#LoadModule cache_module modules/mod_cache.so -#LoadModule cache_disk_module modules/mod_cache_disk.so -#LoadModule cache_socache_module modules/mod_cache_socache.so -#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so -#LoadModule socache_dbm_module modules/mod_socache_dbm.so -#LoadModule socache_memcache_module modules/mod_socache_memcache.so -#LoadModule socache_redis_module modules/mod_socache_redis.so -#LoadModule watchdog_module modules/mod_watchdog.so -#LoadModule macro_module modules/mod_macro.so -#LoadModule dbd_module modules/mod_dbd.so -#LoadModule bucketeer_module modules/mod_bucketeer.so -#LoadModule dumpio_module modules/mod_dumpio.so -#LoadModule echo_module modules/mod_echo.so -#LoadModule example_hooks_module modules/mod_example_hooks.so -#LoadModule case_filter_module modules/mod_case_filter.so -#LoadModule case_filter_in_module modules/mod_case_filter_in.so -#LoadModule example_ipc_module modules/mod_example_ipc.so -#LoadModule buffer_module modules/mod_buffer.so -#LoadModule data_module modules/mod_data.so -#LoadModule ratelimit_module modules/mod_ratelimit.so LoadModule reqtimeout_module modules/mod_reqtimeout.so #LoadModule ext_filter_module modules/mod_ext_filter.so #LoadModule request_module modules/mod_request.so #LoadModule include_module modules/mod_include.so LoadModule filter_module modules/mod_filter.so -#LoadModule reflector_module modules/mod_reflector.so -#LoadModule substitute_module modules/mod_substitute.so -#LoadModule sed_module modules/mod_sed.so -#LoadModule charset_lite_module modules/mod_charset_lite.so -#LoadModule deflate_module modules/mod_deflate.so -#LoadModule xml2enc_module modules/mod_xml2enc.so -#LoadModule proxy_html_module modules/mod_proxy_html.so -#LoadModule brotli_module modules/mod_brotli.so LoadModule mime_module modules/mod_mime.so #LoadModule ldap_module modules/mod_ldap.so LoadModule log_config_module modules/mod_log_config.so -#LoadModule log_debug_module modules/mod_log_debug.so -#LoadModule log_forensic_module modules/mod_log_forensic.so -#LoadModule logio_module modules/mod_logio.so -#LoadModule lua_module modules/mod_lua.so LoadModule env_module modules/mod_env.so #LoadModule mime_magic_module modules/mod_mime_magic.so #LoadModule cern_meta_module modules/mod_cern_meta.so -#LoadModule expires_module modules/mod_expires.so +LoadModule expires_module modules/mod_expires.so LoadModule headers_module modules/mod_headers.so #LoadModule ident_module modules/mod_ident.so #LoadModule usertrack_module modules/mod_usertrack.so @@ -148,29 +51,7 @@ LoadModule proxy_http_module modules/mod_proxy_http.so #LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so #LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so -#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so -#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so -#LoadModule proxy_express_module modules/mod_proxy_express.so -#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so -#LoadModule session_module modules/mod_session.so -#LoadModule session_cookie_module modules/mod_session_cookie.so -#LoadModule session_crypto_module modules/mod_session_crypto.so -#LoadModule session_dbd_module modules/mod_session_dbd.so -#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so -#LoadModule slotmem_plain_module modules/mod_slotmem_plain.so LoadModule ssl_module modules/mod_ssl.so -#LoadModule optional_hook_export_module modules/mod_optional_hook_export.so -#LoadModule optional_hook_import_module modules/mod_optional_hook_import.so -#LoadModule optional_fn_import_module modules/mod_optional_fn_import.so -#LoadModule optional_fn_export_module modules/mod_optional_fn_export.so -#LoadModule dialup_module modules/mod_dialup.so -#LoadModule http2_module modules/mod_http2.so -#LoadModule proxy_http2_module modules/mod_proxy_http2.so -#LoadModule md_module modules/mod_md.so -#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so -#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so -#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so -#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so LoadModule unixd_module modules/mod_unixd.so #LoadModule heartbeat_module modules/mod_heartbeat.so #LoadModule heartmonitor_module modules/mod_heartmonitor.so @@ -186,10 +67,6 @@ LoadModule autoindex_module modules/mod_autoindex.so #LoadModule cgi_module modules/mod_cgi.so -#LoadModule dav_fs_module modules/mod_dav_fs.so -#LoadModule dav_lock_module modules/mod_dav_lock.so -#LoadModule vhost_alias_module modules/mod_vhost_alias.so -#LoadModule negotiation_module modules/mod_negotiation.so LoadModule dir_module modules/mod_dir.so #LoadModule imagemap_module modules/mod_imagemap.so #LoadModule actions_module modules/mod_actions.so @@ -198,23 +75,6 @@ LoadModule dir_module modules/mod_dir.so LoadModule alias_module modules/mod_alias.so LoadModule rewrite_module modules/mod_rewrite.so - - RewriteEngine On - - # Exclude /openmina-node from the general rewrite rules - RewriteCond %{REQUEST_URI} ^/openmina-node/ [NC,OR] - RewriteCond %{REQUEST_URI} ^/mina/webrtc/signal [NC] - RewriteRule ^ - [L] - - # If an existing asset or directory is requested go to it as it is - RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] - RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d - RewriteRule ^ - [L] - - # If the requested resource doesn't exist, use index.html - RewriteRule ^ /index.html - - SSLProxyEngine On @@ -228,121 +88,70 @@ SSLProxyEngine On -# -# If you wish httpd to run as a different user or group, you must run -# httpd as root initially and it will switch. -# -# User/Group: The name (or #number) of the user/group to run httpd as. -# It is usually good practice to create a dedicated user and group for -# running httpd, as with most system services. -# + User www-data Group www-data -# 'Main' server configuration -# -# The directives in this section set up the values used by the 'main' -# server, which responds to any requests that aren't handled by a -# definition. These values also provide defaults for -# any containers you may define later in the file. -# -# All of these directives may appear inside containers, -# in which case these default settings will be overridden for the -# virtual host being defined. -# - -# -# ServerAdmin: Your address, where problems with the server should be -# e-mailed. This address appears on some server-generated pages, such -# as error documents. e.g. admin@your-domain.com -# +# send issue emails to ServerAdmin you@example.com -# -# ServerName gives the name and port that the server uses to identify itself. -# This can often be determined automatically, but we recommend you specify -# it explicitly to prevent problems during startup. -# -# If your host doesn't have a registered DNS name, enter its IP address here. -# -#ServerName www.example.com:80 - -# # Deny access to the entirety of your server's filesystem. You must # explicitly permit access to web content directories in other # blocks below. -# AllowOverride none Require all denied -# -# Note that from this point forward you must specifically allow -# particular features to be enabled - so if something's not working as -# you might expect, make sure that you have specifically enabled it -# below. -# - -# -# DocumentRoot: The directory out of which you will serve your -# documents. By default, all requests are taken from this directory, but -# symbolic links and aliases may be used to point to other locations. -# DocumentRoot "/usr/local/apache2/htdocs" - # - # Possible values for the Options directive are "None", "All", - # or any combination of: - # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews - # - # Note that "MultiViews" must be named *explicitly* --- "Options All" - # doesn't give it to you. - # - # The Options directive is both complicated and important. Please see - # http://httpd.apache.org/docs/2.4/mod/core.html#options - # for more information. - # Options Indexes FollowSymLinks - - # - # AllowOverride controls what directives may be placed in .htaccess files. - # It can be "All", "None", or any combination of the keywords: - # AllowOverride FileInfo AuthConfig Limit - # - AllowOverride None - - # - # Controls who can get stuff from this server. - # + AllowOverride All Require all granted + DirectoryIndex index.html + DirectorySlash On -# -# DirectoryIndex: sets the file that Apache will serve if a directory -# is requested. -# DirectoryIndex index.html -# + + + + RewriteEngine On + + # Exclude specific paths from rewriting if needed + RewriteCond %{REQUEST_URI} ^/openmina-node/ [NC,OR] + RewriteCond %{REQUEST_URI} ^/mina/webrtc/signal [NC] + RewriteRule ^ - [L] + + # Serve correct MIME type for hashed JS files + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.+)\.([0-9a-f]+)\.js$ $1.js [L] + + # Your existing Angular routing rules + RewriteRule ^index\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.html [L] + + + AddType application/javascript .js + # The following lines prevent .htaccess and .htpasswd files from being # viewed by Web clients. -# Require all denied -# # ErrorLog: The location of the error log file. # If you do not specify an ErrorLog directive within a # container, error messages relating to that virtual host will be # logged here. If you *do* define an error logfile for a # container, that host's errors will be logged there and not here. -# ErrorLog /proc/self/fd/2 # @@ -387,32 +196,6 @@ LogLevel warn - # - # Redirect: Allows you to tell clients about documents that used to - # exist in your server's namespace, but do not anymore. The client - # will make a new request for the document at its new location. - # Example: - # Redirect permanent /foo http://www.example.com/bar - - # - # Alias: Maps web paths into filesystem paths and is used to - # access content that does not live under the DocumentRoot. - # Example: - # Alias /webpath /full/filesystem/path - # - # If you include a trailing / on /webpath then the server will - # require it to be present in the URL. You will also likely - # need to provide a section to allow access to - # the filesystem path. - - # - # ScriptAlias: This controls which directories contain server scripts. - # ScriptAliases are essentially the same as Aliases, except that - # documents in the target directory are treated as applications and - # run by the server when requested rather than as documents sent to the - # client. The same rules about trailing "/" apply to ScriptAlias - # directives as to Alias. - # ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/" @@ -425,10 +208,8 @@ LogLevel warn #Scriptsock cgisock -# # "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased # CGI directory exists, if you have that configured. -# AllowOverride None Options None @@ -436,137 +217,48 @@ LogLevel warn - # # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied # backend servers which have lingering "httpoxy" defects. # 'Proxy' request header is undefined by the IETF, not listed by IANA - # RequestHeader unset Proxy early - # # TypesConfig points to the file containing the list of mappings from # filename extension to MIME-type. - # TypesConfig conf/mime.types - # - # AddType allows you to add to or override the MIME configuration - # file specified in TypesConfig for specific file types. - # - #AddType application/x-gzip .tgz - # - # AddEncoding allows you to have certain browsers uncompress - # information on the fly. Note: Not all browsers support this. - # - #AddEncoding x-compress .Z - #AddEncoding x-gzip .gz .tgz - # - # If the AddEncoding directives above are commented-out, then you - # probably should define those extensions to indicate media types: - # - AddType application/x-compress .Z - AddType application/x-gzip .gz .tgz - - # - # AddHandler allows you to map certain file extensions to "handlers": - # actions unrelated to filetype. These can be either built into the server - # or added with the Action directive (see below) - # - # To use CGI scripts outside of ScriptAliased directories: - # (You will also need to add "ExecCGI" to the "Options" directive.) - # - #AddHandler cgi-script .cgi - - # For type maps (negotiated resources): - #AddHandler type-map var - - # - # Filters allow you to process content before it is sent to the client. - # - # To parse .shtml files for server-side includes (SSI): - # (You will also need to add "Includes" to the "Options" directive.) - # - #AddType text/html .shtml - #AddOutputFilter INCLUDES .shtml + AddType application/javascript .js -# -# The mod_mime_magic module allows the server to use various hints from the -# contents of the file itself to determine its type. The MIMEMagicFile -# directive tells the module where the hint definitions are located. -# -#MIMEMagicFile conf/magic +# Specific handling for webnode JS files + + # Force JavaScript MIME type + ForceType application/javascript -# -# Customizable error responses come in three flavors: -# 1) plain text 2) local redirects 3) external redirects -# -# Some examples: -#ErrorDocument 500 "The server made a boo boo." -#ErrorDocument 404 /missing.html -#ErrorDocument 404 "/cgi-bin/missing_handler.pl" -#ErrorDocument 402 http://www.example.com/subscription_info.html -# + # Ensure these files are not rewritten + RewriteEngine Off -# -# MaxRanges: Maximum number of Ranges in a request before -# returning the entire resource, or one of the special -# values 'default', 'none' or 'unlimited'. -# Default setting is to accept 200 Ranges. -#MaxRanges unlimited + # Debug headers + Header set X-Content-Debug "%{CONTENT_TYPE}e" + Header set X-Handler-Debug "%{HANDLER}e" -# -# EnableMMAP and EnableSendfile: On systems that support it, -# memory-mapping or the sendfile syscall may be used to deliver -# files. This usually improves server performance, but must -# be turned off when serving from networked-mounted -# filesystems or if support for these functions is otherwise -# broken on your system. -# Defaults: EnableMMAP On, EnableSendfile Off -# -#EnableMMAP off -#EnableSendfile on - -# Supplemental configuration -# -# The configuration files in the conf/extra/ directory can be -# included to add extra features or to modify the default configuration of -# the server, or you may simply copy their contents here and change as -# necessary. - -# Server-pool management (MPM specific) -#Include conf/extra/httpd-mpm.conf - -# Multi-language error messages -#Include conf/extra/httpd-multilang-errordoc.conf - -# Fancy directory listings -#Include conf/extra/httpd-autoindex.conf + # Ensure proper CORS headers + Header set Access-Control-Allow-Origin "*" + Header set Access-Control-Allow-Methods "GET, OPTIONS" + -# Language settings -#Include conf/extra/httpd-languages.conf - -# User home directories -#Include conf/extra/httpd-userdir.conf - -# Real-time info on requests and configuration -#Include conf/extra/httpd-info.conf - -# Virtual hosts -#Include conf/extra/httpd-vhosts.conf - -# Local access to the Apache HTTP Server Manual -#Include conf/extra/httpd-manual.conf - -# Distributed authoring and versioning (WebDAV) -#Include conf/extra/httpd-dav.conf +# Map all SPA-looking routes to our single page app index file + + FallbackResource /index.html + -# Various default settings -#Include conf/extra/httpd-default.conf +# Requests for files should return a real 404 and not fallback to the index.html page, though + + FallbackResource disabled + ErrorDocument 404 "File not found" + -# Configure mod_proxy_html to understand HTML4/XHTML1 Include conf/extra/proxy-html.conf diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a1ee521509..f7e1bb1481 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,36 +1,44 @@ { "name": "frontend", - "version": "0.8.20", + "version": "1.0.50", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.8.20", + "version": "1.0.50", "dependencies": { "@angular/animations": "^17.3.12", "@angular/cdk": "^17.3.10", "@angular/common": "^17.3.12", "@angular/compiler": "^17.3.12", "@angular/core": "^17.3.12", + "@angular/fire": "^17.1.0", "@angular/forms": "^17.3.12", "@angular/material": "^17.3.10", "@angular/platform-browser": "^17.3.12", "@angular/platform-browser-dynamic": "^17.3.12", + "@angular/platform-server": "^17.3.12", "@angular/router": "^17.3.12", + "@angular/ssr": "^17.3.10", "@ledgerhq/hw-transport-webhid": "^6.29.4", "@ngneat/until-destroy": "^10.0.0", "@ngrx/effects": "^17.2.0", "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", - "@openmina/shared": "^0.102.0", + "@nguniversal/express-engine": "^7.0.2", + "@openmina/shared": "^0.117.0", "@sentry/angular": "^8.35.0", + "@sentry/cli": "^2.38.2", "@sentry/tracing": "^7.114.0", "base-x": "^5.0.0", "bs58check": "^4.0.0", "buffer": "^6.0.3", "d3": "^7.8.4", "eigen": "^0.2.2", + "express": "^4.18.2", + "firebase": "^11.0.1", + "jszip": "^3.10.1", "mathjs": "^12.3.0", "mina-signer": "^3.0.7", "ngx-json-viewer": "^3.2.1", @@ -46,9 +54,11 @@ "@angular/compiler-cli": "^17.3.12", "@ngrx/store-devtools": "^17.2.0", "@types/d3": "^7.4.0", + "@types/express": "^4.17.17", "@types/jasmine": "~4.3.0", - "@types/node": "^20.4.8", + "@types/node": "^18.19.64", "@types/w3c-web-hid": "^1.0.6", + "browser-sync": "^3.0.0", "cypress": "^13.3.2", "cypress-real-events": "^1.10.0", "jasmine-core": "~4.6.0", @@ -557,7 +567,6 @@ "version": "17.3.10", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.10.tgz", "integrity": "sha512-czdl54yxU5DOAGy/uUPNjJruoBDTgwi/V+eOgLNybYhgrc+TsY0f7uJ11yEk/pz5sCov7xIiS7RdRv96waS7vg==", - "dev": true, "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -583,14 +592,12 @@ "node_modules/@angular-devkit/core/node_modules/jsonc-parser": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" }, "node_modules/@angular-devkit/core/node_modules/picomatch": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", - "dev": true, "engines": { "node": ">=12" }, @@ -602,7 +609,6 @@ "version": "17.3.10", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.10.tgz", "integrity": "sha512-FHcNa1ktYRd0SKExCsNJpR75RffsyuPIV8kvBXzXnLHmXMqvl25G2te3yYJ9yYqy9OLy/58HZznZTxWRyUdHOg==", - "dev": true, "dependencies": { "@angular-devkit/core": "17.3.10", "jsonc-parser": "3.2.1", @@ -619,8 +625,7 @@ "node_modules/@angular-devkit/schematics/node_modules/jsonc-parser": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" }, "node_modules/@angular/animations": { "version": "17.3.12", @@ -899,668 +904,1024 @@ "zone.js": "~0.14.0" } }, - "node_modules/@angular/forms": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz", - "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==", + "node_modules/@angular/fire": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/@angular/fire/-/fire-17.1.0.tgz", + "integrity": "sha512-G/hktuErkAoqLc21BsLL+4LPXP8xV+Wu2vl0rqLgx7hr3DinAV+xJ1EsXJpdF8WexLQdtjEyr3ATgAao50Ykxw==", + "license": "MIT", + "dependencies": { + "@angular-devkit/schematics": "^17.0.0", + "@schematics/angular": "^17.0.0", + "firebase": "^10.12.0", + "fs-extra": "^8.0.1", + "fuzzy": "^0.1.3", + "inquirer": "^8.1.1", + "inquirer-autocomplete-prompt": "^1.0.1", + "jsonc-parser": "^3.0.0", + "node-fetch": "^2.6.1", + "open": "^8.0.0", + "ora": "^5.3.0", + "rxfire": "^6.0.5", + "semver": "^7.1.3", + "triple-beam": "^1.3.0", + "tslib": "^2.3.0", + "winston": "^3.0.0" + }, + "peerDependencies": { + "@angular/common": "^17.0.0", + "@angular/core": "^17.0.0", + "@angular/platform-browser": "^17.0.0", + "@angular/platform-browser-dynamic": "^17.0.0", + "firebase-tools": "^13.0.0", + "rxjs": "~7.8.0" + }, + "peerDependenciesMeta": { + "firebase-tools": { + "optional": true + } + } + }, + "node_modules/@angular/fire/node_modules/@firebase/analytics": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz", + "integrity": "sha512-CVnHcS4iRJPqtIDc411+UmFldk0ShSK3OB+D0bKD8Ck5Vro6dbK5+APZpkuWpbfdL359DIQUnAaMLE+zs/PVyA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": "^18.13.0 || >=20.9.0" + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/analytics-compat": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.14.tgz", + "integrity": "sha512-unRVY6SvRqfNFIAA/kwl4vK+lvQAL2HVcgu9zTrUtTyYDmtIt/lOuHJynBMYEgLnKm39YKBDhtqdapP2e++ASw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.8", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@angular/common": "17.3.12", - "@angular/core": "17.3.12", - "@angular/platform-browser": "17.3.12", - "rxjs": "^6.5.3 || ^7.4.0" + "@firebase/app-compat": "0.x" } }, - "node_modules/@angular/material": { - "version": "17.3.10", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz", - "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==", + "node_modules/@angular/fire/node_modules/@firebase/app": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.13.tgz", + "integrity": "sha512-OZiDAEK/lDB6xy/XzYAyJJkaDqmQ+BCtOEPLqFvxWKUz5JbBmej7IiiRHdtiIOD/twW7O5AxVsfaaGA/V1bNsA==", + "license": "Apache-2.0", "dependencies": { - "@material/animation": "15.0.0-canary.7f224ddd4.0", - "@material/auto-init": "15.0.0-canary.7f224ddd4.0", - "@material/banner": "15.0.0-canary.7f224ddd4.0", - "@material/base": "15.0.0-canary.7f224ddd4.0", - "@material/button": "15.0.0-canary.7f224ddd4.0", - "@material/card": "15.0.0-canary.7f224ddd4.0", - "@material/checkbox": "15.0.0-canary.7f224ddd4.0", - "@material/chips": "15.0.0-canary.7f224ddd4.0", - "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", - "@material/data-table": "15.0.0-canary.7f224ddd4.0", - "@material/density": "15.0.0-canary.7f224ddd4.0", - "@material/dialog": "15.0.0-canary.7f224ddd4.0", - "@material/dom": "15.0.0-canary.7f224ddd4.0", - "@material/drawer": "15.0.0-canary.7f224ddd4.0", - "@material/elevation": "15.0.0-canary.7f224ddd4.0", - "@material/fab": "15.0.0-canary.7f224ddd4.0", - "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", - "@material/floating-label": "15.0.0-canary.7f224ddd4.0", - "@material/form-field": "15.0.0-canary.7f224ddd4.0", - "@material/icon-button": "15.0.0-canary.7f224ddd4.0", - "@material/image-list": "15.0.0-canary.7f224ddd4.0", - "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", - "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", - "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", - "@material/list": "15.0.0-canary.7f224ddd4.0", - "@material/menu": "15.0.0-canary.7f224ddd4.0", - "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", - "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", - "@material/radio": "15.0.0-canary.7f224ddd4.0", - "@material/ripple": "15.0.0-canary.7f224ddd4.0", - "@material/rtl": "15.0.0-canary.7f224ddd4.0", - "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", - "@material/select": "15.0.0-canary.7f224ddd4.0", - "@material/shape": "15.0.0-canary.7f224ddd4.0", - "@material/slider": "15.0.0-canary.7f224ddd4.0", - "@material/snackbar": "15.0.0-canary.7f224ddd4.0", - "@material/switch": "15.0.0-canary.7f224ddd4.0", - "@material/tab": "15.0.0-canary.7f224ddd4.0", - "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", - "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", - "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", - "@material/textfield": "15.0.0-canary.7f224ddd4.0", - "@material/theme": "15.0.0-canary.7f224ddd4.0", - "@material/tooltip": "15.0.0-canary.7f224ddd4.0", - "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", - "@material/touch-target": "15.0.0-canary.7f224ddd4.0", - "@material/typography": "15.0.0-canary.7f224ddd4.0", - "tslib": "^2.3.0" + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/app-check": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.8.tgz", + "integrity": "sha512-O49RGF1xj7k6BuhxGpHmqOW5hqBIAEbt2q6POW0lIywx7emYtzPDeQI+ryQpC4zbKX646SoVZ711TN1DBLNSOQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@angular/animations": "^17.0.0 || ^18.0.0", - "@angular/cdk": "17.3.10", - "@angular/common": "^17.0.0 || ^18.0.0", - "@angular/core": "^17.0.0 || ^18.0.0", - "@angular/forms": "^17.0.0 || ^18.0.0", - "@angular/platform-browser": "^17.0.0 || ^18.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "@firebase/app": "0.x" } }, - "node_modules/@angular/platform-browser": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz", - "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==", + "node_modules/@angular/fire/node_modules/@firebase/app-check-compat": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.15.tgz", + "integrity": "sha512-zFIvIFFNqDXpOT2huorz9cwf56VT3oJYRFjSFYdSbGYEJYEaXjLJbfC79lx/zjx4Fh+yuN8pry3TtvwaevrGbg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@firebase/app-check": "0.8.8", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": "^18.13.0 || >=20.9.0" + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/app-compat": { + "version": "0.2.43", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.43.tgz", + "integrity": "sha512-HM96ZyIblXjAC7TzE8wIk2QhHlSvksYkQ4Ukh1GmEenzkucSNUmUX4QvoKrqeWsLEQ8hdcojABeCV8ybVyZmeg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.10.13", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/auth": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", + "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { - "@angular/animations": "17.3.12", - "@angular/common": "17.3.12", - "@angular/core": "17.3.12" + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" }, "peerDependenciesMeta": { - "@angular/animations": { + "@react-native-async-storage/async-storage": { "optional": true } } }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.12.tgz", - "integrity": "sha512-DQwV7B2x/DRLRDSisngZRdLqHdYbbrqZv2Hmu4ZbnNYaWPC8qvzgE/0CvY+UkDat3nCcsfwsMnlDeB6TL7/IaA==", + "node_modules/@angular/fire/node_modules/@firebase/auth-compat": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.14.tgz", + "integrity": "sha512-2eczCSqBl1KUPJacZlFpQayvpilg3dxXLy9cSMTKtQMTQSmondUtPI47P3ikH3bQAXhzKLOE+qVxJ3/IRtu9pw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0" + "@firebase/auth": "1.7.9", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { - "@angular/common": "17.3.12", - "@angular/compiler": "17.3.12", - "@angular/core": "17.3.12", - "@angular/platform-browser": "17.3.12" + "@firebase/app-compat": "0.x" } }, - "node_modules/@angular/router": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.12.tgz", - "integrity": "sha512-dg7PHBSW9fmPKTVzwvHEeHZPZdpnUqW/U7kj8D29HTP9ur8zZnx9QcnbplwPeYb8yYa62JMnZSEel2X4PxdYBg==", + "node_modules/@angular/fire/node_modules/@firebase/component": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0" + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/data-connect": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.1.0.tgz", + "integrity": "sha512-vSe5s8dY13ilhLnfY0eYRmQsdTbH7PUFZtBbqU6JVX/j8Qp9A6G5gG6//ulbX9/1JFOF1IWNOne9c8S/DOCJaQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@angular/common": "17.3.12", - "@angular/core": "17.3.12", - "@angular/platform-browser": "17.3.12", - "rxjs": "^6.5.3 || ^7.4.0" + "@firebase/app": "0.x" } }, - "node_modules/@babel/code-frame": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz", - "integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/database": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", + "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", + "license": "Apache-2.0", "dependencies": { - "@babel/highlight": "^7.25.9", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.9.tgz", - "integrity": "sha512-yD+hEuJ/+wAJ4Ox2/rpNv5HIuPG82x3ZlQvYVn8iYCprdxzE7P1udpGF1jyjQVBU4dgznN+k2h103vxZ7NdPyw==", - "dev": true, - "engines": { - "node": ">=6.9.0" + "node_modules/@angular/fire/node_modules/@firebase/database-compat": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", + "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/database": "1.0.8", + "@firebase/database-types": "1.0.5", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" } }, - "node_modules/@babel/core": { - "version": "7.22.9", - "dev": true, - "license": "MIT", + "node_modules/@angular/fire/node_modules/@firebase/database-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", + "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", + "license": "Apache-2.0", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.9", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.8", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.1" + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.0" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/firestore": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.3.tgz", + "integrity": "sha512-NwVU+JPZ/3bhvNSJMCSzfcBZZg8SUGyzZ2T0EW3/bkUeefCyzMISSt/TTIfEHc8cdyXGlMqfGe3/62u9s74UEg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "@firebase/webchannel-wrapper": "1.0.1", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "engines": { - "node": ">=6.9.0" + "node": ">=10.10.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@babel/generator": { - "version": "7.22.9", - "dev": true, - "license": "MIT", + "node_modules/@angular/fire/node_modules/@firebase/firestore-compat": { + "version": "0.3.38", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.38.tgz", + "integrity": "sha512-GoS0bIMMkjpLni6StSwRJarpu2+S5m346Na7gr9YZ/BZ/W3/8iHGNr9PxC+f0rNZXqS4fGRn88pICjrZEgbkqQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.22.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@firebase/component": "0.6.9", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/functions": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.8.tgz", + "integrity": "sha512-Lo2rTPDn96naFIlSZKVd1yvRRqqqwiJk7cf9TZhUerwnPKgBzXy+aHE22ry+6EjCaQusUoNai6mU6p+G8QZT1g==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.22.5" + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/functions-compat": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.14.tgz", + "integrity": "sha512-dZ0PKOKQFnOlMfcim39XzaXonSuPPAVuzpqA4ONTIdyaJK/OnBaIEVs/+BH4faa1a2tLeR+Jy15PKqDRQoNIJw==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@firebase/component": "0.6.9", + "@firebase/functions": "0.11.8", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/installations": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.9.tgz", + "integrity": "sha512-hlT7AwCiKghOX3XizLxXOsTFiFCQnp/oj86zp1UxwDGmyzsyoxtX+UIZyVyH/oBF5+XtblFG9KZzZQ/h+dpy+Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@angular/fire/node_modules/@firebase/installations-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.9.tgz", + "integrity": "sha512-2lfdc6kPXR7WaL4FCQSQUhXcPbI7ol3wF+vkgtU25r77OxPf8F/VmswQ7sgIkBBWtymn5ZF20TIKtnOj9rjb6w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/logger": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/messaging": { + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.12.tgz", + "integrity": "sha512-6q0pbzYBJhZEtUoQx7hnPhZvAbuMNuBXKQXOx2YlWhSrlv9N1m0ZzlNpBbu/ItTzrwNKTibdYzUyaaxdWLg+4w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "idb": "7.1.1", + "tslib": "^2.1.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@firebase/app": "0.x" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/messaging-compat": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.12.tgz", + "integrity": "sha512-pKsiUVZrbmRgdImYqhBNZlkKJbqjlPkVdQRZGRbkTyX4OSGKR0F/oJeCt1a8jEg5UnBp4fdVwSWSp4DuCovvEQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.25.9" + "@firebase/component": "0.6.9", + "@firebase/messaging": "0.12.12", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@angular/fire/node_modules/@firebase/performance": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.9.tgz", + "integrity": "sha512-PnVaak5sqfz5ivhua+HserxTJHtCar/7zM0flCX6NkzBNzJzyzlH4Hs94h2Il0LQB99roBqoE5QT1JqWqcLJHQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/performance-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.9.tgz", + "integrity": "sha512-dNl95IUnpsu3fAfYBZDCVhXNkASE0uo4HYaEPd2/PKscfTvsgqFAOxfAXzBEDOnynDWiaGUnb5M1O00JQ+3FXA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", - "semver": "^6.3.1" + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.9", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@angular/fire/node_modules/@firebase/remote-config": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.9.tgz", + "integrity": "sha512-EO1NLCWSPMHdDSRGwZ73kxEEcTopAxX1naqLJFNApp4hO8WfKfmEpmjxmP5TrrnypjIf2tUkYaKsfbEA7+AMmA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@firebase/app": "0.x" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/remote-config-compat": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.9.tgz", + "integrity": "sha512-AxzGpWfWFYejH2twxfdOJt5Cfh/ATHONegTd/a0p5flEzsD5JsxXgfkFToop+mypEL3gNwawxrxlZddmDoNxyA==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.25.9" + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@firebase/app-compat": "0.x" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node_modules/@angular/fire/node_modules/@firebase/storage": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.2.tgz", + "integrity": "sha512-fxuJnHshbhVwuJ4FuISLu+/76Aby2sh+44ztjF2ppoe0TELIDxPW6/r1KGlWYt//AD0IodDYYA8ZTN89q8YqUw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" + }, + "peerDependencies": { + "@firebase/app": "0.x" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/storage-compat": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.12.tgz", + "integrity": "sha512-hA4VWKyGU5bWOll+uwzzhEMMYGu9PlKQc1w4DWxB3aIErWYzonrZjF0icqNQZbwKNIdh8SHjZlFeB2w6OSsjfg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@firebase/component": "0.6.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@firebase/app-compat": "0.x" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node_modules/@angular/fire/node_modules/@firebase/util": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, + "node_modules/@angular/fire/node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz", + "integrity": "sha512-jmEnr/pk0yVkA7mIlHNnxCi+wWzOFUg0WyIotgkKAb2u1J7fAeDBcVNSTjTihbAYNusCLQdW5s9IJ5qwnEufcQ==", + "license": "Apache-2.0" + }, + "node_modules/@angular/fire/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, + "node_modules/@angular/fire/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.9.tgz", - "integrity": "sha512-TvLZY/F3+GvdRYFZFyxMvnsKi+4oJdgZzU3BoGN9Uc2d9C6zfNwJcKKhjqLAhK8i46mv93jsO74fDh3ih6rpHA==", - "dev": true, + "node_modules/@angular/fire/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@angular/fire/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "color-name": "~1.1.4" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, + "node_modules/@angular/fire/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@angular/fire/node_modules/firebase": { + "version": "10.14.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.14.1.tgz", + "integrity": "sha512-0KZxU+Ela9rUCULqFsUUOYYkjh7OM1EWdIfG6///MtXd0t2/uUIf0iNV5i0KariMhRQ5jve/OY985nrAXFaZeQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.25.9" - }, + "@firebase/analytics": "0.10.8", + "@firebase/analytics-compat": "0.2.14", + "@firebase/app": "0.10.13", + "@firebase/app-check": "0.8.8", + "@firebase/app-check-compat": "0.3.15", + "@firebase/app-compat": "0.2.43", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.9", + "@firebase/auth-compat": "0.5.14", + "@firebase/data-connect": "0.1.0", + "@firebase/database": "1.0.8", + "@firebase/database-compat": "1.0.8", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-compat": "0.3.38", + "@firebase/functions": "0.11.8", + "@firebase/functions-compat": "0.3.14", + "@firebase/installations": "0.6.9", + "@firebase/installations-compat": "0.2.9", + "@firebase/messaging": "0.12.12", + "@firebase/messaging-compat": "0.2.12", + "@firebase/performance": "0.6.9", + "@firebase/performance-compat": "0.2.9", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-compat": "0.2.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-compat": "0.3.12", + "@firebase/util": "1.10.0", + "@firebase/vertexai-preview": "0.0.4" + } + }, + "node_modules/@angular/fire/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", - "dev": true, + "node_modules/@angular/fire/node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, "engines": { - "node": ">=6.9.0" + "node": ">=12.0.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, + "node_modules/@angular/fire/node_modules/inquirer-autocomplete-prompt": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.4.0.tgz", + "integrity": "sha512-qHgHyJmbULt4hI+kCmwX92MnSxDs/Yhdt4wPA30qnoa01OF6uTXV8yvH4hKXgdaTNmkZ9D01MHjqKYEuJN+ONw==", + "license": "ISC", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "figures": "^3.2.0", + "run-async": "^2.4.0", + "rxjs": "^6.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "inquirer": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, + "node_modules/@angular/fire/node_modules/inquirer-autocomplete-prompt/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.25.9" + "tslib": "^1.9.0" }, "engines": { - "node": ">=6.9.0" + "npm": ">=2.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", - "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, + "node_modules/@angular/fire/node_modules/inquirer-autocomplete-prompt/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@angular/fire/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, + "node_modules/@angular/fire/node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "license": "MIT", "engines": { - "node": ">=6.9.0" - }, + "node": ">=0.12.0" + } + }, + "node_modules/@angular/fire/node_modules/rxfire": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/rxfire/-/rxfire-6.0.5.tgz", + "integrity": "sha512-ycBsANGbya3GNtOBKzZVATLEV+0S9gUrlTfwnN15TCXtgG8OgIMAuv2k9+kMeVaevp/DRp1KT+vYf6Wkop6gvw==", + "license": "Apache-2.0", "peerDependencies": { - "@babel/core": "^7.0.0" + "firebase": "^9.0.0 || ^10.0.0", + "rxjs": "^6.0.0 || ^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dev": true, + "node_modules/@angular/fire/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, + "node_modules/@angular/fire/node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=18.17" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "dev": true, + "node_modules/@angular/fire/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, + "node_modules/@angular/forms": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz", + "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.9.0" + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" + "node_modules/@angular/material": { + "version": "17.3.10", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.3.10.tgz", + "integrity": "sha512-hHMQES0tQPH5JW33W+mpBPuM8ybsloDTqFPuRV8cboDjosAWfJhzAKF3ozICpNlUrs62La/2Wu/756GcQrxebg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/auto-init": "15.0.0-canary.7f224ddd4.0", + "@material/banner": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/card": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/chips": "15.0.0-canary.7f224ddd4.0", + "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", + "@material/data-table": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dialog": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/drawer": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/fab": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/form-field": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/image-list": "15.0.0-canary.7f224ddd4.0", + "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/radio": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/slider": "15.0.0-canary.7f224ddd4.0", + "@material/snackbar": "15.0.0-canary.7f224ddd4.0", + "@material/switch": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/textfield": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tooltip": "15.0.0-canary.7f224ddd4.0", + "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^17.0.0 || ^18.0.0", + "@angular/cdk": "17.3.10", + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "@angular/platform-browser": "^17.0.0 || ^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, + "node_modules/@angular/platform-browser": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz", + "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.9.0" + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/animations": "17.3.12", + "@angular/common": "17.3.12", + "@angular/core": "17.3.12" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, + "node_modules/@angular/platform-browser-dynamic": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.12.tgz", + "integrity": "sha512-DQwV7B2x/DRLRDSisngZRdLqHdYbbrqZv2Hmu4ZbnNYaWPC8qvzgE/0CvY+UkDat3nCcsfwsMnlDeB6TL7/IaA==", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "17.3.12", + "@angular/compiler": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12" } }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, + "node_modules/@angular/platform-server": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-17.3.12.tgz", + "integrity": "sha512-P3xBzyeT2w/iiGsqGUNuLRYdqs2e+5nRnVYU9tc/TjhYDAgwEgq946U7Nie1xq5Ts/8b7bhxcK9maPKWG237Kw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.3.0", + "xhr2": "^0.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/animations": "17.3.12", + "@angular/common": "17.3.12", + "@angular/compiler": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12" } }, - "node_modules/@babel/helpers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.9.tgz", - "integrity": "sha512-oKWp3+usOJSzDZOucZUAMayhPz/xVjzymyDzUN8dk0Wd3RWMlGLXi07UCQ/CgQVb8LvXx3XBajJH4XGgkt7H7g==", - "dev": true, + "node_modules/@angular/router": { + "version": "17.3.12", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.12.tgz", + "integrity": "sha512-dg7PHBSW9fmPKTVzwvHEeHZPZdpnUqW/U7kj8D29HTP9ur8zZnx9QcnbplwPeYb8yYa62JMnZSEel2X4PxdYBg==", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helpers/node_modules/@babel/template": { + "node_modules/@angular/ssr": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-17.3.11.tgz", + "integrity": "sha512-8AslXZnj5bu0fJrSSoZf202HXptc+vS8hSvEIobK1+UpEVmtrk3StiBxYTdbN4Pe76r7RGRmBt40fHe+88AZoA==", + "license": "MIT", + "dependencies": { + "critters": "0.0.22", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^17.0.0", + "@angular/core": "^17.0.0" + } + }, + "node_modules/@babel/code-frame": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz", + "integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/highlight": "^7.25.9", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { + "node_modules/@babel/compat-data": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", - "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.9.tgz", + "integrity": "sha512-yD+hEuJ/+wAJ4Ox2/rpNv5HIuPG82x3ZlQvYVn8iYCprdxzE7P1udpGF1jyjQVBU4dgznN+k2h103vxZ7NdPyw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.9", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", - "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.9", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, - "bin": { - "parser": "bin/babel-parser.js" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", + "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "node_modules/@babel/helper-compilation-targets": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1569,451 +1930,402 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", + "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { + "node_modules/@babel/helper-module-imports": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.9.tgz", - "integrity": "sha512-4GHX5uzr5QMOOuzV0an9MFju4hKlm0OyePl/lHhcsTVae5t/IKVHnb8W67Vr6FuLlk5lPqLB7n7O+K5R46emYg==", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { + "node_modules/@babel/helper-module-transforms": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.9.tgz", - "integrity": "sha512-u3EN9ub8LyYvgTnrgp8gboElouayiwPdnM7x5tcnW3iSt09/lQYPwMNK40I9IUxo7QOZhAsPHCmmuO7EPdruqg==", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.9.tgz", + "integrity": "sha512-TvLZY/F3+GvdRYFZFyxMvnsKi+4oJdgZzU3BoGN9Uc2d9C6zfNwJcKKhjqLAhK8i46mv93jsO74fDh3ih6rpHA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/types": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/types": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/helper-simple-access": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { + "node_modules/@babel/helper-validator-identifier": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { + "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", - "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { + "node_modules/@babel/helpers": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.9.tgz", + "integrity": "sha512-oKWp3+usOJSzDZOucZUAMayhPz/xVjzymyDzUN8dk0Wd3RWMlGLXi07UCQ/CgQVb8LvXx3XBajJH4XGgkt7H7g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-properties": { + "node_modules/@babel/helpers/node_modules/@babel/template": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { + "node_modules/@babel/highlight": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.9.tgz", - "integrity": "sha512-UIf+72C7YJ+PJ685/PpATbCz00XqiFEzHX5iysRwfvNT0Ko+FaXSvRgLytFSp8xUItrG9pFM/KoBBZDrY/cYyg==", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-classes": { + "node_modules/@babel/parser": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", + "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", "dev": true, "dependencies": { "@babel/types": "^7.25.9" }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-computed-properties/node_modules/@babel/template": { + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-transform-destructuring": { + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, "engines": { "node": ">=6.9.0" }, @@ -2021,44 +2333,37 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -2067,46 +2372,37 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { + "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.9.tgz", + "integrity": "sha512-4GHX5uzr5QMOOuzV0an9MFju4hKlm0OyePl/lHhcsTVae5t/IKVHnb8W67Vr6FuLlk5lPqLB7n7O+K5R46emYg==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2115,10 +2411,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-json-strings": { + "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.9.tgz", + "integrity": "sha512-u3EN9ub8LyYvgTnrgp8gboElouayiwPdnM7x5tcnW3iSt09/lQYPwMNK40I9IUxo7QOZhAsPHCmmuO7EPdruqg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2130,141 +2426,124 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -2273,25 +2552,26 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", - "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { + "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2303,15 +2583,16 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { "node": ">=6.9.0" @@ -2320,14 +2601,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -2336,10 +2618,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { + "node_modules/@babel/plugin-transform-block-scoped-functions": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2351,14 +2633,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { + "node_modules/@babel/plugin-transform-block-scoping": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2367,12 +2648,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-parameters": { + "node_modules/@babel/plugin-transform-class-properties": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "dev": true, "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -2382,10 +2664,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-methods": { + "node_modules/@babel/plugin-transform-class-static-block": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.9.tgz", + "integrity": "sha512-UIf+72C7YJ+PJ685/PpATbCz00XqiFEzHX5iysRwfvNT0Ko+FaXSvRgLytFSp8xUItrG9pFM/KoBBZDrY/cYyg==", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", @@ -2395,18 +2677,21 @@ "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { + "node_modules/@babel/plugin-transform-classes": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" @@ -2415,7 +2700,7 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", @@ -2427,13 +2712,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-property-literals": { + "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2442,26 +2728,24 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { + "node_modules/@babel/plugin-transform-computed-properties/node_modules/@babel/template": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { + "node_modules/@babel/plugin-transform-destructuring": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2473,18 +2757,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", - "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "semver": "^6.3.1" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2493,19 +2773,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { + "node_modules/@babel/plugin-transform-duplicate-keys": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2517,14 +2788,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { + "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2533,12 +2803,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { + "node_modules/@babel/plugin-transform-exponentiation-operator": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", + "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", "dev": true, "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -2548,10 +2819,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { + "node_modules/@babel/plugin-transform-export-namespace-from": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2563,13 +2834,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { + "node_modules/@babel/plugin-transform-for-of": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2578,13 +2850,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { + "node_modules/@babel/plugin-transform-function-name": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2593,13 +2867,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { + "node_modules/@babel/plugin-transform-json-strings": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -2609,13 +2882,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { + "node_modules/@babel/plugin-transform-literals": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -2625,108 +2897,28 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", - "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.24.0", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2735,601 +2927,1857 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "dev": true, "dependencies": { - "regenerator-runtime": "^0.14.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/template": { - "version": "7.22.5", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/traverse": { + "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.9.tgz", - "integrity": "sha512-omlUGkr5EaoIJrhLf9CJ0TvjBRpd9+AXRG//0GEQ9THSo8wPiTlbpy1/Ow8ZTrbXpjd9FHXfbFQx32I04ht0FA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "dev": true, "dependencies": { - "@babel/types": "^7.25.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/template": { + "node_modules/@babel/plugin-transform-new-target": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "dev": true, - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { - "node": ">=6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/types": { + "node_modules/@babel/plugin-transform-numeric-separator": { "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", - "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, "engines": { - "node": ">=0.1.90" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cypress/request": { - "version": "3.0.1", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.10.4", - "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { - "node": ">= 6" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cypress/request/node_modules/form-data": { - "version": "2.3.3", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "dev": true, - "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { - "node": ">= 0.12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cypress/request/node_modules/qs": { - "version": "6.10.4", + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { - "node": ">=0.6" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "dev": true, - "license": "MIT", "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "dev": true, - "license": "MIT", + "@babel/types": "^7.25.9" + }, "engines": { - "node": ">=10.0.0" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", - "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "dev": true, - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", - "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", - "cpu": [ - "arm" - ], + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", - "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", - "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", + "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "semver": "^6.3.1" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", - "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", - "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", - "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", - "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", - "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", - "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", - "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", - "cpu": [ - "loong64" - ], + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", - "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", - "cpu": [ - "mips64el" - ], + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", - "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", - "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", - "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", - "cpu": [ - "s390x" - ], + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", - "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", - "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/preset-env": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", + "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", - "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", - "cpu": [ - "x64" - ], + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.24.0", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "optional": true, - "os": [ - "openbsd" - ], + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.9.tgz", + "integrity": "sha512-omlUGkr5EaoIJrhLf9CJ0TvjBRpd9+AXRG//0GEQ9THSo8wPiTlbpy1/Ow8ZTrbXpjd9FHXfbFQx32I04ht0FA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/types": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", + "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, "engines": { "node": ">=12" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", - "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", - "cpu": [ - "x64" - ], + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.10.4", + "safe-buffer": "^5.1.2", + "tough-cookie": "^4.1.3", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/form-data": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.10.4", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.9", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.9.tgz", + "integrity": "sha512-FrvW6u6xDBKXUGYUy1WIUh0J9tvbppMsk90mig0JhHST8iLveKu/dIBVeVE/ZYZhmXy4fkI7SPSWvD1V0O4tXw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/installations": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.15.tgz", + "integrity": "sha512-C5to422Sr8FkL0MPwXcIecbMnF4o2Ll7MtoWvIm4Q/LPJvvM+tWa1DiU+LzsCdsd1/CYE9EIW9Ma3ko9XnAAYw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.9", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.10", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.10.15", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.15.tgz", + "integrity": "sha512-he6qlG3pmwL+LHdG/BrSMBQeJzzutciq4fpXN3lGa1uSwYSijJ24VtakS/bP2X9SiDf8jGywJ4u+OgXAenJsNg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.9.tgz", + "integrity": "sha512-YzVn1mMLzD2JboMPVVO0Pe20YOgWzrF+aXoAmmd0v3xec051n83YpxSUZbacL69uYvk0dHrEsbea44QtQ5WPDA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.16.tgz", + "integrity": "sha512-AxIGzLRXrTFNL+H6V+4BO0w/gERloROfRbWI/FoJUnQd0qPZIzyfdHZBbThFzFGLfDt/mVs2kdjYFx/l9I8NhQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.8.9", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", + "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.45", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.45.tgz", + "integrity": "sha512-5rYbXq1ndtMTg+07oH4WrkYuP+NZq61uzVwW1hlmybp/gr4cXq2SfaP9fc6/9IzTKmu3dh3H0fjj++HG7Z7o/w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.10.15", + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.8.0.tgz", + "integrity": "sha512-/O7UDWE5S5ux456fzNHSLx/0YN/Kykw/WyAzgDQ6wvkddZhSEmPX19EzxgsFldzhuFjsl5uOZTz8kzlosCiJjg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.15.tgz", + "integrity": "sha512-jz6k1ridPiecKI8CBRiqCM6IMOhwYp2MD+YvoxnMiK8nQLSTm57GvHETlPNX3WlbyQnCjMCOvrAhe27whyxAEg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.8.0", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.10", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", + "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.10.tgz", + "integrity": "sha512-OsNbEKyz9iLZSmMUhsl6+kCADzte00iisJIRUspnUqvDCX+RSGZOBIqekukv/jN177ovjApBQNFaxSYIDc/SyQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.1.1.tgz", + "integrity": "sha512-RBJ7XE/a3oXFv31Jlw8cbMRdsxQoI8F3L7xm4n93ab+bIr1NQUiYGgW9L7TTw7obdNev91ZnW0xfqJtXcPA5yA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.9.tgz", + "integrity": "sha512-EkiPSKSu2TJJGtOjyISASf3UFpFJDil1lMbfqnxilfbmIsilvC8DzgjuLoYD+eOitcug4wtU9Fh1tt2vgBhskA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.0.tgz", + "integrity": "sha512-2xlODKWwf/vNAxCmou0GFhymx2pqZKkhXMN9B5aiTjZ6+81sOxGim53ELY2lj+qKG2IvgiCYFc4X+ZJA2Ad5vg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/database": "1.0.9", + "@firebase/database-types": "1.0.6", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.6.tgz", + "integrity": "sha512-sMI7IynSZBsyGbUugc8PKE1jwKbnvaieAz/RxuM57PZQNCi6Rteiviwcw/jqZOX6igqYJwXWZ3UzKOZo2nUDRA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.1" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.4.tgz", + "integrity": "sha512-K2nq4w+NF8J1waGawY5OHLawP/Aw5CYxyDstVv1NZemGPcM3U+LZ9EPaXr1PatYIrPA7fS4DxZoWcbB0aGJ8Zg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "@firebase/webchannel-wrapper": "1.0.2", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.39", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.39.tgz", + "integrity": "sha512-CsK8g34jNeHx95LISDRTcArJLonW+zJCqHI1Ez9WNiLAK2X8FeQ4UiD+RwOwxAIR+t2a6xED/5Fe6ZIqx7MuoQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/firestore": "4.7.4", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", + "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.11.9", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.9.tgz", + "integrity": "sha512-dhO5IUfQRCsrc20YD20nSOX+QCT+cH6N86HlZOLz2XgyEFgzOdBQnUot4EabBJQRkMBI7fZWUrbYfRcnov53ug==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.10", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.15.tgz", + "integrity": "sha512-eiHpc6Sd9Y/SNhBsGi944SapiFbfTPKsiSUQ74QxNSs0yoxvABeIRolVMFk4TokP57NGmstGYpYte02XGNPcYw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/functions": "0.11.9", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", + "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.10.tgz", + "integrity": "sha512-TuGSOMqkFrllxa0X/8VZIqBCRH4POndU/iWKWkRmkh12+/xKSpdp+y/kWaVbsySrelltan6LeYlcYPmLibWbwg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/util": "1.10.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.10.tgz", + "integrity": "sha512-YTonkcVz3AK7RF8xFhvs5CwDuJ0xbzzCJIwXoV14gnzdYbMgy6vWlUUbzkvbtEDXzPRHB0n7aGZl56oy9dLOFw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/installations": "0.6.10", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", + "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.3.tgz", + "integrity": "sha512-Th42bWJg18EF5bJwhRosn2M/eYxmbWCwXZr4hHX7ltO0SE3QLrpgiMKeRBR/NW7vJke7i0n3i8esbCW2s93qBw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.13", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.13.tgz", + "integrity": "sha512-YLa8PWl+BgiOVR5WOyzl21fVJFJeBRfniNuN25d9DBrQzppSAahuN6yS+vt1OIjvZNPN4pZ/lcRLYupbGu4W0w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/installations": "0.6.10", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.1", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.13.tgz", + "integrity": "sha512-9ootPClS6m2c2KIzo7AqSHaWzAw28zWcjQPjVv7WeQDu6wjufpbOg+7tuVzb+gqpF9Issa3lDoYOwlO0ZudO3g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/messaging": "0.12.13", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", + "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.10.tgz", + "integrity": "sha512-x/mNYKGxq7A+QV0EiEZeD2S+E+kw+UcZ8FXuE7qDJyGGt/0Wd+bIIL7RakG/VrFt7/UYc//nKygDc7/Ig7sOmQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/installations": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.10.tgz", + "integrity": "sha512-0h1qYkF6I79DSSpHfTQFvb91fo8shmmwiPzWFYAPdPK02bSWpKwVssNYlZX2iUnumxerDMbl7dWN+Im/W3bnXA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/performance": "0.6.10", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", + "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.10.tgz", + "integrity": "sha512-jTRjy3TdqzVna19m5a1HEHE5BG4Z3BQTxBgvQRTmMKlHacx4QS0CToAas7R9M9UkxpgFcVuAE7FpWIOWQGCEWw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/installations": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.10.tgz", + "integrity": "sha512-fIi5OB2zk0zpChMV/tTd0oEZcZI8TlwQDlLlcrDpMOV5l5dqd0JNlWKh6Fwmh4izmytk+rZIAIpnak/NjGVesQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/remote-config": "0.4.10", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", + "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.3.tgz", + "integrity": "sha512-B5HiJ7isYKaT4dOEV43f2ySdhQxzq+SQEm7lqXebJ8AYCsebdHrgGzrPR0LR962xGjPzJHFKx63gA8Be/P2MCw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.13.tgz", + "integrity": "sha512-15kje7JALswRCBKsCSvKg5FbqUYykaIMqMbZRD7I6uVRWwdyTvez5MBQfMhBia2JcEmPiDpXhJTXH4PAWFiA8g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.10", + "@firebase/storage": "0.13.3", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", + "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.1.tgz", + "integrity": "sha512-AIhFnCCjM8FmCqSNlNPTuOk3+gpHC1RkeNUBLtPbcqGYpN5MxI5q7Yby+rxycweOZOCboDzfIj8WyaY4tpQG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/vertexai": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@firebase/vertexai/-/vertexai-1.0.0.tgz", + "integrity": "sha512-48N3Lp/9GgiCCRfrSdHS+Y1IiMdYXvnHFO/f+HL1PgUtBq7WQ/fWmYOX3mzAN36zvytq13nb68ImF+GALopp+Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.10", + "@firebase/logger": "0.4.3", + "@firebase/util": "1.10.1", + "tslib": "^2.1.0" + }, "engines": { - "node": ">=12" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", - "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.4.tgz", + "integrity": "sha512-EBSqyu9eg8frQlVU9/HjKtHN7odqbh9MtAcVz3WwHj4gLCLOoN9F/o+oxlq3CxvFrd3CNTZwu6d2mZtVlEInng==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + }, "engines": { - "node": ">=12" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", - "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], + "node_modules/@firebase/vertexai-preview/node_modules/@firebase/component": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/vertexai-preview/node_modules/@firebase/logger": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/vertexai-preview/node_modules/@firebase/util": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.2.tgz", + "integrity": "sha512-3F4iA2E+NtdMbOU0XC1cHE8q6MqpGIKRj62oGOF38S6AAx5VHR9cXmoDUSj7ejvTAT7m6jxuEeQkHeq0F+mU2w==", + "license": "Apache-2.0" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, "engines": { - "node": ">=12" + "node": "^8.13.0 || >=10.10.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", - "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, "engines": { - "node": ">=12" + "node": ">=6" } }, "node_modules/@isaacs/cliui": { @@ -3493,7 +4941,6 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -4410,6 +5857,18 @@ "webpack": "^5.54.0" } }, + "node_modules/@nguniversal/express-engine": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@nguniversal/express-engine/-/express-engine-7.0.2.tgz", + "integrity": "sha512-mmNgDpUrcuFcjBOfUlmAliS3St4tSekLdQZWu6PK/d+kw5zicDDlSO/UoUw5g+wz7YHkGQK+VDy3wHc0Bo36rA==", + "license": "MIT", + "peerDependencies": { + "@angular/common": ">=6.0.0", + "@angular/core": ">=6.0.0", + "@angular/platform-server": ">=6.0.0", + "express": "^4.15.2" + } + }, "node_modules/@noble/hashes": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", @@ -4746,9 +6205,10 @@ } }, "node_modules/@openmina/shared": { - "version": "0.102.0", - "resolved": "https://registry.npmjs.org/@openmina/shared/-/shared-0.102.0.tgz", - "integrity": "sha512-Tk2kf0b+NQqLQTSqpF+qCeanfGtAfr34RHJcnE/T9iRhzej02Ss5IH2cgzvy5kgSW1IjxT6qsCpUONaRMMmZAw==", + "version": "0.117.0", + "resolved": "https://registry.npmjs.org/@openmina/shared/-/shared-0.117.0.tgz", + "integrity": "sha512-ZGf3JWQKK0rEoFGdhSwUT/TPAljP0wq//1TmWd0uToEKgst+XsMiuT6vR1QcnK2ugTfXpKJf6adJ8ldIrD50vA==", + "license": "Apache License, Version 2.0", "dependencies": { "tslib": ">=2.3.0" }, @@ -4784,6 +6244,70 @@ "dev": true, "license": "MIT" }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", @@ -4996,7 +6520,6 @@ "version": "17.3.10", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.10.tgz", "integrity": "sha512-cI+VB/WXlOeAMamni932lE/AZgui8o81dMyEXNXqCuYagNAMuKXliW79Mi5BwYQEABv/BUb4hB4zYtbQqHyACA==", - "dev": true, "dependencies": { "@angular-devkit/core": "17.3.10", "@angular-devkit/schematics": "17.3.10", @@ -5011,8 +6534,7 @@ "node_modules/@schematics/angular/node_modules/jsonc-parser": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" }, "node_modules/@sentry-internal/browser-utils": { "version": "8.35.0", @@ -5150,6 +6672,196 @@ "node": ">=14.18" } }, + "node_modules/@sentry/cli": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.38.2.tgz", + "integrity": "sha512-CR0oujpAnhegK2pBAv6ZReMqbFTuNJLDZLvoD1B+syrKZX+R+oxkgT2e1htsBbht+wGxAsluVWsIAydSws1GAA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.38.2", + "@sentry/cli-linux-arm": "2.38.2", + "@sentry/cli-linux-arm64": "2.38.2", + "@sentry/cli-linux-i686": "2.38.2", + "@sentry/cli-linux-x64": "2.38.2", + "@sentry/cli-win32-i686": "2.38.2", + "@sentry/cli-win32-x64": "2.38.2" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.38.2.tgz", + "integrity": "sha512-21ywIcJCCFrCTyiF1o1PaT7rbelFC2fWmayKYgFElnQ55IzNYkcn8BYhbh/QknE0l1NBRaeWMXwTTdeoqETCCg==", + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.38.2.tgz", + "integrity": "sha512-+AiKDBQKIdQe4NhBiHSHGl0KR+b//HHTrnfK1SaTrOm9HtM4ELXAkjkRF3bmbpSzSQCS5WzcbIxxCJOeaUaO0A==", + "cpu": [ + "arm" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.38.2.tgz", + "integrity": "sha512-4Fp/jjQpNZj4Th+ZckMQvldAuuP0ZcyJ9tJCP1CCOn5poIKPYtY6zcbTP036R7Te14PS4ALOcDNX3VNKfpsifA==", + "cpu": [ + "arm64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.38.2.tgz", + "integrity": "sha512-6zVJN10dHIn4R1v+fxuzlblzVBhIVwsaN/S7aBED6Vn1HhAyAcNG2tIzeCLGeDfieYjXlE2sCI82sZkQBCbAGw==", + "cpu": [ + "x86", + "ia32" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.38.2.tgz", + "integrity": "sha512-4UiLu9zdVtqPeltELR5MDGKcuqAdQY9xz3emISuA6bm+MXGbt2W1WgX+XY3GElwjZbmH8qpyLUEd34sw6sdcbQ==", + "cpu": [ + "x64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.38.2.tgz", + "integrity": "sha512-DYfSvd5qLPerLpIxj3Xu2rRe3CIlpGOOfGSNI6xvJ5D8j6hqbOHlCzvfC4oBWYVYGtxnwQLMeDGJ7o7RMYulig==", + "cpu": [ + "x86", + "ia32" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.38.2.tgz", + "integrity": "sha512-W5UX58PKY1hNUHo9YJxWNhGvgvv2uOYHI27KchRiUvFYBIqlUUcIdPZDfyzetDfd8qBCxlAsFnkL2VJSNdpA9A==", + "cpu": [ + "x64" + ], + "license": "BSD-3-Clause", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@sentry/cli/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sentry/cli/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/@sentry/cli/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@sentry/core": { "version": "8.35.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.35.0.tgz", @@ -5706,8 +7418,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.3", - "dev": true, + "version": "18.19.64", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz", + "integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -5789,6 +7502,12 @@ "@types/node": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@types/w3c-web-hid": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/w3c-web-hid/-/w3c-web-hid-1.0.6.tgz", @@ -5999,7 +7718,6 @@ }, "node_modules/accepts": { "version": "1.3.8", - "dev": true, "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -6092,7 +7810,6 @@ }, "node_modules/ajv": { "version": "8.12.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -6107,7 +7824,6 @@ }, "node_modules/ajv-formats": { "version": "2.1.1", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -6142,7 +7858,6 @@ }, "node_modules/ansi-escapes": { "version": "4.3.2", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" @@ -6168,7 +7883,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6188,7 +7902,7 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -6234,8 +7948,7 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/asn1": { "version": "0.2.6", @@ -6263,8 +7976,17 @@ }, "node_modules/async": { "version": "3.2.5", + "license": "MIT" + }, + "node_modules/async-each-series": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } }, "node_modules/asynckit": { "version": "0.4.0", @@ -6500,7 +8222,7 @@ }, "node_modules/binary-extensions": { "version": "2.2.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -6508,7 +8230,6 @@ }, "node_modules/bl": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -6520,7 +8241,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -6559,7 +8279,6 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -6575,59 +8294,448 @@ "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-3.0.3.tgz", + "integrity": "sha512-91hoBHKk1C4pGeD+oE9Ld222k2GNQEAsI5AElqR8iLLWNrmZR2LPP8B0h8dpld9u7kro5IEUB3pUb0DJ3n1cRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "browser-sync-client": "^3.0.3", + "browser-sync-ui": "^3.0.3", + "bs-recipes": "1.3.4", + "chalk": "4.1.2", + "chokidar": "^3.5.1", + "connect": "3.6.6", + "connect-history-api-fallback": "^1", + "dev-ip": "^1.0.1", + "easy-extender": "^2.3.4", + "eazy-logger": "^4.0.1", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "fs-extra": "3.0.1", + "http-proxy": "^1.18.1", + "immutable": "^3", + "micromatch": "^4.0.8", + "opn": "5.3.0", + "portscanner": "2.2.0", + "raw-body": "^2.3.2", + "resp-modifier": "6.0.2", + "rx": "4.1.0", + "send": "^0.19.0", + "serve-index": "^1.9.1", + "serve-static": "^1.16.2", + "server-destroy": "1.0.1", + "socket.io": "^4.4.1", + "ua-parser-js": "^1.0.33", + "yargs": "^17.3.1" + }, + "bin": { + "browser-sync": "dist/bin.js" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/browser-sync-client": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-3.0.3.tgz", + "integrity": "sha512-TOEXaMgYNjBYIcmX5zDlOdjEqCeCN/d7opf/fuyUD/hhGVCfP54iQIDhENCi012AqzYZm3BvuFl57vbwSTwkSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "etag": "1.8.1", + "fresh": "0.5.2", + "mitt": "^1.1.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/browser-sync-ui": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-3.0.3.tgz", + "integrity": "sha512-FcGWo5lP5VodPY6O/f4pXQy5FFh4JK0f2/fTBsp0Lx1NtyBWs/IfPPJbW8m1ujTW/2r07oUXKTF2LYZlCZktjw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async-each-series": "0.1.1", + "chalk": "4.1.2", + "connect-history-api-fallback": "^1", + "immutable": "^3", + "server-destroy": "1.0.1", + "socket.io-client": "^4.4.1", + "stream-throttle": "^0.1.3" + } + }, + "node_modules/browser-sync-ui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync-ui/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/browser-sync-ui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync-ui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/browser-sync-ui/node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/browser-sync-ui/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync-ui/node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browser-sync-ui/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/browser-sync/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/browser-sync/node_modules/connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/browser-sync/node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/browser-sync/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/browser-sync/node_modules/finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", + "node_modules/browser-sync/node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" } }, - "node_modules/body-parser/node_modules/ms": { + "node_modules/browser-sync/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync/node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browser-sync/node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/browser-sync/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, - "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "node_modules/browser-sync/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", + "node_modules/browser-sync/node_modules/statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 0.6" } }, - "node_modules/braces": { - "version": "3.0.2", + "node_modules/browser-sync/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, + "node_modules/browser-sync/node_modules/ua-parser-js": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.39.tgz", + "integrity": "sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/browserslist": { "version": "4.24.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", @@ -6660,6 +8768,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-recipes": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz", + "integrity": "sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw==", + "dev": true, + "license": "ISC" + }, "node_modules/bs58": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", @@ -6715,7 +8830,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -6806,7 +8920,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -6880,8 +8993,7 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/check-more-types": { "version": "2.24.0", @@ -6893,7 +9005,7 @@ }, "node_modules/chokidar": { "version": "3.5.3", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -6958,7 +9070,6 @@ }, "node_modules/cli-cursor": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -6969,7 +9080,6 @@ }, "node_modules/cli-spinners": { "version": "2.9.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7018,7 +9128,6 @@ }, "node_modules/cliui": { "version": "8.0.1", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -7031,7 +9140,6 @@ }, "node_modules/clone": { "version": "1.0.4", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -7050,11 +9158,20 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -7062,14 +9179,33 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } }, "node_modules/colorette": { "version": "2.0.20", "dev": true, "license": "MIT" }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "dev": true, @@ -7215,7 +9351,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -7225,7 +9360,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7247,8 +9381,7 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/copy-anything": { "version": "2.0.6", @@ -7311,8 +9444,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -7380,7 +9512,6 @@ "version": "0.0.22", "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", - "dev": true, "dependencies": { "chalk": "^4.1.0", "css-select": "^5.1.0", @@ -7395,7 +9526,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -7410,7 +9540,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7426,7 +9555,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -7437,14 +9565,12 @@ "node_modules/critters/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/critters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -7453,7 +9579,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7555,7 +9680,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -7571,7 +9695,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, "engines": { "node": ">= 6" }, @@ -7661,14 +9784,6 @@ "cypress": "^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "18.19.2", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/cypress/node_modules/ansi-styles": { "version": "4.3.0", "dev": true, @@ -8254,7 +10369,6 @@ }, "node_modules/debug": { "version": "4.3.4", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -8286,7 +10400,6 @@ }, "node_modules/defaults": { "version": "1.0.4", - "dev": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" @@ -8299,7 +10412,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -8314,7 +10426,6 @@ }, "node_modules/define-lazy-prop": { "version": "2.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8337,7 +10448,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -8345,7 +10455,6 @@ }, "node_modules/destroy": { "version": "1.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8", @@ -8358,6 +10467,18 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/dev-ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "dev": true, + "bin": { + "dev-ip": "lib/dev-ip.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/di": { "version": "0.0.1", "dev": true, @@ -8410,7 +10531,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -8424,7 +10544,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, "funding": [ { "type": "github", @@ -8436,42 +10555,140 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/easy-extender": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", + "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/eazy-logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.0.1.tgz", + "integrity": "sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==", + "dev": true, + "dependencies": { + "chalk": "4.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eazy-logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eazy-logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eazy-logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eazy-logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eazy-logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, + "license": "MIT", "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "node": ">=8" } }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "node_modules/eazy-logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "dev": true, @@ -8483,7 +10700,6 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "dev": true, "license": "MIT" }, "node_modules/eigen": { @@ -8502,7 +10718,6 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -8513,9 +10728,14 @@ "node": ">= 4" } }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "1.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -8525,7 +10745,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -8535,7 +10754,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -8572,6 +10790,42 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.1", "dev": true, @@ -8632,7 +10886,6 @@ }, "node_modules/entities": { "version": "4.5.0", - "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -8681,7 +10934,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -8693,7 +10945,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -8758,14 +11009,12 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/escape-html": { "version": "1.0.3", - "dev": true, "license": "MIT" }, "node_modules/escape-latex": { @@ -8774,7 +11023,6 @@ }, "node_modules/escape-string-regexp": { "version": "1.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -8844,7 +11092,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -8918,7 +11165,6 @@ "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", - "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -8960,7 +11206,6 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -8969,7 +11214,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "dependencies": { "ms": "2.0.0" } @@ -8978,7 +11222,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -8987,7 +11230,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -9004,14 +11246,12 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -9025,7 +11265,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -9078,7 +11317,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -9113,7 +11351,6 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -9129,9 +11366,14 @@ "pend": "~1.2.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/figures": { "version": "3.2.0", - "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" @@ -9144,8 +11386,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "dev": true, + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -9222,6 +11466,42 @@ "node": ">=8" } }, + "node_modules/firebase": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.0.1.tgz", + "integrity": "sha512-qsFb8dMcQINEDhJteG7RP+GqwgSRvfyiexQqHd5JToDdm87i9I2rGC4XQsGawKGxzKwZ/ISdgwNWxXAFYdCC6A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.9", + "@firebase/analytics-compat": "0.2.15", + "@firebase/app": "0.10.15", + "@firebase/app-check": "0.8.9", + "@firebase/app-check-compat": "0.3.16", + "@firebase/app-compat": "0.2.45", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.8.0", + "@firebase/auth-compat": "0.5.15", + "@firebase/data-connect": "0.1.1", + "@firebase/database": "1.0.9", + "@firebase/database-compat": "2.0.0", + "@firebase/firestore": "4.7.4", + "@firebase/firestore-compat": "0.3.39", + "@firebase/functions": "0.11.9", + "@firebase/functions-compat": "0.3.15", + "@firebase/installations": "0.6.10", + "@firebase/installations-compat": "0.2.10", + "@firebase/messaging": "0.12.13", + "@firebase/messaging-compat": "0.2.13", + "@firebase/performance": "0.6.10", + "@firebase/performance-compat": "0.2.10", + "@firebase/remote-config": "0.4.10", + "@firebase/remote-config-compat": "0.2.10", + "@firebase/storage": "0.13.3", + "@firebase/storage-compat": "0.3.13", + "@firebase/util": "1.10.1", + "@firebase/vertexai": "1.0.0" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -9236,6 +11516,12 @@ "dev": true, "license": "ISC" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.3", "dev": true, @@ -9295,7 +11581,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -9317,14 +11602,12 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/fs-extra": { "version": "8.1.0", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -9372,12 +11655,19 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuzzy": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz", + "integrity": "sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -9388,7 +11678,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -9398,7 +11687,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -9470,7 +11758,7 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -9536,7 +11824,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -9546,7 +11833,6 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "dev": true, "license": "ISC" }, "node_modules/gzip-size": { @@ -9582,7 +11868,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -9592,7 +11877,6 @@ }, "node_modules/has-proto": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9603,7 +11887,6 @@ }, "node_modules/has-symbols": { "version": "1.0.3", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9623,7 +11906,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -9716,7 +11998,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -9745,7 +12026,6 @@ }, "node_modules/http-errors": { "version": "2.0.0", - "dev": true, "license": "MIT", "dependencies": { "depd": "2.0.0", @@ -9760,7 +12040,6 @@ }, "node_modules/http-errors/node_modules/statuses": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -9769,8 +12048,7 @@ "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" }, "node_modules/http-proxy": { "version": "1.18.1", @@ -9859,7 +12137,6 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -9880,6 +12157,12 @@ "postcss": "^8.1.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, "node_modules/ieee754": { "version": "1.2.1", "funding": [ @@ -9954,6 +12237,12 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -10013,7 +12302,6 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -10159,7 +12447,7 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -10196,7 +12484,6 @@ }, "node_modules/is-docker": { "version": "2.2.1", - "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -10210,7 +12497,7 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10218,7 +12505,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10226,7 +12512,7 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -10252,7 +12538,6 @@ }, "node_modules/is-interactive": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10266,12 +12551,24 @@ }, "node_modules/is-number": { "version": "7.0.0", - "dev": true, + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" } }, + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "dev": true, @@ -10305,7 +12602,6 @@ }, "node_modules/is-stream": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10321,7 +12617,6 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10337,7 +12632,6 @@ }, "node_modules/is-wsl": { "version": "2.2.0", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -10349,8 +12643,7 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isbinaryfile": { "version": "4.0.10", @@ -10365,7 +12658,6 @@ }, "node_modules/isexe": { "version": "2.0.0", - "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -10596,7 +12888,6 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { @@ -10615,9 +12906,14 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, "node_modules/jsonfile": { "version": "4.0.0", - "dev": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" @@ -10646,6 +12942,48 @@ "verror": "1.10.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/karma": { "version": "6.4.2", "dev": true, @@ -10809,6 +13147,12 @@ "node": ">= 8" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/launch-editor": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", @@ -10935,6 +13279,21 @@ } } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10996,7 +13355,12 @@ }, "node_modules/lodash": { "version": "4.17.21", - "dev": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -11005,6 +13369,13 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "dev": true, @@ -11012,7 +13383,6 @@ }, "node_modules/log-symbols": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -11027,7 +13397,6 @@ }, "node_modules/log-symbols/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -11041,7 +13410,6 @@ }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -11056,7 +13424,6 @@ }, "node_modules/log-symbols/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -11067,12 +13434,10 @@ }, "node_modules/log-symbols/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/log-symbols/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11080,7 +13445,6 @@ }, "node_modules/log-symbols/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -11180,6 +13544,38 @@ "node": ">=8.0" } }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "dev": true, @@ -11192,7 +13588,6 @@ "version": "0.30.8", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -11286,7 +13681,6 @@ }, "node_modules/media-typer": { "version": "0.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11308,7 +13702,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -11330,17 +13723,18 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { - "version": "4.0.5", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -11360,7 +13754,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11368,7 +13761,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -11379,7 +13771,6 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11626,6 +14017,13 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/mitt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", + "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", + "dev": true, + "license": "MIT" + }, "node_modules/mkdirp": { "version": "0.5.6", "dev": true, @@ -11647,7 +14045,6 @@ }, "node_modules/ms": { "version": "2.1.2", - "dev": true, "license": "MIT" }, "node_modules/multicast-dns": { @@ -11674,7 +14071,6 @@ }, "node_modules/nanoid": { "version": "3.3.7", - "dev": true, "funding": [ { "type": "github", @@ -11729,7 +14125,6 @@ }, "node_modules/negotiator": { "version": "0.6.3", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -11769,6 +14164,26 @@ "dev": true, "optional": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -11928,7 +14343,7 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12061,7 +14476,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -12079,7 +14493,6 @@ }, "node_modules/object-inspect": { "version": "1.13.1", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12093,7 +14506,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -12119,9 +14531,17 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "5.1.2", - "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -12135,7 +14555,6 @@ }, "node_modules/open": { "version": "8.4.2", - "dev": true, "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", @@ -12157,9 +14576,31 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/opn/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ora": { "version": "5.4.1", - "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -12181,7 +14622,6 @@ }, "node_modules/ora/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -12195,7 +14635,6 @@ }, "node_modules/ora/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -12210,7 +14649,6 @@ }, "node_modules/ora/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -12221,12 +14659,10 @@ }, "node_modules/ora/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/ora/node_modules/has-flag": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12234,7 +14670,6 @@ }, "node_modules/ora/node_modules/supports-color": { "version": "7.2.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -12247,7 +14682,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12364,6 +14798,12 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12450,7 +14890,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -12511,8 +14950,7 @@ "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", - "dev": true + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -12535,12 +14973,11 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -12646,9 +15083,33 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/portscanner/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/postcss": { "version": "8.4.31", - "dev": true, "funding": [ { "type": "opencollective", @@ -12707,8 +15168,7 @@ "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==" }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", @@ -12819,8 +15279,16 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } }, "node_modules/promise-inflight": { "version": "1.0.1", @@ -12841,11 +15309,34 @@ "node": ">=10" } }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -12858,7 +15349,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -12890,7 +15380,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -12908,7 +15397,6 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, "dependencies": { "side-channel": "^1.0.6" }, @@ -12953,7 +15441,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -12961,7 +15448,6 @@ }, "node_modules/raw-body": { "version": "2.5.2", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -13066,7 +15552,6 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -13079,7 +15564,7 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -13188,7 +15673,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13196,7 +15680,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13268,9 +15751,38 @@ "node": ">=0.10.0" } }, + "node_modules/resp-modifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", + "integrity": "sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/resp-modifier/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/resp-modifier/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/restore-cursor": { "version": "3.1.0", - "dev": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -13391,6 +15903,13 @@ "version": "1.3.3", "license": "BSD-3-Clause" }, + "node_modules/rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/rxjs": { "version": "7.8.1", "license": "Apache-2.0", @@ -13400,7 +15919,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "dev": true, "funding": [ { "type": "github", @@ -13417,6 +15935,15 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -13561,7 +16088,6 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13585,7 +16111,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "dependencies": { "ms": "2.0.0" } @@ -13593,14 +16118,12 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, "bin": { "mime": "cli.js" }, @@ -13611,14 +16134,12 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/send/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -13704,7 +16225,6 @@ "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -13719,16 +16239,21 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, "engines": { "node": ">= 0.8" } }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true, + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -13741,9 +16266,14 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", - "dev": true, "license": "ISC" }, "node_modules/shallow-clone": { @@ -13789,7 +16319,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -13805,7 +16334,6 @@ }, "node_modules/signal-exit": { "version": "3.0.7", - "dev": true, "license": "ISC" }, "node_modules/sigstore": { @@ -13825,6 +16353,21 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sirv": { "version": "2.0.3", "dev": true, @@ -13947,6 +16490,22 @@ } } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "dev": true, @@ -14000,7 +16559,6 @@ }, "node_modules/source-map": { "version": "0.7.4", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 8" @@ -14010,7 +16568,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -14167,6 +16724,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "1.5.0", "dev": true, @@ -14175,6 +16741,23 @@ "node": ">= 0.6" } }, + "node_modules/stream-throttle": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", + "integrity": "sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^2.2.0", + "limiter": "^1.0.5" + }, + "bin": { + "throttleproxy": "bin/throttleproxy.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/streamroller": { "version": "3.1.5", "dev": true, @@ -14190,7 +16773,6 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -14198,7 +16780,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -14226,7 +16807,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -14501,6 +17081,12 @@ "node": ">=8" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/throttleit": { "version": "1.0.1", "dev": true, @@ -14511,7 +17097,6 @@ }, "node_modules/through": { "version": "2.3.8", - "dev": true, "license": "MIT" }, "node_modules/thunky": { @@ -14528,7 +17113,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -14538,7 +17122,9 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "dev": true, + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -14549,7 +17135,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.6" @@ -14585,6 +17170,12 @@ "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tree-kill": { "version": "1.2.2", "dev": true, @@ -14593,6 +17184,15 @@ "tree-kill": "cli.js" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -14687,7 +17287,6 @@ }, "node_modules/type-fest": { "version": "0.21.3", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -14698,7 +17297,6 @@ }, "node_modules/type-is": { "version": "1.6.18", - "dev": true, "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -14766,7 +17364,6 @@ }, "node_modules/undici-types": { "version": "5.26.5", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -14835,7 +17432,6 @@ }, "node_modules/universalify": { "version": "0.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -14843,7 +17439,6 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -14889,7 +17484,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -14906,12 +17500,10 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "dev": true, "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -14952,7 +17544,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -15496,12 +18087,17 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -15836,7 +18432,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -15850,11 +18445,20 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, "engines": { "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "1.3.1", "dev": true, @@ -15871,9 +18475,53 @@ "dev": true, "license": "MIT" }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -15940,7 +18588,6 @@ }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -15954,7 +18601,6 @@ }, "node_modules/wrap-ansi/node_modules/color-convert": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -15965,7 +18611,6 @@ }, "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", - "dev": true, "license": "MIT" }, "node_modules/wrappy": { @@ -15993,9 +18638,26 @@ } } }, + "node_modules/xhr2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -16008,7 +18670,6 @@ }, "node_modules/yargs": { "version": "17.7.2", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -16025,7 +18686,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/frontend/package.json b/frontend/package.json index 925c2379d1..f77c5d1ec6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,16 +1,25 @@ { "name": "frontend", - "version": "0.8.20", + "version": "1.0.59", "scripts": { "install:deps": "npm install", "start": "npm install && ng serve --configuration local --open", "start:dev": "ng serve --configuration development", + "start:dev:mobile": "ng serve --configuration development --host 0.0.0.0", "build": "ng build", - "build:prod": "ng build --configuration production", + "build:prod": "ng build --configuration production && npm run sentry:sourcemaps", "tests": "npx cypress open --config baseUrl=http://localhost:4200", "tests:headless": "npx cypress run --headless --config baseUrl=http://localhost:4200", "docker": "npm run build:prod && docker buildx build --platform linux/amd64 -t openmina/frontend:latest . && docker push openmina/frontend:latest", - "start:bundle": "npx http-server dist/frontend -p 4200" + "start:bundle": "serve dist/frontend/browser -s -l 4200", + "prebuild": "node scripts/update-frontend-version.js", + "dev:ssr": "ng run frontend:serve-ssr", + "serve:ssr": "node dist/frontend/server/main.js", + "build:ssr": "ng build && ng run frontend:server", + "prerender": "ng run frontend:prerender", + "sentry:sourcemaps": "sentry-cli sourcemaps inject --org openmina-uv --project openmina ./dist/frontend/browser && sentry-cli sourcemaps upload --org openmina-uv --project openmina ./dist/frontend/browser", + "copy-env": "cp dist/frontend/browser/assets/environments/webnode.js dist/frontend/browser/assets/environments/env.js", + "deploy": "npm run prebuild && npm run build:prod && npm run copy-env && firebase deploy" }, "private": true, "dependencies": { @@ -19,24 +28,32 @@ "@angular/common": "^17.3.12", "@angular/compiler": "^17.3.12", "@angular/core": "^17.3.12", + "@angular/fire": "^17.1.0", "@angular/forms": "^17.3.12", "@angular/material": "^17.3.10", "@angular/platform-browser": "^17.3.12", "@angular/platform-browser-dynamic": "^17.3.12", + "@angular/platform-server": "^17.3.12", "@angular/router": "^17.3.12", + "@angular/ssr": "^17.3.10", "@ledgerhq/hw-transport-webhid": "^6.29.4", "@ngneat/until-destroy": "^10.0.0", "@ngrx/effects": "^17.2.0", "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", - "@openmina/shared": "^0.102.0", + "@nguniversal/express-engine": "^7.0.2", + "@openmina/shared": "^0.117.0", "@sentry/angular": "^8.35.0", + "@sentry/cli": "^2.38.2", "@sentry/tracing": "^7.114.0", "base-x": "^5.0.0", "bs58check": "^4.0.0", "buffer": "^6.0.3", "d3": "^7.8.4", "eigen": "^0.2.2", + "express": "^4.18.2", + "firebase": "^11.0.1", + "jszip": "^3.10.1", "mathjs": "^12.3.0", "mina-signer": "^3.0.7", "ngx-json-viewer": "^3.2.1", @@ -52,9 +69,11 @@ "@angular/compiler-cli": "^17.3.12", "@ngrx/store-devtools": "^17.2.0", "@types/d3": "^7.4.0", + "@types/express": "^4.17.17", "@types/jasmine": "~4.3.0", - "@types/node": "^20.4.8", + "@types/node": "^18.19.64", "@types/w3c-web-hid": "^1.0.6", + "browser-sync": "^3.0.0", "cypress": "^13.3.2", "cypress-real-events": "^1.10.0", "jasmine-core": "~4.6.0", @@ -68,4 +87,4 @@ "webpack": "^5.88.2", "webpack-bundle-analyzer": "^4.9.0" } -} +} \ No newline at end of file diff --git a/frontend/scripts/download-webnode.sh b/frontend/scripts/download-webnode.sh new file mode 100644 index 0000000000..619cc97347 --- /dev/null +++ b/frontend/scripts/download-webnode.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Set the base URL for OpenMina +OPENMINA_BASE_URL="https://github.com/openmina" + +# Function to download circuit files +download_circuit_files() { + CIRCUITS_BASE_URL="$OPENMINA_BASE_URL/circuit-blobs/releases/download" + CIRCUITS_VERSION="3.0.1devnet" + + DEVNET_CIRCUIT_FILES=( + "block_verifier_index.postcard" + "transaction_verifier_index.postcard" + "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_gates.json" + "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_internal_vars.bin" + "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_rows_rev.bin" + "step-step-proving-key-transaction-snark-merge-1-ba1d52dfdc2dd4d2e61f6c66ff2a5b2f_gates.json" + "step-step-proving-key-transaction-snark-merge-1-ba1d52dfdc2dd4d2e61f6c66ff2a5b2f_internal_vars.bin" + "step-step-proving-key-transaction-snark-merge-1-ba1d52dfdc2dd4d2e61f6c66ff2a5b2f_rows_rev.bin" + "step-step-proving-key-transaction-snark-opt_signed-3-9eefed16953d2bfa78a257adece02d47_gates.json" + "step-step-proving-key-transaction-snark-opt_signed-3-9eefed16953d2bfa78a257adece02d47_internal_vars.bin" + "step-step-proving-key-transaction-snark-opt_signed-3-9eefed16953d2bfa78a257adece02d47_rows_rev.bin" + "step-step-proving-key-transaction-snark-opt_signed-opt_signed-2-48925e6a97197028e1a7c1ecec09021d_gates.json" + "step-step-proving-key-transaction-snark-opt_signed-opt_signed-2-48925e6a97197028e1a7c1ecec09021d_internal_vars.bin" + "step-step-proving-key-transaction-snark-opt_signed-opt_signed-2-48925e6a97197028e1a7c1ecec09021d_rows_rev.bin" + "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341_gates.json" + "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341_internal_vars.bin" + "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341_rows_rev.bin" + "step-step-proving-key-transaction-snark-transaction-0-c33ec5211c07928c87e850a63c6a2079_gates.json" + "step-step-proving-key-transaction-snark-transaction-0-c33ec5211c07928c87e850a63c6a2079_internal_vars.bin" + "step-step-proving-key-transaction-snark-transaction-0-c33ec5211c07928c87e850a63c6a2079_rows_rev.bin" + "wrap-wrap-proving-key-blockchain-snark-bbecaf158ca543ec8ac9e7144400e669_gates.json" + "wrap-wrap-proving-key-blockchain-snark-bbecaf158ca543ec8ac9e7144400e669_internal_vars.bin" + "wrap-wrap-proving-key-blockchain-snark-bbecaf158ca543ec8ac9e7144400e669_rows_rev.bin" + "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_gates.json" + "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_internal_vars.bin" + "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_rows_rev.bin" + ) + DOWNLOAD_DIR="../src/assets/webnode/circuit-blobs/$CIRCUITS_VERSION" + + mkdir -p "$DOWNLOAD_DIR" + + for FILE in "${DEVNET_CIRCUIT_FILES[@]}"; do + if [[ -f "$DOWNLOAD_DIR/$FILE" ]]; then + echo "$FILE already exists in $DOWNLOAD_DIR, skipping download." + else + echo "Downloading $FILE to $DOWNLOAD_DIR..." + curl -s -L --retry 3 --retry-delay 5 -o "$DOWNLOAD_DIR/$FILE" "$CIRCUITS_BASE_URL/$CIRCUITS_VERSION/$FILE" + if [[ $? -ne 0 ]]; then + echo "Failed to download $FILE after 3 attempts, exiting." + exit 1 + else + echo "$FILE downloaded successfully to $DOWNLOAD_DIR" + fi + fi + done +} + +# Call the function to download circuit files +download_circuit_files diff --git a/frontend/scripts/update-frontend-version.js b/frontend/scripts/update-frontend-version.js new file mode 100644 index 0000000000..a186a2cc50 --- /dev/null +++ b/frontend/scripts/update-frontend-version.js @@ -0,0 +1,56 @@ +import {readFileSync, writeFileSync} from 'fs'; +import {platform, release} from 'os'; + +console.log('⏳ Updating Deployment Info...'); + +const packageJsonPath = './package.json'; +const packageJson = readFileSync(packageJsonPath, 'utf8'); +const packageJsonObj = JSON.parse(packageJson); +const version = packageJsonObj.version.split('.'); +const newVersion = `${version[0]}.${version[1]}.${parseInt(version[2]) + 1}`; +packageJsonObj.version = newVersion; +writeFileSync(packageJsonPath, JSON.stringify(packageJsonObj, null, 2)); + +const publicIp = await getPublicIpAddress(); +const deploymentInfo = { + dateUTC: new Date().toISOString(), + deviceIp: publicIp, + deviceOS: formatOS(platform()), + deviceOSVersion: release(), + version: newVersion, +}; + +const newDeploymentScript = ` +const deployment = { + dateUTC: '${deploymentInfo.dateUTC}', + deviceIp: '${deploymentInfo.deviceIp}', + deviceOS: '${deploymentInfo.deviceOS}', + deviceOSVersion: '${deploymentInfo.deviceOSVersion}', + version: '${deploymentInfo.version}', +}; +window.deployment = deployment; +`; +const deploymentScriptPattern = /const deployment = \{[\s\S]*?\};[\s\S]*?window\.deployment = deployment;/; + +// Read and update index.html +const indexPath = './src/index.html'; +let indexHtml = readFileSync(indexPath, 'utf8'); +indexHtml = indexHtml.replace(deploymentScriptPattern, newDeploymentScript.trim()); + +writeFileSync(indexPath, indexHtml); + +console.log('✅ Updated Deployment Info!'); + +async function getPublicIpAddress() { + const response = await fetch('https://api.ipify.org/'); + return await response.text(); +} + +function formatOS(platform) { + const osMap = { + 'darwin': 'macOS', + 'win32': 'Windows', + 'linux': 'Linux', + }; + return osMap[platform] || platform; +} diff --git a/frontend/server.ts b/frontend/server.ts new file mode 100644 index 0000000000..a127b5bb47 --- /dev/null +++ b/frontend/server.ts @@ -0,0 +1,69 @@ +import 'zone.js/node'; + +import { APP_BASE_HREF } from '@angular/common'; +import { CommonEngine } from '@angular/ssr'; +import * as express from 'express'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import AppServerModule from './src/main.server'; + +// The Express app is exported so that it can be used by serverless Functions. +export function app(): express.Express { + const server = express(); + const distFolder = join(process.cwd(), 'dist/frontend/browser'); + const indexHtml = existsSync(join(distFolder, 'index.original.html')) + ? join(distFolder, 'index.original.html') + : join(distFolder, 'index.html'); + + const commonEngine = new CommonEngine(); + + server.set('view engine', 'html'); + server.set('views', distFolder); + + // Example Express Rest API endpoints + // server.get('/api/**', (req, res) => { }); + // Serve static files from /browser + server.get('*.*', express.static(distFolder, { + maxAge: '1y' + })); + + // All regular routes use the Angular engine + server.get('*', (req, res, next) => { + const { protocol, originalUrl, baseUrl, headers } = req; + + commonEngine + .render({ + bootstrap: AppServerModule, + documentFilePath: indexHtml, + url: `${protocol}://${headers.host}${originalUrl}`, + publicPath: distFolder, + providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], + }) + .then((html) => res.send(html)) + .catch((err) => next(err)); + }); + + return server; +} + +function run(): void { + const port = process.env['PORT'] || 4000; + + // Start up the Node server + const server = app(); + server.listen(port, () => { + console.log(`Node Express server listening on http://localhost:${port}`); + }); +} + +// Webpack will replace 'require' with '__webpack_require__' +// '__non_webpack_require__' is a proxy to Node 'require' +// The below code is to ensure that the server is run only when not requiring the bundle. +declare const __non_webpack_require__: NodeRequire; +const mainModule = __non_webpack_require__.main; +const moduleFilename = mainModule && mainModule.filename || ''; +if (moduleFilename === __filename || moduleFilename.includes('iisnode')) { + run(); +} + +export default AppServerModule; diff --git a/frontend/src/app/app.actions.ts b/frontend/src/app/app.actions.ts index 0045b01454..59afd73900 100644 --- a/frontend/src/app/app.actions.ts +++ b/frontend/src/app/app.actions.ts @@ -2,6 +2,7 @@ import { MinaNode } from '@shared/types/core/environment/mina-env.type'; import { createType } from '@shared/constants/store-functions'; import { createAction, props } from '@ngrx/store'; import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; export const APP_KEY = 'app'; export const APP_PREFIX = 'App'; @@ -14,6 +15,8 @@ const initSuccess = createAction(type('Init Success'), props<{ activeNode: MinaN const changeActiveNode = createAction(type('Change Active Node'), props<{ node: MinaNode }>()); const getNodeDetails = createAction(type('Get Node Details')); const getNodeDetailsSuccess = createAction(type('Get Node Details Success'), props<{ details: AppNodeDetails }>()); +const getNodeEnvBuild = createAction(type('Get Node Env Build')); +const getNodeEnvBuildSuccess = createAction(type('Get Node Env Build Success'), props<{ envBuild: AppEnvBuild }>()); const deleteNode = createAction(type('Delete Node'), props<{ node: MinaNode }>()); const addNode = createAction(type('Add Node'), props<{ node: MinaNode }>()); @@ -28,6 +31,8 @@ export const AppActions = { changeActiveNode, getNodeDetails, getNodeDetailsSuccess, + getNodeEnvBuild, + getNodeEnvBuildSuccess, deleteNode, addNode, changeMenuCollapsing, diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index fdd63676c1..bf25b5a9c8 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,30 +1,38 @@ @if (showLandingPage$ | async) { -} @else { +} @else if (showLoadingWebNodePage$ | async) { + +} @else if (loaded) { - - -
-
+ @if (isDesktop) { + + +
+
+ } - -
+ } +
- + @if (!isDesktop) { + + + } } diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index a01a79524a..bd5100f44f 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -2,7 +2,7 @@ mat-sidenav { width: 160px; - border-right: 1px solid $base-divider; + border-right: none; background-color: $base-background; transition: 200ms ease-out !important; visibility: visible !important; @@ -44,39 +44,45 @@ mat-sidenav { } mat-sidenav-content { - transition: 200ms ease-out !important; + @media (min-width: 768px) { + transition: 200ms ease-out !important; + } overflow: hidden; } mat-sidenav-container, mat-sidenav-content { color: inherit; - background-color: $base-surface; - - @media (max-width: 699px) { - background-color: $base-background; - } + background-color: $base-background; } -.mina-content { +#mina-content { $toolbar: 40px; height: calc(100% - #{$toolbar}); + border-top-left-radius: 6px; + overflow: hidden; + background-color: $base-surface; &.no-toolbar { height: 100%; } &.mobile { - $toolbar: 56px; + $toolbar: 96px; $subMenus: 56px; - height: calc(100% - #{$toolbar} - #{$subMenus}); + $tabs: 56px; + height: calc(100% - #{$toolbar} - #{$subMenus} - #{$tabs}); + margin-left: 4px; + margin-right: 4px; + margin-bottom: 4px; + border-top-right-radius: 6px; &.no-toolbar { - height: calc(100% - #{$subMenus}); + height: calc(100% - #{$subMenus} - #{$tabs}); } &.no-submenus { - height: calc(100% - #{$toolbar}); + height: calc(100% - #{$toolbar} - #{$tabs}); &.no-toolbar { height: 100%; diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 2d36c7a388..b8cb9147be 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,13 +1,15 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { any, getMergedRoute, MAX_WIDTH_700, MergedRoute } from '@openmina/shared'; +import { any, getMergedRoute, getWindow, isBrowser, isDesktop, MAX_WIDTH_700, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { AppSelectors } from '@app/app.state'; import { AppActions } from '@app/app.actions'; import { filter, map, Observable, Subscription, take, timer } from 'rxjs'; -import { CONFIG, getFirstFeature } from '@shared/constants/config'; +import { CONFIG } from '@shared/constants/config'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { Router } from '@angular/router'; +import { Routes } from '@shared/enums/routes.enum'; +import { WebNodeService } from '@core/services/web-node.service'; @Component({ selector: 'app-root', @@ -18,38 +20,65 @@ import { Router } from '@angular/router'; }) export class AppComponent extends StoreDispatcher implements OnInit { - protected readonly menu$: Observable = this.select$(AppSelectors.menu); - protected readonly showLandingPage$: Observable = this.select$(getMergedRoute).pipe(filter(Boolean), map((route: MergedRoute) => route.url === '/')); + readonly menu$: Observable = this.select$(AppSelectors.menu); + readonly showLandingPage$: Observable = this.select$(getMergedRoute).pipe(filter(Boolean), map((route: MergedRoute) => route.url === '/' || route.url.startsWith('/?'))); + readonly showLoadingWebNodePage$: Observable = this.select$(getMergedRoute).pipe(filter(Boolean), map((route: MergedRoute) => route.url.startsWith(`/${Routes.LOADING_WEB_NODE}`))); subMenusLength: number = 0; hideToolbar: boolean = CONFIG.hideToolbar; + loaded: boolean; + isDesktop: boolean = isDesktop(); private nodeUpdateSubscription: Subscription | null = null; constructor(private breakpointObserver: BreakpointObserver, - private router: Router) { + private router: Router, + private webNodeService: WebNodeService) { super(); - if (any(window).Cypress) { - any(window).config = CONFIG; - any(window).store = this.store; - } + safelyExecuteInBrowser(() => { + if (any(window).Cypress) { + any(window).config = CONFIG; + any(window).store = this.store; + } + }); } ngOnInit(): void { + if (isBrowser()) { + const args = new URLSearchParams(window.location.search).get('a'); + if (!!args) { + localStorage.setItem('webnodeArgs', args); + } + } + this.select( getMergedRoute, () => this.initAppFunctionalities(), filter(Boolean), take(1), - filter((route: MergedRoute) => route.url !== '/'), + filter((route: MergedRoute) => route.url !== '/' && !route.url.startsWith('/?')), + ); + this.select( + getMergedRoute, + () => { + this.loaded = true; + this.detect(); + }, + filter(Boolean), + take(1), ); } goToWebNode(): void { - this.router.navigate([getFirstFeature()]); + this.router.navigate([Routes.LOADING_WEB_NODE], { queryParamsHandling: 'merge' }); this.initAppFunctionalities(); } private initAppFunctionalities(): void { + if (this.webNodeService.hasWebNodeConfig() && !this.webNodeService.isWebNodeLoaded()) { + if (!getWindow()?.location.href.includes(`/${Routes.LOADING_WEB_NODE}`)) { + this.router.navigate([Routes.LOADING_WEB_NODE], { queryParamsHandling: 'preserve' }); + } + } this.dispatch2(AppActions.init()); if (!this.hideToolbar && !CONFIG.hideNodeStats) { this.scheduleNodeUpdates(); diff --git a/frontend/src/app/app.effects.ts b/frontend/src/app/app.effects.ts index 4c4539d715..d72e583f36 100644 --- a/frontend/src/app/app.effects.ts +++ b/frontend/src/app/app.effects.ts @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core'; import { MinaState, selectMinaState } from '@app/app.setup'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { createNonDispatchableEffect, Effect, NonDispatchableEffect, removeParamsFromURL } from '@openmina/shared'; -import { filter, from, map, switchMap, tap } from 'rxjs'; +import { createNonDispatchableEffect, Effect, removeParamsFromURL } from '@openmina/shared'; +import { filter, map, mergeMap, of, switchMap, tap } from 'rxjs'; import { AppActions } from '@app/app.actions'; import { Router } from '@angular/router'; import { FeatureType, MinaNode } from '@shared/types/core/environment/mina-env.type'; @@ -22,9 +22,10 @@ import { AppNodeStatus } from '@shared/types/app/app-node-details.type'; export class AppEffects extends BaseEffect { readonly init$: Effect; - readonly initSuccess$: NonDispatchableEffect; + readonly initSuccess$: Effect; readonly onNodeChange$: Effect; readonly getNodeDetails$: Effect; + readonly getNodeEnvBuild$: Effect; private requestInProgress: boolean = false; @@ -46,7 +47,7 @@ export class AppEffects extends BaseEffect { map((payload: { activeNode: MinaNode, nodes: MinaNode[] }) => AppActions.initSuccess(payload)), )); - this.initSuccess$ = createNonDispatchableEffect(() => this.actions$.pipe( + this.initSuccess$ = createEffect(() => this.actions$.pipe( ofType(AppActions.initSuccess), this.latestActionState(), switchMap(({ state }) => { @@ -55,8 +56,9 @@ export class AppEffects extends BaseEffect { switchMap(() => this.webNodeService.startWasm$()), ); } - return from([]); + return of({}); }), + map(() => AppActions.getNodeEnvBuild()), )); this.onNodeChange$ = createNonDispatchableEffect(() => this.actions$.pipe( @@ -79,9 +81,16 @@ export class AppEffects extends BaseEffect { switchMap(() => this.webNodeService.startWasm$()), ); } - return from([]); + return of({}); }), - map(() => AppActions.getNodeDetails()), + switchMap(() => [AppActions.getNodeDetails(), AppActions.getNodeEnvBuild()]), + )); + + this.getNodeEnvBuild$ = createEffect(() => this.actions$.pipe( + ofType(AppActions.getNodeEnvBuild), + mergeMap(() => this.appService.getEnvBuild()), + map(envBuild => AppActions.getNodeEnvBuildSuccess({ envBuild })), + catchErrorAndRepeat2(MinaErrorType.RUST, AppActions.getNodeEnvBuildSuccess({ envBuild: undefined })), )); this.getNodeDetails$ = createEffect(() => this.actions$.pipe( @@ -89,20 +98,23 @@ export class AppEffects extends BaseEffect { filter(() => !this.requestInProgress), tap(() => this.requestInProgress = true), switchMap(() => this.appService.getActiveNodeDetails()), - tap(() => this.requestInProgress = false), map(details => AppActions.getNodeDetailsSuccess({ details })), catchErrorAndRepeat2(MinaErrorType.GENERIC, AppActions.getNodeDetailsSuccess({ details: { status: AppNodeStatus.OFFLINE, blockHeight: null, blockTime: null, - peers: 0, - download: 0, - upload: 0, + peersConnected: 0, + peersDisconnected: 0, + peersConnecting: 0, transactions: 0, snarks: 0, + producingBlockAt: null, + producingBlockGlobalSlot: null, + producingBlockStatus: null, }, })), + tap(() => this.requestInProgress = false), )); } } diff --git a/frontend/src/app/app.module.server.ts b/frontend/src/app/app.module.server.ts new file mode 100644 index 0000000000..795380cd22 --- /dev/null +++ b/frontend/src/app/app.module.server.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { ServerModule } from '@angular/platform-server'; + +import { AppModule } from './app.module'; +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + AppModule, + ServerModule, + ], + bootstrap: [AppComponent], +}) +export class AppServerModule {} diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f2dbdf9b12..eb0917ec77 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,5 +1,5 @@ -import { APP_INITIALIZER, ErrorHandler, Injectable, LOCALE_ID, NgModule, Provider } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; +import { APP_INITIALIZER, ErrorHandler, Injectable, LOCALE_ID, NgModule } from '@angular/core'; +import { BrowserModule, provideClientHydration } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { AppRouting } from './app.routing'; @@ -10,6 +10,7 @@ import { HorizontalMenuComponent, NgrxRouterStoreModule, OpenminaEagerSharedModule, + safelyExecuteInBrowser, THEME_PROVIDER, } from '@openmina/shared'; import { CommonModule, registerLocaleData } from '@angular/common'; @@ -29,24 +30,98 @@ import { metaReducers, reducers } from '@app/app.setup'; import { EffectsModule } from '@ngrx/effects'; import { AppEffects } from '@app/app.effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, provideHttpClient, withFetch } from '@angular/common/http'; import { NewNodeComponent } from './layout/new-node/new-node.component'; import { ReactiveFormsModule } from '@angular/forms'; import { WebNodeLandingPageComponent } from '@app/layout/web-node-landing-page/web-node-landing-page.component'; import * as Sentry from '@sentry/angular'; import { Router } from '@angular/router'; +import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; +import { getAnalytics, provideAnalytics, ScreenTrackingService } from '@angular/fire/analytics'; +import { getPerformance, providePerformance } from '@angular/fire/performance'; +import { BlockProductionPillComponent } from '@app/layout/block-production-pill/block-production-pill.component'; +import { MenuTabsComponent } from '@app/layout/menu-tabs/menu-tabs.component'; registerLocaleData(localeFr, 'fr'); registerLocaleData(localeEn, 'en'); @Injectable() export class AppGlobalErrorhandler implements ErrorHandler { - constructor(private errorHandlerService: GlobalErrorHandlerService) {} + constructor(private errorHandlerService: GlobalErrorHandlerService) { + safelyExecuteInBrowser(() => { + this.setupErrorHandlers(); + }); + + if (WebAssembly) { + this.interceptWebAssembly(); + } + } + + private setupErrorHandlers(): void { + const self = this; + + // Global error handler + window.onerror = function (msg, url, line, column, error) { + self.handleError(error || msg); + return false; + }; + + // Unhandled promise rejections + window.onunhandledrejection = function (event) { + event.preventDefault(); + self.handleError(event.reason); + }; + + // Regular error listener + window.addEventListener('error', (event: ErrorEvent) => { + event.preventDefault(); + this.handleError(event.error); + }, { capture: true }); + + // Override console.error with proper error extraction + const originalConsoleError = console.error; + console.error = (...args) => { + // Find the actual error object in the arguments + const error = args.find(arg => arg instanceof Error) || + args.join(' '); + + this.handleError(error); + originalConsoleError.apply(console, args); + }; + } + + private interceptWebAssembly(): void { + const self = this; + + const originalInstantiateStreaming = WebAssembly.instantiateStreaming; + if (originalInstantiateStreaming) { + WebAssembly.instantiateStreaming = async function (response: any, importObject?: any): Promise { + try { + return await originalInstantiateStreaming.call(WebAssembly, response, importObject); + } catch (error) { + self.handleError(error); + throw error; + } + }; + } + + const originalInstantiate = WebAssembly.instantiate; + WebAssembly.instantiate = async function (moduleObject: any, importObject?: any): Promise { + try { + return await originalInstantiate.call(WebAssembly, moduleObject, importObject); + } catch (error) { + self.handleError(error); + throw error; + } + }; + } handleError(error: any): void { Sentry.captureException(error); + if (typeof error === 'string') { + error = new Error(error); + } this.errorHandlerService.handleError(error); - console.error(error); } } @@ -86,12 +161,14 @@ export class AppGlobalErrorhandler implements ErrorHandler { ReactiveFormsModule, CopyComponent, WebNodeLandingPageComponent, + BlockProductionPillComponent, + MenuTabsComponent, ], providers: [ THEME_PROVIDER, - { provide: ErrorHandler, useClass: AppGlobalErrorhandler, deps: [GlobalErrorHandlerService] }, { provide: LOCALE_ID, useValue: 'en' }, { provide: ErrorHandler, useValue: Sentry.createErrorHandler() }, + { provide: ErrorHandler, useClass: AppGlobalErrorhandler, deps: [GlobalErrorHandlerService], multi: false }, { provide: Sentry.TraceService, deps: [Router] }, { provide: APP_INITIALIZER, @@ -99,7 +176,29 @@ export class AppGlobalErrorhandler implements ErrorHandler { deps: [Sentry.TraceService], multi: true, }, + provideClientHydration(), + provideHttpClient(withFetch()), + provideFirebaseApp(() => initializeApp({ + 'projectId': 'openminawebnode', + 'appId': '1:120031499786:web:9af56c50ebce25c619f1f3', + 'storageBucket': 'openminawebnode.firebasestorage.app', + 'apiKey': 'AIzaSyBreMkb5-8ANb5zL6yWKgRAk9owbDS1g9s', + 'authDomain': 'openminawebnode.firebaseapp.com', + 'messagingSenderId': '120031499786', + 'measurementId': 'G-V0ZC81T9RQ', + })), + provideAnalytics(() => getAnalytics()), + ScreenTrackingService, + // provideAppCheck(() => { + // // TODO get a reCAPTCHA Enterprise here https://console.cloud.google.com/security/recaptcha?project=_ + // const provider = new ReCaptchaEnterpriseProvider(/* reCAPTCHA Enterprise site key */); + // return initializeAppCheck(undefined, { provider, isTokenAutoRefreshEnabled: true }); + // }), + providePerformance(() => getPerformance()), ], bootstrap: [AppComponent], + exports: [ + MenuComponent, + ], }) export class AppModule {} diff --git a/frontend/src/app/app.reducer.ts b/frontend/src/app/app.reducer.ts index 0688276357..de4c8bb694 100644 --- a/frontend/src/app/app.reducer.ts +++ b/frontend/src/app/app.reducer.ts @@ -3,10 +3,11 @@ import { AppState } from '@app/app.state'; import { MinaNode } from '@shared/types/core/environment/mina-env.type'; import { createReducer, on } from '@ngrx/store'; import { AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { getLocalStorage } from '@openmina/shared'; const initialState: AppState = { menu: { - collapsed: JSON.parse(localStorage.getItem('menu_collapsed')) || false, + collapsed: JSON.parse(getLocalStorage()?.getItem('menu_collapsed') ?? 'false') || false, isMobile: false, open: true, }, @@ -16,12 +17,16 @@ const initialState: AppState = { status: AppNodeStatus.PENDING, blockHeight: null, blockTime: null, - peers: 0, - download: 0, - upload: 0, + peersConnected: 0, + peersDisconnected: 0, + peersConnecting: 0, transactions: 0, snarks: 0, + producingBlockAt: null, + producingBlockGlobalSlot: null, + producingBlockStatus: null, }, + envBuild: undefined, }; export const appReducer = createReducer( @@ -30,7 +35,7 @@ export const appReducer = createReducer( on(AppActions.changeActiveNode, (state, { node }) => ({ ...state, activeNode: node })), on(AppActions.getNodeDetailsSuccess, (state, { details }) => ({ ...state, activeNodeDetails: details })), on(AppActions.changeMenuCollapsing, (state, { isCollapsing }) => { - localStorage.setItem('menu_collapsed', JSON.stringify(isCollapsing)); + getLocalStorage()?.setItem('menu_collapsed', JSON.stringify(isCollapsing)); return { ...state, menu: { ...state.menu, collapsed: isCollapsing } }; }), on(AppActions.toggleMobile, (state, { isMobile }) => ({ @@ -39,14 +44,15 @@ export const appReducer = createReducer( })), on(AppActions.toggleMenuOpening, (state) => ({ ...state, menu: { ...state.menu, open: !state.menu.open } })), on(AppActions.addNode, (state, { node }) => { - const customNodes = JSON.parse(localStorage.getItem('custom_nodes') ?? '[]'); - localStorage.setItem('custom_nodes', JSON.stringify([node, ...customNodes])); + const customNodes = JSON.parse(getLocalStorage()?.getItem('custom_nodes') ?? '[]'); + getLocalStorage()?.setItem('custom_nodes', JSON.stringify([node, ...customNodes])); return { ...state, nodes: [node, ...state.nodes] }; }), on(AppActions.deleteNode, (state, { node }) => { - const customNodes = JSON.parse(localStorage.getItem('custom_nodes') ?? '[]'); - localStorage.setItem('custom_nodes', JSON.stringify(customNodes.filter((n: MinaNode) => n.name !== node.name))); + const customNodes = JSON.parse(getLocalStorage()?.getItem('custom_nodes') ?? '[]'); + getLocalStorage()?.setItem('custom_nodes', JSON.stringify(customNodes.filter((n: MinaNode) => n.name !== node.name))); const nodes = state.nodes.filter(n => n.name !== node.name); return { ...state, nodes, activeNode: state.activeNode?.name === node.name ? nodes[0] : state.activeNode }; }), + on(AppActions.getNodeEnvBuildSuccess, (state, { envBuild }) => ({ ...state, envBuild })), ); diff --git a/frontend/src/app/app.routing.ts b/frontend/src/app/app.routing.ts index ccf6903619..0a8cf9194a 100644 --- a/frontend/src/app/app.routing.ts +++ b/frontend/src/app/app.routing.ts @@ -14,6 +14,7 @@ export const SNARKS_TITLE: string = APP_TITLE + ' - Snarks'; export const BLOCK_PRODUCTION_TITLE: string = APP_TITLE + ' - Block Production'; export const MEMPOOL_TITLE: string = APP_TITLE + ' - Mempool'; export const BENCHMARKS_TITLE: string = APP_TITLE + ' - Benchmarks'; +export const WEBNODE_TITLE: string = APP_TITLE + ' - Web Node'; function generateRoutes(): Routes { @@ -64,6 +65,11 @@ function generateRoutes(): Routes { loadChildren: () => import('./features/benchmarks/benchmarks.module').then(m => m.BenchmarksModule), title: BENCHMARKS_TITLE, }, + { + path: 'loading-web-node', + loadChildren: () => import('./features/webnode/webnode.module').then(m => m.WebnodeModule), + title: WEBNODE_TITLE, + }, ]; if (CONFIG.showWebNodeLandingPage) { routes.push({ @@ -76,7 +82,7 @@ function generateRoutes(): Routes { ...routes, { path: '**', - redirectTo: getFirstFeature(), + redirectTo: CONFIG.showWebNodeLandingPage ? '' : getFirstFeature(), pathMatch: 'full', }, ]; diff --git a/frontend/src/app/app.service.ts b/frontend/src/app/app.service.ts index 7090d81d17..e6fc2bbaf7 100644 --- a/frontend/src/app/app.service.ts +++ b/frontend/src/app/app.service.ts @@ -5,6 +5,9 @@ import { CONFIG } from '@shared/constants/config'; import { RustService } from '@core/services/rust.service'; import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; import { getNetwork } from '@shared/helpers/mina.helper'; +import { getLocalStorage, ONE_MILLION } from '@openmina/shared'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; @Injectable({ providedIn: 'root', @@ -23,24 +26,31 @@ export class AppService { getNodes(): Observable { return of([ ...CONFIG.configs, - ...JSON.parse(localStorage.getItem('custom_nodes') ?? '[]'), + ...JSON.parse(getLocalStorage()?.getItem('custom_nodes') ?? '[]'), ]); } + getEnvBuild(): Observable { + return this.rust.get('/build_env'); + } + getActiveNodeDetails(): Observable { return this.rust.get('/status') .pipe( - map((data: NodeDetailsResponse) => ({ + map((data: NodeDetailsResponse): AppNodeDetails => ({ status: this.getStatus(data), blockHeight: data.transition_frontier.best_tip?.height, blockTime: data.transition_frontier.sync.time, - peers: data.peers.filter(p => p.connection_status === 'Connected').length, - download: 0, - upload: 0, + peersConnected: data.peers.filter(p => p.connection_status === 'Connected').length, + peersDisconnected: data.peers.filter(p => p.connection_status === 'Disconnected').length, + peersConnecting: data.peers.filter(p => p.connection_status === 'Connecting').length, snarks: data.snark_pool.snarks, transactions: data.transaction_pool.transactions, chainId: data.chain_id, network: getNetwork(data.chain_id), + producingBlockAt: data.current_block_production_attempt?.won_slot.slot_time / ONE_MILLION, + producingBlockGlobalSlot: data.current_block_production_attempt?.won_slot.global_slot, + producingBlockStatus: data.current_block_production_attempt?.status, } as AppNodeDetails)), ); } @@ -65,8 +75,38 @@ export interface NodeDetailsResponse { peers: Peer[]; snark_pool: SnarkPool; chain_id: string | undefined; + current_block_production_attempt: BlockProductionAttempt; +} + +export interface BlockProductionAttempt { + won_slot: WonSlot; + block: any; + times: Times; + status: BlockProductionWonSlotsStatus; +} + +export interface WonSlot { + slot_time: number; + global_slot: number; + epoch: number; + delegator: [string, number]; + value_with_threshold: number[]; } +export interface Times { + scheduled: number; + staged_ledger_diff_create_start: any; + staged_ledger_diff_create_end: any; + produced: any; + proof_create_start: any; + proof_create_end: any; + block_apply_start: any; + block_apply_end: any; + committed: any; + discarded: any; +} + + interface TransitionFrontier { best_tip: BestTip; sync: Sync; diff --git a/frontend/src/app/app.state.ts b/frontend/src/app/app.state.ts index bacf1f859b..5899469521 100644 --- a/frontend/src/app/app.state.ts +++ b/frontend/src/app/app.state.ts @@ -3,12 +3,14 @@ import { AppMenu } from '@shared/types/app/app-menu.type'; import { createSelector, MemoizedSelector } from '@ngrx/store'; import { MinaNode } from '@shared/types/core/environment/mina-env.type'; import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; export interface AppState { menu: AppMenu; nodes: MinaNode[]; activeNode: MinaNode; activeNodeDetails: AppNodeDetails; + envBuild: AppEnvBuild | undefined; } const select = (selector: (state: AppState) => T): MemoizedSelector => createSelector( @@ -20,10 +22,12 @@ const menu = select(state => state.menu); const nodes = select(state => state.nodes); const activeNode = select(state => state.activeNode); const activeNodeDetails = select(state => state.activeNodeDetails); +const envBuild = select(state => state.envBuild); export const AppSelectors = { menu, nodes, activeNode, activeNodeDetails, + envBuild, }; diff --git a/frontend/src/app/core/helpers/file-progress.helper.ts b/frontend/src/app/core/helpers/file-progress.helper.ts new file mode 100644 index 0000000000..2c5652b6b9 --- /dev/null +++ b/frontend/src/app/core/helpers/file-progress.helper.ts @@ -0,0 +1,100 @@ +import { BehaviorSubject } from 'rxjs'; +import { safelyExecuteInBrowser } from '@openmina/shared'; + +class AssetMonitor { + readonly downloads: Map = new Map(); + readonly progress$: BehaviorSubject; + + constructor(progress$: BehaviorSubject) { + this.progress$ = progress$; + safelyExecuteInBrowser(() => { + this.setupInterceptor(); + }); + } + + private setupInterceptor(): void { + const originalFetch = window.fetch; + const self = this; + + window.fetch = async function (resource, options) { + // Only intercept asset requests (you can modify these extensions as needed) + const assetExtensions = ['.wasm']; + const isAsset = assetExtensions.some(ext => + resource.toString().toLowerCase().endsWith(ext), + ); + + if (!isAsset) { + return originalFetch(resource, options); + } + + const startTime = performance.now(); + const downloadInfo = { + url: resource.toString(), + startTime, + progress: 0, + totalSize: 30111552, + status: 'pending', + endTime: 0, + duration: 0, + }; + + self.downloads.set(resource.toString(), downloadInfo); + self.emitProgress(downloadInfo); + + try { + const response = await originalFetch(resource, options); + const reader = response.clone().body.getReader(); + let receivedLength = 0; + + while (true) { + try { + const { done, value } = await reader.read(); + + if (done) { + break; + } + + receivedLength += value.length; + downloadInfo.progress = (receivedLength / downloadInfo.totalSize) * 100; + self.emitProgress(downloadInfo); + } catch (error) { + downloadInfo.status = 'error'; + self.emitProgress(downloadInfo); + throw error; + } + } + + downloadInfo.status = 'complete'; + downloadInfo.endTime = performance.now(); + downloadInfo.duration = downloadInfo.endTime - downloadInfo.startTime; + self.emitProgress(downloadInfo); + return await response; + } catch (error_1) { + downloadInfo.status = 'error'; + self.emitProgress(downloadInfo); + throw error_1; + } + }; + } + + private emitProgress(downloadInfo: any): void { + this.progress$.next({ + url: downloadInfo.url, + progress: downloadInfo.progress.toFixed(2), + totalSize: downloadInfo.totalSize, + status: downloadInfo.status, + duration: downloadInfo.duration, + startTime: downloadInfo.startTime, + endTime: downloadInfo.endTime, + downloaded: downloadInfo.progress * downloadInfo.totalSize / 100, + }); + } +} + +export class FileProgressHelper { + static progress$: BehaviorSubject = new BehaviorSubject(null); + + static initDownloadProgress(): void { + new AssetMonitor(this.progress$); + } +} diff --git a/frontend/src/app/core/services/rust.service.ts b/frontend/src/app/core/services/rust.service.ts index 6f428c7436..0a2d8a64a1 100644 --- a/frontend/src/app/core/services/rust.service.ts +++ b/frontend/src/app/core/services/rust.service.ts @@ -18,6 +18,10 @@ export class RustService { this.node = node; } + get activeNodeIsWebNode(): boolean { + return this.node.isWebNode; + } + get URL(): string { return this.node.url; } @@ -68,6 +72,8 @@ export class RustService { return this.webNodeService.accounts$; case '/best-chain-user-commands': return this.webNodeService.bestChainUserCommands$; + case '/build_env': + return this.webNodeService.envBuildDetails$; default: throw new Error(`Web node doesn't support "${path}" path!`); } diff --git a/frontend/src/app/core/services/sentry.service.ts b/frontend/src/app/core/services/sentry.service.ts new file mode 100644 index 0000000000..18a3d1db70 --- /dev/null +++ b/frontend/src/app/core/services/sentry.service.ts @@ -0,0 +1,70 @@ +import { inject, Injectable } from '@angular/core'; +import { NodesOverviewLedger, NodesOverviewLedgerStepState } from '@shared/types/nodes/dashboard/nodes-overview-ledger.type'; +import * as Sentry from '@sentry/angular'; +import { NodesOverviewBlock, NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/nodes-overview-block.type'; +import { lastItem, ONE_BILLION } from '@openmina/shared'; +import { RustService } from '@core/services/rust.service'; + +@Injectable({ + providedIn: 'root', +}) +export class SentryService { + + private ledgerIsSynced: boolean = false; + private blockIsSynced: boolean = false; + private rustService: RustService = inject(RustService); + + updateLedgerSyncStatus(ledger: NodesOverviewLedger): void { + if (this.ledgerIsSynced) { + return; + } + if (ledger.rootStaged.state === NodesOverviewLedgerStepState.SUCCESS) { + this.ledgerIsSynced = true; + const syncDetails = { + stakingLedger: { + fetchHashes: ledger.stakingEpoch.snarked.fetchHashesDuration + 's', + fetchAccounts: ledger.stakingEpoch.snarked.fetchAccountsDuration + 's', + }, + nextEpochLedger: { + fetchHashes: ledger.nextEpoch.snarked.fetchHashesDuration + 's', + fetchAccounts: ledger.nextEpoch.snarked.fetchAccountsDuration + 's', + }, + snarkedRootLedger: { + fetchHashes: ledger.rootSnarked.snarked.fetchHashesDuration + 's', + fetchAccounts: ledger.rootSnarked.snarked.fetchAccountsDuration + 's', + }, + stagedRootLedger: { + fetchParts: ledger.rootStaged.staged.fetchPartsDuration + 's', + reconstruct: ledger.rootStaged.staged.reconstructDuration + 's', + }, + }; + + const syncedIn = Math.round((ledger.rootStaged.staged.reconstructEnd - ledger.stakingEpoch.snarked.fetchHashesStart) / ONE_BILLION); + + Sentry.captureMessage(`Ledger synced in ${syncedIn}s`, { + level: 'info', + tags: { type: 'webnode', subType: 'sync.ledger' }, + contexts: { ledger: syncDetails }, + }); + } + } + + updateBlockSyncStatus(blocks: NodesOverviewBlock[], startTime: number): void { + if (this.blockIsSynced || !this.rustService.activeNodeIsWebNode) { + return; + } + + const blocksSynced = blocks.every(b => b.status === NodesOverviewNodeBlockStatus.APPLIED); + if (blocksSynced && blocks[0]) { + this.blockIsSynced = true; + blocks = blocks.slice(1); + const bestTipBlock = blocks[0].height; + const root = lastItem(blocks).height; + Sentry.captureMessage(`Last 290 blocks synced in ${Math.round((Date.now() - startTime) / 1000)}s`, { + level: 'info', + tags: { type: 'webnode', subType: 'sync.block' }, + contexts: { blocks: { bestTipBlock, root } }, + }); + } + } +} diff --git a/frontend/src/app/core/services/web-node.service.ts b/frontend/src/app/core/services/web-node.service.ts index 612b29b6eb..5f5f0beca2 100644 --- a/frontend/src/app/core/services/web-node.service.ts +++ b/frontend/src/app/core/services/web-node.service.ts @@ -1,10 +1,12 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, catchError, filter, from, fromEvent, map, merge, Observable, of, switchMap, tap } from 'rxjs'; +import { BehaviorSubject, catchError, EMPTY, filter, from, fromEvent, map, merge, Observable, of, switchMap, tap, throwError } from 'rxjs'; import base from 'base-x'; -import { any } from '@openmina/shared'; +import { any, isBrowser, safelyExecuteInBrowser } from '@openmina/shared'; import { HttpClient } from '@angular/common/http'; import { sendSentryEvent } from '@shared/helpers/webnode.helper'; import { DashboardPeerStatus } from '@shared/types/dashboard/dashboard.peer'; +import { FileProgressHelper } from '@core/helpers/file-progress.helper'; +import { CONFIG } from '@shared/constants/config'; @Injectable({ providedIn: 'root', @@ -12,68 +14,131 @@ import { DashboardPeerStatus } from '@shared/types/dashboard/dashboard.peer'; export class WebNodeService { private readonly webnode$: BehaviorSubject = new BehaviorSubject(null); + private readonly wasm$: BehaviorSubject = new BehaviorSubject(null); private webNodeKeyPair: { publicKey: string, privateKey: string }; + private webNodeNetwork: String; private webNodeStartTime: number; private sentryEvents: any = {}; + readonly webnodeProgress$: BehaviorSubject = new BehaviorSubject(''); + + memory: WebAssembly.MemoryDescriptor; + constructor(private http: HttpClient) { + FileProgressHelper.initDownloadProgress(); const basex = base('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'); - any(window)['bs58btc'] = { - encode: (buffer: Uint8Array | number[]) => 'z' + basex.encode(buffer), - decode: (string: string) => basex.decode(string.substring(1)), - }; + safelyExecuteInBrowser(() => { + any(window).bs58btc = { + encode: (buffer: Uint8Array | number[]) => 'z' + basex.encode(buffer), + decode: (string: string) => basex.decode(string.substring(1)), + }; + }); } - loadWasm$(): Observable { - sendSentryEvent('Loading WebNode JS'); - return merge( - of(any(window).webnode).pipe(filter(Boolean)), - fromEvent(window, 'webNodeLoaded'), - ).pipe( - switchMap(() => this.http.get<{ publicKey: string, privateKey: string }>('assets/webnode/web-node-secrets.json')), - tap(data => { - this.webNodeKeyPair = data; - sendSentryEvent('WebNode JS Loaded. Loading WebNode Wasm'); - }), - map(() => void 0), - ); + hasWebNodeConfig(): boolean { + return CONFIG.configs.some(c => c.isWebNode); } - startWasm$(): Observable { - return of(any(window).webnode) - .pipe( - switchMap((wasm: any) => from(wasm.default('assets/webnode/pkg/openmina_node_web_bg.wasm')).pipe(map(() => wasm))), - switchMap((wasm) => { - sendSentryEvent('WebNode Wasm loaded. Starting WebNode'); - return from(wasm.run(this.webNodeKeyPair.privateKey)); - }), - tap((webnode: any) => { - sendSentryEvent('WebNode Started'); - this.webNodeStartTime = Date.now(); - (window as any)['webnode'] = webnode; - this.webnode$.next(webnode); + isWebNodeLoaded(): boolean { + if (isBrowser()) { + return !!any(window).webnode; + } + return false; + } + + loadWasm$(): Observable { + this.webNodeStartTime = Date.now(); + + if (isBrowser()) { + const args = (() => { + const raw = localStorage.getItem('webnodeArgs'); + if (raw === null) { + return null; + } + return JSON.parse(atob(raw)); + })(); + return merge( + of(any(window).webnode).pipe(filter(Boolean)), + fromEvent(window, 'webNodeLoaded'), + ).pipe( + switchMap(() => { + const DEFAULT_NETWORK = 'devnet'; + if (!args) { + return this.http.get<{ publicKey: string, privateKey: string }>('assets/webnode/web-node-secrets.json') + .pipe(map(blockProducer => ({ blockProducer, network: DEFAULT_NETWORK }))); + } + const data = { network: args['network'] || DEFAULT_NETWORK, blockProducer: {} as any }; + if (!!args['block_producer']) { + data['blockProducer'] = { + privateKey: args['block_producer'].sec_key, + publicKey: args['block_producer'].pub_key, + }; + } + return of(data); }), - catchError((error) => { - sendSentryEvent('WebNode failed to start'); - console.error(error); - return of(null); + tap(data => { + this.webNodeKeyPair = data.blockProducer; + this.webNodeNetwork = data.network; }), - switchMap(() => this.webnode$.asObservable()), - filter(Boolean), + map(() => void 0), ); + } + return EMPTY; + } + + startWasm$(): Observable { + if (isBrowser()) { + return of(any(window).webnode) + .pipe( + switchMap((wasm: any) => { + this.wasm$.next(wasm); + return from(wasm.default(undefined, new WebAssembly.Memory(this.memory))) + .pipe(map(() => wasm)); + }), + switchMap((wasm) => { + this.webnodeProgress$.next('Loaded'); + const urls = (() => { + if (typeof this.webNodeNetwork === 'number') { + const url = `${window.location.origin}/clusters/${this.webNodeNetwork}/`; + return { + seeds: url + 'seeds', + genesisConfig: url + 'genesis/config', + }; + } else { + return { + seeds: 'https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt', + }; + } + })(); + console.log('webnode config:', !!this.webNodeKeyPair.privateKey, this.webNodeNetwork, urls); + return from(wasm.run(this.webNodeKeyPair.privateKey, urls.seeds, urls.genesisConfig)); + }), + tap((webnode: any) => { + any(window).webnode = webnode; + this.webnode$.next(webnode); + this.webnodeProgress$.next('Started'); + }), + catchError((error) => { + sendSentryEvent('WebNode failed to start: ' + error.message); + return throwError(() => new Error(error.message)); + }), + switchMap(() => this.webnode$.asObservable()), + ); + } + return EMPTY; } get status$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).status())), + switchMap(handle => from(any(handle).status())), ); } get blockProducerStats$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).stats().block_producer())), + switchMap(handle => from(any(handle).stats().block_producer())), ); } @@ -82,20 +147,18 @@ export class WebNodeService { filter(Boolean), switchMap(handle => from(any(handle).state().peers())), tap((peers) => { - if (!this.sentryEvents.sentNoPeersEvent && Date.now() - this.webNodeStartTime >= 5000 && peers.length === 0) { - console.log('WebNode has no peers after 5 seconds from startup.'); - sendSentryEvent('WebNode has no peers after 5 seconds from startup.'); - this.sentryEvents.sentNoPeersEvent = true; - } - if (!this.sentryEvents.sentPeersEvent && peers.length > 0) { - const seconds = (Date.now() - this.webNodeStartTime) / 1000; - sendSentryEvent(`WebNode found its first peer after ${seconds}s`); - this.sentryEvents.sentPeersEvent = true; - } + // if (!this.sentryEvents.sentNoPeersEvent && Date.now() - this.webNodeStartTime >= 5000 && peers.length === 0) { + // sendSentryEvent('WebNode has no peers after 5 seconds from startup.'); + // this.sentryEvents.sentNoPeersEvent = true; + // } + // if (!this.sentryEvents.sentPeersEvent && peers.length > 0) { + // this.sentryEvents.sentPeersEvent = true; + // } if (!this.sentryEvents.firstPeerConnected && peers.some((p: any) => p.connection_status === DashboardPeerStatus.CONNECTED)) { const seconds = (Date.now() - this.webNodeStartTime) / 1000; - sendSentryEvent(`WebNode connected to its first peer after ${seconds}s`); + sendSentryEvent(`WebNode connected in ${seconds.toFixed(1)}s`, 'info'); this.sentryEvents.firstPeerConnected = true; + this.webnodeProgress$.next('Connected'); } }), ); @@ -104,42 +167,49 @@ export class WebNodeService { get messageProgress$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).state().message_progress())), + switchMap(handle => from(any(handle).state().message_progress())), ); } get sync$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).stats().sync())), + switchMap(handle => from(any(handle).stats().sync())), ); } get accounts$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).ledger().latest().accounts().all())), + switchMap(handle => from(any(handle).ledger().latest().accounts().all())), ); } get bestChainUserCommands$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).transition_frontier().best_chain().user_commands())), + switchMap(handle => from(any(handle).transition_frontier().best_chain().user_commands())), ); } sendPayment$(payment: any): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).transaction_pool().inject().payment(payment))), + switchMap(handle => from(any(handle).transaction_pool().inject().payment(payment))), ); } get transactionPool$(): Observable { return this.webnode$.asObservable().pipe( filter(Boolean), - switchMap(handle => from((handle as any).transaction_pool().get())), + switchMap(handle => from(any(handle).transaction_pool().get())), + ); + } + + get envBuildDetails$(): Observable { + return this.wasm$.asObservable().pipe( + filter(Boolean), + map(handle => handle.build_env()), ); } } diff --git a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets-zk.service.ts b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets-zk.service.ts index 2c425391df..f1896a2c64 100644 --- a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets-zk.service.ts +++ b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets-zk.service.ts @@ -3,7 +3,7 @@ import { BehaviorSubject, catchError, filter, map, Observable, of, switchMap } f import { BenchmarksZkapp } from '@shared/types/benchmarks/transactions/benchmarks-zkapp.type'; import { fromPromise } from 'rxjs/internal/observable/innerFrom'; import { CONFIG } from '@shared/constants/config'; -import { any } from '@openmina/shared'; +import { any, safelyExecuteInBrowser } from '@openmina/shared'; import { DOCUMENT } from '@angular/common'; @Injectable() @@ -16,7 +16,9 @@ export class BenchmarksWalletsZkService { readonly updates$ = this.updates.asObservable(); loadO1js(): void { - this.loadScript(); + safelyExecuteInBrowser(() => { + this.loadScript(); + }); } sendZkApp(zkApps: BenchmarksZkapp[]): Observable
- -
+ } +
diff --git a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts index 54f5215991..d831c35149 100644 --- a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts +++ b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { MinaState, selectMinaState } from '@app/app.setup'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Effect } from '@openmina/shared'; -import { EMPTY, forkJoin, map, switchMap } from 'rxjs'; +import { combineLatest, EMPTY, map, switchMap } from 'rxjs'; import { Store } from '@ngrx/store'; import { BENCHMARKS_WALLETS_CLOSE, @@ -11,8 +11,11 @@ import { BENCHMARKS_WALLETS_GET_WALLETS, BENCHMARKS_WALLETS_GET_WALLETS_SUCCESS, BENCHMARKS_WALLETS_SEND_TX_SUCCESS, - BENCHMARKS_WALLETS_SEND_TXS, BENCHMARKS_WALLETS_SEND_ZKAPPS, BENCHMARKS_WALLETS_SEND_ZKAPPS_SUCCESS, - BenchmarksWalletsActions, BenchmarksWalletsClose, + BENCHMARKS_WALLETS_SEND_TXS, + BENCHMARKS_WALLETS_SEND_ZKAPPS, + BENCHMARKS_WALLETS_SEND_ZKAPPS_SUCCESS, + BenchmarksWalletsActions, + BenchmarksWalletsClose, BenchmarksWalletsGetWallets, BenchmarksWalletsSendTxs, } from '@benchmarks/wallets/benchmarks-wallets.actions'; @@ -74,7 +77,7 @@ export class BenchmarksWalletsEffects extends MinaRustBaseEffect this.actions$.pipe( ofType(BENCHMARKS_WALLETS_GET_ALL_TXS), switchMap(() => - forkJoin([ + combineLatest([ this.mempoolService.getTransactionPool(), this.benchmarksService.getAllIncludedTransactions(), ]), diff --git a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.reducer.ts b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.reducer.ts index 326dba5040..6c6aadad29 100644 --- a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.reducer.ts +++ b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.reducer.ts @@ -21,7 +21,7 @@ import { BenchmarksWalletTransactionStatus, } from '@shared/types/benchmarks/wallets/benchmarks-wallet.type'; import { BenchmarksWalletTransaction } from '@shared/types/benchmarks/wallets/benchmarks-wallet-transaction.type'; -import { hasValue, lastItem, ONE_BILLION } from '@openmina/shared'; +import { getLocalStorage, hasValue, lastItem, ONE_BILLION } from '@openmina/shared'; import { BenchmarksWalletsState } from '@benchmarks/wallets/benchmarks-wallets.state'; import { getTimeFromMemo } from '@shared/helpers/transaction.helper'; import { BenchmarksZkapp } from '@shared/types/benchmarks/transactions/benchmarks-zkapp.type'; @@ -110,7 +110,7 @@ export function reducer(state: BenchmarksWalletsState = initialState, action: Be .map((wallet: BenchmarksWallet, i: number) => { const nonce = getNonceForWallet(wallet, state).toString(); const counter = state.sentTxCount + i; - const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + localStorage.getItem('browserId'); + const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + getLocalStorage()?.getItem('browserId'); const payment = { from: wallet.publicKey, nonce, @@ -133,7 +133,7 @@ export function reducer(state: BenchmarksWalletsState = initialState, action: Be txsToSend = Array(state.txSendingBatch).fill(void 0).map((_, i: number) => { const counter = state.sentTxCount + i; - const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + localStorage.getItem('browserId'); + const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + getLocalStorage()?.getItem('browserId'); const payment = { from: wallet.publicKey, nonce: nonce.toString(), @@ -299,7 +299,7 @@ export function reducer(state: BenchmarksWalletsState = initialState, action: Be .map((wallet: BenchmarksWallet, i: number) => { const nonce = getNonceForWallet(wallet, state).toString(); const counter = state.sentTxCount + i; - const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + localStorage.getItem('browserId'); + const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + getLocalStorage()?.getItem('browserId'); return { payerPublicKey: wallet.publicKey, payerPrivateKey: wallet.privateKey, @@ -315,7 +315,7 @@ export function reducer(state: BenchmarksWalletsState = initialState, action: Be zkAppsToSend = Array(state.zkAppsSendingBatch).fill(void 0).map((_, i: number) => { const counter = state.sentTxCount + i; - const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + localStorage.getItem('browserId'); + const memo = 'S.T.' + Date.now() + ',' + (counter + 1) + ',' + getLocalStorage()?.getItem('browserId'); const payment = { payerPublicKey: wallet.publicKey, payerPrivateKey: wallet.privateKey, diff --git a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.service.ts b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.service.ts index 8fafa4ff04..b91a1462b6 100644 --- a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.service.ts +++ b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.service.ts @@ -11,7 +11,7 @@ import { ZkappCommand, } from '@shared/types/mempool/mempool-transaction.type'; import { decodeMemo, getTimeFromMemo, removeUnicodeEscapes } from '@shared/helpers/transaction.helper'; -import { ONE_BILLION } from '@openmina/shared'; +import { getLocalStorage, ONE_BILLION } from '@openmina/shared'; import { MempoolTransactionResponseKind } from '@app/features/mempool/mempool.service'; export const WALLETS: { privateKey: string, publicKey: string }[] = [ @@ -4029,8 +4029,8 @@ export class BenchmarksWalletsService { private client: Client = new Client({ network: 'testnet' }); constructor(private rust: RustService) { - if (!localStorage.getItem('browserId')) { - localStorage.setItem('browserId', Math.floor(Math.random() * 999999999).toString()); + if (!getLocalStorage()?.getItem('browserId')) { + getLocalStorage()?.setItem('browserId', Math.floor(Math.random() * 999999999).toString()); } } @@ -4120,7 +4120,7 @@ export class BenchmarksWalletsService { memo: removeUnicodeEscapes(memo), transactionData: tx, sentFromStressingTool: memo.includes('S.T.'), - sentByMyBrowser: memo.includes(localStorage.getItem('browserId')), + sentByMyBrowser: memo.includes(getLocalStorage()?.getItem('browserId')), } as MempoolTransaction; case MempoolTransactionResponseKind.ZkappCommand: const zkTx = command as ZkappCommand; @@ -4133,7 +4133,7 @@ export class BenchmarksWalletsService { memo: removeUnicodeEscapes(zkMemo), transactionData: zkTx, sentFromStressingTool: zkMemo.includes('S.T.'), - sentByMyBrowser: zkMemo.includes(localStorage.getItem('browserId')), + sentByMyBrowser: zkMemo.includes(getLocalStorage()?.getItem('browserId')), } as MempoolTransaction; } diff --git a/frontend/src/app/features/block-production/overview/block-production-overview.component.ts b/frontend/src/app/features/block-production/overview/block-production-overview.component.ts index 5fec6775a5..dcf8ba1de0 100644 --- a/frontend/src/app/features/block-production/overview/block-production-overview.component.ts +++ b/frontend/src/app/features/block-production/overview/block-production-overview.component.ts @@ -1,17 +1,14 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { BlockProductionOverviewActions } from '@block-production/overview/block-production-overview.actions'; -import { getMergedRoute, isDesktop, MergedRoute } from '@openmina/shared'; -import { debounceTime, filter, fromEvent, skip, take } from 'rxjs'; +import { getMergedRoute, isDesktop, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; +import { debounceTime, filter, fromEvent, take } from 'rxjs'; import { isNaN } from 'mathjs'; import { untilDestroyed } from '@ngneat/until-destroy'; import { BlockProductionOverviewSelectors } from '@block-production/overview/block-production-overview.state'; import { SLOTS_PER_EPOCH } from '@shared/constants/mina'; -import { - BlockProductionOverviewEpoch, -} from '@shared/types/block-production/overview/block-production-overview-epoch.type'; +import { BlockProductionOverviewEpoch } from '@shared/types/block-production/overview/block-production-overview-epoch.type'; import { AppSelectors } from '@app/app.state'; -import { MinaNode } from '@shared/types/core/environment/mina-env.type'; @Component({ selector: 'mina-block-production-overview', @@ -64,16 +61,18 @@ export class BlockProductionOverviewComponent extends StoreDispatcher implements } private listenToResize(): void { - fromEvent(window, 'resize') - .pipe( - debounceTime(100), - filter(() => this.showSidePanel !== isDesktop()), - untilDestroyed(this), - ) - .subscribe(() => { - this.showSidePanel = isDesktop(); - this.detect(); - }); + safelyExecuteInBrowser(() => { + fromEvent(window, 'resize') + .pipe( + debounceTime(100), + filter(() => this.showSidePanel !== isDesktop()), + untilDestroyed(this), + ) + .subscribe(() => { + this.showSidePanel = isDesktop(); + this.detect(); + }); + }); } override ngOnDestroy(): void { diff --git a/frontend/src/app/features/block-production/overview/slot-details/block-production-overview-slot-details.component.ts b/frontend/src/app/features/block-production/overview/slot-details/block-production-overview-slot-details.component.ts index fda854dbd3..788621a3e6 100644 --- a/frontend/src/app/features/block-production/overview/slot-details/block-production-overview-slot-details.component.ts +++ b/frontend/src/app/features/block-production/overview/slot-details/block-production-overview-slot-details.component.ts @@ -6,6 +6,7 @@ import { AppSelectors } from '@app/app.state'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; import { filter } from 'rxjs'; +import { safelyExecuteInBrowser } from '@openmina/shared'; @Component({ selector: 'mina-block-production-overview-slot-details', @@ -32,6 +33,6 @@ export class BlockProductionOverviewSlotDetailsComponent extends StoreDispatcher viewInMinaExplorer(): void { const network = this.minaExplorer !== 'mainnet' ? (this.minaExplorer + '.') : ''; const url = `https://${network}minaexplorer.com/block/${this.activeSlot.hash}`; - window.open(url, '_blank'); + safelyExecuteInBrowser(() => window.open(url, '_blank')); } } diff --git a/frontend/src/app/features/block-production/overview/slots/block-production-overview-slots.component.ts b/frontend/src/app/features/block-production/overview/slots/block-production-overview-slots.component.ts index 0e3b5e325c..32149d6196 100644 --- a/frontend/src/app/features/block-production/overview/slots/block-production-overview-slots.component.ts +++ b/frontend/src/app/features/block-production/overview/slots/block-production-overview-slots.component.ts @@ -13,7 +13,7 @@ import { BlockProductionOverviewFilters, } from '@shared/types/block-production/overview/block-production-overview-filters.type'; import { untilDestroyed } from '@ngneat/until-destroy'; -import { ONE_THOUSAND, toReadableDate } from '@openmina/shared'; +import { ONE_THOUSAND, safelyExecuteInBrowser, toReadableDate } from '@openmina/shared'; import { BlockProductionOverviewActions } from '@block-production/overview/block-production-overview.actions'; import { Router } from '@angular/router'; import { Routes } from '@shared/enums/routes.enum'; @@ -211,7 +211,10 @@ export class BlockProductionOverviewSlotsComponent extends StoreDispatcher imple let desiredLeft = Math.min(nodeRect.left + nodeRect.width / 2 - tooltipWidth / 2, chartLeft + this.width - tooltipWidth); desiredLeft = Math.max(desiredLeft, chartLeft); - let desiredTop = nodeRect.bottom + window.scrollY + 12; + let desiredTop = 0; + safelyExecuteInBrowser(() => { + desiredTop = nodeRect.bottom + window.scrollY + 12; + }); if (desiredTop + tooltipHeight > chartBottom) { desiredTop = nodeRect.top - tooltipHeight - 12; } diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.html b/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.html new file mode 100644 index 0000000000..9b82e4dbad --- /dev/null +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.html @@ -0,0 +1 @@ +{{ epoch }} diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.scss b/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.ts new file mode 100644 index 0000000000..d51a76a631 --- /dev/null +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component.ts @@ -0,0 +1,46 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { BlockProductionWonSlotsSelectors } from '@block-production/won-slots/block-production-won-slots.state'; +import { filter } from 'rxjs'; +import { BlockProductionWonSlotsSlot } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; +import { BlockProductionWonSlotsEpoch } from '@shared/types/block-production/won-slots/block-production-won-slots-epoch.type'; +import { ONE_BILLION, ONE_THOUSAND } from '@openmina/shared'; +import { getTimeDiff } from '@shared/helpers/date.helper'; + +@Component({ + selector: 'mina-block-production-won-slots-epoch', + templateUrl: './block-production-won-slots-epoch.component.html', + styleUrl: './block-production-won-slots-epoch.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'h-lg pl-12 f-600 fx-row-vert-cent border-bottom' }, +}) +export class BlockProductionWonSlotsEpochComponent extends StoreDispatcher implements OnInit { + epoch: string; + startedAgo: string; + + ngOnInit(): void { + this.listenToEpoch(); + this.listenToEpoch2(); + } + + private listenToEpoch(): void { + this.select(BlockProductionWonSlotsSelectors.slots, (slots: BlockProductionWonSlotsSlot[]) => { + this.epoch = 'Epoch ' + slots[0].epoch; + this.detect(); + }, filter(slots => slots.length > 0)); + } + + private listenToEpoch2(): void { + this.select(BlockProductionWonSlotsSelectors.epoch, (epoch: BlockProductionWonSlotsEpoch) => { + const epochStartTime = this.addMinutesToTimestamp(Math.floor(epoch.currentTime / ONE_BILLION), -(epoch.currentGlobalSlot - epoch.start) * 3); + this.startedAgo = getTimeDiff(Math.floor(epochStartTime * ONE_THOUSAND)).diff; + + this.detect(); + }, filter(Boolean)); + } + + private addMinutesToTimestamp(timestampInSeconds: number, minutesToAdd: number): number { + const secondsToAdd = minutesToAdd * 60; + return timestampInSeconds + secondsToAdd; + } +} diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts index e3bf056f9b..a027623765 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts @@ -26,6 +26,7 @@ const getSlotsSuccess = createAction(type('Get Slots Success'), props<{ }>()); const changeFilters = createAction(type('Change Filters'), props<{ filters: BlockProductionWonSlotsFilters }>()); const setActiveSlot = createAction(type('Set Active Slot'), props<{ slot: BlockProductionWonSlotsSlot }>()); +const setActiveSlotNumber = createAction(type('Set Active Slot Number'), props<{ slotNumber: number }>()); const sort = createAction(type('Sort'), props<{ sort: TableSort }>()); const toggleSidePanel = createAction(type('Toggle Side Panel')); @@ -36,6 +37,7 @@ export const BlockProductionWonSlotsActions = { getSlotsSuccess, changeFilters, setActiveSlot, + setActiveSlotNumber, sort, toggleSidePanel, }; diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html index 0f2bc3efd6..811b8e7310 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html @@ -1,21 +1,55 @@ - - - - -
- -
- - +@if ((isPending || nodeIsBootstrapping || isCalculatingVRF || isLoading) && emptySlots) { +
+ +
+ @if (isPending || isLoading) { +
Loading
+ } @else if (nodeIsBootstrapping) { +
Waiting For Sync
+
Only a synced node can calculate block production rights
+ } @else { +
Calculating Block Producing Rights
+
+ Epoch {{ epoch }} - Slot {{ vrfStats?.evaluated }}/{{ vrfStats?.total }} +
+ }
- +} @else { + + + + +
+ + @if (isDesktop) { + + } +
+ + @if (!emptySlots) { + + } @else { +
+ cancel_presentation +
No won slots yet
+
+ New won slots will appear here when the node receives them +
+
+ } +
+
+
- - - + + + +} diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss index 76009ed69f..053b423114 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss @@ -1,3 +1,18 @@ -mina-block-production-won-slots-cards + div { +@media (max-width: 767px) { + :host ::ng-deep mina-horizontal-resizable-container aside { + right: unset !important; + transition: left .4s cubic-bezier(.22, 1, .36, 1) !important; + &.in-view { + left: 0; + } + + &:not(.in-view) { + left: -100%; + } + } +} + +.mina-icon { + font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' -25, 'opsz' 24 !important; } diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts index 666a02d44d..15951b189e 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts @@ -1,37 +1,73 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; -import { getMergedRoute, isDesktop, isMobile, MergedRoute } from '@openmina/shared'; -import { debounceTime, filter, fromEvent, skip, take, timer } from 'rxjs'; +import { getMergedRoute, isDesktop, MergedRoute } from '@openmina/shared'; +import { take, timer } from 'rxjs'; import { untilDestroyed } from '@ngneat/until-destroy'; import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; import { AppSelectors } from '@app/app.state'; import { BlockProductionWonSlotsSelectors } from '@block-production/won-slots/block-production-won-slots.state'; +import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { animate, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'mina-block-production-won-slots', templateUrl: './block-production-won-slots.component.html', styleUrls: ['./block-production-won-slots.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fadeInOut', [ + transition(':enter', [ + style({ opacity: 0 }), + animate('400ms ease-in', style({ opacity: 1 })), + ]), + transition(':leave', [ + animate('400ms ease-out', style({ opacity: 0 })), + ]), + ]), + ], }) export class BlockProductionWonSlotsComponent extends StoreDispatcher implements OnInit, OnDestroy { showSidePanel: boolean = isDesktop(); + isDesktop: boolean = isDesktop(); + nodeIsBootstrapping: boolean = false; + isPending: boolean = true; + isCalculatingVRF: boolean = false; + vrfStats: { + evaluated: number; + total: number; + }; + epoch: number; + emptySlots: boolean = true; + isLoading: boolean = true; constructor(protected el: ElementRef) { super(); } ngOnInit(): void { this.listenToActiveNode(); + this.listenToNodeChange(); timer(10000, 10000) .pipe(untilDestroyed(this)) .subscribe(() => { this.dispatch2(BlockProductionWonSlotsActions.getSlots()); }); this.listenToResize(); + this.listenToActiveEpoch(); + this.listenToSlots(); + } + + private listenToNodeChange(): void { + this.select(AppSelectors.activeNodeDetails, (node: AppNodeDetails) => { + this.nodeIsBootstrapping = node?.status === AppNodeStatus.BOOTSTRAP; + this.isPending = node?.status === AppNodeStatus.PENDING; + this.detect(); + }); } private listenToActiveNode(): void { this.select(AppSelectors.activeNode, () => { this.select(getMergedRoute, (data: MergedRoute) => { + this.isLoading = true; this.dispatch2(BlockProductionWonSlotsActions.init({ activeSlotRoute: data.params['id'] })); }, take(1)); }); @@ -44,6 +80,29 @@ export class BlockProductionWonSlotsComponent extends StoreDispatcher implements }); } + private listenToActiveEpoch(): void { + this.select(BlockProductionWonSlotsSelectors.epoch, (activeEpoch) => { + this.epoch = activeEpoch?.epochNumber; + this.vrfStats = activeEpoch.vrfStats; + this.isCalculatingVRF = activeEpoch.vrfStats?.evaluated < activeEpoch.vrfStats?.total; + this.detect(); + }); + } + + private listenToSlots(): void { + this.select(BlockProductionWonSlotsSelectors.slots, (slots) => { + const emptySlots = slots.length === 0; + if (emptySlots !== this.emptySlots) { + this.emptySlots = emptySlots; + this.detect(); + } + }); + this.select(BlockProductionWonSlotsSelectors.serverResponded, (responded) => { + this.isLoading = !responded; + this.detect(); + }); + } + override ngOnDestroy(): void { super.ngOnDestroy(); this.dispatch2(BlockProductionWonSlotsActions.close()); diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts index 6cb1e24b4f..68f2412866 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { MinaState, selectMinaState } from '@app/app.setup'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { Effect } from '@openmina/shared'; +import { Effect, isDesktop, isMobile } from '@openmina/shared'; import { EMPTY, map, switchMap } from 'rxjs'; import { catchErrorAndRepeat2 } from '@shared/constants/store-functions'; import { MinaErrorType } from '@shared/types/error-preview/mina-error-type.enum'; @@ -9,13 +9,8 @@ import { Store } from '@ngrx/store'; import { BaseEffect } from '@shared/base-classes/mina-rust-base.effect'; import { BlockProductionModule } from '@block-production/block-production.module'; import { BlockProductionWonSlotsService } from '@block-production/won-slots/block-production-won-slots.service'; -import { - BLOCK_PRODUCTION_WON_SLOTS_KEY, - BlockProductionWonSlotsActions, -} from '@block-production/won-slots/block-production-won-slots.actions'; -import { - BlockProductionWonSlotsStatus, -} from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; +import { BLOCK_PRODUCTION_WON_SLOTS_KEY, BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; import { Router } from '@angular/router'; import { Routes } from '@shared/enums/routes.enum'; import { fromPromise } from 'rxjs/internal/observable/innerFrom'; @@ -27,6 +22,7 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { readonly init$: Effect; readonly getSlots$: Effect; + readonly setActiveSlotNumber$: Effect; constructor(private router: Router, private actions$: Actions, @@ -47,17 +43,26 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { ? EMPTY : this.wonSlotsService.getSlots().pipe( switchMap(({ slots, epoch }) => { - const activeSlotRoute = state.blockProduction[BLOCK_PRODUCTION_WON_SLOTS_KEY].activeSlotRoute; + const bpState = state.blockProduction[BLOCK_PRODUCTION_WON_SLOTS_KEY]; + const activeSlotRoute = bpState.activeSlotRoute; let newActiveSlot = slots.find(s => s.globalSlot.toString() === activeSlotRoute); - if (!activeSlotRoute || (activeSlotRoute && !newActiveSlot)) { + if ( + (isDesktop() && !activeSlotRoute) + || (activeSlotRoute && !newActiveSlot) + || (isMobile() && !activeSlotRoute && bpState.slots.length === 0) + ) { newActiveSlot = slots.find(s => s.active) ?? slots.find(s => s.status === BlockProductionWonSlotsStatus.Committed) ?? slots.find(s => s.status === BlockProductionWonSlotsStatus.Scheduled) - ?? null; + ?? slots.find(s => !s.status); } const routes: string[] = [Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS]; - if (newActiveSlot) { - routes.push(newActiveSlot.globalSlot.toString()); + if ( + newActiveSlot && isDesktop() + || (activeSlotRoute && !bpState.activeSlot) + || (activeSlotRoute && bpState.openSidePanel) + ) { + routes.push(newActiveSlot?.globalSlot.toString() ?? ''); } return fromPromise(this.router.navigate(routes, { queryParamsHandling: 'merge' })).pipe(map(() => ({ slots, @@ -74,5 +79,13 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { activeSlot: undefined, })), )); + + this.setActiveSlotNumber$ = createEffect(() => this.actions$.pipe( + ofType(BlockProductionWonSlotsActions.setActiveSlotNumber), + this.latestActionState(), + map(({ action, state }) => BlockProductionWonSlotsActions.setActiveSlot({ + slot: state.blockProduction[BLOCK_PRODUCTION_WON_SLOTS_KEY].slots.find(s => s.globalSlot === action.slotNumber), + })), + )); } } diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.module.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.module.ts index df36ef612c..ce1b55df9c 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.module.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.module.ts @@ -20,6 +20,7 @@ import { BlockProductionWonSlotsCardsComponent, } from '@block-production/won-slots/cards/block-production-won-slots-cards.component'; import { SharedModule } from '@shared/shared.module'; +import { BlockProductionWonSlotsEpochComponent } from '@block-production/won-slots/block-production-won-slots-epoch/block-production-won-slots-epoch.component'; @NgModule({ @@ -29,6 +30,7 @@ import { SharedModule } from '@shared/shared.module'; BlockProductionWonSlotsSidePanelComponent, BlockProductionWonSlotsFiltersComponent, BlockProductionWonSlotsCardsComponent, + BlockProductionWonSlotsEpochComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts index 9243daa5f7..8cd720605e 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts @@ -1,6 +1,6 @@ import { createReducer, on } from '@ngrx/store'; import { BlockProductionWonSlotsState } from '@block-production/won-slots/block-production-won-slots.state'; -import { isMobile, sort, SortDirection, TableSort } from '@openmina/shared'; +import { isDesktop, isMobile, sort, SortDirection, TableSort } from '@openmina/shared'; import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; import { BlockProductionWonSlotsSlot, @@ -24,6 +24,7 @@ const initialState: BlockProductionWonSlotsState = { sortBy: 'slotTime', sortDirection: SortDirection.ASC, }, + serverResponded: false, }; export const blockProductionWonSlotsReducer = createReducer( @@ -31,6 +32,7 @@ export const blockProductionWonSlotsReducer = createReducer( on(BlockProductionWonSlotsActions.init, (state, { activeSlotRoute }) => ({ ...state, activeSlotRoute, + serverResponded: false, })), on(BlockProductionWonSlotsActions.getSlotsSuccess, (state, { slots, epoch, activeSlot }) => ({ ...state, @@ -38,7 +40,8 @@ export const blockProductionWonSlotsReducer = createReducer( epoch, filteredSlots: filterSlots(sortSlots(slots, state.sort), state.filters), activeSlot, - openSidePanel: !!activeSlot, + openSidePanel: state.activeSlot ? state.openSidePanel : isDesktop(), + serverResponded: true, })), on(BlockProductionWonSlotsActions.setActiveSlot, (state, { slot }) => ({ ...state, @@ -56,7 +59,12 @@ export const blockProductionWonSlotsReducer = createReducer( filters, filteredSlots: filterSlots(sortSlots(state.slots, state.sort), filters), })), - on(BlockProductionWonSlotsActions.toggleSidePanel, state => ({ ...state, openSidePanel: !state.openSidePanel })), + on(BlockProductionWonSlotsActions.toggleSidePanel, state => ({ + ...state, + openSidePanel: !state.openSidePanel, + activeSlot: state.openSidePanel ? undefined : state.activeSlot, + activeSlotRoute: state.openSidePanel ? undefined : state.activeSlotRoute, + })), on(BlockProductionWonSlotsActions.close, () => initialState), ); diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts index fb6e7bfbab..fa83219e63 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts @@ -5,16 +5,13 @@ import { BlockProductionWonSlotsSlot, BlockProductionWonSlotsStatus, } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; -import { BlockProductionModule } from '@block-production/block-production.module'; -import { hasValue, nanOrElse, ONE_BILLION, ONE_MILLION } from '@openmina/shared'; +import { hasValue, isDesktop, nanOrElse, ONE_BILLION, ONE_MILLION } from '@openmina/shared'; import { getTimeDiff } from '@shared/helpers/date.helper'; import { RustService } from '@core/services/rust.service'; -import { - BlockProductionWonSlotsEpoch, -} from '@shared/types/block-production/won-slots/block-production-won-slots-epoch.type'; +import { BlockProductionWonSlotsEpoch } from '@shared/types/block-production/won-slots/block-production-won-slots-epoch.type'; @Injectable({ - providedIn: BlockProductionModule, + providedIn: 'root', }) export class BlockProductionWonSlotsService { @@ -25,11 +22,11 @@ export class BlockProductionWonSlotsService { .pipe( map((response: WonSlotResponse) => { if (!response) { - throw new Error('Empty response from /stats/block_producer'); + return { slots: [], epoch: undefined }; } const attemptsSlots = response.attempts.map((attempt: Attempt) => { attempt.won_slot.slot_time = Math.floor(attempt.won_slot.slot_time / ONE_MILLION); // converted to milliseconds - attempt.active = BlockProductionWonSlotsService.getActive(attempt); + attempt.active = this.getActive(attempt); let slot = { epoch: attempt.won_slot.epoch, message: this.getMessage(attempt), @@ -90,7 +87,7 @@ export class BlockProductionWonSlotsService { const futureWonSlots = response.future_won_slots.map((slot: WonSlot) => { slot.slot_time = Math.floor(slot.slot_time / ONE_MILLION); return { - message: 'Upcoming Won Slot', + message: this.getMessage({ won_slot: slot } as Attempt), age: this.calculateTimeAgo({ won_slot: slot }), slotTime: slot.slot_time, globalSlot: slot.global_slot, @@ -102,17 +99,22 @@ export class BlockProductionWonSlotsService { return { slots: [...attemptsSlots, ...futureWonSlots], epoch: { + epochNumber: response.current_epoch, start: response.epoch_start, end: response.epoch_end, currentGlobalSlot: response.current_global_slot, currentTime: response.current_time, + vrfStats: { + evaluated: response.current_epoch_vrf_stats?.evaluated_slots, + total: response.current_epoch_vrf_stats?.total_slots, + }, }, }; }), ); } - private static getActive(attempt: Attempt): boolean { + private getActive(attempt: Attempt): boolean { const slotTime = attempt.won_slot.slot_time; const now = Date.now(); return slotTime <= now && (now < 3 * 60 * 1000 + slotTime) && !attempt.times?.discarded; @@ -120,10 +122,10 @@ export class BlockProductionWonSlotsService { private getMessage(attempt: Attempt): string { if (attempt.active) { - return 'Produced'; + return 'Producing'; } if (attempt.status === BlockProductionWonSlotsStatus.Scheduled) { - return 'Production Scheduled'; + return (isDesktop() ? 'Production ' : '') + 'Scheduled'; } else if (attempt.status === BlockProductionWonSlotsStatus.Canonical) { return 'Produced Block'; } else if (attempt.status === BlockProductionWonSlotsStatus.Orphaned) { @@ -131,9 +133,9 @@ export class BlockProductionWonSlotsService { } else if (attempt.status === BlockProductionWonSlotsStatus.Discarded) { return BlockProductionWonSlotsStatus.Discarded + ' Block'; } else if (attempt.status === BlockProductionWonSlotsStatus.Committed) { - return 'Waiting for Confirmation'; + return isDesktop() ? 'Waiting for Confirmation' : 'Confirming'; } - return 'Upcoming Won Slot'; + return (isDesktop() ? 'Upcoming ' : '') + 'Won Slot'; } private calculateTimeAgo({ active, won_slot }: { active?: boolean; won_slot: WonSlot }): string { @@ -158,6 +160,17 @@ export interface WonSlotResponse { current_time: number; epoch_end: number; epoch_start: number; + current_epoch: number; + current_epoch_vrf_stats: { + evaluated_slots: number; + total_slots: number; + }; + vrf_stats: { + [epochNumber: string]: { + evaluated_slots: number; + total_slots: number; + }; + }; } interface Attempt { diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts index 1a0f211b2a..f97282a156 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts @@ -21,6 +21,7 @@ export interface BlockProductionWonSlotsState { openSidePanel: boolean; filters: BlockProductionWonSlotsFilters; sort: TableSort; + serverResponded: boolean; } @@ -36,6 +37,7 @@ const activeSlot = select(state => state.activeSlot); const filters = select(state => state.filters); const sort = select(state => state.sort); const openSidePanel = select(state => state.openSidePanel); +const serverResponded = select(state => state.serverResponded); export const BlockProductionWonSlotsSelectors = { epoch, @@ -45,4 +47,5 @@ export const BlockProductionWonSlotsSelectors = { filters, sort, openSidePanel, + serverResponded, }; diff --git a/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html b/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html index 37d961d2b3..e20de21d1f 100644 --- a/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html +++ b/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html @@ -2,38 +2,31 @@
- diff --git a/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.ts b/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.ts index bc5a056ce0..57c63f1808 100644 --- a/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.ts +++ b/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.ts @@ -22,12 +22,11 @@ import { BlockProductionWonSlotsActions } from '@block-production/won-slots/bloc }) export class BlockProductionWonSlotsCardsComponent extends StoreDispatcher implements OnInit { - card1: { epoch: number; startedAgo: string; } = { epoch: null, startedAgo: null }; - card2: { nextWonSlot: string; slot: number; } = { nextWonSlot: '-', slot: null }; - card3: { wonSlots: number; slotsUsed: number; } = { wonSlots: null, slotsUsed: null }; - card4: { acceptedBlocks: number; lastBlockTime: string; } = { acceptedBlocks: null, lastBlockTime: null }; - card5: { epochProgress: string; endIn: string; } = { epochProgress: '-', endIn: null }; - card6: { totalRewards: string; } = { totalRewards: null }; + card1: { nextWonSlot: string; slot: number; } = { nextWonSlot: '-', slot: null }; + card2: { wonSlots: number; slotsUsed: number; } = { wonSlots: null, slotsUsed: null }; + card3: { acceptedBlocks: number; lastBlockTime: string; } = { acceptedBlocks: null, lastBlockTime: null }; + card4: { epochProgress: string; endIn: string; } = { epochProgress: '-', endIn: null }; + card5: { totalRewards: string; } = { totalRewards: null }; ngOnInit(): void { this.listenToSlots(); @@ -36,12 +35,9 @@ export class BlockProductionWonSlotsCardsComponent extends StoreDispatcher imple private listenToEpoch(): void { this.select(BlockProductionWonSlotsSelectors.epoch, (epoch: BlockProductionWonSlotsEpoch) => { - const epochStartTime = this.addMinutesToTimestamp(Math.floor(epoch.currentTime / ONE_BILLION), -(epoch.currentGlobalSlot - epoch.start) * 3); - this.card1.startedAgo = getTimeDiff(Math.floor(epochStartTime * ONE_THOUSAND)).diff; - const epochEndTime = this.addMinutesToTimestamp(epoch.currentTime / ONE_BILLION, (epoch.end - epoch.currentGlobalSlot) * 3); - this.card5.endIn = getTimeDiff(epochEndTime * ONE_THOUSAND).diff; - this.card5.epochProgress = Math.floor((epoch.currentGlobalSlot - epoch.start) / (epoch.end - epoch.start) * 100) + '%'; + this.card4.endIn = getTimeDiff(epochEndTime * ONE_THOUSAND).diff; + this.card4.epochProgress = Math.floor((epoch.currentGlobalSlot - epoch.start) / (epoch.end - epoch.start) * 100) + '%'; this.detect(); }, filter(Boolean)); @@ -49,29 +45,28 @@ export class BlockProductionWonSlotsCardsComponent extends StoreDispatcher imple private listenToSlots(): void { this.select(BlockProductionWonSlotsSelectors.slots, (slots: BlockProductionWonSlotsSlot[]) => { - this.card1.epoch = slots[0].epoch; const nextSlot = slots.find(s => s.status === BlockProductionWonSlotsStatus.Scheduled || !s.status); if (nextSlot) { - this.card2.nextWonSlot = getTimeDiff(nextSlot.slotTime).diff; - this.card2.slot = nextSlot.globalSlot; + this.card1.nextWonSlot = getTimeDiff(nextSlot.slotTime).diff; + this.card1.slot = nextSlot.globalSlot; } else { - this.card2.nextWonSlot = 'Now'; - this.card2.slot = slots.find(s => s.active)?.globalSlot; + this.card1.nextWonSlot = 'Now'; + this.card1.slot = slots.find(s => s.active)?.globalSlot; } - this.card3.wonSlots = slots.length; - this.card3.slotsUsed = slots.filter( + this.card2.wonSlots = slots.length; + this.card2.slotsUsed = slots.filter( s => [BlockProductionWonSlotsStatus.Canonical, BlockProductionWonSlotsStatus.Orphaned, BlockProductionWonSlotsStatus.Discarded].includes(s.status), ).length; - this.card4.acceptedBlocks = slots.filter(s => s.status === BlockProductionWonSlotsStatus.Canonical).length; - this.card4.lastBlockTime = getTimeDiff(lastItem(slots.filter(s => s.status === BlockProductionWonSlotsStatus.Canonical))?.slotTime).diff; + this.card3.acceptedBlocks = slots.filter(s => s.status === BlockProductionWonSlotsStatus.Canonical).length; + this.card3.lastBlockTime = getTimeDiff(lastItem(slots.filter(s => s.status === BlockProductionWonSlotsStatus.Canonical))?.slotTime).diff; - this.card6.totalRewards = slots + this.card5.totalRewards = slots .filter(s => [BlockProductionWonSlotsStatus.Canonical].includes(s.status)) .map(s => s.coinbaseRewards + s.txFeesRewards).reduce((a, b) => a + b, 0).toFixed(0); - this.card6.totalRewards = isNaN(+this.card6.totalRewards) ? '0' : this.card6.totalRewards; + this.card5.totalRewards = isNaN(+this.card5.totalRewards) ? '0' : this.card5.totalRewards; this.detect(); }, filter(slots => slots.length > 0)); } diff --git a/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.html b/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.html index 4c48efdc23..dc6ca90815 100644 --- a/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.html +++ b/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.html @@ -1,13 +1,19 @@ -
- {{ title }} +
+
+ @if (isMobile) { +
+
+ arrow_back +
+
+ } + {{ title }} +
@if (percentage !== null && percentage !== undefined) {
{{ percentage }}%
} - @if (isMobile) { - close - }
@@ -15,7 +21,7 @@
-
+
Global slot diff --git a/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.scss b/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.scss index d2b6dceb15..bb189a824f 100644 --- a/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.scss +++ b/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.scss @@ -1,10 +1,23 @@ @import 'openmina'; +$blue: #57d7ff; +$pink: #fda2ff; +$orange: #ff833d; + +:host { + //linear-gradient(64deg, rgba(87, 215, 255, 0.15) 10%, rgba(253, 162, 255, 0.15) 60%, rgba(255, 131, 61, 0.15) 82%) + background: linear-gradient(70deg, rgba($blue, .15) 5%, rgba($pink, .15) 45%, rgba($orange, .15) 90%); +} + +.top { + border-bottom: 2px solid transparent; +} + .progress-bar { overflow: hidden; height: 6px; left: 0; - bottom: 0; + bottom: -3px; transition: width 0.4s ease-in-out; min-width: 20px; diff --git a/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.ts b/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.ts index 6183ba1631..486440ef00 100644 --- a/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.ts +++ b/frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.ts @@ -1,12 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - OnDestroy, - OnInit, - TemplateRef, - ViewChild, - ViewContainerRef, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { BlockProductionWonSlotsSelectors } from '@block-production/won-slots/block-production-won-slots.state'; import { @@ -15,25 +7,20 @@ import { BlockProductionWonSlotTimes, } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; import { getTimeDiff } from '@shared/helpers/date.helper'; -import { - any, - hasValue, - isMobile, - noMillisFormat, - ONE_THOUSAND, - SecDurationConfig, - toReadableDate, -} from '@openmina/shared'; +import { any, hasValue, isDesktop, isMobile, noMillisFormat, ONE_THOUSAND, safelyExecuteInBrowser, SecDurationConfig, toReadableDate } from '@openmina/shared'; import { filter } from 'rxjs'; import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; import { AppSelectors } from '@app/app.state'; import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { Router } from '@angular/router'; +import { Routes } from '@shared/enums/routes.enum'; @Component({ selector: 'mina-block-production-won-slots-side-panel', templateUrl: './block-production-won-slots-side-panel.component.html', styleUrls: ['./block-production-won-slots-side-panel.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column h-100' }, }) export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher implements OnInit, OnDestroy { @@ -70,6 +57,8 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i @ViewChild('discarded') private discardedTemplate: TemplateRef; + constructor(private router: Router) {super();} + ngOnInit(): void { this.listenToActiveSlot(); this.parseRemainingTime(); @@ -119,7 +108,7 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i viewInMinaExplorer(): void { const network = this.minaExplorer !== 'mainnet' ? (this.minaExplorer + '.') : ''; const url = `https://${network}minaexplorer.com/block/${this.slot.hash}`; - window.open(url, '_blank'); + safelyExecuteInBrowser(() => window.open(url, '_blank')); } private get getVrfText(): string { @@ -198,6 +187,7 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i } closeSidePanel(): void { + this.router.navigate([Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS]); this.dispatch2(BlockProductionWonSlotsActions.toggleSidePanel()); } @@ -205,4 +195,5 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i super.ngOnDestroy(); clearInterval(this.timer); } + } diff --git a/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.html b/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.html index 3e42122963..1c4d6d44ca 100644 --- a/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.html +++ b/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.html @@ -6,7 +6,7 @@ *ngIf="row.active || row.status === BlockProductionWonSlotsStatus.Committed; else icon"> schedule + class="schedule mina-icon icon-200 f-20">schedule circle diff --git a/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.scss b/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.scss index 25d6f79317..f8fc4575fc 100644 --- a/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.scss +++ b/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.scss @@ -1,5 +1,9 @@ @import 'openmina'; +$blue: #57d7ff; +$pink: #fda2ff; +$orange: #ff833d; + .mina-icon.f-big { font-variation-settings: 'FILL' 1, 'wght' 400 !important; } @@ -36,6 +40,21 @@ border-color: $selected-primary !important; border-top-color: transparent !important; } + + .schedule, + .Scheduled, + .Producing { + background: linear-gradient(5deg, $blue 30%, $pink 55%, $orange 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + + .schedule { + @media (max-width: 767px) { + font-size: 16px !important; + } + } } .fx-row-vert-cent { diff --git a/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.ts b/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.ts index 0334986ccb..4f23b56c9c 100644 --- a/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.ts +++ b/frontend/src/app/features/block-production/won-slots/table/block-production-won-slots-table.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { MinaTableRustWrapper } from '@shared/base-classes/mina-table-rust-wrapper.class'; -import { getMergedRoute, MergedRoute, SecDurationConfig, TableColumnList } from '@openmina/shared'; +import { getMergedRoute, isDesktop, MergedRoute, SecDurationConfig, TableColumnList } from '@openmina/shared'; import { Router } from '@angular/router'; import { SnarksWorkPoolToggleSidePanel } from '@snarks/work-pool/snarks-work-pool.actions'; import { filter, take } from 'rxjs'; @@ -57,7 +57,7 @@ export class BlockProductionWonSlotsTableComponent extends MinaTableRustWrapper< } protected override setupTable(): void { - this.table.gridTemplateColumns = [210, 140, 90, 140, 120, 120, 120, 120, 150, 150]; + this.table.gridTemplateColumns = isDesktop() ? [210, 140, 90, 142, 120, 120, 120, 124, 159, 150] : [150, 100, 90, 130, 100, 105, 100, 114, 139, 130]; this.table.propertyForActiveCheck = 'globalSlot'; this.table.thGroupsTemplate = this.thGroupsTemplate; this.table.sortAction = BlockProductionWonSlotsActions.sort; @@ -96,7 +96,7 @@ export class BlockProductionWonSlotsTableComponent extends MinaTableRustWrapper< this.table.activeRow = slot; this.table.detect(); this.detect(); - }, filter(Boolean)); + }); } private scrollToElement(): void { @@ -107,11 +107,11 @@ export class BlockProductionWonSlotsTableComponent extends MinaTableRustWrapper< this.onRowClick(this.table.rows[i]); } - protected override onRowClick(slot: BlockProductionWonSlotsSlot): void { + protected override onRowClick(slot: BlockProductionWonSlotsSlot, isRealClick?: boolean): void { if (!slot) { return; } - if (this.table.activeRow?.globalSlot !== slot?.globalSlot) { + if (this.table.activeRow?.globalSlot !== slot?.globalSlot || isRealClick) { this.dispatch2(BlockProductionWonSlotsActions.setActiveSlot({ slot })); this.router.navigate([Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS, slot.globalSlot], { queryParamsHandling: 'merge' }); } diff --git a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html index 46037f53df..0e89fd94b9 100644 --- a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html +++ b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html @@ -1,38 +1,46 @@ -
-
-
Last 290 blocks
-
{{ syncProgress }}
-
-
-
+
+
+
+ @if (appliedPercentage === 100) { + check_circle + } @else { + + } +
Last 290 blocks
+
{{ remaining ? 'ETA ~' + remaining + 's' : syncProgress }}
-
+
- + @if (isDesktop) { + + } - + @if (isDesktop) { + + }
diff --git a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.scss b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.scss index 51d9324e02..687cb478cd 100644 --- a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.scss +++ b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.scss @@ -1,42 +1,16 @@ -@import 'openmina'; -.progress-bar { - overflow: hidden; - height: 6px; - left: 0; - bottom: 0; - transition: width 0.6s ease-in-out; - min-width: 20px; - - .highlight { - height: 4px; - top: 1px; - left: -50px; - width: 50px; - background: radial-gradient($success-primary, $success-primary, $success-primary, $success-primary, transparent, transparent); - border-radius: 20px; - animation: move 9s linear infinite; +:host { + .mina-icon { + font-variation-settings: 'FILL' 1, 'wght' 300 !important; } - .progress { - height: 2px; - background-color: $success-primary; - top: 2px; - left: 0; - } -} + @media (max-width: 767px) { + .mobile mina-card { + width: 33.333%; -@keyframes move { - 0% { - left: -50px; - } - 10% { - left: -50px; - } - 80% { - left: 100%; - } - 100% { - left: 100%; + &:not(:first-child) { + padding-left: 10px !important; + } + } } } diff --git a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts index bd29e3cb04..465b5bdbd4 100644 --- a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts +++ b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts @@ -2,10 +2,10 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { selectDashboardNodesAndPeers } from '@dashboard/dashboard.state'; import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; -import { filter } from 'rxjs'; import { NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/nodes-overview-block.type'; -import { lastItem, ONE_MILLION } from '@openmina/shared'; +import { isDesktop, lastItem, ONE_MILLION } from '@openmina/shared'; import { DashboardPeer } from '@shared/types/dashboard/dashboard.peer'; +import { SentryService } from '@core/services/sentry.service'; const PENDING = 'Pending'; const SYNCED = 'Synced'; @@ -28,6 +28,12 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI bestTipBlockSyncedText: string = PENDING; targetBlock: number; syncProgress: string; + isDesktop: boolean = isDesktop(); + remaining: number; + + private syncStartTime: number = Date.now(); + + constructor(private sentryService: SentryService) {super();} ngOnInit(): void { this.listenToNodesChanges(); @@ -47,8 +53,36 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI this.targetBlock = undefined; this.syncProgress = undefined; } else { + const blocks = nodes[0].blocks; + + const blocksFetched = blocks.filter(b => b.fetchEnd).length; + const blocksApplied = blocks.filter(b => b.applyEnd).length; + if (blocksApplied < 291) { + + const syncStart = Math.min(...blocks.map(b => b.fetchStart).filter(Boolean)) / ONE_MILLION; + const now = Date.now(); + const secondsPassed = (now - syncStart) / 1000; + + const fetchWeight = 1; + const applyWeight = 5; + + // Apply weights: 1 for each fetched block, and 5 for each applied block + const weightedBlocksTotal = (blocksFetched * fetchWeight) + (blocksApplied * applyWeight); + const blocksPerSecond = weightedBlocksTotal / secondsPassed; + + if (blocksPerSecond > 0) { + const weightedBlocksRemaining = (291 * fetchWeight) + (291 * applyWeight) - weightedBlocksTotal; + const secondsRemaining = weightedBlocksRemaining / blocksPerSecond; + this.remaining = Math.ceil(secondsRemaining); + } + } else { + this.remaining = null; + } + this.extractNodesData(nodes); this.extractPeersData(peers); + + this.sentryService.updateBlockSyncStatus(nodes[0].blocks, this.syncStartTime); } this.detect(); }); @@ -69,6 +103,9 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI this.bestTipBlock = blocks[0].height; this.bestTipBlockSyncedText = 'Fetched ' + this.calculateProgressTime(nodes[0].bestTipReceivedTimestamp * ONE_MILLION).slice(7); this.syncProgress = this.bestTipBlockSyncedText.slice(8); + if (lastItem(blocks).status !== NodesOverviewNodeBlockStatus.APPLIED) { + this.syncProgress = 'Pending'; + } } if (blocks.length === 291) { @@ -85,8 +122,8 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI this.fetched = blocks.filter(b => ![NodesOverviewNodeBlockStatus.MISSING, NodesOverviewNodeBlockStatus.FETCHING].includes(b.status)).length; this.applied = blocks.filter(b => b.status === NodesOverviewNodeBlockStatus.APPLIED).length; - this.fetchedPercentage = Math.round(this.fetched * 100 / 291) + '%'; - this.appliedPercentage = Math.round(this.applied * 100 / 291); + this.fetchedPercentage = Math.round(this.fetched * 100 / 290) + '%'; + this.appliedPercentage = Math.round(this.applied * 100 / 290); } private calculateProgressTime(timestamp: number): string { diff --git a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html index def4089c6b..84703092f4 100644 --- a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html +++ b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html @@ -1,12 +1,14 @@ -
-
-
Ledgers
-
{{ progress }}
-
-
-
+
+
+
+ @if (totalProgress === 100) { + check_circle + } @else { + + } +
Ledgers
+
{{ progress }}
Staking ledger
-
{{ stakingProgress | number: '1.0-0' }}% -
+ @if (stakingProgress === 100) { +
100%
+ } @else if (remainingStakingLedger) { +
ETA ~{{ remainingStakingLedger }}s
+ }
Next epoch ledger
-
- {{ nextProgress | number: '1.0-0' }}% -
+ @if (nextProgress === 100) { +
100%
+ } @else if (remainingNextLedger) { +
ETA ~{{ remainingNextLedger }}s
+ }
Snarked ledger at the root
-
- {{ rootSnarkedProgress | number: '1.0-0' }}% -
+ @if (rootSnarkedProgress === 100) { +
100%
+ } @else if (remainingRootSnarkedLedger) { +
ETA ~{{ remainingRootSnarkedLedger }}s
+ }
Staged ledger at the root
-
- {{ rootStagedProgress | number: '1.0-0' }}% -
+ @if ((rootStagedProgress === 100 || !isWebNode) && rootSnarkedProgress === 100) { +
{{ rootStagedProgress }}%
+ } @else if (remainingReconstruct && remainingRootStagedLedgerFetchParts + remainingReconstruct) { +
ETA ~{{ remainingRootStagedLedgerFetchParts + remainingReconstruct }}s
+ }
diff --git a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.scss b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.scss index 331b09ebc6..acb2f24ab6 100644 --- a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.scss +++ b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.scss @@ -1,44 +1,7 @@ @import 'openmina'; -.progress-bar { - overflow: hidden; - height: 6px; - left: 0; - bottom: 0; - transition: width 0.6s ease-in-out; - min-width: 20px; - - .highlight { - height: 4px; - top: 1px; - left: -50px; - width: 50px; - background: radial-gradient($success-primary, $success-primary, $success-primary, $success-primary, transparent, transparent); - border-radius: 20px; - animation: move 9s linear infinite; - } - - .progress { - height: 2px; - background-color: $success-primary; - top: 2px; - left: 0; - } -} - -@keyframes move { - 0% { - left: -50px; - } - 10% { - left: -50px; - } - 80% { - left: 100%; - } - 100% { - left: 100%; - } +.mina-icon { + font-variation-settings: 'FILL' 1, 'wght' 300 !important; } .group { @@ -53,7 +16,7 @@ top: -5px; left: 10px; width: 1px; - height: calc(100% - 13px); + height: calc(100% + 10px); background-color: $base-divider; } @@ -63,7 +26,7 @@ &::before { content: ''; position: absolute; - top: calc(50% - 1px); + top: calc(50% + 0px); left: -29px; width: 20px; height: 1px; @@ -72,6 +35,10 @@ } } + &:last-child .steps::before { + height: calc(100% - 13px); + } + &.pending { .group-title { color: $base-tertiary; @@ -83,9 +50,13 @@ } &.success { + .group-title { + color: $success-primary; + } + .steps::before, .steps .step::before { - background-color: $success-tertiary; + background-color: $base-tertiary; } } diff --git a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts index bb4d2ea011..b0c46c1526 100644 --- a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts +++ b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts @@ -1,12 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - OnDestroy, - OnInit, - TemplateRef, - ViewChild, - ViewContainerRef, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { selectDashboardNodesAndRpcStats } from '@dashboard/dashboard.state'; import { @@ -15,12 +7,14 @@ import { NodesOverviewLedgerStepState, NodesOverviewRootStagedLedgerStep, } from '@shared/types/nodes/dashboard/nodes-overview-ledger.type'; -import { filter } from 'rxjs'; import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; import { ONE_MILLION, SecDurationConfig } from '@openmina/shared'; import { Overlay, OverlayRef } from '@angular/cdk/overlay'; import { TemplatePortal } from '@angular/cdk/portal'; import { DashboardRpcStats } from '@shared/types/dashboard/dashboard-rpc-stats.type'; +import { AppSelectors } from '@app/app.state'; +import { MinaNode } from '@shared/types/core/environment/mina-env.type'; +import { SentryService } from '@core/services/sentry.service'; type LedgerConfigMap = { stakingEpoch: SecDurationConfig, @@ -85,19 +79,41 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, rootSnarkedProgress: number = 0; rootStagedProgress: number = 0; totalProgress: number; + isWebNode: boolean; + + private startSync: number = Date.now(); @ViewChild('tooltipRef') private tooltipRef: TemplateRef<{ start: number, end: number }>; private overlayRef: OverlayRef; constructor(private overlay: Overlay, - private viewContainerRef: ViewContainerRef) { + private viewContainerRef: ViewContainerRef, + private sentryService: SentryService) { super(); } ngOnInit(): void { + this.listenToActiveNode(); this.listenToNodesChanges(); } + private listenToActiveNode(): void { + this.select(AppSelectors.activeNode, (node: MinaNode) => { + this.isWebNode = node.isWebNode; + }); + } + + remainingStakingLedger: number; + private previousStakingLedgerDownloaded: number; + remainingNextLedger: number; + private previousNextLedgerDownloaded: number; + remainingRootSnarkedLedger: number; + private previousRootSnarkedLedgerDownloaded: number; + remainingRootStagedLedgerFetchParts: number; + private previousRootStagedLedgerDownloaded: number; + remainingReconstruct: number = 20; + private reconstructTimer: any; + private listenToNodesChanges(): void { this.select(selectDashboardNodesAndRpcStats, ([nodes, rpcStats]: [NodesOverviewNode[], DashboardRpcStats]) => { if (nodes.length === 0) { @@ -132,10 +148,77 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, rootStaged: getConfig(this.ledgers.rootStaged.state), }; this.setProgressTime(); + + const stakingLedgerStartTime = this.ledgers.stakingEpoch.snarked.fetchHashesStart / ONE_MILLION; + const currentStakingLedgerDownloaded = rpcStats.stakingLedger?.fetched; + if ( + this.previousStakingLedgerDownloaded && currentStakingLedgerDownloaded && + stakingLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - stakingLedgerStartTime; + const remaining = rpcStats.stakingLedger?.estimation - currentStakingLedgerDownloaded; + const remainingTime = (remaining / currentStakingLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingStakingLedger = Math.floor(remainingTime / 1000); + } + this.previousStakingLedgerDownloaded = currentStakingLedgerDownloaded; + + const nextLedgerStartTime = this.ledgers.nextEpoch.snarked.fetchHashesStart / ONE_MILLION; + const currentNextLedgerDownloaded = rpcStats.nextLedger?.fetched; + if ( + this.previousNextLedgerDownloaded && currentNextLedgerDownloaded && + nextLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - nextLedgerStartTime; + const remaining = rpcStats.nextLedger?.estimation - currentNextLedgerDownloaded; + const remainingTime = (remaining / currentNextLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingNextLedger = Math.floor(remainingTime / 1000); + } + this.previousNextLedgerDownloaded = currentNextLedgerDownloaded; + + const rootSnarkedLedgerStartTime = this.ledgers.rootSnarked.snarked.fetchHashesStart / ONE_MILLION; + const currentRootSnarkedLedgerDownloaded = rpcStats.snarkedRootLedger?.fetched; + if ( + this.previousRootSnarkedLedgerDownloaded && currentRootSnarkedLedgerDownloaded && + rootSnarkedLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - rootSnarkedLedgerStartTime; + const remaining = rpcStats.snarkedRootLedger?.estimation - currentRootSnarkedLedgerDownloaded; + const remainingTime = (remaining / currentRootSnarkedLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingRootSnarkedLedger = Math.floor(remainingTime / 1000); + } + this.previousRootSnarkedLedgerDownloaded = currentRootSnarkedLedgerDownloaded; + + if (this.isWebNode) { + const rootStagedLedgerStartTime = this.ledgers.rootStaged.staged.fetchPartsStart / ONE_MILLION; + const currentRootStagedLedgerDownloaded = rpcStats.stagedRootLedger?.fetched; + if ( + this.previousRootStagedLedgerDownloaded && currentRootStagedLedgerDownloaded && + rootStagedLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - rootStagedLedgerStartTime; + const remaining = rpcStats.stagedRootLedger?.estimation - currentRootStagedLedgerDownloaded; + const remainingTime = (remaining / currentRootStagedLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingRootStagedLedgerFetchParts = Math.floor(remainingTime / 1000); + } + this.previousRootStagedLedgerDownloaded = currentRootStagedLedgerDownloaded; + } + this.stakingProgress = rpcStats.stakingLedger?.fetched / rpcStats.stakingLedger?.estimation * 100 || 0; this.nextProgress = rpcStats.nextLedger?.fetched / rpcStats.nextLedger?.estimation * 100 || 0; - this.rootSnarkedProgress = rpcStats.rootLedger?.fetched / rpcStats.rootLedger?.estimation * 100 || 0; - this.rootStagedProgress = this.ledgers.rootStaged.staged.fetchPartsEnd ? 50 : 0; + this.rootSnarkedProgress = rpcStats.snarkedRootLedger?.fetched / rpcStats.snarkedRootLedger?.estimation * 100 || 0; + + this.rootStagedProgress = 0; + if (this.ledgers.rootStaged.staged.fetchPartsEnd) { + this.rootStagedProgress += 50; + } + if (this.ledgers.rootStaged.staged.reconstructEnd) { + this.rootStagedProgress += 50; + } + if (this.rootStagedProgress < 100 && this.isWebNode && this.ledgers.rootStaged.staged.fetchPartsEnd && !this.reconstructTimer) { + this.startTimerForReconstruct(); + } else if (this.rootStagedProgress === 100) { + clearTimeout(this.reconstructTimer); + } if (this.ledgers.stakingEpoch.state === NodesOverviewLedgerStepState.SUCCESS) { this.stakingProgress = 100; @@ -150,11 +233,23 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, this.rootStagedProgress = 100; } this.totalProgress = (this.stakingProgress + this.nextProgress + this.rootSnarkedProgress + this.rootStagedProgress) / 4; + + this.sentryService.updateLedgerSyncStatus(this.ledgers); } this.detect(); }); } + startTimerForReconstruct(): void { + this.reconstructTimer = setInterval(() => { + this.remainingReconstruct = this.remainingReconstruct - 1; + this.detect(); + if (this.remainingReconstruct === Math.floor(Math.random() * 2) + 2) { + clearTimeout(this.reconstructTimer); + } + }, 1000); + } + show(event: MouseEvent, start: number, end: number): void { if (this.overlayRef?.hasAttached()) { this.overlayRef.detach(); @@ -183,9 +278,7 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, } hide(): void { - if (this.overlayRef?.hasAttached()) { - this.overlayRef.detach(); - } + this.overlayRef?.dispose(); } private get emptyConfig(): SecDurationConfig { @@ -205,11 +298,6 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, }; } - override ngOnDestroy(): void { - super.ngOnDestroy(); - this.hide(); - } - private setProgressTime(): void { if (!this.ledgers.stakingEpoch.snarked.fetchHashesStart) { return; @@ -238,4 +326,9 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, return `${action} <1m ago`; } } + + override ngOnDestroy(): void { + super.ngOnDestroy(); + this.hide(); + } } diff --git a/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.html b/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.html index cc24d97e3d..6b758e9783 100644 --- a/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.html +++ b/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.html @@ -1,19 +1,13 @@ -
- - Network -
-
-
-
Peers
-
-
-
-
+
+
+ @if (stats.connected) { + check_circle + } @else { + + } +
Peers
-
+
-
+
- - - account_tree - - - - diff --git a/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.scss b/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.scss index 818da2c61d..6e1afba0ea 100644 --- a/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.scss +++ b/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.scss @@ -1,60 +1,3 @@ -@import 'openmina'; - -:host { - max-width: 384px; -} - -.loading-icon { - animation: spin 2s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0); - } - 100% { - transform: rotate(360deg); - } -} - - -.progress-bar { - overflow: hidden; - height: 6px; - left: 0; - bottom: 0; - transition: width 0.6s ease-in-out; - min-width: 20px; - - .highlight { - height: 4px; - top: 1px; - left: -50px; - width: 50px; - background: radial-gradient($success-primary, $success-primary, $success-primary, $success-primary, transparent, transparent); - border-radius: 20px; - animation: move 9s linear infinite; - } - - .progress { - height: 2px; - background-color: $success-primary; - top: 2px; - left: 0; - } -} - -@keyframes move { - 0% { - left: -50px; - } - 10% { - left: -50px; - } - 80% { - left: 100%; - } - 100% { - left: 100%; - } +.mina-icon { + font-variation-settings: 'FILL' 1, 'wght' 300 !important; } diff --git a/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.ts b/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.ts index 3c8b63e204..ff47a7e17f 100644 --- a/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.ts +++ b/frontend/src/app/features/dashboard/dashboard-network/dashboard-network.component.ts @@ -3,7 +3,6 @@ import { DashboardPeersStats } from '@shared/types/dashboard/dashboard-peers-sta import { selectDashboardPeersStats } from '@dashboard/dashboard.state'; import { skip } from 'rxjs'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; -import { NodesOverviewLedgerStepState } from '@shared/types/nodes/dashboard/nodes-overview-ledger.type'; @Component({ selector: 'mina-dashboard-network', diff --git a/frontend/src/app/features/dashboard/dashboard-peers-minimal-table/dashboard-peers-minimal-table.component.scss b/frontend/src/app/features/dashboard/dashboard-peers-minimal-table/dashboard-peers-minimal-table.component.scss index bb3787a918..53e4ca2058 100644 --- a/frontend/src/app/features/dashboard/dashboard-peers-minimal-table/dashboard-peers-minimal-table.component.scss +++ b/frontend/src/app/features/dashboard/dashboard-peers-minimal-table/dashboard-peers-minimal-table.component.scss @@ -14,6 +14,7 @@ :host ::ng-deep cdk-virtual-scroll-viewport { overflow-y: auto !important; + border-bottom: none !important; } .fx-row-vert-cent { diff --git a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.html b/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.html deleted file mode 100644 index 549dcca28f..0000000000 --- a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - Transition frontier -
-
- - -
- - - margin - - - - diff --git a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.scss b/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.scss deleted file mode 100644 index 17d80cb52d..0000000000 --- a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.scss +++ /dev/null @@ -1,34 +0,0 @@ -@import 'openmina'; - -.tracing-container { - gap: 8px; - - mina-dashboard-ledger { - width: 100%; - max-width: 410px; - min-width: 250px; - } - - mina-dashboard-blocks-sync { - width: 100%; - max-width: 640px; - } - - @media (max-width: 1374px) { - display: flex; - flex-direction: column; - } -} - -.loading-icon { - animation: spin 2s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.ts b/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.ts deleted file mode 100644 index 7af4da1482..0000000000 --- a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; -import { selectDashboardNodes } from '@dashboard/dashboard.state'; -import { NodesOverviewNode, NodesOverviewNodeKindType } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; - -@Component({ - selector: 'mina-dashboard-transition-frontier', - templateUrl: './dashboard-transition-frontier.component.html', - styleUrls: ['./dashboard-transition-frontier.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'flex-column flex-1' }, -}) -export class DashboardTransitionFrontierComponent extends StoreDispatcher implements OnInit { - - loading: boolean = true; - - ngOnInit(): void { - this.listenToNodesChanges(); - } - - private listenToNodesChanges(): void { - this.select(selectDashboardNodes, (nodes: NodesOverviewNode[]) => { - this.loading = !nodes.length || nodes[0].kind !== NodesOverviewNodeKindType.SYNCED; - this.detect(); - }); - } -} diff --git a/frontend/src/app/features/dashboard/dashboard.component.html b/frontend/src/app/features/dashboard/dashboard.component.html index 97e54aa457..7e2a718d84 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.html +++ b/frontend/src/app/features/dashboard/dashboard.component.html @@ -1,4 +1,18 @@ -
+
+
+ @if (connectedProgress + ledgerProgress + blockSyncProgress < 100) { +
+ } +
+
+
+ {{ updateAction }}{{ updateDetail }} +
+
+
- + +
diff --git a/frontend/src/app/features/dashboard/dashboard.component.scss b/frontend/src/app/features/dashboard/dashboard.component.scss index cd151fd9f7..6f38a175ae 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.scss +++ b/frontend/src/app/features/dashboard/dashboard.component.scss @@ -4,6 +4,89 @@ background-color: $base-background; } -div { +.comps { gap: 8px; + padding-right: 8px; +} + +mina-dashboard-network { + max-width: 384px; +} + +mina-dashboard-ledger { + width: 100%; + max-width: 384px; + min-width: 250px; +} + +mina-dashboard-blocks-sync { + width: 100%; + max-width: 644px; +} + +@media (max-width: 767px) { + mina-dashboard-network, + mina-dashboard-ledger, + mina-dashboard-blocks-sync { + max-width: 100%; + } +} + +.header { + transition: height .6s ease-in, opacity .4s .4s ease-in; + + &.hide { + height: 0 !important; + opacity: 0; + } + + @media (max-width: 767px) { + &.hide { + height: 8px !important; + } + .primary { + padding-left: 4px; + } + } +} + +.progress-bar { + overflow: hidden; + height: 4px; + left: 0; + top: -2px; + transition: width 0.6s ease-in-out; + min-width: 20px; + + .highlight { + height: 4px; + top: 1px; + left: -50px; + width: 50px; + background: radial-gradient($success-primary, $success-primary, $success-primary, $success-primary, transparent, transparent); + border-radius: 20px; + animation: move 9s linear infinite; + } + + .progress { + height: 2px; + background-color: $success-primary; + top: 2px; + left: 0; + } +} + +@keyframes move { + 0% { + left: -50px; + } + 10% { + left: -50px; + } + 80% { + left: 100%; + } + 100% { + left: 100%; + } } diff --git a/frontend/src/app/features/dashboard/dashboard.component.ts b/frontend/src/app/features/dashboard/dashboard.component.ts index cc2dbaaf7e..1ff872cf57 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.ts +++ b/frontend/src/app/features/dashboard/dashboard.component.ts @@ -1,33 +1,162 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { DashboardGetData, DashboardInit } from '@dashboard/dashboard.actions'; -import { filter, skip, tap, timer } from 'rxjs'; -import { untilDestroyed } from '@ngneat/until-destroy'; +import { filter, skip, Subscription, tap, timer } from 'rxjs'; import { AppSelectors } from '@app/app.state'; +import { selectDashboardNodesAndRpcStats, selectDashboardPeersStats } from '@dashboard/dashboard.state'; +import { DashboardPeersStats } from '@shared/types/dashboard/dashboard-peers-stats.type'; +import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; +import { DashboardRpcStats } from '@shared/types/dashboard/dashboard-rpc-stats.type'; +import { NodesOverviewLedgerStepState } from '@shared/types/nodes/dashboard/nodes-overview-ledger.type'; +import { NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/nodes-overview-block.type'; +import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { untilDestroyed } from '@ngneat/until-destroy'; @Component({ selector: 'mina-dashboard', templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'h-100 w-100 flex-row' }, + host: { class: 'h-100 w-100 flex-column' }, }) -export class DashboardComponent extends StoreDispatcher implements OnInit { +export class DashboardComponent extends StoreDispatcher implements OnInit, OnDestroy { + + updateAction: string; + updateDetail: string; + connectedProgress: number = 0; + ledgerProgress: number = 0; + blockSyncProgress: number = 0; + + private connected: boolean = false; + timer: Subscription; + lastStatus: AppNodeStatus; ngOnInit(): void { + // this.document.getElementById('mina-content').style.borderTopLeftRadius = '0'; + this.updateAction = 'Connecting to peers'; + this.listenToNodeChanging(); + this.listenToPeersChanges(); + this.getDashboardData(); + this.listenToNodesChanges(); + } + + private getDashboardData(): void { this.dispatch(DashboardInit); - timer(4000, 4000) - .pipe( - tap(() => this.dispatch(DashboardGetData)), - untilDestroyed(this), - ) - .subscribe(); + this.resetTimer(); } private listenToNodeChanging(): void { this.select(AppSelectors.activeNode, () => { this.dispatch(DashboardGetData, { force: true }); - }, filter(Boolean), skip(1), untilDestroyed(this)); + }, filter(Boolean), skip(1)); + this.select(AppSelectors.activeNodeDetails, (details: AppNodeDetails) => { + if (this.lastStatus !== details.status) { + this.lastStatus = details.status; + this.resetTimer(); + } + }); + } + + private resetTimer(): void { + this.timer?.unsubscribe(); + const timerInterval = this.lastStatus === AppNodeStatus.SYNCED || this.lastStatus === AppNodeStatus.OFFLINE ? 5000 : 1000; + this.timer = timer(timerInterval, timerInterval) + .pipe( + tap(() => this.dispatch(DashboardGetData)), + untilDestroyed(this), + ).subscribe(); } + + private listenToPeersChanges(): void { + this.select(selectDashboardPeersStats, (stats: DashboardPeersStats) => { + if (this.connected && stats.connected === 0 || !this.connected) { + if (stats.connected > 0) { + this.updateAction = 'Downloading Staking Epoch Ledger'; + } else if (stats.connecting > 0) { + this.updateAction = `Connecting to ${stats.connecting} peer${stats.connecting !== 1 ? 's' : ''}`; + } else { + this.updateAction = 'Looking for peers'; + } + if (stats.connected) { + this.connectedProgress = 33; + } else { + this.connectedProgress = 0; + this.ledgerProgress = 0; + this.blockSyncProgress = 0; + } + this.detect(); + } + this.connected = stats.connected > 0; + }, skip(1)); + } + + private listenToNodesChanges(): void { + this.select(selectDashboardNodesAndRpcStats, ([nodes, rpcStats]: [NodesOverviewNode[], DashboardRpcStats]) => { + if (nodes.length !== 0) { + const ledgers = nodes[0].ledgers; + + let stakingProgress = rpcStats.stakingLedger?.fetched / rpcStats.stakingLedger?.estimation * 100 || 0; + let nextProgress = rpcStats.nextLedger?.fetched / rpcStats.nextLedger?.estimation * 100 || 0; + let rootSnarkedProgress = rpcStats.snarkedRootLedger?.fetched / rpcStats.snarkedRootLedger?.estimation * 100 || 0; + let rootStagedProgress = rpcStats.stagedRootLedger?.fetched / rpcStats.stagedRootLedger?.estimation * 100 || 0; + + if (ledgers.stakingEpoch.state === NodesOverviewLedgerStepState.SUCCESS) { + stakingProgress = 100; + this.updateAction = 'Downloading Next Epoch Ledger'; + } + if (ledgers.nextEpoch.state === NodesOverviewLedgerStepState.SUCCESS) { + nextProgress = 100; + this.updateAction = 'Downloading Root Snarked Ledger'; + } + if (ledgers.rootSnarked.state === NodesOverviewLedgerStepState.SUCCESS) { + rootSnarkedProgress = 100; + this.updateAction = 'Downloading Root Staged Ledger'; + } + if (ledgers.rootStaged.state === NodesOverviewLedgerStepState.SUCCESS) { + rootStagedProgress = 100; + this.updateAction = 'Fetching Blocks'; + } + const ledgerProgressTotal = (stakingProgress + nextProgress + rootSnarkedProgress + rootStagedProgress) / 4; + this.ledgerProgress = ledgerProgressTotal * 0.33; + if (ledgerProgressTotal !== 100) { + this.blockSyncProgress = 0; + this.detect(); + return; + } + + let blocks = nodes[0].blocks; + + blocks = blocks.slice(1); + + const fetched = blocks.filter(b => ![NodesOverviewNodeBlockStatus.MISSING, NodesOverviewNodeBlockStatus.FETCHING].includes(b.status)).length; + const fetchedPercentage = Math.round(fetched * 100 / 291); + + const applied = blocks.filter(b => b.status === NodesOverviewNodeBlockStatus.APPLIED).length; + const appliedPercentage = Math.round(applied * 100 / 291); + this.blockSyncProgress = appliedPercentage * 0.34; + + if (fetchedPercentage < 100) { + this.updateAction = `Fetching Blocks (${fetchedPercentage}%)`; + } else if (appliedPercentage < 100) { + this.updateAction = `Applying Blocks (${appliedPercentage}%)`; + } else { + this.updateAction = ''; + } + + } else { + this.ledgerProgress = 0; + this.blockSyncProgress = 0; + } + this.detect(); + }); + } + + override ngOnDestroy(): void { + super.ngOnDestroy(); + } + + // cleanup() { + // document.getElementById('mina-content').style.borderTopLeftRadius = '6px'; + // } } diff --git a/frontend/src/app/features/dashboard/dashboard.module.ts b/frontend/src/app/features/dashboard/dashboard.module.ts index e06adfdf47..ed91563182 100644 --- a/frontend/src/app/features/dashboard/dashboard.module.ts +++ b/frontend/src/app/features/dashboard/dashboard.module.ts @@ -9,13 +9,9 @@ import { LoadingSpinnerComponent } from '@shared/loading-spinner/loading-spinner import { CopyComponent } from '@openmina/shared'; import { DashboardNetworkComponent } from './dashboard-network/dashboard-network.component'; import { DashboardLedgerComponent } from './dashboard-ledger/dashboard-ledger.component'; -import { - DashboardTransitionFrontierComponent, -} from './dashboard-transition-frontier/dashboard-transition-frontier.component'; import { DashboardBlocksSyncComponent } from './dashboard-blocks-sync/dashboard-blocks-sync.component'; -import { - DashboardPeersMinimalTableComponent, -} from './dashboard-peers-minimal-table/dashboard-peers-minimal-table.component'; +import { DashboardPeersMinimalTableComponent } from './dashboard-peers-minimal-table/dashboard-peers-minimal-table.component'; +import { BlockProductionPillComponent } from '@app/layout/block-production-pill/block-production-pill.component'; @NgModule({ @@ -23,7 +19,6 @@ import { DashboardComponent, DashboardNetworkComponent, DashboardLedgerComponent, - DashboardTransitionFrontierComponent, DashboardBlocksSyncComponent, DashboardPeersMinimalTableComponent, ], @@ -33,6 +28,7 @@ import { EffectsModule.forFeature(DashboardEffects), LoadingSpinnerComponent, CopyComponent, + BlockProductionPillComponent, ], }) export class DashboardModule {} diff --git a/frontend/src/app/features/dashboard/dashboard.reducer.ts b/frontend/src/app/features/dashboard/dashboard.reducer.ts index 7ee9016523..0d9a23b56d 100644 --- a/frontend/src/app/features/dashboard/dashboard.reducer.ts +++ b/frontend/src/app/features/dashboard/dashboard.reducer.ts @@ -24,7 +24,8 @@ const initialState: DashboardState = { peerResponses: [], stakingLedger: null, nextLedger: null, - rootLedger: null, + snarkedRootLedger: null, + stagedRootLedger: null, }, nodeBootstrappingPercentage: 0, appliedBlocks: 0, diff --git a/frontend/src/app/features/dashboard/dashboard.service.ts b/frontend/src/app/features/dashboard/dashboard.service.ts index 889502a595..507f5c89c3 100644 --- a/frontend/src/app/features/dashboard/dashboard.service.ts +++ b/frontend/src/app/features/dashboard/dashboard.service.ts @@ -52,19 +52,23 @@ export class DashboardService { ); } - private mapMessageProgressResponse(response: MessageProgressResponse): DashboardRpcStats { - const peerResponses = Object.keys(response.messages_stats).map(peerId => ({ + private mapMessageProgressResponse(progress: MessageProgressResponse): DashboardRpcStats { + const peerResponses = Object.keys(progress.messages_stats).map(peerId => ({ peerId, requestsMade: Object - .keys(response.messages_stats[peerId].responses) - .reduce((sum: number, curr: string) => sum + response.messages_stats[peerId].responses[curr], 0), + .keys(progress.messages_stats[peerId].responses) + .reduce((sum: number, curr: string) => sum + progress.messages_stats[peerId].responses[curr], 0), } as DashboardPeerRpcResponses)); return { peerResponses, - stakingLedger: response.staking_ledger_sync, - nextLedger: response.next_epoch_ledger_sync, - rootLedger: response.root_ledger_sync, + stakingLedger: progress.staking_ledger_sync, + nextLedger: progress.next_epoch_ledger_sync, + snarkedRootLedger: progress.root_ledger_sync, + stagedRootLedger: { + fetched: progress.root_ledger_sync?.staged?.fetched, + estimation: progress.root_ledger_sync?.staged?.total, + }, }; } } @@ -84,7 +88,12 @@ export interface MessageProgressResponse { messages_stats: MessagesStats; staking_ledger_sync: Estimation; next_epoch_ledger_sync: Estimation; - root_ledger_sync: Estimation; + root_ledger_sync: Estimation & { + staged: { + fetched: number; + total: number; + } + }; } export interface MessagesStats { diff --git a/frontend/src/app/features/dashboard/dashboard.state.ts b/frontend/src/app/features/dashboard/dashboard.state.ts index 5e002c51e7..f84b91456c 100644 --- a/frontend/src/app/features/dashboard/dashboard.state.ts +++ b/frontend/src/app/features/dashboard/dashboard.state.ts @@ -3,7 +3,6 @@ import { createFeatureSelector, createSelector, MemoizedSelector } from '@ngrx/s import { MinaState } from '@app/app.setup'; import { DashboardPeersStats } from '@shared/types/dashboard/dashboard-peers-stats.type'; import { TableSort } from '@openmina/shared'; -import { DashboardPeersSort } from '@dashboard/dashboard.actions'; import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; import { DashboardRpcStats } from '@shared/types/dashboard/dashboard-rpc-stats.type'; @@ -33,5 +32,4 @@ export const selectDashboardPeersStats = select((state: DashboardState): Dashboa export const selectDashboardPeersSort = select((state: DashboardState): TableSort => state.peersSort); export const selectDashboardNodes = select((state: DashboardState): NodesOverviewNode[] => state.nodes); export const selectDashboardNodesAndPeers = select((state: DashboardState): [NodesOverviewNode[], DashboardPeer[]] => [state.nodes, state.peers]); -export const selectDashboardRpcStats = select((state: DashboardState): DashboardRpcStats => state.rpcStats); export const selectDashboardNodesAndRpcStats = select((state: DashboardState): [NodesOverviewNode[], DashboardRpcStats] => [state.nodes, state.rpcStats]); diff --git a/frontend/src/app/features/dashboard/mina-card/mina-card.component.html b/frontend/src/app/features/dashboard/mina-card/mina-card.component.html deleted file mode 100644 index a83c6194b1..0000000000 --- a/frontend/src/app/features/dashboard/mina-card/mina-card.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
- {{ label }} - {{ icon }} -
-
{{ value ?? '-' }}
-
{{ hint }}
diff --git a/frontend/src/app/features/dashboard/mina-card/mina-card.component.scss b/frontend/src/app/features/dashboard/mina-card/mina-card.component.scss deleted file mode 100644 index a5a87f8b45..0000000000 --- a/frontend/src/app/features/dashboard/mina-card/mina-card.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -:host { - height: 128px; - width: 128px; - - @media (max-width: 700px) { - height: 140px; - width: 140px; - } -} - -.mina-icon { - margin-left: 3px; -} - -.value { - font-size: 24px; - letter-spacing: 0.15px; -} diff --git a/frontend/src/app/features/dashboard/mina-card/mina-card.component.ts b/frontend/src/app/features/dashboard/mina-card/mina-card.component.ts deleted file mode 100644 index 9681cbd29a..0000000000 --- a/frontend/src/app/features/dashboard/mina-card/mina-card.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; - -@Component({ - selector: 'mina-card', - templateUrl: './mina-card.component.html', - styleUrls: ['./mina-card.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'flex-column flex-between pt-8 pb-8 pl-12 border-rad-8 bg-surface' }, -}) -export class MinaCardComponent { - - @Input() color: string = 'var(--base-primary)'; - @Input() labelColor: string = 'var(--base-primary)'; - @Input() hintColor: string = 'var(--base-tertiary)'; - @Input() icon: string; - @Input() label: string | number; - @Input() value: string | number; - @Input() hint: string | number; - @Input() tooltipText: string; - -} diff --git a/frontend/src/app/features/mempool/mempool.service.ts b/frontend/src/app/features/mempool/mempool.service.ts index 649d0ff0c1..94f302a8fb 100644 --- a/frontend/src/app/features/mempool/mempool.service.ts +++ b/frontend/src/app/features/mempool/mempool.service.ts @@ -8,7 +8,7 @@ import { ZkappCommand, } from '@shared/types/mempool/mempool-transaction.type'; import { decodeMemo, removeUnicodeEscapes } from '@shared/helpers/transaction.helper'; -import { ONE_BILLION } from '@openmina/shared'; +import { getLocalStorage, ONE_BILLION } from '@openmina/shared'; @Injectable({ providedIn: 'root', @@ -31,7 +31,7 @@ export class MempoolService { const memo = decodeMemo(tx.data[1].payload.common.memo); return { kind: MempoolTransactionKind.PAYMENT, - txHash: tx.hash.join(''), + txHash: tx.hash, sender: tx.data[1].payload.common.fee_payer_pk, fee: Number(tx.data[1].payload.common.fee), amount: Number(tx.data[1].payload.body[1].amount) / ONE_BILLION, @@ -39,14 +39,14 @@ export class MempoolService { memo: removeUnicodeEscapes(memo), transactionData: tx.data[1], sentFromStressingTool: memo.includes('S.T.'), - sentByMyBrowser: memo.includes(localStorage.getItem('browserId')), + sentByMyBrowser: memo.includes(getLocalStorage()?.getItem('browserId')), } as MempoolTransaction; case MempoolTransactionResponseKind.ZkappCommand: const zkapp = tx.data[1] as ZkappCommand; const zkMemo = decodeMemo(zkapp.memo); return { kind: MempoolTransactionKind.ZK_APP, - txHash: tx.hash.join(''), + txHash: tx.hash, sender: zkapp.fee_payer.body.public_key, fee: Number(zkapp.fee_payer.body.fee), amount: null, @@ -54,7 +54,7 @@ export class MempoolService { memo: removeUnicodeEscapes(zkMemo), transactionData: tx.data[1], sentFromStressingTool: zkMemo.includes('S.T.'), - sentByMyBrowser: zkMemo.includes(localStorage.getItem('browserId')), + sentByMyBrowser: zkMemo.includes(getLocalStorage()?.getItem('browserId')), } as MempoolTransaction; } }); @@ -64,7 +64,7 @@ export class MempoolService { export interface MempoolTransactionResponse { data: [MempoolTransactionResponseKind, SignedCommand | ZkappCommand]; - hash: number[]; + hash: string; } export enum MempoolTransactionResponseKind { diff --git a/frontend/src/app/features/network/messages/network-messages-side-panel/network-messages-side-panel.component.ts b/frontend/src/app/features/network/messages/network-messages-side-panel/network-messages-side-panel.component.ts index a8e044d207..5b90dbc1b3 100644 --- a/frontend/src/app/features/network/messages/network-messages-side-panel/network-messages-side-panel.component.ts +++ b/frontend/src/app/features/network/messages/network-messages-side-panel/network-messages-side-panel.component.ts @@ -8,7 +8,7 @@ import { selectNetworkFullMessage, selectNetworkMessageHex, } from '@network/messages/network-messages.state'; -import { downloadJson, downloadJsonFromURL, ExpandTracking, MinaJsonViewerComponent } from '@openmina/shared'; +import { downloadJson, downloadJsonFromURL, ExpandTracking, MinaJsonViewerComponent, safelyExecuteInBrowser } from '@openmina/shared'; import { filter } from 'rxjs'; import { Router } from '@angular/router'; import { Routes } from '@shared/enums/routes.enum'; @@ -154,6 +154,8 @@ export class NetworkMessagesSidePanelComponent extends StoreDispatcher implement } copyToClipboard(): void { - this.clipboard.copy(window.location.href); + safelyExecuteInBrowser(() => { + this.clipboard.copy(window.location.href); + }); } } diff --git a/frontend/src/app/features/network/splits/dashboard-splits-graph/dashboard-splits-graph.component.ts b/frontend/src/app/features/network/splits/dashboard-splits-graph/dashboard-splits-graph.component.ts index 755b73c63e..1f290c00dc 100644 --- a/frontend/src/app/features/network/splits/dashboard-splits-graph/dashboard-splits-graph.component.ts +++ b/frontend/src/app/features/network/splits/dashboard-splits-graph/dashboard-splits-graph.component.ts @@ -16,6 +16,7 @@ import { DashboardSplitsSet } from '@shared/types/network/splits/dashboard-split import { DashboardSplitsSetActivePeer } from '@network/splits/dashboard-splits.actions'; import { eigs } from 'mathjs'; import * as math from 'mathjs'; +import { getLocalStorage } from '@openmina/shared'; type DashboardSplitsPeerSimulation = DashboardSplitsPeer & SimulationNodeDatum; type DashboardSplitsLinkSimulation = { @@ -128,7 +129,7 @@ export class DashboardSplitsGraphComponent extends StoreDispatcher implements On console.log('Laplacian Matrix:', laplacianMatrix); // check is same as localstorage - let item = localStorage.getItem('laplacianMatrix'); + let item = getLocalStorage()?.getItem('laplacianMatrix'); if (item) { let storedMatrix = JSON.parse(item); let storedMatrixString = JSON.stringify(storedMatrix); @@ -139,7 +140,7 @@ export class DashboardSplitsGraphComponent extends StoreDispatcher implements On console.log('Laplacian Matrix is different from the stored one'); } } - localStorage.setItem('laplacianMatrix', JSON.stringify(laplacianMatrix)); + getLocalStorage()?.setItem('laplacianMatrix', JSON.stringify(laplacianMatrix)); const computeEigenvalues = async (matrix: number[][]): Promise => { return eigs(matrix).values as number[]; diff --git a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss index 60c7f13cc0..d747628141 100644 --- a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss +++ b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss @@ -8,7 +8,7 @@ border-bottom: 1px solid $base-tertiary; &:last-child { - min-width: 600px; + min-width: 510px; } } } diff --git a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts index efc1983514..d720f69351 100644 --- a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts +++ b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts @@ -37,11 +37,6 @@ export class NodesOverviewTableComponent extends MinaTableRustWrapper implements OnDestroy { activeResource: MemoryResource; - tooltipWidth: number = Math.max(window.innerWidth - 40, 1500); + tooltipWidth: number = isBrowser() ? Math.max(window.innerWidth - 40, 1500) : 0; position: TooltipPosition = TooltipPosition.RIGHT; protected readonly tableHeads: TableColumnList = [ diff --git a/frontend/src/app/features/resources/memory/memory-resources-treemap/memory-resources-treemap.component.ts b/frontend/src/app/features/resources/memory/memory-resources-treemap/memory-resources-treemap.component.ts index 6cf4e6ef52..538d5f0777 100644 --- a/frontend/src/app/features/resources/memory/memory-resources-treemap/memory-resources-treemap.component.ts +++ b/frontend/src/app/features/resources/memory/memory-resources-treemap/memory-resources-treemap.component.ts @@ -32,7 +32,7 @@ import { ResourcesSizePipe } from '@resources/memory/memory-resources.pipe'; import { MemoryResourcesSetActiveResource } from '@resources/memory/memory-resources.actions'; import { untilDestroyed } from '@ngneat/until-destroy'; import { TreemapView } from '@shared/types/resources/memory/treemap-view.type'; -import { isDesktop, TooltipService } from '@openmina/shared'; +import { isDesktop, safelyExecuteInBrowser, TooltipService } from '@openmina/shared'; import { AppSelectors } from '@app/app.state'; @Component({ @@ -83,9 +83,11 @@ export class MemoryResourcesTreemapComponent extends StoreDispatcher implements private listenToResizeEvent(): void { this.ngZone.runOutsideAngular(() => { - fromEvent(window, 'resize') - .pipe(untilDestroyed(this), debounceTime(200)) - .subscribe(() => this.redrawChart()); + safelyExecuteInBrowser(() => + fromEvent(window, 'resize') + .pipe(untilDestroyed(this), debounceTime(200)) + .subscribe(() => this.redrawChart()), + ); this.select( AppSelectors.menu, () => this.redrawChart(), @@ -358,7 +360,10 @@ export class MemoryResourcesTreemapComponent extends StoreDispatcher implements const x = event.clientX - tooltipWidth / 2 + xMargin; const y = event.clientY + yMargin; - const maxX = window.innerWidth - tooltipWidth - xMargin; + let maxX = 0; + safelyExecuteInBrowser(() => { + maxX = window.innerWidth - tooltipWidth - xMargin; + }); if (x > maxX) { this.tooltip.style.left = maxX + 'px'; } else { diff --git a/frontend/src/app/features/resources/memory/memory-resources.reducer.ts b/frontend/src/app/features/resources/memory/memory-resources.reducer.ts index 9d4978bbcb..df61e6ff4e 100644 --- a/frontend/src/app/features/resources/memory/memory-resources.reducer.ts +++ b/frontend/src/app/features/resources/memory/memory-resources.reducer.ts @@ -8,13 +8,14 @@ import { MemoryResourcesActions, } from '@resources/memory/memory-resources.actions'; import { TreemapView } from '@shared/types/resources/memory/treemap-view.type'; +import { getLocalStorage, nanOrElse } from '@openmina/shared'; const initialState: MemoryResourcesState = { resource: undefined, activeResource: undefined, breadcrumbs: [], - granularity: Number(localStorage.getItem('memory-granularity')) || 512, - treemapView: localStorage.getItem('memory-view') as TreemapView || TreemapView.BINARY, + granularity: nanOrElse(Number(getLocalStorage()?.getItem('memory-granularity')), 512), + treemapView: getLocalStorage()?.getItem('memory-view') as TreemapView || TreemapView.BINARY, }; export function memoryResourcesReducer(state: MemoryResourcesState = initialState, action: MemoryResourcesActions): MemoryResourcesState { @@ -51,7 +52,7 @@ export function memoryResourcesReducer(state: MemoryResourcesState = initialStat } case MEMORY_RESOURCES_SET_GRANULARITY: { - localStorage.setItem('memory-granularity', String(action.payload)); + getLocalStorage()?.setItem('memory-granularity', String(action.payload)); return { ...state, granularity: action.payload, @@ -62,7 +63,7 @@ export function memoryResourcesReducer(state: MemoryResourcesState = initialStat } case MEMORY_RESOURCES_SET_TREEMAP_VIEW: { - localStorage.setItem('memory-view', String(action.payload)); + getLocalStorage()?.setItem('memory-view', String(action.payload)); return { ...state, treemapView: action.payload, diff --git a/frontend/src/app/features/snarks/scan-state/scan-state-side-panel/scan-state-side-panel.component.ts b/frontend/src/app/features/snarks/scan-state/scan-state-side-panel/scan-state-side-panel.component.ts index 68ca64921b..da9442c169 100644 --- a/frontend/src/app/features/snarks/scan-state/scan-state-side-panel/scan-state-side-panel.component.ts +++ b/frontend/src/app/features/snarks/scan-state/scan-state-side-panel/scan-state-side-panel.component.ts @@ -11,6 +11,7 @@ import { TemplatePortal } from '@angular/cdk/portal'; import { ScanStateLeaf } from '@shared/types/snarks/scan-state/scan-state-leaf.type'; import { AppSelectors } from '@app/app.state'; import { getFeaturesConfig } from '@shared/constants/config'; +import { getWindow } from '@openmina/shared'; @Component({ selector: 'mina-scan-state-side-panel', @@ -124,8 +125,8 @@ export class ScanStateSidePanelComponent extends StoreDispatcher implements OnIn goToWorkPool(): void { const queryParams = this.router.parseUrl(this.router.url).queryParams; - let url = `${window.location.origin}/${Routes.SNARKS}/${Routes.WORK_POOL}/${this.activeLeaf.bundle_job_id}`; + let url = `${getWindow()?.location.origin}/${Routes.SNARKS}/${Routes.WORK_POOL}/${this.activeLeaf.bundle_job_id}`; url += `?node=${queryParams['node']}`; - window.open(url, '_blank'); + getWindow()?.open(url, '_blank'); } } diff --git a/frontend/src/app/features/snarks/scan-state/scan-state-tree-chart/scan-state-tree-chart.component.ts b/frontend/src/app/features/snarks/scan-state/scan-state-tree-chart/scan-state-tree-chart.component.ts index 3ef320c3cc..61b9bfe01b 100644 --- a/frontend/src/app/features/snarks/scan-state/scan-state-tree-chart/scan-state-tree-chart.component.ts +++ b/frontend/src/app/features/snarks/scan-state/scan-state-tree-chart/scan-state-tree-chart.component.ts @@ -4,7 +4,7 @@ import { ScanStateTree } from '@shared/types/snarks/scan-state/scan-state-tree.t import { ScanStateLeaf, ScanStateLeafStatus } from '@shared/types/snarks/scan-state/scan-state-leaf.type'; import { debounceTime, delay, distinctUntilChanged, filter, fromEvent, skip, tap } from 'rxjs'; import { untilDestroyed } from '@ngneat/until-destroy'; -import { any, hasValue, isMobile } from '@openmina/shared'; +import { any, hasValue, isMobile, safelyExecuteInBrowser } from '@openmina/shared'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { ScanStateSetActiveLeaf } from '@snarks/scan-state/scan-state.actions'; import { Router } from '@angular/router'; @@ -110,9 +110,11 @@ export class ScanStateTreeChartComponent extends StoreDispatcher implements OnIn } private handleResizing(): void { - fromEvent(window, 'resize') - .pipe(untilDestroyed(this), debounceTime(200)) - .subscribe(() => this.redrawChart()); + safelyExecuteInBrowser(() => { + fromEvent(window, 'resize') + .pipe(untilDestroyed(this), debounceTime(200)) + .subscribe(() => this.redrawChart()); + }); this.select(selectScanStateSideBarResized, () => this.redrawChart(), filter(Boolean)); this.select(selectScanStateOpenSidePanel, () => this.redrawChart(), delay(400), skip(1)); this.select(AppSelectors.menu, () => this.redrawChart(), @@ -372,9 +374,11 @@ export class ScanStateTreeChartComponent extends StoreDispatcher implements OnIn const chartLeft = this.chartContainer.nativeElement.getBoundingClientRect().left; let desiredLeft = Math.min(nodeRect.left + nodeRect.width / 2 - tooltipWidth / 2, chartLeft + this.width - tooltipWidth); desiredLeft = Math.max(desiredLeft, chartLeft); - selection - .style('left', `${desiredLeft}px`) - .style('top', `${nodeRect.bottom + window.scrollY + 12}px`); + safelyExecuteInBrowser(() => { + selection + .style('left', `${desiredLeft}px`) + .style('top', `${nodeRect.bottom + window.scrollY + 12}px`); + }); } private mouseOutHandler(event: MouseEvent & { target: HTMLElement }, d: any): void { diff --git a/frontend/src/app/features/snarks/scan-state/scan-state.reducer.ts b/frontend/src/app/features/snarks/scan-state/scan-state.reducer.ts index d78d12936c..1c7832a573 100644 --- a/frontend/src/app/features/snarks/scan-state/scan-state.reducer.ts +++ b/frontend/src/app/features/snarks/scan-state/scan-state.reducer.ts @@ -10,9 +10,9 @@ import { SCAN_STATE_START, SCAN_STATE_TOGGLE_SIDE_PANEL, SCAN_STATE_TOGGLE_TREE_VIEW, - ScanStateActions + ScanStateActions, } from '@snarks/scan-state/scan-state.actions'; -import { isDesktop } from '@openmina/shared'; +import { getLocalStorage, isDesktop } from '@openmina/shared'; const initialState: ScanStateState = { block: undefined, @@ -21,7 +21,7 @@ const initialState: ScanStateState = { openSidePanel: isDesktop(), sideBarResized: 0, stream: true, - treeView: JSON.parse(localStorage.getItem('scan_state_tree_view')) || false, + treeView: JSON.parse(getLocalStorage()?.getItem('scan_state_tree_view') ?? 'false') || false, highlightSnarkPool: true, }; @@ -90,7 +90,7 @@ export function reducer(state: ScanStateState = initialState, action: ScanStateA } case SCAN_STATE_TOGGLE_TREE_VIEW: { - localStorage.setItem('scan_state_tree_view', JSON.stringify(!state.treeView)); + getLocalStorage()?.setItem('scan_state_tree_view', JSON.stringify(!state.treeView)); return { ...state, diff --git a/frontend/src/app/features/snarks/work-pool/snarks-work-pool-side-panel/snarks-work-pool-side-panel.component.ts b/frontend/src/app/features/snarks/work-pool/snarks-work-pool-side-panel/snarks-work-pool-side-panel.component.ts index cc2a04989a..3bc9a53d3c 100644 --- a/frontend/src/app/features/snarks/work-pool/snarks-work-pool-side-panel/snarks-work-pool-side-panel.component.ts +++ b/frontend/src/app/features/snarks/work-pool/snarks-work-pool-side-panel/snarks-work-pool-side-panel.component.ts @@ -7,6 +7,7 @@ import { Router } from '@angular/router'; import { Routes } from '@shared/enums/routes.enum'; import { Overlay, OverlayRef } from '@angular/cdk/overlay'; import { TemplatePortal } from '@angular/cdk/portal'; +import { getWindow } from '@openmina/shared'; @Component({ selector: 'mina-snarks-work-pool-side-panel', @@ -89,9 +90,9 @@ export class SnarksWorkPoolSidePanelComponent extends StoreDispatcher implements goToScanState(): void { const queryParams = this.router.parseUrl(this.router.url).queryParams; const jobId = this.router.url.split('/').pop().split('?')[0]; - let url = `${window.location.origin}/${Routes.SNARKS}/${Routes.SCAN_STATE}`; + let url = `${getWindow()?.location.origin}/${Routes.SNARKS}/${Routes.SCAN_STATE}`; url += `?node=${queryParams['node']}`; url += `&jobId=${jobId}`; - window.open(url, '_blank'); + getWindow()?.open(url, '_blank'); } } diff --git a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.html b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.html new file mode 100644 index 0000000000..5fac9c6f20 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.html @@ -0,0 +1,52 @@ +
+ +
+
+
+
+
+ Produce blocks, + right in your browser +
+ +
{{ loadingMessage || downloadingMessage }} +
+
+ +
+ +
+ @for (item of loading; track $index) { +
+ @if (item.status === WebNodeStepStatus.LOADING) { + + } @else if (item.status === WebNodeStepStatus.PENDING) { + panorama_fish_eye + } @else { + check_circle + } +
{{ item.name }} +
+
+ } +
+
+ +
+ diff --git a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss new file mode 100644 index 0000000000..d81a6a14e8 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.scss @@ -0,0 +1,102 @@ +@import 'openmina'; + +$green: #59bfb5; +$light-peach: #0d0d0d; +$peach: #acdea0; +$sand: #5bb3fb; +$white: #000000; + +.font-16 { + font-size: 16px; +} + +.logo-header { + height: 56px; + + img { + opacity: 0.5; + } +} + +.data-wrapper { + height: calc(100% - 56px - 72px); + + .loading-content { + max-width: 568px; + + .header { + .loading-webnode { + font-size: 20px; + line-height: 30px; + } + } + + mina-loading-spinner { + margin: 0 2px; + } + + .mina-icon.circle-check { + font-variation-settings: 'FILL' 1, 'wght' 300 !important; + } + + .mina-icon.aware-primary { + animation: fadeIn 1500ms ease-in-out infinite; + } + + .progress { + min-height: 256px; + @media (max-width: 768px) { + min-height: 30vh; + } + } + } +} + +.footer { + height: 72px; + max-width: 568px; + + button { + width: 158px; + height: 48px !important; + background-color: $base-background; + color: $base-primary; + filter: invert(1); + + &.disabled { + opacity: 0.25; + pointer-events: none; + } + } + + @media (max-width: 768px) { + border: none !important; + > div { + display: none; + } + button { + width: 100%; + } + } +} + +@keyframes fadeIn { + 10% { + opacity: 1; + } + 38% { + opacity: 0.1; + } + 42% { + opacity: 0.1; + } + 70% { + opacity: 1; + } +} + +@media (max-width: 568px) { + .loading-webnode span { + display: block; + } +} diff --git a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts new file mode 100644 index 0000000000..2c563d5c13 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts @@ -0,0 +1,366 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { untilDestroyed } from '@ngneat/until-destroy'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { WebNodeService } from '@core/services/web-node.service'; +import { any, GlobalErrorHandlerService, safelyExecuteInBrowser } from '@openmina/shared'; +import { NgClass, NgForOf, NgIf, NgOptimizedImage } from '@angular/common'; +import { Router } from '@angular/router'; +import { CONFIG, getFirstFeature } from '@shared/constants/config'; +import { animate, style, transition, trigger } from '@angular/animations'; +import { filter, switchMap, timer } from 'rxjs'; +import { LoadingSpinnerComponent } from '@shared/loading-spinner/loading-spinner.component'; +import { FileProgressHelper } from '@core/helpers/file-progress.helper'; +import * as d3 from 'd3'; + +export enum WebNodeStepStatus { + DONE, + LOADING, + PENDING, +} + +export interface WebNodeLoadingStep { + name: string; + loaded: boolean; + status: WebNodeStepStatus; +} + +@Component({ + selector: 'mina-web-node-initialization', + templateUrl: './web-node-initialization.component.html', + styleUrls: ['./web-node-initialization.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column h-100 w-100 align-center' }, + standalone: true, + imports: [ + NgClass, + NgIf, + NgForOf, + NgOptimizedImage, + LoadingSpinnerComponent, + ], + animations: [ + trigger('messageChange', [ + transition('* => *', [ + style({ opacity: 0, transform: 'translateY(-10px)' }), + animate('300ms ease-out', style({ opacity: 1, transform: 'translateY(0)' })), + ]), + ]), + ], +}) +export class WebNodeInitializationComponent extends StoreDispatcher implements OnInit, AfterViewInit { + + protected readonly WebNodeStepStatus = WebNodeStepStatus; + readonly loading: WebNodeLoadingStep[] = [ + { name: 'Setting up browser for Web Node', loaded: false, status: WebNodeStepStatus.LOADING }, + { name: 'Getting ready to produce blocks', loaded: false, status: WebNodeStepStatus.PENDING }, + { name: 'Connecting directly to Mina network', loaded: false, status: WebNodeStepStatus.PENDING }, + ]; + loadingMessage: string = ''; + downloadingMessage: string = ''; + ready: boolean = false; + hasError: boolean = false; + hasWarn: boolean = false; + errors: string[] = []; + + private stepsPercentages: number[]; + private secondStepInterval: any; + private thirdStepInterval: any; + private progress: number = 0; + private svg: any; + private progressBar: any; + private arc: any; + @ViewChild('progress', { static: true }) private chartContainer: ElementRef; + + constructor(private errorHandler: GlobalErrorHandlerService, + private webNodeService: WebNodeService, + private router: Router) { super(); } + + ngOnInit(): void { + if (!this.webNodeService.hasWebNodeConfig()) { + this.router.navigate([getFirstFeature()]); + return; + } + safelyExecuteInBrowser(() => { + window.dispatchEvent(new CustomEvent('startWebNode')); + }); + this.stepsPercentages = this.getStepPercentages(); + if (this.webNodeService.isWebNodeLoaded()) { + this.loading.forEach((step: WebNodeLoadingStep) => { + step.loaded = true; + step.status = WebNodeStepStatus.DONE; + }); + this.ready = true; + return; + } + this.fetchProgress(); + this.listenToErrorIssuing(); + this.checkWebNodeProgress(); + this.fetchPeersInformation(); + } + + ngAfterViewInit(): void { + this.buildProgressBar(); + } + + private checkWebNodeProgress(): void { + this.webNodeService.webnodeProgress$.pipe(untilDestroyed(this)).subscribe((state: string) => { + if (state === 'Loaded') { + this.updateLoadingMessage('~5 seconds left'); + setTimeout(() => { + if (!this.hasError && this.loading[1].status !== WebNodeStepStatus.DONE) { + this.updateLoadingMessage('Slower than usual'); + this.hasWarn = true; + this.detect(); + } + }, 5000); + this.loading[0].loaded = true; + this.loading[0].status = WebNodeStepStatus.DONE; + this.loading[1].status = WebNodeStepStatus.LOADING; + this.advanceProgressFor2ndStep(); + } else if (state === 'Started') { + this.updateLoadingMessage('~3 seconds left'); + setTimeout(() => { + if (!this.hasError && this.loading[2].status !== WebNodeStepStatus.DONE) { + this.updateLoadingMessage('Slower than usual'); + this.hasWarn = true; + this.detect(); + } + }, 3500); + clearInterval(this.secondStepInterval); + this.loading[0].loaded = true; + this.loading[1].loaded = true; + this.loading[0].status = WebNodeStepStatus.DONE; + this.loading[1].status = WebNodeStepStatus.DONE; + this.loading[2].status = WebNodeStepStatus.LOADING; + this.advanceProgressFor3rdStep(); + } else if (state === 'Connected') { + this.updateLoadingMessage('Web Node is ready'); + clearInterval(this.thirdStepInterval); + this.loading[0].status = WebNodeStepStatus.DONE; + this.loading[1].status = WebNodeStepStatus.DONE; + this.loading[2].status = WebNodeStepStatus.DONE; + this.loading.forEach((step: WebNodeLoadingStep) => step.loaded = true); + this.goToEndProgress(); + } + this.detect(); + }); + } + + private getStepPercentages(): number[] { + // random between 65 and 80 + const first = Math.floor(Math.random() * 15) + 65; + // random between 15 and 17 + const second = Math.floor(Math.random() * 2) + 15; + const third = 100 - first - second; + return [first, second, third]; + } + + private advanceProgressFor2ndStep(): void { + // first step is done, now we are working on the second step. + // each second add 1% to the progress. + // but the second step is only 10% of the total progress. + // so never go above 10%. Stop at 9% if the second step is not done yet. + let progress = 0; + this.secondStepInterval = setInterval(() => { + if (progress < this.stepsPercentages[1] - 1) { + progress += 0.125; + this.updateProgressBar(this.stepsPercentages[0] + progress); + } + }, 75); + } + + private async advanceProgressFor3rdStep(): Promise { + // second step is done, now we are working on the third step. + // each second add 1% to the progress. + // but the third step is only 10% of the total progress. + // so never go above 10%. Stop at 9% if the third step is not done yet. + + const currentProgress = this.progress; + const targetProgress = this.stepsPercentages[0] + this.stepsPercentages[1] - 1; + // run fast 5 increments to reach the target progress + const diff = targetProgress - currentProgress; + + for (let i = 0; i < diff; i++) { + await new Promise(resolve => setTimeout(resolve, 25)); + this.updateProgressBar(currentProgress + i); + this.detect(); + } + + let progress = 0; + this.thirdStepInterval = setInterval(() => { + if (progress < this.stepsPercentages[2] - 1) { + progress += 0.125; + this.updateProgressBar(this.stepsPercentages[0] + this.stepsPercentages[1] + progress); + } + }, 75); + } + + private goToEndProgress(): void { + // increase it to 100% to make sure it's done. + // make it smooth, do a lot of increases from where it left + // to make it look like it's finishing. + + let progress = this.progress; + let interval = setInterval(() => { + if (progress <= 100) { + progress += 1; + this.updateProgressBar(progress); + } else { + clearInterval(interval); + } + }, 30); + setTimeout(() => this.detect(), 500); + } + + private fetchPeersInformation(): void { + timer(0, 1000).pipe( + switchMap(() => this.webNodeService.peers$), + untilDestroyed(this), + ).subscribe(); + } + + private updateLoadingMessage(message: string): void { + if (this.hasError || this.hasWarn) { + return; + } + this.loadingMessage = message; + } + + private listenToErrorIssuing(): void { + this.errorHandler.errors$ + .pipe(filter(errors => !!errors.length), untilDestroyed(this)) + .subscribe((error: string) => { + this.errors.push(error); + this.loadingMessage = error; + this.hasError = true; + this.markErrorOnD3(); + + this.detect(); + }); + } + + private fetchProgress(): void { + FileProgressHelper.progress$.pipe( + filter(Boolean), + untilDestroyed(this), + ).subscribe((progress) => { + this.downloadingMessage = `Downloading ${(progress.downloaded / 1e6).toFixed(1)} of ${(progress.totalSize / 1e6).toFixed(1)} MB`; + if (this.svg) { + const totalProgress = (progress.progress * this.stepsPercentages[0]) / 100; + this.updateProgressBar(totalProgress); + } + this.detect(); + }); + } + + goToDashboard(): void { + if (!this.ready) { + return; + } + this.router.navigate([getFirstFeature()]); + } + + private buildProgressBar(): void { + const width = this.chartContainer.nativeElement.offsetWidth; + const height = this.chartContainer.nativeElement.offsetHeight; + const barWidth = 4; + const progress = 0; + + this.svg = d3 + .select(this.chartContainer.nativeElement) + .append('svg') + .attr('width', width) + .attr('height', height); + + this.progressBar = this.svg.append('g') + .attr('transform', `translate(${width / 2}, ${height / 2})`); + + const radius = Math.min(width, height) / 2 - barWidth; + + this.progressBar.append('circle') + .attr('r', radius) + .attr('fill', 'none') + .attr('stroke', 'var(--base-tertiary2)') + .attr('stroke-width', 1); + + this.arc = d3.arc() + .innerRadius(radius - barWidth) + .outerRadius(radius) + .startAngle(0) + .endAngle(Math.PI * 2 * (progress / 100)); + + this.progressBar.append('path') + .attr('d', this.arc) + .attr('opacity', 0.8) + .attr('fill', 'url(#progress-gradient)'); + + const defs = this.svg.append('defs'); + const gradient = defs.append('linearGradient') + .attr('id', 'progress-gradient') + .attr('x1', '0%') + .attr('x2', '75%') + .attr('y1', '25%') + .attr('y2', '0%'); + + gradient.append('stop') + .attr('offset', '8%') + .attr('stop-color', '#57d7ff'); + gradient.append('stop') + .attr('offset', '60%') + .attr('stop-color', '#fda2ff'); + gradient.append('stop') + .attr('offset', '100%') + .attr('stop-color', '#ff833d'); + + this.progressBar.append('text') + .attr('text-anchor', 'middle') + .attr('alignment-baseline', 'central') + .attr('dominant-baseline', 'central') + .attr('font-size', '40px') + .attr('fill', 'var(--base-primary)') + .attr('opacity', 0.8) + .attr('dx', '-.2em') + .text((progress).toFixed(0)); + this.progressBar.append('text') + .attr('class', 'symbol') + .attr('text-anchor', 'middle') + .attr('alignment-baseline', 'central') + .attr('dominant-baseline', 'central') + .attr('font-size', '20px') + .attr('fill', 'var(--base-tertiary)') + .attr('opacity', 0.8) + .attr('dy', '-.3em') + .attr('dx', '1.5em') + .text('%'); + } + + private updateProgressBar(newProgress: number): void { + if (newProgress >= 100) { + this.ready = true; + newProgress = 100; + this.detect(); + setTimeout(() => this.detect(), 500); + } + this.progress = newProgress; + this.arc.endAngle(Math.PI * 2 * (newProgress / 100)); + this.progressBar + .select('path') + .attr('d', this.arc); + this.progressBar + .select('text') + .text((newProgress).toFixed(0)); + const numberOfDigits = newProgress.toFixed(0).length + 1; + this.progressBar.select('.symbol') + .attr('dx', `${numberOfDigits * 0.45}em`) + .text('%'); + } + + private markErrorOnD3(): void { + this.progressBar.select('path').attr('fill', 'var(--warn-primary)'); + this.progressBar.select('text').attr('fill', 'var(--warn-primary)'); + this.progressBar.select('.symbol').attr('fill', 'var(--warn-primary)'); + this.progressBar.select('path').attr('opacity', 0.4); + this.progressBar.select('text').attr('opacity', 0.8); + this.progressBar.select('.symbol').attr('opacity', 0.4); + } +} diff --git a/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.html b/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.html new file mode 100644 index 0000000000..ef3d9e3b80 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.html @@ -0,0 +1,50 @@ +
+ +
+@if (isPhone) { +
+
iOS
+
+
Your iOS {{ iOSVersion ?? '' }} isn't supported
+
+ To use the Web Node please update your device to the latest iOS version. +
+
+ + +
+} @else { +
+
+
Your browser isn't supported
+
+
To run a Mina node in your browser,
+
we recommend one of the following.
+
+
+ +
+
+
+ + Chrome +
+
+ + Safari +
+
+ + Edge +
+
+
+
+} +@if (devMode) { +
You entered Dev mode!
+
+ + +
+} diff --git a/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.scss b/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.scss new file mode 100644 index 0000000000..e44bc5b434 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.scss @@ -0,0 +1,68 @@ +@import 'openmina'; + +.header { + height: 56px; + + .mina-svg { + height: 32px; + } +} + +.phone-content, +.browser-content { + height: calc(100% - 56px); + + .ios { + width: 96px; + height: 96px; + font-size: 30px; + border-radius: 100px; + } + + .ios-box { + margin-top: 40px; + margin-bottom: 40px; + padding: 0 32px; + } + + .learn-btn { + background-color: $base-background; + color: $base-primary; + max-width: 330px; + margin: 0 12px; + filter: invert(1); + } + + .headline { + font-size: 20px; + line-height: 30px; + margin-bottom: 8px; + } + + .img-wrapper { + margin-top: 24px; + gap: 30px; + } + + img { + height: 44px; + width: 44px; + } + + .sub-headline { + font-size: 16px; + } + + .browser { + margin-top: 64px; + } +} + +.browser-content { + max-width: 600px; + + img { + height: 56px; + width: 56px; + } +} diff --git a/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.ts b/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.ts new file mode 100644 index 0000000000..37ffb3247a --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-not-supported/web-node-not-supported.component.ts @@ -0,0 +1,59 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { Platform } from '@angular/cdk/platform'; +import { iOSversion, sendSentryEvent } from '@shared/helpers/webnode.helper'; +import { safelyExecuteInBrowser } from '@openmina/shared'; + +const code = [1, 2, 3, 2]; + +@Component({ + selector: 'mina-web-node-not-supported', + standalone: true, + imports: [], + templateUrl: './web-node-not-supported.component.html', + styleUrl: './web-node-not-supported.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'h-100 flex-column align-center' }, +}) +export class WebNodeNotSupportedComponent { + + @Input() isPhone!: boolean; + + @Output() bypassUnsupportedDevice = new EventEmitter(); + + iOSVersion: string = iOSversion().join('.'); + devMode: boolean = false; + private codeVerifier: number[] = []; + + constructor(private platform: Platform) {} + + addDevKey2(): void { + this.codeVerifier.push(code[this.codeVerifier.length]); + this.checkCode(); + } + + addDevKey(key: number): void { + this.codeVerifier.push(key); + this.checkCode(); + } + + private checkCode(): void { + if (this.codeVerifier.length === code.length) { + if (this.codeVerifier.every((v, i) => v === code[i])) { + this.devMode = true; + } else { + this.codeVerifier = []; + } + } + } + + c_hcK1_V_a_l_id(input: HTMLInputElement): void { + if (input.value === 'allowme') { + sendSentryEvent('A developer is testing the app on ' + this.platform, 'debug'); + this.bypassUnsupportedDevice.emit(); + } + } + + howToUpdate(): void { + safelyExecuteInBrowser(() => window.open('https://support.apple.com/en-us/118575', '_blank')); + } +} diff --git a/frontend/src/app/features/webnode/webnode.component.html b/frontend/src/app/features/webnode/webnode.component.html new file mode 100644 index 0000000000..71207172ed --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.component.html @@ -0,0 +1,5 @@ +@if (supported) { + +} @else { + +} diff --git a/frontend/src/app/features/webnode/webnode.component.scss b/frontend/src/app/features/webnode/webnode.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/features/webnode/webnode.component.ts b/frontend/src/app/features/webnode/webnode.component.ts new file mode 100644 index 0000000000..913ed0c4d0 --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.component.ts @@ -0,0 +1,67 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { WebNodeInitializationComponent } from '@app/features/webnode/web-node-initialization/web-node-initialization.component'; +import { Platform } from '@angular/cdk/platform'; +import { WebNodeNotSupportedComponent } from '@app/features/webnode/web-node-not-supported/web-node-not-supported.component'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { getMergedRoute, MergedRoute } from '@openmina/shared'; +import { filter } from 'rxjs'; +import { WebNodeService } from '@core/services/web-node.service'; +import { iOSversion } from '@shared/helpers/webnode.helper'; + +@Component({ + selector: 'mina-webnode', + standalone: true, + imports: [ + WebNodeInitializationComponent, + WebNodeNotSupportedComponent, + ], + templateUrl: './webnode.component.html', + styleUrl: './webnode.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class WebnodeComponent extends StoreDispatcher implements OnInit { + + supported: boolean = false; + isPhone: boolean = false; + + constructor(private platform: Platform, + private webNodeService: WebNodeService) { super(); } + + ngOnInit(): void { + this.checkIfDeviceIsSupported(); + this.listenToRoute(); + } + + private listenToRoute(): void { + this.select(getMergedRoute, (route: MergedRoute) => { + let initial = 174; + if (route.queryParams['initial']) { + initial = Number(route.queryParams['initial']); + } + let maximum = 65536; + if (route.queryParams['maximum']) { + maximum = Number(route.queryParams['maximum']); + } + let shared = true; + if (route.queryParams['shared']) { + shared = route.queryParams['shared'] === 'true'; + } + this.webNodeService.memory = { initial, maximum, shared }; + }, filter(Boolean)); + } + + private checkIfDeviceIsSupported(): void { + if (this.platform.IOS) { + this.supported = iOSversion()[0] >= 18; + this.isPhone = true; + return; + } + + if (this.platform.FIREFOX) { + this.supported = false; + return; + } + + this.supported = true; + } +} diff --git a/frontend/src/app/features/webnode/webnode.module.ts b/frontend/src/app/features/webnode/webnode.module.ts new file mode 100644 index 0000000000..b9f10c30ef --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { WebnodeRouting } from './webnode.routing'; + + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + WebnodeRouting, + ], +}) +export class WebnodeModule {} diff --git a/frontend/src/app/features/webnode/webnode.routing.ts b/frontend/src/app/features/webnode/webnode.routing.ts new file mode 100644 index 0000000000..fc0e9b4e9d --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.routing.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { WebnodeComponent } from '@app/features/webnode/webnode.component'; + +const routes: Routes = [ + { + path: '', + component: WebnodeComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class WebnodeRouting {} diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.html b/frontend/src/app/layout/block-production-pill/block-production-pill.component.html new file mode 100644 index 0000000000..9b6a2015b8 --- /dev/null +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.html @@ -0,0 +1,22 @@ +
+
+
+ @if (!isMobile) { + schedule + } +
Block Production 
+
+ @if (text) { + {{ text }} + } @else { + @if (producingIn) { + in + {{ producingIn }} + } @else { + in ... + } + }
+
+
+
diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss b/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss new file mode 100644 index 0000000000..4e274c7ec0 --- /dev/null +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss @@ -0,0 +1,164 @@ +@import 'openmina'; + +$blue: #57d7ff; +$pink: #fda2ff; +$orange: #ff833d; +@media (max-width: 767px) { + :host.h-sm { + height: 24px !important; + } +} + +:host { + position: relative; + min-width: 178px; + height: 34px; + @media (min-width: 768px) { + margin-right: 8px; + height: 26px; + } +} + +.pill { + background-color: $base-background; + height: 100%; + min-width: 170px; + @media (max-width: 767px) { + font-size: 12px; + min-width: unset; + .pill-inside2 { + justify-content: center; + } + } + + .pill-inside1 { + position: absolute; + background-color: $base-background; + top: 1px; + left: 1px; + height: calc(100% - 2px); + width: calc(100% - 2px); + + .pill-inside2 { + padding-left: 6px; + background: linear-gradient(25deg, rgba($blue, 0.2), rgba($pink, 0.2), rgba($orange, 0.2)); + + .mina-icon, + .bp, + .time { + background: linear-gradient(12deg, $blue, $pink, $orange); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + + &:hover { + .mina-icon, + .bp, + .time { + background: linear-gradient(100deg, $blue, $pink, $orange); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + } + } + } +} + + +.comet-border { + position: relative; + margin: auto; + border-radius: 5px; + overflow: hidden; +} + +.comet-border:before { + content: ""; + background-image: conic-gradient( + $orange 5deg, + $pink 10deg, + $pink 50deg, + $blue 90deg, + transparent 140deg + ); + height: 80px; + width: 80px; + position: absolute; + animation: rotate 5s infinite linear; +} + +@media (min-width: 768px) { + @keyframes rotate { + /* From left bottom to right bottom */ + 0% { + left: -10%; + transform: rotate(-45deg); + } + 5% { + left: -10%; + transform: rotate(-185deg); + } + /* Turn around */ + 15% { + transform: rotate(-235deg); + } + /* From right top to left top */ + 50% { + left: 71%; + transform: rotate(-235deg); + } + 55% { + left: 65%; + } + 62% { + transform: rotate(-420deg); + } + 80% { + transform: rotate(-420deg); + } + 95% { + left: 0; + } + /* No turn around. -405 = -45 visually */ + 100% { + left: -10%; + transform: rotate(-405deg); + } + } +} + +@media (max-width: 767px) { + @keyframes rotate { + 0% { + left: -10%; + transform: rotate(-45deg); + } + 5% { + left: -10%; + transform: rotate(-185deg); + } + 15% { + transform: rotate(-255deg); + } + 40% { + transform: rotate(-255deg); + } + 45% { + left: 75%; + transform: rotate(-340deg); + } + 50% { + transform: rotate(-405deg); + } + 58% { + left: 50%; + transform: rotate(-405deg); + } + 100% { + left: -10%; + transform: rotate(-405deg); + } + } +} diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts b/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts new file mode 100644 index 0000000000..ccc7c581d9 --- /dev/null +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts @@ -0,0 +1,84 @@ +import { ChangeDetectionStrategy, Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; +import { AppSelectors } from '@app/app.state'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { getTimeDiff } from '@shared/helpers/date.helper'; +import { Router } from '@angular/router'; +import { Routes } from '@shared/enums/routes.enum'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; +import { filter } from 'rxjs'; +import { isSubFeatureEnabled } from '@shared/constants/config'; +import { getMergedRoute, isMobile, MergedRoute, removeParamsFromURL } from '@openmina/shared'; +import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; + +@Component({ + selector: 'mina-block-production-pill', + standalone: true, + imports: [], + templateUrl: './block-production-pill.component.html', + styleUrl: './block-production-pill.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'border-rad-6' }, +}) +export class BlockProductionPillComponent extends StoreDispatcher implements OnInit, OnDestroy { + text: string = null; + producingIn: string = null; + isMobile: boolean = isMobile(); + + private globalSlot: number = null; + private interval: any; + private producingValue: number = null; + private activeSubMenu: string; + + constructor(private router: Router) { super(); } + + ngOnInit(): void { + this.listenToActiveNode(); + this.listenToRouteChange(); + } + + private listenToActiveNode(): void { + this.select(AppSelectors.activeNodeDetails, (details: AppNodeDetails) => { + if (details.producingBlockStatus === BlockProductionWonSlotsStatus.Committed) { + this.text = 'active'; + } else if (details.producingBlockStatus === BlockProductionWonSlotsStatus.Produced) { + this.text = 'done'; + } else { + this.text = null; + } + this.globalSlot = details.producingBlockGlobalSlot; + this.producingValue = details.producingBlockAt; + this.producingIn = getTimeDiff(this.producingValue, { only1unit: true }).diff; + this.detect(); + }, filter((details: AppNodeDetails) => !!details)); + } + + private listenToRouteChange(): void { + this.select(getMergedRoute, (route: MergedRoute) => { + this.activeSubMenu = route.url.split('/')[2] ? removeParamsFromURL(route.url.split('/')[2]) : null; + }, filter(Boolean)); + } + + private clearInterval(): void { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + } + + goToWonSlots(): void { + if (!this.globalSlot) { + return; + } + if (this.activeSubMenu === Routes.WON_SLOTS) { + this.dispatch2(BlockProductionWonSlotsActions.setActiveSlotNumber({ slotNumber: this.globalSlot })); + return; + } + this.router.navigate([Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS, this.globalSlot], { queryParamsHandling: 'merge' }); + } + + override ngOnDestroy(): void { + super.ngOnDestroy(); + this.clearInterval(); + } +} diff --git a/frontend/src/app/layout/env-build-modal/env-build-modal.component.html b/frontend/src/app/layout/env-build-modal/env-build-modal.component.html new file mode 100644 index 0000000000..02ece42212 --- /dev/null +++ b/frontend/src/app/layout/env-build-modal/env-build-modal.component.html @@ -0,0 +1,9 @@ +
+
Build Details
+
+ close +
+
+
+ +
diff --git a/frontend/src/app/layout/env-build-modal/env-build-modal.component.scss b/frontend/src/app/layout/env-build-modal/env-build-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/layout/env-build-modal/env-build-modal.component.ts b/frontend/src/app/layout/env-build-modal/env-build-modal.component.ts new file mode 100644 index 0000000000..f88cc05b1b --- /dev/null +++ b/frontend/src/app/layout/env-build-modal/env-build-modal.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component, EventEmitter } from '@angular/core'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; +import { ManualDetection, MinaJsonViewerComponent } from '@openmina/shared'; + +@Component({ + selector: 'mina-env-build-modal', + standalone: true, + imports: [ + MinaJsonViewerComponent, + ], + templateUrl: './env-build-modal.component.html', + styleUrl: './env-build-modal.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column w-100 bg-surface border-rad-6 border pb-12' }, +}) +export class EnvBuildModalComponent extends ManualDetection { + envBuild: AppEnvBuild; + close: EventEmitter = new EventEmitter(); +} diff --git a/frontend/src/app/layout/error-preview/error-preview.component.ts b/frontend/src/app/layout/error-preview/error-preview.component.ts index d75d835854..71f80dac62 100644 --- a/frontend/src/app/layout/error-preview/error-preview.component.ts +++ b/frontend/src/app/layout/error-preview/error-preview.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, ComponentRef, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { MinaState } from '@app/app.setup'; -import { ManualDetection } from '@openmina/shared'; +import { isDesktop, isMobile, ManualDetection } from '@openmina/shared'; import { selectErrorPreviewErrors } from '@error-preview/error-preview.state'; import { filter, take } from 'rxjs'; import { Overlay, OverlayRef } from '@angular/cdk/overlay'; @@ -23,6 +23,7 @@ export class ErrorPreviewComponent extends ManualDetection implements OnInit { newError: MinaError; unreadErrors: boolean; openedOverlay: boolean; + isMobile: boolean = isMobile(); private overlayRef: OverlayRef; private errorListComponent: ComponentRef; diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.html b/frontend/src/app/layout/menu-tabs/menu-tabs.component.html new file mode 100644 index 0000000000..cfa70df50e --- /dev/null +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.html @@ -0,0 +1,68 @@ + + + + + + + +
+
+
+ commit + Build +
+
+ {{ envBuild ? envBuild.git.commit_hash.slice(0, 6) : '' }}.. + open_in_full +
+
+
+
+ wifi_tethering + {{ network }} +
+
+ {{ chainId?.slice(0, 6) }}.. + content_copy +
+
+
+
+ wb_sunny + Light +
+ @if (currentTheme === ThemeType.LIGHT) { + check + } +
+
+
+ dark_mode + Dark +
+ @if (currentTheme === ThemeType.DARK) { + check + } +
+
+
diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss b/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss new file mode 100644 index 0000000000..19811948a3 --- /dev/null +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss @@ -0,0 +1,28 @@ +@import 'openmina'; + +:host { + height: 56px; + + .menus { + gap: 12px; + + .menu { + color: $base-tertiary; + min-width: 72px; + flex: 1 0 auto; + + &.active { + color: $selected-primary; + + .mina-icon { + color: $selected-primary; + font-variation-settings: 'FILL' 1, 'wght' 300, 'GRAD' 0, 'opsz' 20; + } + } + } + } +} + +.theme { + font-variation-settings: 'FILL' 1, 'wght' 200 !important; +} diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts b/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts new file mode 100644 index 0000000000..de338345d4 --- /dev/null +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts @@ -0,0 +1,174 @@ +import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; +import { + getMergedRoute, + HorizontalMenuComponent, isDesktop, + MergedRoute, + OpenminaEagerSharedModule, + removeParamsFromURL, + ThemeSwitcherService, + ThemeType, +} from '@openmina/shared'; +import { getAvailableFeatures } from '@shared/constants/config'; +import { MENU_ITEMS, MenuItem } from '@app/layout/menu/menu.component'; +import { filter, map, merge, skip, take, tap } from 'rxjs'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { MinaNode } from '@shared/types/core/environment/mina-env.type'; +import { AppSelectors } from '@app/app.state'; +import { RouterLink } from '@angular/router'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal'; +import { MinaNetwork } from '@shared/types/core/mina/mina.type'; +import { EnvBuildModalComponent } from '@app/layout/env-build-modal/env-build-modal.component'; + +@UntilDestroy() +@Component({ + selector: 'mina-menu-tabs', + standalone: true, + imports: [ + HorizontalMenuComponent, + RouterLink, + OpenminaEagerSharedModule, + ], + templateUrl: './menu-tabs.component.html', + styleUrl: './menu-tabs.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column w-100' }, +}) +export class MenuTabsComponent extends StoreDispatcher implements OnInit { + + menuItems: MenuItem[] = this.allowedMenuItems; + activeRoute: string; + activeNode: MinaNode; + network?: MinaNetwork; + chainId?: string; + envBuild: AppEnvBuild; + isOpenMore: boolean; + currentTheme: ThemeType; + readonly trackMenus = (_: number, item: MenuItem): string => item.name; + readonly ThemeType = ThemeType; + + @ViewChild('dropdown') private dropdown: TemplateRef; + + private overlayRef: OverlayRef; + + constructor(private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private themeService: ThemeSwitcherService) {super();} + + ngOnInit(): void { + this.currentTheme = this.themeService.activeTheme; + this.listenToActiveNodeChange(); + this.listenToEnvBuild(); + this.listenToNetwork(); + + let lastUrl: string; + this.store.select(getMergedRoute) + .pipe( + filter(Boolean), + map((route: MergedRoute) => route.url), + filter(url => url !== lastUrl), + tap(url => lastUrl = url), + untilDestroyed(this), + ) + .subscribe((url: string) => { + this.activeRoute = removeParamsFromURL(url).split('/')[1]; + this.detect(); + }); + } + + changeTheme(type: ThemeType): void { + this.closeOverlay(); + if (type === this.currentTheme) { + return; + } + this.themeService.changeTheme(); + this.currentTheme = this.themeService.activeTheme; + } + + + private listenToActiveNodeChange(): void { + this.select(AppSelectors.activeNode, (node: MinaNode) => { + this.activeNode = node; + this.menuItems = this.allowedMenuItems; + this.detect(); + }, filter(node => !!node)); + } + + private get allowedMenuItems(): MenuItem[] { + const features = getAvailableFeatures(this.activeNode || { features: {} } as any); + return MENU_ITEMS.filter((opt: MenuItem) => features.find(f => f === opt.name.toLowerCase().split(' ').join('-'))); + } + + private listenToEnvBuild(): void { + this.select(AppSelectors.envBuild, (env: AppEnvBuild) => { + this.envBuild = env; + this.detect(); + }); + } + + private listenToNetwork(): void { + this.select(AppSelectors.activeNodeDetails, ({ chainId, network }) => { + this.chainId = chainId; + this.network = network as MinaNetwork; + this.detect(); + }, filter(Boolean)); + } + + openMore(anchor: HTMLDivElement): void { + this.isOpenMore = true; + if (this.overlayRef?.hasAttached()) { + this.closeOverlay(); + return; + } + this.overlayRef = this.overlay.create({ + hasBackdrop: false, + width: window.innerWidth - 6, + positionStrategy: this.overlay.position() + .flexibleConnectedTo(anchor) + .withPositions([{ + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom', + offsetY: -10, + offsetX: -4, + }]), + }); + + const portal = new TemplatePortal(this.dropdown, this.viewContainerRef); + this.overlayRef.attach(portal); + } + + closeOverlay(): void { + this.overlayRef?.dispose(); + this.isOpenMore = false; + this.detect(); + } + + openEnvBuildModal(): void { + this.closeOverlay(); + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'openmina-backdrop', + width: 'calc(100% - 8px)', + height: 'calc(100% - 8px)', + positionStrategy: this.overlay.position().global().centerVertically().centerHorizontally(), + }); + + const portal = new ComponentPortal(EnvBuildModalComponent); + const component = this.overlayRef.attach(portal); + component.instance.envBuild = this.envBuild; + component.instance.detect(); + + merge( + component.instance.close, + this.overlayRef.backdropClick(), + ) + .pipe(take(1)) + .subscribe(() => this.overlayRef.dispose()); + + } + +} diff --git a/frontend/src/app/layout/menu/menu.component.html b/frontend/src/app/layout/menu/menu.component.html index 474dbd42f5..c4e13250c8 100644 --- a/frontend/src/app/layout/menu/menu.component.html +++ b/frontend/src/app/layout/menu/menu.component.html @@ -47,7 +47,22 @@
-
+
+
+
+ commit + Build +
+
+ {{ envBuild ? envBuild.git.commit_hash.slice(0, 6) : '' }}.. + open_in_full +
+
+
+
- {{ chainId.slice(0, 6) }}.. + {{ chainId?.slice(0, 6) }}.. content_copy
diff --git a/frontend/src/app/layout/menu/menu.component.scss b/frontend/src/app/layout/menu/menu.component.scss index 1e8329053b..81b7ae7000 100644 --- a/frontend/src/app/layout/menu/menu.component.scss +++ b/frontend/src/app/layout/menu/menu.component.scss @@ -5,7 +5,6 @@ padding: 8px 12px; border: none; border-radius: 0; - border-bottom: 1px solid $base-divider; background-color: unset; transition: width 200ms ease-out, background-color 80ms; @@ -103,7 +102,7 @@ } } -@media (max-width: 768px) { +@media (max-width: 767px) { .menu { .menu-toggle { height: 56px !important; @@ -148,7 +147,7 @@ transform: translate(0) rotate(0); animation: rotWithOpc 0.3s linear; - @media (min-width: 769px) { + @media (min-width: 768px) { left: 42px; &.collapsed { transform: translate(-75px, -76px) rotate(-90deg); diff --git a/frontend/src/app/layout/menu/menu.component.ts b/frontend/src/app/layout/menu/menu.component.ts index 49d59de000..97486f0e83 100644 --- a/frontend/src/app/layout/menu/menu.component.ts +++ b/frontend/src/app/layout/menu/menu.component.ts @@ -6,7 +6,8 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { AppActions } from '@app/app.actions'; import { - getMergedRoute, + getMergedRoute, isDesktop, + isMobile, ManualDetection, MergedRoute, removeParamsFromURL, @@ -15,17 +16,21 @@ import { TooltipPosition, } from '@openmina/shared'; import { MinaNode } from '@shared/types/core/environment/mina-env.type'; -import { filter, map, tap } from 'rxjs'; +import { filter, map, merge, take, tap } from 'rxjs'; import { CONFIG, getAvailableFeatures } from '@shared/constants/config'; import { MinaNetwork } from '@shared/types/core/mina/mina.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { EnvBuildModalComponent } from '@app/layout/env-build-modal/env-build-modal.component'; -interface MenuItem { +export interface MenuItem { name: string; icon: string; tooltip?: string; } -const MENU_ITEMS: MenuItem[] = [ +export const MENU_ITEMS: MenuItem[] = [ { name: 'Dashboard', icon: 'dashboard' }, { name: 'Block Production', icon: 'library_add' }, { name: 'Nodes', icon: 'margin' }, @@ -57,14 +62,20 @@ export class MenuComponent extends ManualDetection implements OnInit { activeRoute: string; network?: MinaNetwork; chainId?: string; + envBuild: AppEnvBuild; - constructor(private store: Store, + private overlayRef: OverlayRef; + + constructor(private overlay: Overlay, + private store: Store, private themeService: ThemeSwitcherService) { super(); } ngOnInit(): void { this.currentTheme = this.themeService.activeTheme; this.listenToCollapsingMenu(); this.listenToActiveNodeChange(); + this.listenToEnvBuild(); + let lastUrl: string; this.store.select(getMergedRoute) .pipe( @@ -118,6 +129,18 @@ export class MenuComponent extends ManualDetection implements OnInit { }); } + private listenToEnvBuild(): void { + this.store.select(AppSelectors.envBuild) + .pipe( + filter(Boolean), + untilDestroyed(this), + ) + .subscribe((env: AppEnvBuild | undefined) => { + this.envBuild = env; + this.detect(); + }); + } + private get allowedMenuItems(): MenuItem[] { const features = getAvailableFeatures(this.activeNode || { features: {} } as any); return MENU_ITEMS.filter((opt: MenuItem) => features.find(f => f === opt.name.toLowerCase().split(' ').join('-'))); @@ -140,4 +163,28 @@ export class MenuComponent extends ManualDetection implements OnInit { collapseMenu(): void { this.store.dispatch(AppActions.changeMenuCollapsing({ isCollapsing: !this.menu.collapsed })); } + + openEnvBuildModal(): void { + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'openmina-backdrop', + width: '99%', + height: '99%', + maxWidth: 600, + maxHeight: 460, + positionStrategy: this.overlay.position().global().centerVertically().centerHorizontally(), + }); + + const portal = new ComponentPortal(EnvBuildModalComponent); + const component = this.overlayRef.attach(portal); + component.instance.envBuild = this.envBuild; + component.instance.detect(); + + merge( + component.instance.close, + this.overlayRef.backdropClick(), + ) + .pipe(take(1)) + .subscribe(() => this.overlayRef.dispose()); + } } diff --git a/frontend/src/app/layout/parse-files/parse-files.component.html b/frontend/src/app/layout/parse-files/parse-files.component.html new file mode 100644 index 0000000000..ad62091962 --- /dev/null +++ b/frontend/src/app/layout/parse-files/parse-files.component.html @@ -0,0 +1,12 @@ +

parse-files works!

+ + +
+
+

File Contents:

+
{{ fileContent }}
+
+
diff --git a/frontend/src/app/layout/parse-files/parse-files.component.scss b/frontend/src/app/layout/parse-files/parse-files.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/layout/parse-files/parse-files.component.ts b/frontend/src/app/layout/parse-files/parse-files.component.ts new file mode 100644 index 0000000000..d8a7725887 --- /dev/null +++ b/frontend/src/app/layout/parse-files/parse-files.component.ts @@ -0,0 +1,112 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { NgFor, NgIf } from '@angular/common'; +import * as JSZip from 'jszip'; + +@Component({ + selector: 'mina-parse-files', + standalone: true, + imports: [ + NgIf, NgFor, + ], + templateUrl: './parse-files.component.html', + styleUrl: './parse-files.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ParseFilesComponent { + + fileContents: string[] = []; + + onFileSelected(event: any) { + const files: FileList = event.target.files; + + // Reset previous file contents + this.fileContents = []; + + // Loop through selected files + for (let i = 0; i < files.length; i++) { + const file = files[i]; + + // Ensure it's a .txt file + } + this.handleFileUpload(event); + } + + async processZipFile(zipFile: File) { + try { + // Load the ZIP file + const zip = await JSZip.loadAsync(zipFile); + + // Array to store file contents + const fileContents: { name: string, content: string }[] = []; + + // Iterate through each file in the ZIP + await Promise.all(Object.keys(zip.files).map(async (filename) => { + // Skip directories + if (!zip.files[filename].dir) { + try { + // Read file as text + const content = await zip.files[filename].async('string'); + fileContents.push({ + name: filename, + content: content, + }); + } catch (readError) { + console.error(`Error reading file ${filename}:`, readError); + } + } + })); + + // Return or process the file contents + return fileContents; + } catch (error) { + console.error('Error processing ZIP file:', error); + return []; + } + } + +// Usage example + handleFileUpload(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + const zipFile = input.files[0]; + this.processZipFile(zipFile).then(files => { + files.forEach(file => { + console.log(`File: ${file.name}`); + console.log(`Content: ${file.content.substring(0, 200)}...`); + }); + }); + } + } + + private readFileContent(file: File) { + const reader = new FileReader(); + + reader.onload = (e: any) => { + const content = e.target.result as string; + this.fileContents.push(content); + + // Perform operations on the content here + this.processFileContent(content); + }; + + reader.onerror = (e) => { + console.error('Error reading file', e); + }; + + // Read the file as text + reader.readAsText(file); + } + + private processFileContent(content: string) { + // Example operations + const lines = content.split('\n'); + const wordCount = content.split(/\s+/).length; + const characterCount = content.length; + + console.log('Lines:', lines); + console.log('Word Count:', wordCount); + console.log('Character Count:', characterCount); + + // Add your specific file processing logic here + } +} diff --git a/frontend/src/app/layout/server-status/server-status.component.html b/frontend/src/app/layout/server-status/server-status.component.html index a9b01e4f38..6b727bb5c2 100644 --- a/frontend/src/app/layout/server-status/server-status.component.html +++ b/frontend/src/app/layout/server-status/server-status.component.html @@ -1,29 +1,25 @@
- -
+
blur_circular - -
{{ details.transactions }} Txs
-
{{ details.snarks }} SNARKs
-
+
{{ details.transactions }} Tx{{ details.transactions | plural }}
+
{{ details.snarks }} SNARK{{ details.snarks | plural }}
-
language - @if (!isMobile) { -
{{ details.peers }} Peers
-
{{ details.download }} / {{ details.upload }} MBps
- } +
{{ details.peersConnected }} Peer{{ details.peersConnected | plural }}
-
@@ -32,85 +28,87 @@
- dns - -
{{ details.status }}
- #{{ details.blockHeight }} - {{ blockTimeAgo ? blockTimeAgo + ' ago' : '' }} -
+ @if (!isMobile) { + dns + } +
{{ details.status }}
+ #{{ details.blockHeight }} + {{ blockTimeAgo ? blockTimeAgo + ' ago' : '' }}
-
-
- + @if (!isMobile || (isMobile && switchForbidden)) { +
+
{{ !switchForbidden ? activeNode?.name : 'All Nodes' }} - - arrow_drop_down + @if (!switchForbidden && (canAddNodes || nodes.length > 1)) { + arrow_drop_down + } +
-
+ }
diff --git a/frontend/src/app/layout/server-status/server-status.component.scss b/frontend/src/app/layout/server-status/server-status.component.scss index 505c0ea331..a3de40e2d0 100644 --- a/frontend/src/app/layout/server-status/server-status.component.scss +++ b/frontend/src/app/layout/server-status/server-status.component.scss @@ -100,24 +100,46 @@ .chip { gap: 4px; - margin-left: 4px; + } + + @media (max-width: 767px) { + width: 100%; + &.h-sm, + .h-sm { + height: 32px !important; + } } } .node-status { - &.can-add-nodes { - .chip, - .chip::before { - border-bottom-right-radius: 0 !important; - border-top-right-radius: 0 !important; + + @media (min-width: 768px) { + &.can-add-nodes { + .chip, + .chip::before { + border-bottom-right-radius: 0 !important; + border-top-right-radius: 0 !important; + } } - } - .chip { + .chip { + &, + &::before { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; + } + } + } + @media (max-width: 767px) { + font-size: 12px; &, - &::before { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; + .shine-parent, + .chip { + width: 100%; + margin: 0 !important; + } + .chip { + justify-content: center; } } diff --git a/frontend/src/app/layout/server-status/server-status.component.ts b/frontend/src/app/layout/server-status/server-status.component.ts index 60dba80042..4f459b9705 100644 --- a/frontend/src/app/layout/server-status/server-status.component.ts +++ b/frontend/src/app/layout/server-status/server-status.component.ts @@ -62,8 +62,7 @@ export class ServerStatusComponent extends StoreDispatcher implements OnInit { protected readonly AppNodeStatus = AppNodeStatus; protected readonly canAddNodes = CONFIG.canAddNodes; - @Input() switchForbidden: boolean; - + switchForbidden: boolean; isMobile: boolean; activeNode: MinaNode; details: AppNodeDetails; @@ -73,7 +72,7 @@ export class ServerStatusComponent extends StoreDispatcher implements OnInit { @ViewChild('overlayOpener') private overlayOpener: ElementRef; - private nodes: MinaNode[] = []; + nodes: MinaNode[] = []; private tooltipOverlayRef: OverlayRef; private nodePickerOverlayRef: OverlayRef; private nodePickerComponent: ComponentRef; @@ -162,7 +161,7 @@ export class ServerStatusComponent extends StoreDispatcher implements OnInit { minWidth: isMobile() ? '100%' : '220px', scrollStrategy: this.overlay.scrollStrategies.close(), positionStrategy: this.overlay.position() - .flexibleConnectedTo(this.overlayOpener.nativeElement) + .flexibleConnectedTo(this.overlayOpener ? this.overlayOpener.nativeElement : (event.target as HTMLElement)) .withPositions([{ originX: 'end', originY: 'bottom', diff --git a/frontend/src/app/layout/toolbar/loading.reducer.ts b/frontend/src/app/layout/toolbar/loading.reducer.ts index 8f9666026f..6fcb7d8253 100644 --- a/frontend/src/app/layout/toolbar/loading.reducer.ts +++ b/frontend/src/app/layout/toolbar/loading.reducer.ts @@ -8,16 +8,8 @@ import { STATE_ACTIONS_GET_EARLIEST_SLOT, STATE_ACTIONS_GET_EARLIEST_SLOT_SUCCESS, } from '@state/actions/state-actions.actions'; -import { - NODES_OVERVIEW_CLOSE, - NODES_OVERVIEW_GET_NODES_SUCCESS, - NODES_OVERVIEW_INIT, -} from '@nodes/overview/nodes-overview.actions'; -import { - NODES_BOOTSTRAP_CLOSE, - NODES_BOOTSTRAP_GET_NODES_SUCCESS, - NODES_BOOTSTRAP_INIT, -} from '@nodes/bootstrap/nodes-bootstrap.actions'; +import { NODES_OVERVIEW_CLOSE, NODES_OVERVIEW_GET_NODES_SUCCESS, NODES_OVERVIEW_INIT } from '@nodes/overview/nodes-overview.actions'; +import { NODES_BOOTSTRAP_CLOSE, NODES_BOOTSTRAP_GET_NODES_SUCCESS, NODES_BOOTSTRAP_INIT } from '@nodes/bootstrap/nodes-bootstrap.actions'; import { NODES_LIVE_CLOSE, NODES_LIVE_GET_NODES_SUCCESS, NODES_LIVE_INIT } from '@nodes/live/nodes-live.actions'; import { SNARKS_WORK_POOL_CLOSE, @@ -27,17 +19,8 @@ import { SNARKS_WORK_POOL_INIT, } from '@snarks/work-pool/snarks-work-pool.actions'; import { SCAN_STATE_CLOSE, SCAN_STATE_GET_BLOCK_SUCCESS, SCAN_STATE_INIT } from '@snarks/scan-state/scan-state.actions'; -import { DASHBOARD_CLOSE, DASHBOARD_GET_DATA_SUCCESS, DASHBOARD_INIT } from '@dashboard/dashboard.actions'; -import { - MEMORY_RESOURCES_CLOSE, - MEMORY_RESOURCES_GET, - MEMORY_RESOURCES_GET_SUCCESS, -} from '@resources/memory/memory-resources.actions'; -import { - NETWORK_NODE_DHT_CLOSE, - NETWORK_NODE_DHT_GET_PEERS_SUCCESS, - NETWORK_NODE_DHT_INIT, -} from '@network/node-dht/network-node-dht.actions'; +import { MEMORY_RESOURCES_CLOSE, MEMORY_RESOURCES_GET, MEMORY_RESOURCES_GET_SUCCESS } from '@resources/memory/memory-resources.actions'; +import { NETWORK_NODE_DHT_CLOSE, NETWORK_NODE_DHT_GET_PEERS_SUCCESS, NETWORK_NODE_DHT_INIT } from '@network/node-dht/network-node-dht.actions'; import { NETWORK_BOOTSTRAP_STATS_CLOSE, NETWORK_BOOTSTRAP_STATS_GET_BOOTSTRAP_STATS_SUCCESS, @@ -48,7 +31,8 @@ import { BENCHMARKS_WALLETS_CLOSE, BENCHMARKS_WALLETS_GET_ALL_TXS, BENCHMARKS_WALLETS_GET_ALL_TXS_SUCCESS, - BENCHMARKS_WALLETS_GET_WALLETS, BENCHMARKS_WALLETS_GET_WALLETS_SUCCESS, + BENCHMARKS_WALLETS_GET_WALLETS, + BENCHMARKS_WALLETS_GET_WALLETS_SUCCESS, } from '@benchmarks/wallets/benchmarks-wallets.actions'; export type LoadingState = string[]; @@ -60,8 +44,6 @@ export function loadingReducer(state: LoadingState = initialState, action: Featu /* ------------ ADD ------------ */ case `[${APP_PREFIX}] Init`: - case DASHBOARD_INIT: - case `[${BLOCK_PRODUCTION_PREFIX} Overview] Get Slots`: case `[${BLOCK_PRODUCTION_PREFIX} Won Slots] Init`: @@ -90,11 +72,6 @@ export function loadingReducer(state: LoadingState = initialState, action: Featu case `[${APP_PREFIX}] Init Success`: return remove(state, `[${APP_PREFIX}] Init`); - case DASHBOARD_GET_DATA_SUCCESS: - return remove(state, DASHBOARD_INIT); - case DASHBOARD_CLOSE: - return remove(state, [DASHBOARD_INIT]); - case `[${BLOCK_PRODUCTION_PREFIX} Overview] Get Slots Success`: return remove(state, `[${BLOCK_PRODUCTION_PREFIX} Overview] Get Slots`); case `[${BLOCK_PRODUCTION_PREFIX} Overview] Close`: diff --git a/frontend/src/app/layout/toolbar/toolbar.component.html b/frontend/src/app/layout/toolbar/toolbar.component.html index 9d2980871f..0bdd8f4ad4 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.html +++ b/frontend/src/app/layout/toolbar/toolbar.component.html @@ -1,16 +1,25 @@ -
+
- - {{ title }} - + + + + + {{ title }} + @if (!isMobile) { + + }
-
- - +
+ @if (!isMobile || (isMobile && errors.length)) { + + } +
+ + @if (haveNextBP && !isAllNodesPage) { + + } +
diff --git a/frontend/src/app/layout/toolbar/toolbar.component.scss b/frontend/src/app/layout/toolbar/toolbar.component.scss index bd19539e7c..71c8f6be53 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.scss +++ b/frontend/src/app/layout/toolbar/toolbar.component.scss @@ -2,15 +2,15 @@ :host { height: 40px; - @media (max-width: 768px) { - height: 56px; + @media (max-width: 767px) { + height: 96px; } } .toolbar { - .mobile-toggle { - width: 46px; - } + //.mobile-toggle { + // width: 46px; + //} .percentage { width: 200px; @@ -30,6 +30,26 @@ animation: loading 1.5s infinite; animation-timing-function: linear; } + + .server-pill { + @media (max-width: 767px) { + flex-direction: row-reverse; + gap: 8px; + mina-server-status, + mina-block-production-pill { + max-width: calc(50% - 4px); + flex-grow: 1; + } + + &.bootstrapping { + gap: 0; + + mina-server-status { + max-width: 100%; + } + } + } + } } @keyframes loading { @@ -49,12 +69,12 @@ } } -@media (max-width: 700px) { +@media (max-width: 767px) { :host { border: none !important; - } - .toolbar { - background-color: $base-surface; + .title { + font-size: 24px !important; + } } } diff --git a/frontend/src/app/layout/toolbar/toolbar.component.ts b/frontend/src/app/layout/toolbar/toolbar.component.ts index b3f60800e5..4c66ec1860 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.ts +++ b/frontend/src/app/layout/toolbar/toolbar.component.ts @@ -1,32 +1,42 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { filter, map } from 'rxjs'; import { AppSelectors } from '@app/app.state'; -import { getMergedRoute, MergedRoute, removeParamsFromURL, TooltipService } from '@openmina/shared'; +import { getMergedRoute, hasValue, MergedRoute, removeParamsFromURL, TooltipService } from '@openmina/shared'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { AppActions } from '@app/app.actions'; import { selectLoadingStateLength } from '@app/layout/toolbar/loading.reducer'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { selectErrorPreviewErrors } from '@error-preview/error-preview.state'; +import { MinaError } from '@shared/types/error-preview/mina-error.type'; +import { AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { Routes } from '@shared/enums/routes.enum'; @Component({ selector: 'mina-toolbar', templateUrl: './toolbar.component.html', styleUrls: ['./toolbar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'flex-row align-center border-bottom' }, + host: { class: 'flex-row align-center' }, }) export class ToolbarComponent extends StoreDispatcher implements OnInit { title: string = 'Loading'; isMobile: boolean; + errors: MinaError[] = []; + haveNextBP: boolean; + isAllNodesPage: boolean; @ViewChild('loadingRef') private loadingRef: ElementRef; constructor(private tooltipService: TooltipService) { super(); } ngOnInit(): void { + this.isAllNodesPage = location.pathname.includes(Routes.NODES + '/' + Routes.OVERVIEW); this.listenToRouterChange(); this.listenToMenuChange(); this.listenToLoading(); + this.listenToNewErrors(); + this.listenToNodeDetails(); } private listenToLoading(): void { @@ -42,6 +52,20 @@ export class ToolbarComponent extends StoreDispatcher implements OnInit { }); } + private listenToNodeDetails(): void { + this.select(AppSelectors.activeNodeDetails, details => { + this.haveNextBP = hasValue(details.producingBlockGlobalSlot); + this.detect(); + }); + } + + private listenToNewErrors(): void { + this.select(selectErrorPreviewErrors, (errors: MinaError[]) => { + this.errors = errors; + this.detect(); + }, filter(errors => !!errors.length)); + } + private listenToMenuChange(): void { this.select(AppSelectors.menu, (menu: AppMenu) => { this.isMobile = menu.isMobile; @@ -59,7 +83,7 @@ export class ToolbarComponent extends StoreDispatcher implements OnInit { private listenToRouterChange(): void { this.select(getMergedRoute, (url: string) => { - this.title = removeParamsFromURL(url).split('/')[1].replace(/-/g, ' '); + this.title = removeParamsFromURL(url)?.split('/')[1]?.replace(/-/g, ' '); this.detect(); }, filter(Boolean), map((route: MergedRoute) => route.url)); } diff --git a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html index bf87cf028c..4214b1508e 100644 --- a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html +++ b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html @@ -1,12 +1,11 @@
-

- With the Web Node, anyone can produce blocks directly through their browser + With the Web Node, you can now produce blocks directly through your browser

An in-browser Mina node capable of producing and validating blocks @@ -14,51 +13,11 @@

- +

- -
-
@@ -71,6 +30,7 @@

The private key is pre-set, and the stake is delegated to it.

+
diff --git a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss index 47a212ed2b..2b1f13cb31 100644 --- a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss +++ b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss @@ -74,7 +74,7 @@ $white: #000000; background: linear-gradient(to right, $green 0%, $sand 50%, $peach 100%); background-size: 500%; margin-bottom: 96px; - transition: background-position 2s ease; + transition: background-position 1s ease; background-position: 0 50%; &:hover { @@ -82,36 +82,6 @@ $white: #000000; } } -/* -.card-list { - margin-top: 40px; - margin-bottom: 160px; - padding-left: 24px; - padding-right: 24px; - gap: 24px; - - .card-wrapper { - height: 216px; - max-width: 400px; - background: linear-gradient(148deg, rgba(#59bfb5, 0.3) 20%, rgba(#acdea0, 0.3), rgba(#acdea0, 0.1), rgba(100, 100, 100, 0.1) 70%, transparent 80%); - padding: 1px; - - .card { - padding: 24px; - - .mina-icon { - color: #59bfb5; - font-size: 32px; - } - - > * { - margin-bottom: 24px; - } - } - } -} -*/ - .hardcoded-key { background-color: $warn-container; color: $warn-primary; @@ -251,28 +221,6 @@ $white: #000000; .images .step .step-text { font-size: 4.2vw; } - /* - - .card-list { - padding-left: 6.667vw; - padding-right: 6.667vw; - margin-bottom: 17.778vw; - margin-top: 17.778vw; - - .card-wrapper { - min-height: 55.556vw; - max-width: 100vw; - - .card { - padding: 6.667vw 4.444vw 6.667vw 6.667vw; - - .mina-icon { - font-size: 8.889vw; - } - } - } - } - */ .hardcoded-key { font-size: 5vw; diff --git a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts index f8cd2143ec..0c56241e53 100644 --- a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts +++ b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts @@ -3,12 +3,14 @@ import { NgOptimizedImage } from '@angular/common'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { AppSelectors } from '@app/app.state'; import { filter } from 'rxjs'; +import { ParseFilesComponent } from '@app/layout/parse-files/parse-files.component'; @Component({ selector: 'mina-web-node-landing-page', standalone: true, imports: [ NgOptimizedImage, + ParseFilesComponent, ], templateUrl: './web-node-landing-page.component.html', styleUrl: './web-node-landing-page.component.scss', diff --git a/frontend/src/app/shared/base-classes/mina-table-rust-wrapper.class.ts b/frontend/src/app/shared/base-classes/mina-table-rust-wrapper.class.ts index 569deb4533..05ccb62014 100644 --- a/frontend/src/app/shared/base-classes/mina-table-rust-wrapper.class.ts +++ b/frontend/src/app/shared/base-classes/mina-table-rust-wrapper.class.ts @@ -1,8 +1,8 @@ import { Directive, OnInit } from '@angular/core'; import { AppSelectors } from '@app/app.state'; import { AppMenu } from '@shared/types/app/app-menu.type'; -import { MinaTableWrapper } from '@openmina/shared'; import { MinaState } from '@app/app.setup'; +import { MinaTableWrapper } from '@shared/base-classes/mina-table-wrapper.class'; @Directive() export abstract class MinaTableRustWrapper extends MinaTableWrapper implements OnInit { diff --git a/frontend/src/app/shared/base-classes/mina-table-wrapper.class.ts b/frontend/src/app/shared/base-classes/mina-table-wrapper.class.ts new file mode 100644 index 0000000000..47df6db829 --- /dev/null +++ b/frontend/src/app/shared/base-classes/mina-table-wrapper.class.ts @@ -0,0 +1,33 @@ +import { Directive, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; +import { MinaTableComponent } from '../components/mina-table/mina-table.component'; +import { BaseStoreDispatcher, TableColumnList } from '@openmina/shared'; + +@Directive() +export abstract class MinaTableWrapper extends BaseStoreDispatcher implements OnInit { + + protected abstract readonly tableHeads: TableColumnList; + + @ViewChild('rowTemplate') protected rowTemplate: TemplateRef<{ row: T, i: number }>; + @ViewChild('minaTable', { read: ViewContainerRef }) protected containerRef: ViewContainerRef; + + public table: MinaTableComponent; + + async ngOnInit(): Promise { + await import('../components/mina-table/mina-table.component').then(c => { + this.table = this.containerRef.createComponent(c.MinaTableComponent).instance; + this.table.tableHeads = this.tableHeads; + this.table.rowTemplate = this.rowTemplate; + this.table.rowClickCallback = (row: T, isRealClick: boolean = true) => this.onRowClick(row, isRealClick); + this.setupTable(); + this.table.init(); + }); + } + + protected checkViewport(isMobile: boolean): void { + this.table.checkViewport(isMobile); + } + + protected abstract setupTable(): void; + + protected onRowClick(row: T, isRealClick: boolean): void { } +} diff --git a/frontend/src/app/shared/components/mina-card/mina-card.component.html b/frontend/src/app/shared/components/mina-card/mina-card.component.html index a83c6194b1..942df0661b 100644 --- a/frontend/src/app/shared/components/mina-card/mina-card.component.html +++ b/frontend/src/app/shared/components/mina-card/mina-card.component.html @@ -1,10 +1,11 @@
- {{ label }} + {{ label }} {{ icon }}
{{ value ?? '-' }}
-
{{ hint }}
+ [style.color]="color">{{ value ?? '-' }} +
+
{{ hint }}
diff --git a/frontend/src/app/shared/components/mina-card/mina-card.component.scss b/frontend/src/app/shared/components/mina-card/mina-card.component.scss index a5a87f8b45..1a5a5e6566 100644 --- a/frontend/src/app/shared/components/mina-card/mina-card.component.scss +++ b/frontend/src/app/shared/components/mina-card/mina-card.component.scss @@ -2,7 +2,7 @@ height: 128px; width: 128px; - @media (max-width: 700px) { + @media (max-width: 767px) { height: 140px; width: 140px; } diff --git a/frontend/src/app/shared/components/mina-card/mina-card.component.ts b/frontend/src/app/shared/components/mina-card/mina-card.component.ts index 9681cbd29a..1ecd6b792d 100644 --- a/frontend/src/app/shared/components/mina-card/mina-card.component.ts +++ b/frontend/src/app/shared/components/mina-card/mina-card.component.ts @@ -5,7 +5,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; templateUrl: './mina-card.component.html', styleUrls: ['./mina-card.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'flex-column flex-between pt-8 pb-8 pl-12 border-rad-8 bg-surface' }, + host: { class: 'flex-column flex-between pt-8 pb-8 pl-12 border-rad-8' }, }) export class MinaCardComponent { diff --git a/frontend/src/app/shared/components/mina-table/mina-table.component.html b/frontend/src/app/shared/components/mina-table/mina-table.component.html new file mode 100644 index 0000000000..bcbde1b6ef --- /dev/null +++ b/frontend/src/app/shared/components/mina-table/mina-table.component.html @@ -0,0 +1,57 @@ +
+ +
+ + {{ th.name }} + info + arrow_upward + + +
+ +
+ {{ th.name }} +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+
diff --git a/frontend/src/app/shared/components/mina-table/mina-table.component.scss b/frontend/src/app/shared/components/mina-table/mina-table.component.scss new file mode 100644 index 0000000000..9eb3be7a1c --- /dev/null +++ b/frontend/src/app/shared/components/mina-table/mina-table.component.scss @@ -0,0 +1,251 @@ +@import 'openmina'; + +::ng-deep .mina-table { + @include flexColumn(); + + .row { + height: 36px; + display: grid; + color: $base-tertiary; + + > span { + height: 36px; + line-height: 36px; + padding-left: 12px; + } + + &.active { + background-color: $selected-container; + + > span, a { + color: $selected-primary !important; + } + } + + &:not(.head) { + cursor: pointer; + } + + &.sorting.p-relative:last-child { + overflow: hidden; + } + + &:hover:not(.active):not(.head) { + background-color: $base-divider; + + * { + color: $base-primary !important; + } + } + + &.head { + font-weight: 600; + color: $base-tertiary; + text-transform: capitalize; + + &.sorting { + > span { + @include flexRowVertCenter(); + + .mina-icon.info { + font-variation-settings: 'FILL' 0, 'wght' 300; + margin-left: 4px; + font-size: 20px; + } + + .mina-icon.dir { + transition: 0.2s ease-in-out; + transform: rotateX(0) translateY(6px); + margin-top: 1px; + margin-left: 2px; + opacity: 0; + font-size: 18px; + + &.show { + opacity: 1; + transform: rotateX(0) translateY(0); + } + + &.flip { + transform: rotateX(180deg) translateY(0); + + &:not(.show) { + transform: rotateX(180deg) translateY(6px); + } + } + } + + &:hover, + &.active { + color: $base-primary; + transition: 200ms; + + .mina-icon:not(.show):not(.info) { + opacity: 0.5; + transform: rotateX(0) translateY(0); + + &.flip { + transform: rotateX(180deg) translateY(0); + } + } + } + } + } + } + + .underline { + line-height: 12px; + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + + span mina-copy .cpy { + opacity: 0; + } + + span:hover mina-copy .cpy { + opacity: 1; + } + } + + &.active { + background-color: blue; + color: #fff; + } + + .cdk-virtual-scroll-viewport { + overflow-x: hidden; + //@media (max-width: 768px) { + // padding-bottom: 80px; + //} + } +} + +::ng-deep { + .to-top { + display: none; + } + + @media (max-width: 767px) { + .to-top { + display: block; + position: fixed; + rotate: -90deg; + color: $base-secondary; + left: -1000px; + + &.hide { + animation: bounceHide 0.5s cubic-bezier(0.5, 0.1, 0.5, 1.25) forwards; + } + + &:not(.hide) { + animation: bounceShow 0.5s cubic-bezier(0.17, 0.67, 0.83, 0.67) forwards; + } + } + + .mina-table { + .row { + span { + font-size: 12px; + } + + &.head { + .mina-icon.info { + font-size: 16px; + } + + .mina-icon.dir { + font-size: 14px; + } + } + } + } + //.cdk-virtual-scroll-content-wrapper { + // width: 100%; + //} + + //.mina-table { + // .row { + // height: initial; + // display: flex; + // background-color: initial !important; + // padding: 5px 10px; + // + // .mob-row { + // padding: 0 10px; + // outline: 1px solid $base-divider; + // background-color: $base-surface; + // border-radius: 6px; + // width: 100%; + // display: flex; + // flex-direction: row; + // justify-content: space-between; + // font-size: 12px; + // overflow: hidden; + // + // .th, .td { + // display: flex; + // flex-direction: column; + // + // span { + // height: 26px; + // line-height: 26px; + // } + // } + // + // .th { + // text-transform: capitalize; + // } + // + // .td { + // flex: 1; + // min-width: 0; + // align-items: flex-end; + // + // span { + // max-width: 100%; + // } + // } + // + // span mina-copy .cpy { + // opacity: 1; + // margin-right: -5px; + // } + // } + // } + //} + } +} + +@keyframes bounceHide { + 0% { + opacity: 1; + transform: translateX(0); + } + 30% { + opacity: 0; + transform: translateX(-20px); + } + 100% { + opacity: 0; + transform: translateX(-500px); + } +} + +@keyframes bounceShow { + 0% { + opacity: 0; + transform: translateX(-500px); + } + 70% { + opacity: 0; + transform: translateX(-20px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} diff --git a/frontend/src/app/shared/components/mina-table/mina-table.component.ts b/frontend/src/app/shared/components/mina-table/mina-table.component.ts new file mode 100644 index 0000000000..ee5c06c0db --- /dev/null +++ b/frontend/src/app/shared/components/mina-table/mina-table.component.ts @@ -0,0 +1,156 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Inject, TemplateRef, ViewChild } from '@angular/core'; +import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { untilDestroyed } from '@ngneat/until-destroy'; +import { debounceTime } from 'rxjs'; +import { CommonModule, DOCUMENT } from '@angular/common'; +import { ActionCreator } from '@ngrx/store'; +import { TypedAction } from '@ngrx/store/src/models'; +import { SharedModule } from '@shared/shared.module'; +import { BaseStoreDispatcher, hasValue, isMobile, SortDirection, TableColumnList, TableSort } from '@openmina/shared'; + +const DESKTOP_ROW_HEIGHT = 36; + +@Component({ + standalone: true, + imports: [SharedModule, CommonModule], + selector: 'mina-table', + templateUrl: './mina-table.component.html', + styleUrls: ['./mina-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'h-100 flex-column' }, +}) +export class MinaTableComponent extends BaseStoreDispatcher implements AfterViewInit { + + rowSize: number = DESKTOP_ROW_HEIGHT; + isMobile: boolean; + + rows: T[] = []; + activeRow: T; + tableHeads: TableColumnList; + rowTemplate: TemplateRef<{ row: T, i: number }>; + currentSort: TableSort; + thGroupsTemplate: TemplateRef; + propertyForActiveCheck: keyof T; + gridTemplateColumns: Array = []; + minWidth: number; + sortClz: new (payload: TableSort) => { type: string, payload: TableSort }; + sortAction: ActionCreator; }) => { sort: TableSort; } & TypedAction>; + sortSelector: (state: any) => TableSort; + rowClickCallback: (row: T, isRealClick: boolean) => void; + trackByFn: (index: number, row: T) => any = (_: number, row: T) => row; + + tableLevel: number = 1; + + @ViewChild(CdkVirtualScrollViewport) private vs: CdkVirtualScrollViewport; + @ViewChild('toTop') private toTop: ElementRef; + private hiddenToTop: boolean = true; + + constructor(@Inject(DOCUMENT) private document: Document, + private el: ElementRef) { super(); } + + init(): void { + this.minWidth = this.minWidth || this.gridTemplateColumns.reduce((acc: number, curr: number | string) => acc + Number(curr), 0); + this.addGridTemplateColumnsInCssFile(); + this.listenToSortingChanges(); + this.detect(); + } + + ngAfterViewInit(): void { + this.listenToScrolling(); + this.positionToTop(); + } + + private addGridTemplateColumnsInCssFile(): void { + let value = `mina-table #table${this.tableLevel}.mina-table .row{grid-template-columns:`; + this.gridTemplateColumns.forEach(v => value += typeof v === 'number' ? `${v}px ` : `${v} `); + this.document.getElementById('table-style' + this.tableLevel).textContent = value + '}'; + } + + sortTable(sortBy: string | keyof T): void { + const sortDirection = sortBy !== this.currentSort.sortBy + ? this.currentSort.sortDirection + : this.currentSort.sortDirection === SortDirection.ASC ? SortDirection.DSC : SortDirection.ASC; + const sort = { sortBy: sortBy as keyof T, sortDirection }; + if (this.sortClz) { + this.dispatch(this.sortClz, sort); + } else if (this.sortAction) { + this.dispatch2(this.sortAction({ sort })); + } + } + + scrollToTop(): void { + this.vs.scrollToIndex(0, 'smooth'); + this.toTop.nativeElement.classList.add('hide'); + this.hiddenToTop = true; + } + + scrollToElement(rowFinder: (row: T) => boolean): void { + const topElements = Math.round(this.vs.elementRef.nativeElement.offsetHeight / 2 / this.rowSize) - 3; + const jobIndex = this.rows.findIndex(rowFinder); + this.vs.scrollToIndex(jobIndex - topElements); + } + + get virtualScroll(): CdkVirtualScrollViewport { + return this.vs; + } + + private listenToScrolling(): void { + this.vs.scrolledIndexChange + .pipe(debounceTime(this.hiddenToTop ? 200 : 0), untilDestroyed(this)) + .subscribe(index => { + if (index === 0) { + this.toTop.nativeElement.classList.add('hide'); + } else { + this.toTop.nativeElement.classList.remove('hide'); + } + this.hiddenToTop = index === 0; + }); + } + + private listenToSortingChanges(): void { + if (!this.sortSelector) return; + this.select(this.sortSelector, (sort: TableSort) => { + this.currentSort = sort; + this.detect(); + }); + } + + checkViewport(isMobile: boolean): void { + // if (this.isMobile !== isMobile) { + // this.isMobile = isMobile; + // this.rowSize = isMobile ? (26 * this.tableHeads.length + 10) : DESKTOP_ROW_HEIGHT; + // this.vs?.checkViewportSize(); + // this.detect(); + // } + } + + private positionToTop(): void { + if (!isMobile()) { + return; + } + const rect = this.el.nativeElement.getBoundingClientRect(); + + this.toTop.nativeElement.style.top = `${rect.top + rect.height - 60}px`; + this.toTop.nativeElement.style.left = `${rect.left + rect.width - 60}px`; + } + + onVsClick(event: MouseEvent): void { + let target = event.target as any; + let idx: number = null; + while (target && target.getAttribute) { + let attrValue = target.getAttribute('idx'); + if (attrValue) { + attrValue = Number(attrValue); + idx = attrValue; + } + if (hasValue(idx) || target === this.vs.elementRef.nativeElement) { + break; + } + target = target.parentElement; + } + + if (hasValue(idx)) { + this.rowClickCallback(this.rows[idx], true); + } + } +} diff --git a/frontend/src/app/shared/constants/config.ts b/frontend/src/app/shared/constants/config.ts index 56f87c5709..e4d8c533e3 100644 --- a/frontend/src/app/shared/constants/config.ts +++ b/frontend/src/app/shared/constants/config.ts @@ -1,6 +1,6 @@ import { FeaturesConfig, FeatureType, MinaEnv, MinaNode } from '@shared/types/core/environment/mina-env.type'; import { environment } from '@environment/environment'; -import { hasValue } from '@openmina/shared'; +import { getOrigin, hasValue, isBrowser, safelyExecuteInBrowser } from '@openmina/shared'; export const CONFIG: Readonly = { ...environment, @@ -8,7 +8,7 @@ export const CONFIG: Readonly = { ...environment.globalConfig, graphQL: getURL(environment.globalConfig?.graphQL), }, - configs: environment.configs.map((config) => ({ + configs: !isBrowser() ? [] : environment.configs.map((config) => ({ ...config, url: getURL(config.url), memoryProfiler: getURL(config.memoryProfiler), @@ -16,13 +16,18 @@ export const CONFIG: Readonly = { })), }; -(window as any).config = CONFIG; +safelyExecuteInBrowser(() => { + (window as any).config = CONFIG; +}); export function getAvailableFeatures(config: MinaNode): FeatureType[] { return Object.keys(getFeaturesConfig(config)) as FeatureType[]; } export function getFirstFeature(config: MinaNode = CONFIG.configs[0]): FeatureType { + if (!isBrowser()) { + return '' as FeatureType; + } if (Array.isArray(config?.features)) { return config.features[0]; } @@ -48,12 +53,15 @@ export function isSubFeatureEnabled(config: MinaNode, feature: FeatureType, subF } export function getURL(pathOrUrl: string): string { - if (pathOrUrl) { - let href = new URL(pathOrUrl, origin).href; - if (href.endsWith('/')) { - href = href.slice(0, -1); + if (isBrowser()) { + if (pathOrUrl) { + let href = new URL(pathOrUrl, getOrigin()).href; + if (href.endsWith('/')) { + href = href.slice(0, -1); + } + return href; } - return href; + return pathOrUrl; } return pathOrUrl; } diff --git a/frontend/src/app/shared/constants/store-functions.ts b/frontend/src/app/shared/constants/store-functions.ts index 337dbe5597..abb537b8e0 100644 --- a/frontend/src/app/shared/constants/store-functions.ts +++ b/frontend/src/app/shared/constants/store-functions.ts @@ -1,7 +1,7 @@ import { catchError, map, Observable, of, OperatorFunction, repeat } from 'rxjs'; import { ADD_ERROR, ErrorAdd } from '@error-preview/error-preview.actions'; import { HttpErrorResponse } from '@angular/common/http'; -import { FeatureAction, toReadableDate } from '@openmina/shared'; +import { any, FeatureAction, toReadableDate } from '@openmina/shared'; import { MinaErrorType } from '@shared/types/error-preview/mina-error-type.enum'; import { Selector, Store } from '@ngrx/store'; import { MinaState } from '@app/app.setup'; @@ -28,7 +28,7 @@ export const addError = (error: HttpErrorResponse | Error, type: MinaErrorType): payload: { type, message: error.message, - status: (error as any).status ? `${(error as any).status} ${(error as any).statusText}` : undefined, + status: any(error).status ? `${any(error).status} ${any(error).statusText}` : undefined, timestamp: toReadableDate(Number(Date.now()), 'HH:mm:ss'), seen: false, }, diff --git a/frontend/src/app/shared/enums/routes.enum.ts b/frontend/src/app/shared/enums/routes.enum.ts index f4a62ababd..cefbede581 100644 --- a/frontend/src/app/shared/enums/routes.enum.ts +++ b/frontend/src/app/shared/enums/routes.enum.ts @@ -37,4 +37,5 @@ export enum Routes { BLOCK_PRODUCTION = 'block-production', WON_SLOTS = 'won-slots', MEMPOOL = 'mempool', + LOADING_WEB_NODE = 'loading-web-node', } diff --git a/frontend/src/app/shared/helpers/date.helper.ts b/frontend/src/app/shared/helpers/date.helper.ts index cf11804011..84289d0a8a 100644 --- a/frontend/src/app/shared/helpers/date.helper.ts +++ b/frontend/src/app/shared/helpers/date.helper.ts @@ -6,7 +6,7 @@ import { ONE_THOUSAND } from '@openmina/shared'; * @param time * @param config - withSecs: boolean, fromTime: number */ -export function getTimeDiff(time: number, config?: { withSecs: boolean, fromTime?: number }): { +export function getTimeDiff(time: number, config?: { withSecs?: boolean, only1unit?: boolean, fromTime?: number }): { diff: string, inFuture: boolean } { @@ -36,6 +36,36 @@ export function getTimeDiff(time: number, config?: { withSecs: boolean, fromTime const seconds = Math.floor(timeDifference / 1000); let timeAgo = ''; + + if (config?.only1unit) { + if (days > 0) { + if (hours >= 12) { + timeAgo += `<${days + 1}d `; + } else { + timeAgo += `~${days}d `; + } + } else if (hours > 0) { + if (minutes >= 30) { + timeAgo += `<${hours + 1}h `; + } else { + timeAgo += `~${hours}h `; + } + } else if (minutes > 0) { + if (seconds >= 30) { + timeAgo += `<${minutes + 1}m `; + } else { + timeAgo += `~${minutes}m `; + } + } else { + if (config?.withSecs) { + timeAgo += `${seconds}s `; + } else { + timeAgo = '<1m '; + } + } + return { diff: timeAgo.trim(), inFuture }; + } + if (days > 0) { timeAgo += `${days}d `; } @@ -57,3 +87,17 @@ export function getTimeDiff(time: number, config?: { withSecs: boolean, fromTime return { diff: timeAgo.trim(), inFuture }; } + +export function getSecondsDiff(time: number, config?: { fromTime?: number }): number { + if (!time) { + return 0; + } + if (time.toString().length === 10) { + time *= ONE_THOUSAND; + } + + const paramTime = new Date(time).getTime(); + const currentTime = config?.fromTime ? new Date(config.fromTime).getTime() : Date.now(); + + return Math.floor((paramTime - currentTime) / 1000); +} diff --git a/frontend/src/app/shared/helpers/ledger.helper.ts b/frontend/src/app/shared/helpers/ledger.helper.ts index 6195fe04c3..fb4d0aeefd 100644 --- a/frontend/src/app/shared/helpers/ledger.helper.ts +++ b/frontend/src/app/shared/helpers/ledger.helper.ts @@ -5,10 +5,12 @@ import { Transport, TxType, } from '@shared/types/ledger/ledger.type'; import { Buffer } from 'buffer'; -import { any } from '@openmina/shared'; +import { any, safelyExecuteInBrowser } from '@openmina/shared'; -// @ts-ignore -window.Buffer = Buffer; +safelyExecuteInBrowser(() => { + // @ts-ignore + window.Buffer = Buffer; +}); const CLA_LEDGER = 0xb0; const INS_LEDGER = { diff --git a/frontend/src/app/shared/helpers/webnode.helper.ts b/frontend/src/app/shared/helpers/webnode.helper.ts index 60d49ed541..3b9a2057f1 100644 --- a/frontend/src/app/shared/helpers/webnode.helper.ts +++ b/frontend/src/app/shared/helpers/webnode.helper.ts @@ -1,5 +1,15 @@ import * as Sentry from '@sentry/angular'; +import { SeverityLevel } from '@sentry/angular'; -export function sendSentryEvent(message: string): void { - Sentry.captureEvent({ message: message, tags: { type: 'webnode' } }); +export function sendSentryEvent(message: string, level: SeverityLevel = 'error'): void { + Sentry.captureEvent({ message: message, level, tags: { type: 'webnode' } }); +} + +export function iOSversion(): number[] { + if (/iP(hone|od|ad)/.test(navigator.platform)) { + // supports iOS 2.0 and later: + const v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/); + return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || '0', 10)]; + } + return [0]; } diff --git a/frontend/src/app/shared/types/app/app-env-build.type.ts b/frontend/src/app/shared/types/app/app-env-build.type.ts new file mode 100644 index 0000000000..9e093455df --- /dev/null +++ b/frontend/src/app/shared/types/app/app-env-build.type.ts @@ -0,0 +1,28 @@ +export interface AppEnvBuild { + time: string; + git: GitEnvBuild; + cargo: CargoEnvBuild; + rustc: RustcEnvBuild; +} + +interface GitEnvBuild { + commit_time: string; + commit_hash: string; + branch: string; +} + +interface CargoEnvBuild { + features: string; + opt_level: number; + target: string; + is_debug: boolean; +} + +interface RustcEnvBuild { + channel: string; + commit_date: string; + commit_hash: string; + host: string; + version: string; + llvm_version: string; +} diff --git a/frontend/src/app/shared/types/app/app-node-details.type.ts b/frontend/src/app/shared/types/app/app-node-details.type.ts index eeb87cab61..3e333d84bf 100644 --- a/frontend/src/app/shared/types/app/app-node-details.type.ts +++ b/frontend/src/app/shared/types/app/app-node-details.type.ts @@ -1,17 +1,22 @@ import { MinaNetwork } from '@shared/types/core/mina/mina.type'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; export interface AppNodeDetails { status: AppNodeStatus; blockHeight: number; blockTime: number; - peers: number; - download: number; - upload: number; + peersConnected: number; + peersDisconnected: number; + peersConnecting: number; transactions: number; snarks: number; + producingBlockAt: number; + producingBlockGlobalSlot: number; + producingBlockStatus: BlockProductionWonSlotsStatus; + chainId?: string; network?: MinaNetwork; } diff --git a/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts b/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts index a6f285c728..432f7992ab 100644 --- a/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts +++ b/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts @@ -1,6 +1,11 @@ export interface BlockProductionWonSlotsEpoch { + epochNumber: number; start: number; end: number; currentGlobalSlot: number; currentTime: number; + vrfStats: { + evaluated: number; + total: number; + }; } diff --git a/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts b/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts index 407259349d..d34552507e 100644 --- a/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts +++ b/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts @@ -2,7 +2,8 @@ export interface DashboardRpcStats { peerResponses: DashboardPeerRpcResponses[]; stakingLedger: DashboardLedgerStepStats; nextLedger: DashboardLedgerStepStats; - rootLedger: DashboardLedgerStepStats; + snarkedRootLedger: DashboardLedgerStepStats; + stagedRootLedger: DashboardLedgerStepStats; } export interface DashboardPeerRpcResponses { diff --git a/frontend/src/assets/environments/webnode.js b/frontend/src/assets/environments/webnode.js index 1f66bf4d31..d6dd167f07 100644 --- a/frontend/src/assets/environments/webnode.js +++ b/frontend/src/assets/environments/webnode.js @@ -16,7 +16,7 @@ export default { }, sentry: { dsn: 'https://69aba72a6290383494290cf285ab13b3@o4508216158584832.ingest.de.sentry.io/4508216160616528', - tracingOrigins: ['https://www.openmina.com'], + tracingOrigins: ['https://www.openmina.com', 'openminawebnode.firebaseapp.com', 'openminawebnode.firebasestorage.app'], }, configs: [ { diff --git a/frontend/src/assets/images/web-node-demo/android.svg b/frontend/src/assets/images/web-node-demo/android.svg new file mode 100644 index 0000000000..6633ec161e --- /dev/null +++ b/frontend/src/assets/images/web-node-demo/android.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/web-node-demo/chrome.svg b/frontend/src/assets/images/web-node-demo/chrome.svg new file mode 100644 index 0000000000..bda269e4ba --- /dev/null +++ b/frontend/src/assets/images/web-node-demo/chrome.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/web-node-demo/edge.svg b/frontend/src/assets/images/web-node-demo/edge.svg new file mode 100644 index 0000000000..d3141091dc --- /dev/null +++ b/frontend/src/assets/images/web-node-demo/edge.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/web-node-demo/macos.svg b/frontend/src/assets/images/web-node-demo/macos.svg new file mode 100644 index 0000000000..13de47be23 --- /dev/null +++ b/frontend/src/assets/images/web-node-demo/macos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/assets/images/web-node-demo/safari.svg b/frontend/src/assets/images/web-node-demo/safari.svg new file mode 100644 index 0000000000..7692f8674c --- /dev/null +++ b/frontend/src/assets/images/web-node-demo/safari.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/web-node-demo/windows.svg b/frontend/src/assets/images/web-node-demo/windows.svg new file mode 100644 index 0000000000..cc42207007 --- /dev/null +++ b/frontend/src/assets/images/web-node-demo/windows.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/assets/styles/openmina.scss b/frontend/src/assets/styles/openmina.scss index 3a9f54fe6a..2e13e54f52 100644 --- a/frontend/src/assets/styles/openmina.scss +++ b/frontend/src/assets/styles/openmina.scss @@ -281,7 +281,7 @@ $raleway-font: 'Raleway', sans-serif; } /* SMALL - MOBILE LANDSCAPE */ -@media (max-width: 768px) { +@media (max-width: 767px) { .flex-row-sm { display: flex; flex-direction: row; @@ -292,6 +292,11 @@ $raleway-font: 'Raleway', sans-serif; flex-direction: column; } + .flex-column-reverse-sm { + display: flex; + flex-direction: column-reverse; + } + .flex-between-sm { justify-content: space-between; } @@ -620,7 +625,7 @@ $raleway-font: 'Raleway', sans-serif; @extend .w-sm; } -@media (max-width: 768px) { +@media (max-width: 767px) { .h-xs { height: 24px !important; } @@ -914,176 +919,6 @@ textarea { - -.mina-table { - @include flexColumn(); - - .row { - height: 36px; - display: grid; - - &.row-even { - background-color: $base-container; - } - - &.active { - background-color: $selected-container; - - > span, a { - color: $selected-primary !important; - } - } - - > span { - height: 36px; - line-height: 36px; - padding-left: 12px; - } - - &:hover:not(.active):not(.head) { - background-color: $base-divider; - - * { - color: $base-primary !important; - } - } - - .underline { - line-height: 12px; - cursor: pointer; - - &:hover { - text-decoration: underline; - } - } - - &.head { - font-weight: 600; - color: $base-tertiary; - text-transform: capitalize; - - &.sorting { - > span { - @include flexRowVertCenter(); - - .mina-icon.info { - font-variation-settings: 'FILL' 0, 'wght' 300; - margin-left: 4px; - } - - .mina-icon:not(.info) { - transition: 0.2s ease-in-out; - transform: rotateX(0) translateY(6px); - margin-top: 1px; - margin-left: 2px; - opacity: 0; - - &.show { - opacity: 1; - transform: rotateX(0) translateY(0); - } - - &.flip { - transform: rotateX(180deg) translateY(0); - - &:not(.show) { - transform: rotateX(180deg) translateY(6px); - } - } - } - - &:hover, - &.active { - color: $base-primary; - transition: 200ms; - - .mina-icon:not(.show):not(.info) { - opacity: 0.5; - transform: rotateX(0) translateY(0); - - &.flip { - transform: rotateX(180deg) translateY(0); - } - } - } - } - } - } - - span mina-copy .cpy { - opacity: 0; - } - - span:hover mina-copy .cpy { - opacity: 1; - } - } - - &.active { - background-color: blue; - color: #fff; - } -} - -@media (max-width: 768px) { - .cdk-virtual-scroll-content-wrapper { - width: 100%; - } - - .mina-table { - .row { - height: initial; - display: flex; - background-color: initial !important; - padding: 5px 10px; - - .mob-row { - padding: 0 10px; - outline: 1px solid $base-divider; - background-color: $base-surface; - border-radius: 6px; - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - font-size: 12px; - overflow: hidden; - - .th, .td { - display: flex; - flex-direction: column; - - span { - height: 26px; - line-height: 26px; - } - } - - .th { - text-transform: capitalize; - } - - .td { - flex: 1; - min-width: 0; - align-items: flex-end; - - span { - max-width: 100%; - } - } - - span mina-copy .cpy { - opacity: 1; - margin-right: -5px; - } - } - } - } -} - - - $z-index-bigger-than-cdk-overlay: 1001; #mina-tooltip { top: 0; @@ -1638,7 +1473,7 @@ span.mina-icon { } /* SMALL - MOBILE LANDSCAPE */ -@media (max-width: 768px) { +@media (max-width: 767px) { .d-none-sm { display: none !important; } @@ -2126,3 +1961,4 @@ span.mina-icon { color: $code-magenta !important; } + diff --git a/frontend/src/assets/webnode/.gitignore b/frontend/src/assets/webnode/.gitignore index f735b09de9..8045c492a4 100644 --- a/frontend/src/assets/webnode/.gitignore +++ b/frontend/src/assets/webnode/.gitignore @@ -1,4 +1,6 @@ #.gitignore +/circuit-blobs circuit-blobs +circuit-blobs/** pkg web-node-secrets.json diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts index b511552cee..4325e3ef25 100644 --- a/frontend/src/environments/environment.prod.ts +++ b/frontend/src/environments/environment.prod.ts @@ -1,6 +1,6 @@ import { MinaEnv } from '@shared/types/core/environment/mina-env.type'; -const env = (window as any).env; +const env = typeof window !== 'undefined' ? (window as any).env : {}; export const environment: Readonly = { production: true, configs: env.configs, diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index 92de0479fe..a4fcf27a89 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -4,7 +4,7 @@ export const environment: Readonly = { production: false, identifier: 'Dev FE', canAddNodes: true, - showWebNodeLandingPage: true, + showWebNodeLandingPage: false, globalConfig: { features: { dashboard: [], @@ -16,7 +16,7 @@ export const environment: Readonly = { 'block-production': ['won-slots'], mempool: [], benchmarks: ['wallets'], - zk: ['test'], + // zk: ['test'], }, graphQL: 'https://adonagy.com/graphql', // graphQL: 'https://api.minascan.io/node/devnet/v1/graphql', @@ -24,8 +24,8 @@ export const environment: Readonly = { }, configs: [ // { - // name: 'http://116.202.128.230:11010', - // url: 'http://116.202.128.230:11010', + // name: 'http://localhost:8070', + // url: 'http://127.0.0.1:8070/openmina-node', // }, // { // name: 'Producer-0', @@ -39,22 +39,22 @@ export const environment: Readonly = { // name: 'Producer-2', // url: 'https://staging-devnet-openmina-bp-2-dashboard.minaprotocol.network', // }, - { - name: 'staging-devnet-bp-0', - url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-1', - url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-2', - url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-3', - url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', - }, + // { + // name: 'staging-devnet-bp-0', + // url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-1', + // url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-2', + // url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-3', + // url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', + // }, { name: 'Web Node 1', isWebNode: true, @@ -66,7 +66,6 @@ export const environment: Readonly = { // { // name: 'Local rust node', // url: 'http://127.0.0.1:3000', - // memoryProfiler: 'http://1.k8.openmina.com:31164', // }, // { // name: 'feat/frontend-api-peers', @@ -79,18 +78,18 @@ export const environment: Readonly = { // resources: ['memory'], // }, // }, - // { - // name: 'Docker 11010', - // url: 'http://localhost:11010', - // }, - // { - // name: 'Docker 11012', - // url: 'http://localhost:11012', - // }, - // { - // name: 'Docker 11014', - // url: 'http://localhost:11014', - // }, + { + name: 'Docker 11010', + url: 'http://localhost:11010', + }, + { + name: 'Docker 11012', + url: 'http://localhost:11012', + }, + { + name: 'Docker 11014', + url: 'http://localhost:11014', + }, // { // name: 'Producer', // url: 'http://65.109.105.40:3000', diff --git a/frontend/src/index.html b/frontend/src/index.html index 0ae6d07c19..2cbd41a14a 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -2,21 +2,34 @@ - Open Mina + @@ -31,28 +44,35 @@ - - + diff --git a/frontend/src/main.server.ts b/frontend/src/main.server.ts new file mode 100644 index 0000000000..dfb6fdb3f1 --- /dev/null +++ b/frontend/src/main.server.ts @@ -0,0 +1 @@ +export { AppServerModule as default } from './app/app.module.server'; diff --git a/frontend/src/main.ts b/frontend/src/main.ts index a55461eea9..618cc3ffbe 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,6 +1,6 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from '@app/app.module'; +import { AppModule } from './app/app.module'; import { CONFIG } from '@shared/constants/config'; import * as Sentry from '@sentry/angular'; import type { ErrorEvent } from '@sentry/types/build/types/event'; diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 04b03573c2..1ab4659dc4 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -23,7 +23,7 @@ body { font-family: $inter-font; font-weight: 400; color: $base-primary; - background-color: $base-background; + background-color: var(--base-background, #000000); } .theme-transition { @@ -49,6 +49,10 @@ p { margin-bottom: 0; } +.openmina-backdrop { + background: rgba(13, 13, 13, 0.80); +} + /* User Select */ body { ::-moz-selection { diff --git a/frontend/tsconfig.server.json b/frontend/tsconfig.server.json new file mode 100644 index 0000000000..e2ebe5a729 --- /dev/null +++ b/frontend/tsconfig.server.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "outDir": "./out-tsc/server", + "types": [ + "node" + ] + }, + "files": [ + "src/main.server.ts", + "server.ts" + ] +} diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml index c7affb9310..1813a10c7b 100644 --- a/fuzzer/Cargo.toml +++ b/fuzzer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-fuzzer" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 98d1468f79..78e4fc6168 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mina-tree" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -27,6 +27,7 @@ kimchi = { workspace = true } mina-poseidon = { workspace = true } poly-commitment = { workspace = true } juniper = { workspace = true } +poseidon = { workspace = true } openmina-macros = { path = "../macros" } strum = "0.26.2" strum_macros = "0.26.4" @@ -45,7 +46,7 @@ libc = "0.2" itertools = "0.10" -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +ark-ff = { workspace = true } ark-ec = { version = "0.3.0", features = [ "std" ] } ark-serialize = { version = "0.3.0", features = [ "std" ] } ark-poly = { version = "0.3.0", features = [ "std" ] } @@ -77,8 +78,6 @@ fraction = { version = "=0.15.1", default-features = false, features = ["with-se getrandom = { version = "0.2", features = ["js"] } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" -js-sys = "0.3" -web-sys = { version = "0.3", features = ["Window", "Response"] } [target.'cfg(not(target_family = "wasm"))'.dependencies] zstd = { version = "0.12", optional = true } diff --git a/ledger/src/account/account.rs b/ledger/src/account/account.rs index b5dbe4e1c3..171d0eca3a 100644 --- a/ledger/src/account/account.rs +++ b/ledger/src/account/account.rs @@ -1,4 +1,4 @@ -use std::{fmt::Write, io::Cursor, str::FromStr, sync::Arc}; +use std::{io::Cursor, str::FromStr, sync::Arc}; use ark_ff::{BigInteger256, One, UniformRand, Zero}; use mina_hasher::Fp; @@ -7,14 +7,13 @@ use mina_p2p_messages::{ v2, }; use mina_signer::CompressedPubKey; -use once_cell::sync::OnceCell; +use once_cell::sync::{Lazy, OnceCell}; use openmina_core::constants::PROTOCOL_VERSION; use rand::{prelude::ThreadRng, seq::SliceRandom, Rng}; use serde::{Deserialize, Serialize}; use crate::{ gen_compressed, - hash::{hash_noinputs, hash_with_kimchi, Inputs}, proofs::{ field::{Boolean, FieldWitness, ToBoolean}, numbers::{ @@ -33,7 +32,15 @@ use crate::{ transaction_logic::account_min_balance_at_slot, }, zkapps::snark::FlaggedOption, - MerklePath, MyCow, ToInputs, + AppendToInputs as _, MerklePath, MyCow, ToInputs, +}; +use poseidon::hash::{ + hash_noinputs, hash_with_kimchi, + params::{ + get_merkle_param_for_height, MINA_ACCOUNT, MINA_DERIVE_TOKEN_ID, MINA_SIDELOADED_VK, + MINA_ZKAPP_ACCOUNT, MINA_ZKAPP_URI, NO_INPUT_ZKAPP_ACTION_STATE_EMPTY_ELT, + }, + Inputs, }; use super::common::*; @@ -511,8 +518,6 @@ impl ToInputs for VerificationKey { } impl VerificationKey { - pub const HASH_PARAM: &'static str = "MinaSideLoadedVk"; - /// https://github.com/MinaProtocol/mina/blob/436023ba41c43a50458a551b7ef7a9ae61670b25/src/lib/pickles/side_loaded_verification_key.ml#L310 pub fn dummy() -> Arc { static VK: OnceCell> = OnceCell::new(); @@ -550,7 +555,7 @@ impl VerificationKey { } pub fn hash(&self) -> Fp { - self.hash_with_param(Self::HASH_PARAM) + self.hash_with_param(&MINA_SIDELOADED_VK) } pub fn gen() -> Self { @@ -596,6 +601,16 @@ impl std::fmt::Debug for ZkAppUri { } } +fn default_zkapp_uri_hash() -> Fp { + static HASH: Lazy = Lazy::new(|| { + let mut inputs = Inputs::new(); + inputs.append(&Fp::zero()); + inputs.append(&Fp::zero()); + hash_with_kimchi(&MINA_ZKAPP_URI, &inputs.to_fields()) + }); + *HASH +} + impl ZkAppUri { #[allow(clippy::new_without_default)] pub fn new() -> Self { @@ -612,24 +627,17 @@ impl ZkAppUri { } fn opt_to_field(opt: Option<&ZkAppUri>) -> Fp { + let Some(zkapp_uri) = opt else { + return default_zkapp_uri_hash(); + }; let mut inputs = Inputs::new(); - - match opt { - Some(zkapp_uri) => { - for c in zkapp_uri.0.as_slice() { - for j in 0..8 { - inputs.append_bool((c & (1 << j)) != 0); - } - } - inputs.append_bool(true); - } - None => { - inputs.append_field(Fp::zero()); - inputs.append_field(Fp::zero()); + for c in zkapp_uri.0.as_slice() { + for j in 0..8 { + inputs.append_bool((c & (1 << j)) != 0); } } - - hash_with_kimchi("MinaZkappUri", &inputs.to_fields()) + inputs.append_bool(true); + hash_with_kimchi(&MINA_ZKAPP_URI, &inputs.to_fields()) } } @@ -880,15 +888,13 @@ impl Default for ZkAppAccount { } impl ZkAppAccount { - pub const HASH_PARAM: &'static str = "MinaZkappAccount"; - pub fn hash(&self) -> Fp { - self.hash_with_param(Self::HASH_PARAM) + self.hash_with_param(&MINA_ZKAPP_ACCOUNT) } /// empty_state_element pub fn empty_action_state() -> Fp { - cache_one!(Fp, { hash_noinputs("MinaZkappActionStateEmptyElt") }) + hash_noinputs(&NO_INPUT_ZKAPP_ACTION_STATE_EMPTY_ELT) } pub fn is_default(&self) -> bool { @@ -1029,8 +1035,6 @@ impl ToInputs for AccountId { } impl AccountId { - pub const DERIVE_TOKEN_ID_HASH_PARAM: &'static str = "MinaDeriveTokenId"; - pub fn empty() -> Self { Self { public_key: CompressedPubKey::empty(), @@ -1050,7 +1054,7 @@ impl AccountId { }; TokenId(hash_with_kimchi( - Self::DERIVE_TOKEN_ID_HASH_PARAM, + &MINA_DERIVE_TOKEN_ID, &[self.public_key.x, self.token_id.0, is_odd_field], )) } @@ -1515,8 +1519,7 @@ impl Account { } pub fn hash(&self) -> Fp { - let inputs = self.to_inputs_owned(); - hash_with_kimchi("MinaAccount", &inputs.to_fields()) + self.hash_with_param(&MINA_ACCOUNT) } pub fn checked_hash(&self, w: &mut Witness) -> Fp { @@ -1524,7 +1527,7 @@ impl Account { let inputs = self.to_inputs_owned(); - checked_hash("MinaAccount", &inputs.to_fields(), w) + checked_hash(&MINA_ACCOUNT, &inputs.to_fields(), w) } pub fn rand() -> Self { @@ -1635,6 +1638,14 @@ impl Account { } } +pub fn default_zkapp_hash() -> Fp { + static HASH: Lazy = Lazy::new(|| { + let default = ZkAppAccount::default(); + default.hash() + }); + *HASH +} + impl ToInputs for Account { fn to_inputs(&self, inputs: &mut Inputs) { let Self { @@ -1652,11 +1663,11 @@ impl ToInputs for Account { } = self; // Self::zkapp - let field_zkapp = { - let zkapp = MyCow::borrow_or_default(zkapp); - zkapp.hash() + let field_zkapp = match zkapp.as_ref() { + Some(zkapp) => zkapp.hash(), + None => default_zkapp_hash(), }; - inputs.append_field(field_zkapp); + inputs.append(&field_zkapp); inputs.append(permissions); // Self::timing @@ -1699,21 +1710,17 @@ impl ToInputs for Account { fn verify_merkle_path(account: &Account, merkle_path: &[MerklePath]) -> Fp { let account_hash = account.hash(); - let mut param = String::with_capacity(16); merkle_path .iter() .enumerate() - .fold(account_hash, |accum, (depth, path)| { + .fold(account_hash, |accum, (height, path)| { let hashes = match path { MerklePath::Left(right) => [accum, *right], MerklePath::Right(left) => [*left, accum], }; - - param.clear(); - write!(&mut param, "MinaMklTree{:03}", depth).unwrap(); - - crate::hash::hash_with_kimchi(param.as_str(), &hashes) + let param = get_merkle_param_for_height(height); + hash_with_kimchi(param, &hashes) }) } @@ -1726,22 +1733,19 @@ pub fn checked_verify_merkle_path( use crate::proofs::transaction::transaction_snark::checked_hash; let account_hash = account.checked_hash(w); - let mut param = String::with_capacity(16); merkle_path .iter() .enumerate() - .fold(account_hash, |accum, (depth, path)| { + .fold(account_hash, |accum, (height, path)| { let hashes = match path { MerklePath::Left(right) => [accum, *right], MerklePath::Right(left) => [*left, accum], }; - - param.clear(); - write!(&mut param, "MinaMklTree{:03}", depth).unwrap(); - w.exists(hashes); - checked_hash(param.as_str(), &hashes, w) + + let param = get_merkle_param_for_height(height); + checked_hash(param, &hashes, w) }) } @@ -1756,6 +1760,8 @@ mod tests { #[test] fn test_size_account() { + const SIZE_WITH_9LIMBS: usize = 296; + #[cfg(not(target_family = "wasm"))] const SIZE: usize = 280; @@ -1763,7 +1769,11 @@ mod tests { #[cfg(target_family = "wasm")] const SIZE: usize = 280; - assert_eq!(std::mem::size_of::(), SIZE); + if std::mem::size_of::() == 9 * 4 { + assert_eq!(std::mem::size_of::(), SIZE_WITH_9LIMBS); + } else { + assert_eq!(std::mem::size_of::(), SIZE); + } } #[test] diff --git a/ledger/src/account/common.rs b/ledger/src/account/common.rs index d7cd4fb913..9cb807d563 100644 --- a/ledger/src/account/common.rs +++ b/ledger/src/account/common.rs @@ -65,7 +65,7 @@ impl ToFieldElements for VotingFor { } impl ToInputs for VotingFor { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) { inputs.append_field(self.0); } } @@ -74,14 +74,12 @@ impl ToInputs for VotingFor { pub struct ReceiptChainHash(pub Fp); impl ToInputs for ReceiptChainHash { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) { inputs.append_field(self.0); } } impl ReceiptChainHash { - pub const HASH_PREFIX: &'static str = "CodaReceiptUC"; - pub fn empty_legacy() -> Self { // Value of `Receipt.Chain_hash.empty` in Ocaml (`compatible` branch) Self::from_hex("0b143c0645497a5987a7b88f66340e03db943f0a0df48b69a3a82921ce97b10a").unwrap() diff --git a/ledger/src/database/database.rs b/ledger/src/database/database.rs index f72cc0db73..da1739bf7f 100644 --- a/ledger/src/database/database.rs +++ b/ledger/src/database/database.rs @@ -560,15 +560,13 @@ export function performance_now() { #[test] fn test_hash_empty() { - let heights = [0, 5, 10, 11, 100, 2000]; + let heights = [0, 5, 10, 11]; let hexs = [ "f3ee39f42a7b2cac196c8eb1c9fe00f853678c920c0c9ce3724c0b7fe911c731", "d305ebed68f3d4ff16cfc9c6857c274bfbd4a5e83db6fb26e009f75711005524", "bfae9c6290bcc9cf282889c6b880e4eac236d4e50b22395639731e1465939915", "def7de4e2f2f13aa638f5671eccf9367ab9d39ae49ed683fca0169b33b31c416", - "145494b371b318e94e1976e4b67768a6b1793841b47a6269996354e93d5c603c", - "00fb3327a47dc00f9b04e5c5c59af2851501cb9e602a03908f1ec7b04453540c", ]; let result: Vec<_> = heights diff --git a/ledger/src/database/database_impl.rs b/ledger/src/database/database_impl.rs index 94bdce123e..425a9c31df 100644 --- a/ledger/src/database/database_impl.rs +++ b/ledger/src/database/database_impl.rs @@ -312,7 +312,7 @@ impl DatabaseImpl { let path = match dir_name { Some(dir_name) => dir_name, None => { - let directory = "minadb-".to_owned() + &uuid; + let directory = format!("minadb-{uuid}"); let mut path = PathBuf::from("/tmp"); path.push(&directory); @@ -328,7 +328,7 @@ impl DatabaseImpl { // path // ); - std::fs::create_dir_all(&path).ok(); + // std::fs::create_dir_all(&path).ok(); Self { depth, diff --git a/ledger/src/generators/zkapp_command.rs b/ledger/src/generators/zkapp_command.rs index 11f3b03895..175fb956ae 100644 --- a/ledger/src/generators/zkapp_command.rs +++ b/ledger/src/generators/zkapp_command.rs @@ -47,8 +47,8 @@ use crate::{ use super::{Failure, NotPermitedOf, Role}; -/// Value when we run `dune runtest src/lib/staged_ledger -f` -//const ACCOUNT_CREATION_FEE: Fee = Fee::from_u64(1000000000); +// /// Value when we run `dune runtest src/lib/staged_ledger -f` +// const ACCOUNT_CREATION_FEE: Fee = Fee::from_u64(1000000000); /// https://github.com/MinaProtocol/mina/blob/2ff0292b637684ce0372e7b8e23ec85404dc5091/src/lib/mina_generators/zkapp_command_generators.ml#L443 fn gen_invalid_protocol_state_precondition(psv: &ProtocolStateView) -> ZkAppPreconditions { diff --git a/ledger/src/hash.rs b/ledger/src/hash.rs index b417c9a2a7..5002828cf7 100644 --- a/ledger/src/hash.rs +++ b/ledger/src/hash.rs @@ -1,244 +1,8 @@ -use ark_ff::{BigInteger, BigInteger256, Field, FromBytes}; use mina_hasher::Fp; use mina_signer::CompressedPubKey; -// use oracle::{poseidon::{ArithmeticSponge, Sponge}, constants::PlonkSpongeConstantsKimchi, pasta::fp_kimchi::static_params}; -use crate::{ - poseidon::{static_params, ArithmeticSponge, PlonkSpongeConstantsKimchi, Sponge}, - proofs::witness::Witness, - scan_state::currency, - FpExt, SpongeParamsForField, -}; - -enum Item { - Bool(bool), - U2(u8), - U8(u8), - U32(u32), - U48([u8; 6]), - U64(u64), -} - -impl std::fmt::Debug for Item { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Bool(arg0) => f.write_fmt(format_args!("{}_bool", i32::from(*arg0))), - Self::U2(arg0) => f.write_fmt(format_args!("{}_u2", arg0)), - Self::U8(arg0) => f.write_fmt(format_args!("{}_u8", arg0)), - Self::U32(arg0) => f.write_fmt(format_args!("{}_u32", arg0)), - Self::U48(arg0) => f.write_fmt(format_args!("{:?}_u48", arg0)), - Self::U64(arg0) => f.write_fmt(format_args!("{}_u64", arg0)), - } - } -} - -impl Item { - fn nbits(&self) -> u32 { - match self { - Item::Bool(_) => 1, - Item::U2(_) => 2, - Item::U8(_) => 8, - Item::U32(_) => 32, - Item::U48(_) => 48, - Item::U64(_) => 64, - } - } - - fn as_bigint(&self) -> BigInteger256 { - match self { - Item::Bool(v) => { - if *v { - 1.into() - } else { - 0.into() - } - } - Item::U2(v) => (*v as u64).into(), - Item::U8(v) => (*v as u64).into(), - Item::U32(v) => (*v as u64).into(), - Item::U48(v) => { - let mut bytes = <[u8; 32]>::default(); - bytes[..6].copy_from_slice(&v[..]); - BigInteger256::read(&bytes[..]).unwrap() // Never fail with only 6 bytes - } - Item::U64(v) => (*v).into(), - } - } -} - -pub struct Inputs { - fields: Vec, - packeds: Vec, -} - -impl Default for Inputs { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Debug for Inputs { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Inputs") - .field( - &format!("fields[{:?}]", self.fields.len()), - &self - .fields - .iter() - .map(|f| f.to_decimal()) - .collect::>(), - ) - .field(&format!("packeds[{:?}]", self.packeds.len()), &self.packeds) - .finish() - } -} - -impl Inputs { - pub fn new() -> Self { - Self { - fields: Vec::with_capacity(256), - packeds: Vec::with_capacity(256), - } - } - - pub fn append_bool(&mut self, value: bool) { - self.packeds.push(Item::Bool(value)); - } - - pub fn append_u2(&mut self, value: u8) { - self.packeds.push(Item::U2(value)); - } - - pub fn append_u8(&mut self, value: u8) { - self.packeds.push(Item::U8(value)); - } - - pub fn append_u32(&mut self, value: u32) { - self.packeds.push(Item::U32(value)); - } - - pub fn append_u64(&mut self, value: u64) { - self.packeds.push(Item::U64(value)); - } - - pub fn append_u48(&mut self, value: [u8; 6]) { - self.packeds.push(Item::U48(value)); - } - - pub fn append_field(&mut self, value: Fp) { - self.fields.push(value); - } - - pub fn append_bytes(&mut self, value: &[u8]) { - const BITS: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128]; - - self.packeds.reserve(value.len() * 8); - - for byte in value { - for bit in BITS { - self.append_bool(byte & bit != 0); - } - } - } - - pub fn append(&mut self, value: &T) - where - T: ToInputs, - { - value.to_inputs(self); - } - - #[allow(clippy::wrong_self_convention)] - pub fn to_fields(mut self) -> Vec { - let mut nbits = 0; - let mut current: BigInteger256 = 0.into(); - - for (item, item_nbits) in self.packeds.iter().map(|i| (i.as_bigint(), i.nbits())) { - nbits += item_nbits; - - if nbits < 255 { - current.muln(item_nbits); - - // Addition, but we use 'bitwise or' because we know bits of - // `current` are zero (we just shift-left them) - current = BigInteger256([ - current.0[0] | item.0[0], - current.0[1] | item.0[1], - current.0[2] | item.0[2], - current.0[3] | item.0[3], - ]); - } else { - self.fields.push(current.try_into().unwrap()); // Never fail - current = item; - nbits = item_nbits; - } - } - - if nbits > 0 { - self.fields.push(current.try_into().unwrap()); // Never fail - } - - self.fields - } -} - -fn param_to_field_impl(param: &str, default: [u8; 32]) -> Fp { - let param_bytes = param.as_bytes(); - let len = param_bytes.len(); - - let mut fp = default; - fp[..len].copy_from_slice(param_bytes); - - Fp::read(&fp[..]).expect("fp read failed") -} - -pub fn param_to_field(param: &str) -> Fp { - const DEFAULT: [u8; 32] = [ - b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', b'*', - b'*', b'*', b'*', b'*', b'*', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - - if param.len() > 20 { - panic!("must be 20 byte maximum"); - } - - param_to_field_impl(param, DEFAULT) -} - -fn param_to_field_noinputs(param: &str) -> Fp { - const DEFAULT: [u8; 32] = [0; 32]; - - if param.len() > 32 { - panic!("must be 32 byte maximum"); - } - - param_to_field_impl(param, DEFAULT) -} - -pub fn hash_with_kimchi(param: &str, fields: &[Fp]) -> Fp { - let mut sponge = ArithmeticSponge::::new(static_params()); - - sponge.absorb(&[param_to_field(param)]); - sponge.squeeze(); - - sponge.absorb(fields); - sponge.squeeze() -} - -pub fn hash_fields>(fields: &[F]) -> F { - let mut sponge = ArithmeticSponge::::new(F::get_params()); - - sponge.absorb(fields); - sponge.squeeze() -} - -pub fn hash_noinputs(param: &str) -> Fp { - let mut sponge = ArithmeticSponge::::new(static_params()); - // ArithmeticSponge::::new(pasta::fp_kimchi::static_params()); - - sponge.absorb(&[param_to_field_noinputs(param)]); - sponge.squeeze() -} +use crate::{proofs::witness::Witness, scan_state::currency}; +use poseidon::hash::{hash_with_kimchi, Inputs, LazyParam}; pub trait ToInputs { fn to_inputs(&self, inputs: &mut Inputs); @@ -249,13 +13,13 @@ pub trait ToInputs { inputs } - fn hash_with_param(&self, param: &str) -> Fp { + fn hash_with_param(&self, param: &LazyParam) -> Fp { let mut inputs = Inputs::new(); self.to_inputs(&mut inputs); hash_with_kimchi(param, &inputs.to_fields()) } - fn checked_hash_with_param(&self, param: &str, w: &mut Witness) -> Fp { + fn checked_hash_with_param(&self, param: &LazyParam, w: &mut Witness) -> Fp { use crate::proofs::transaction::transaction_snark::checked_hash; let inputs = self.to_inputs_owned(); @@ -309,10 +73,26 @@ where } } +pub trait AppendToInputs { + fn append(&mut self, value: &T) + where + T: ToInputs; +} + +impl AppendToInputs for Inputs { + fn append(&mut self, value: &T) + where + T: ToInputs, + { + value.to_inputs(self); + } +} + #[cfg(test)] mod tests { use o1_utils::FieldHelpers; + use poseidon::hash::param_to_field; #[cfg(target_family = "wasm")] use wasm_bindgen_test::wasm_bindgen_test as test; diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index 7fefb43e9c..19f4de3232 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -53,7 +53,6 @@ mod hash; pub mod mask; pub mod ondisk; mod port_ocaml; -mod poseidon; pub mod proofs; pub mod scan_state; pub mod sparse_ledger; @@ -72,7 +71,6 @@ pub use base::*; pub use database::*; pub use hash::*; pub use mask::*; -pub use poseidon::*; pub use tree::*; pub use tree_version::*; pub use util::*; diff --git a/ledger/src/ondisk/compression.rs b/ledger/src/ondisk/compression.rs index 9ab60daff7..722c059297 100644 --- a/ledger/src/ondisk/compression.rs +++ b/ledger/src/ondisk/compression.rs @@ -3,7 +3,7 @@ pub enum MaybeCompressed<'a> { No(&'a [u8]), } -impl<'a> AsRef<[u8]> for MaybeCompressed<'a> { +impl AsRef<[u8]> for MaybeCompressed<'_> { fn as_ref(&self) -> &[u8] { match self { MaybeCompressed::Compressed(c) => c, diff --git a/ledger/src/port_ocaml/hash.rs b/ledger/src/port_ocaml/hash.rs index f39d0aca0f..7f5b55cda9 100644 --- a/ledger/src/port_ocaml/hash.rs +++ b/ledger/src/port_ocaml/hash.rs @@ -90,14 +90,15 @@ fn hash_field(f: &Fp) -> u32 { let mut acc = 0; let bigint: BigInteger256 = (*f).into(); - let nignore: usize = bigint.0.iter().rev().take_while(|&b| *b == 0).count(); + let bigint = bigint.to_64x4(); + let nignore: usize = bigint.iter().rev().take_while(|&b| *b == 0).count(); - for bigint in bigint.0.iter().take(BigInteger256::NUM_LIMBS - nignore) { + for bigint in bigint.iter().take(BigInteger256::NUM_LIMBS - nignore) { acc = mix(acc, (bigint & 0xFFFF_FFFF) as u32); acc = mix(acc, (bigint >> 32) as u32); } - if bigint.0.last().unwrap() & 0x8000_0000_0000_0000 != 0 { + if bigint.last().unwrap() & 0x8000_0000_0000_0000 != 0 { // TODO: Not sure if that condition is correct acc += 1; } diff --git a/ledger/src/poseidon/fq_params.rs b/ledger/src/poseidon/fq_params.rs deleted file mode 100644 index d946204eb8..0000000000 --- a/ledger/src/poseidon/fq_params.rs +++ /dev/null @@ -1,833 +0,0 @@ -use std::str::FromStr; - -use mina_curves::pasta::Fq; -use once_cell::sync::Lazy; - -use crate::ArithmeticSpongeParams; - -pub fn params() -> ArithmeticSpongeParams { - ArithmeticSpongeParams { - mds: [ - [ - Fq::from_str( - "28115781186772277486790024060542467295096710153315236019619365740021995624782", - ) - .unwrap(), - Fq::from_str( - "22098002279041163367053200604969603243328318626084412751290336872362628294144", - ) - .unwrap(), - Fq::from_str( - "10518156075882958317589806716220047551309200159506906232124952575033472931386", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8515206633865386306014865142947895502833797732365705727001733785057042819852", - ) - .unwrap(), - Fq::from_str( - "19310731234716792175834594131802557577955166208124819468043130037927500684373", - ) - .unwrap(), - Fq::from_str( - "361439796332338311597104753147071943681730695313819021679602959964518909239", - ) - .unwrap(), - ], - [ - Fq::from_str( - "2193808570710678216879007026210418088296432071066284289131688133644970611483", - ) - .unwrap(), - Fq::from_str( - "1201496953174589855481629688627002262719699487577300614284420648015658009380", - ) - .unwrap(), - Fq::from_str( - "11619800255560837597192574795389782851917036920101027584480912719351481334717", - ) - .unwrap(), - ], - ], - round_constants: [ - [ - Fq::from_str( - "2517640872121921965298496967863234221143680281046699148760560696057284005606", - ) - .unwrap(), - Fq::from_str( - "3391756047431116221709518926936538303706203177575259437741546230828058541679", - ) - .unwrap(), - Fq::from_str( - "28193080211857729746868575888309975056941007202713113547154010421664334143056", - ) - .unwrap(), - ], - [ - Fq::from_str( - "25261619184426186938919514618416881383323154981235406731208902193655587998749", - ) - .unwrap(), - Fq::from_str( - "5438499261516835502981531641588657477212528137520578797088407969732830437134", - ) - .unwrap(), - Fq::from_str( - "1447697894671779324954748568939217281372628544919576009518449387265606369859", - ) - .unwrap(), - ], - [ - Fq::from_str( - "5035532530235542599906399941203951970682478985022204457211063504597080640029", - ) - .unwrap(), - Fq::from_str( - "18548939393800290417015907795270784249198528773378593112394621615021029911007", - ) - .unwrap(), - Fq::from_str( - "28314657632459005492203969796973258399484591559931227050853551342156833947891", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10075465805557971120845970058070916255338843492716768289922460436606689369477", - ) - .unwrap(), - Fq::from_str( - "21985996556868691161386211003270106475915714625334030557267947035839814254081", - ) - .unwrap(), - Fq::from_str( - "9778523497398309788873186849997676949503189428912377745814036481347657299161", - ) - .unwrap(), - ], - [ - Fq::from_str( - "6085447467925843146276340167082679235758707259098174769103982431882228334038", - ) - .unwrap(), - Fq::from_str( - "11214803418623679719680560978819619149235769633101428825693192995405955507848", - ) - .unwrap(), - Fq::from_str( - "20585482519401972421539035665320299097144487427998598740316244173221216198246", - ) - .unwrap(), - ], - [ - Fq::from_str( - "18602266896623204184748247002001496873223612100325866696399863661914256384486", - ) - .unwrap(), - Fq::from_str( - "22165919841309962137671309308234475433816142848229812860682345190836583925843", - ) - .unwrap(), - Fq::from_str( - "22833505632200982123686653495190412951871851216487329681987951602744930627412", - ) - .unwrap(), - ], - [ - Fq::from_str( - "200996541962081036547810490655955282117589336000744078845964972887355639644", - ) - .unwrap(), - Fq::from_str( - "17159390488590225463405148524511348095493761844950655304775985535830170165304", - ) - .unwrap(), - Fq::from_str( - "7519689807382250126180254188667761476713509751388558140260305473388567529705", - ) - .unwrap(), - ], - [ - Fq::from_str( - "14159331841037307097148990917607709903712709092721125605507719995418592745663", - ) - .unwrap(), - Fq::from_str( - "10490695046555645615062072066940833278139280813429718770298136076375411280286", - ) - .unwrap(), - Fq::from_str( - "9996921069626538041923613626115903019578182147993504053879837245826104687293", - ) - .unwrap(), - ], - [ - Fq::from_str( - "28009241574980093348462093077828465154604666812509186537490618830383877236685", - ) - .unwrap(), - Fq::from_str( - "18925279443828804264179873719494108834579217607847079902207023181925588871175", - ) - .unwrap(), - Fq::from_str( - "13126164514615718686767880517156253918404905174962666942976286681458411835722", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1125667389564136291825905670957082668987611691949011617627091942772124917554", - ) - .unwrap(), - Fq::from_str( - "12737072162917928935765906421286553437026542524142430058538254259863452556200", - ) - .unwrap(), - Fq::from_str( - "9855113244149548216327019561589719324434080884827484555441182992249251832158", - ) - .unwrap(), - ], - [ - Fq::from_str( - "6006604346195593001833550983798183088851044846011297061071167569148810544010", - ) - .unwrap(), - Fq::from_str( - "23783465709464699444911580329342599880163107932561352210466223087637763994288", - ) - .unwrap(), - Fq::from_str( - "1581060363083815351710754851350813999229829634252940169154424073664057276774", - ) - .unwrap(), - ], - [ - Fq::from_str( - "24121961545310887440574053281799796355427122479626872394472157625455666323022", - ) - .unwrap(), - Fq::from_str( - "23925781309638869606256007860000699567158045595326122474217734988331349678475", - ) - .unwrap(), - Fq::from_str( - "433512980570318160778040929743715681206456334448542248765142091911433454703", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8080307140515367021419180108267113624095868360927897204642243727009503935719", - ) - .unwrap(), - Fq::from_str( - "13661807750191096117929173962837770733539092996971801228126331071941306856508", - ) - .unwrap(), - Fq::from_str( - "9268394414065063505331314418649987795374055416089324253185088859000252370756", - ) - .unwrap(), - ], - [ - Fq::from_str( - "22374115023493407761095751712373350824513305398485824175669182288521610150311", - ) - .unwrap(), - Fq::from_str( - "22951274634403942446739133926874770994604864227598567536319143390467218980824", - ) - .unwrap(), - Fq::from_str( - "21411532836345163980832919797897483979345524322135010935120723250070247464549", - ) - .unwrap(), - ], - [ - Fq::from_str( - "20688285497159372157224857370703211924056803904697620218749985029000049442943", - ) - .unwrap(), - Fq::from_str( - "8350087190167057556241775495760369408781696125331535735138679647687106863977", - ) - .unwrap(), - Fq::from_str( - "13485893160159637778707269611856683957779710980787754997470728774769162419576", - ) - .unwrap(), - ], - [ - Fq::from_str( - "4621792784192688819920303666439776744566536330750316034321950771579978771021", - ) - .unwrap(), - Fq::from_str( - "13900656491552343190424687336475573267660717627286734246676255663734655019912", - ) - .unwrap(), - Fq::from_str( - "16577037405341365304416318048187907895286388691199320947077947552959834207823", - ) - .unwrap(), - ], - [ - Fq::from_str( - "17453637937712580666297652202332273322112052411250919589546137386514183913993", - ) - .unwrap(), - Fq::from_str( - "9852736110707561006399582579453396957225552488023642073454517393228764176471", - ) - .unwrap(), - Fq::from_str( - "8053970357622019747109700798952789019805031210730923951116580579194625334710", - ) - .unwrap(), - ], - [ - Fq::from_str( - "14566849926060034944494603512439278530775668595134329897253012222562109882008", - ) - .unwrap(), - Fq::from_str( - "8863944349051942080060073891691580009950648437676309749771884964336231381737", - ) - .unwrap(), - Fq::from_str( - "16455762285584757654310476505019438984453107876908065440396394186006196612077", - ) - .unwrap(), - ], - [ - Fq::from_str( - "28098375311516838082882166381119795701982164671360574802728073046992978741339", - ) - .unwrap(), - Fq::from_str( - "13538346067341652694825445642847479918140731375902310280683284825070643960891", - ) - .unwrap(), - Fq::from_str( - "18313412784975078534612748781201087502203257054025866271209086293337241477805", - ) - .unwrap(), - ], - [ - Fq::from_str( - "24807061345703288899043018750567607387907450632666147403804744880717736838940", - ) - .unwrap(), - Fq::from_str( - "16638378638176552952794487891875614248110181610295183306789394461536640085108", - ) - .unwrap(), - Fq::from_str( - "2342874860138849081032934096750004917991517717553229739958552529472431319656", - ) - .unwrap(), - ], - [ - Fq::from_str( - "21631810094765090996871180483650934431972930909326270651252393395613356531282", - ) - .unwrap(), - Fq::from_str( - "2220759912186713489010197903069023809260408491503960321105305330086947471014", - ) - .unwrap(), - Fq::from_str( - "14815764944505758746761442212662459585220143243155504464852948007238083120696", - ) - .unwrap(), - ], - [ - Fq::from_str( - "23947619952183462858644581465494050309407721428302029371055887418452994318961", - ) - .unwrap(), - Fq::from_str( - "25035254658153233628169609451068923631269927394392748023889572264723092874720", - ) - .unwrap(), - Fq::from_str( - "17468020412163678868776493601957969748197290347006692843306595815987772942732", - ) - .unwrap(), - ], - [ - Fq::from_str( - "15262198027618900223004625662874755104828479630165814039838611768431063172994", - ) - .unwrap(), - Fq::from_str( - "25161066724266754383358798644805908588326959881061318668106454787543611445887", - ) - .unwrap(), - Fq::from_str( - "2454250001039770891411267760383268680504653332090622148533496270387793031332", - ) - .unwrap(), - ], - [ - Fq::from_str( - "9171946491887082474979985164918822959719377078284664312866368737511724712644", - ) - .unwrap(), - Fq::from_str( - "6672870238005411132577302023934139592378291207852994424857452575898007687159", - ) - .unwrap(), - Fq::from_str( - "2950400608762766076731526167833938554190979516192019010641815746350334547745", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10653725154501691589476837895400001173933804810435931645261606197625601363132", - ) - .unwrap(), - Fq::from_str( - "12717400214508961810851553873706609743505640660238109459222577386574996883747", - ) - .unwrap(), - Fq::from_str( - "5871058785976817081042949511195036111847495052209270758342334312740290470200", - ) - .unwrap(), - ], - [ - Fq::from_str( - "18192562665205900830717234913238180302424621739145466326708104656354353538015", - ) - .unwrap(), - Fq::from_str( - "19946412409172091711185698839696950657650658896270607012902209489827790455314", - ) - .unwrap(), - Fq::from_str( - "21997416257528392077410699901606794827305154904508120972585193876767785262539", - ) - .unwrap(), - ], - [ - Fq::from_str( - "16525092684784199198745517563091041705366544303388462641935777835264970071331", - ) - .unwrap(), - Fq::from_str( - "27613372589672512522307803997948488817865025374001297632527692577079750053456", - ) - .unwrap(), - Fq::from_str( - "23369674747888778238616865774843237791546925005553032792584302158017141634655", - ) - .unwrap(), - ], - [ - Fq::from_str( - "11012136308159330675912474383855146192700147583104742924419195363346115019405", - ) - .unwrap(), - Fq::from_str( - "20632243971343595216801828590185617698839041744000918292113739726624680548813", - ) - .unwrap(), - Fq::from_str( - "10530371852841765918702282883445676639977895775479854136871270050807595649710", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1610594053831245596683250788274018471388810111366046583216577135605955718023", - ) - .unwrap(), - Fq::from_str( - "452300846172044702598793611907955884294868639769163388132276731316720796255", - ) - .unwrap(), - Fq::from_str( - "22297945145153422883128810575530182077542612397826351322358420927950400316504", - ) - .unwrap(), - ], - [ - Fq::from_str( - "28212510899948152845929142163236606049756849316851154583029383581129293825706", - ) - .unwrap(), - Fq::from_str( - "28325924586146971645663587791728624896861517146549428987043066595915712075981", - ) - .unwrap(), - Fq::from_str( - "23489013325315178311518261165509151135555509351661386106070231815049642443022", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10150108696154604591036176090028652090941375062280095655463112192524823306544", - ) - .unwrap(), - Fq::from_str( - "14935856239824547404885450872472169780177654619496758596151670953532153419587", - ) - .unwrap(), - Fq::from_str( - "4367251608666794961207658726914177158125339342277880902441218521648798930454", - ) - .unwrap(), - ], - [ - Fq::from_str( - "14278046449956534912766622635951826857049583276976844525135170835571509013020", - ) - .unwrap(), - Fq::from_str( - "11627801940273881243235293875277734806211947530882079339115454640100174268255", - ) - .unwrap(), - Fq::from_str( - "22853853581419894582873479603685652928885253184240650995805892818180355600894", - ) - .unwrap(), - ], - [ - Fq::from_str( - "4405193089432137585625363585733613667088817369599257533888439029942466720878", - ) - .unwrap(), - Fq::from_str( - "26434497741746827048559732407319982377645052620918789373329661707603241810667", - ) - .unwrap(), - Fq::from_str( - "23558650878002025381506445692526977061352711282820117441110868042756853707843", - ) - .unwrap(), - ], - [ - Fq::from_str( - "27427423077748345654234924309581695092179468167973406115643356520054395647078", - ) - .unwrap(), - Fq::from_str( - "17585801825757985265979208086560185342609289319992678737491966299829354657891", - ) - .unwrap(), - Fq::from_str( - "22079131836316223121286612953926945430480043835170303484162677394496378207190", - ) - .unwrap(), - ], - [ - Fq::from_str( - "20126865597655889981803452476686954944892814234259869552204215672627920656068", - ) - .unwrap(), - Fq::from_str( - "5591585339015997308682985123056479221565470335707041924016523106405300562835", - ) - .unwrap(), - Fq::from_str( - "9422316572086279209843572429137982927615080330725918371521370800874341571474", - ) - .unwrap(), - ], - [ - Fq::from_str( - "2735677349719528139570614238939713941030373684882307164259316901880218894412", - ) - .unwrap(), - Fq::from_str( - "16229147459127626384090303399894157248853232127961182470501666316464149067069", - ) - .unwrap(), - Fq::from_str( - "17151067888069760812629817914442472623785916486309268828873486698948911058517", - ) - .unwrap(), - ], - [ - Fq::from_str( - "13833972862865550568348750465964022581895521701070662509936215512761615491351", - ) - .unwrap(), - Fq::from_str( - "9624679817699048440664645568701817641311119158936258215534754849666144699339", - ) - .unwrap(), - Fq::from_str( - "10273179847163882031630140477902608240997857384703412878925192706057610103613", - ) - .unwrap(), - ], - [ - Fq::from_str( - "3172037826021850467928085880043492158321918352296515787555947245998877188849", - ) - .unwrap(), - Fq::from_str( - "28890802281119993101506497911757988639840653958256859430239635494708187190915", - ) - .unwrap(), - Fq::from_str( - "23496953773368274731821824281559682992786773767847557735733251263969009271239", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1509044982655321910215442389040863370827049078919961070795919190828975736187", - ) - .unwrap(), - Fq::from_str( - "13927172650979098916742472053302036482743492746437467103459483008024082210879", - ) - .unwrap(), - Fq::from_str( - "17248379591027039069313293591621091031164062825086122980769287846951363066520", - ) - .unwrap(), - ], - [ - Fq::from_str( - "11350333545134487336540967650634077894516131586708748380417042089147896079201", - ) - .unwrap(), - Fq::from_str( - "639497848254405996993150855123515463224731962182127668267769103213580096582", - ) - .unwrap(), - Fq::from_str( - "24528361599642320451530127347946798949257664936307333999618279589325586618880", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8217015496508457685301448884203977810298711070026260090660268003968421268717", - ) - .unwrap(), - Fq::from_str( - "6703444480721420507060701216472376128524677965704475494357937059812166295103", - ) - .unwrap(), - Fq::from_str( - "8051365375874262471960241848873604339195556527603956582828833313772444122472", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10412735174026641936105532807659667596947675372330827493649954160029449767122", - ) - .unwrap(), - Fq::from_str( - "8447576362386697729021229138353952824970707645851763166490398451107606293885", - ) - .unwrap(), - Fq::from_str( - "4802965296970904162106502573136505305073730277702271660292532219583823320181", - ) - .unwrap(), - ], - [ - Fq::from_str( - "3244354881334856885788568976540712586633556478250043997221528214026130052269", - ) - .unwrap(), - Fq::from_str( - "817270901440592571623549787267103386561304980129799240746702119063425010300", - ) - .unwrap(), - Fq::from_str( - "6566338353152134577893356938981496347522747926131278635019050445923229718029", - ) - .unwrap(), - ], - [ - Fq::from_str( - "4854521709622003124815206874897232905514824969466266873443062691298769768277", - ) - .unwrap(), - Fq::from_str( - "12830134034124699064152980183243986699241944691238427861184919962819448276943", - ) - .unwrap(), - Fq::from_str( - "24309439157688106320977023683093060719537142150089588950480669629964661236785", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1853791709949511636795588377016980571084333441972847324139062389997895453872", - ) - .unwrap(), - Fq::from_str( - "11399505004623970417786749745036397690793259153591025248188283534764565207306", - ) - .unwrap(), - Fq::from_str( - "6280235834578097246976697944083887557501831809932305676532914637669922657807", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1516294190187225192808636261678393666537186816904214776860202535671714230097", - ) - .unwrap(), - Fq::from_str( - "5835813607391397757416951433662507638966861369364000865214031356023042341328", - ) - .unwrap(), - Fq::from_str( - "25777313996516799380163546628133415256678997511953860435781885414872422583905", - ) - .unwrap(), - ], - [ - Fq::from_str( - "9749298878960864917089442034293906589697892682402070689770627645324414273893", - ) - .unwrap(), - Fq::from_str( - "19986612197193695239708718365565978831607994386509967951279410162135133793419", - ) - .unwrap(), - Fq::from_str( - "5020585421647265067890838871263925730422335215511670656851726444447972642755", - ) - .unwrap(), - ], - [ - Fq::from_str( - "7256822974971238434100017358319972368738353570339258522235883585691301791128", - ) - .unwrap(), - Fq::from_str( - "9789139064283320903202623693175751994730652446378861671859478926598420184293", - ) - .unwrap(), - Fq::from_str( - "19283468246375057076525422714896652730563534118070235174488237489890270899533", - ) - .unwrap(), - ], - [ - Fq::from_str( - "11487321478704551489982188818171823402443882145686911658585221913500937481156", - ) - .unwrap(), - Fq::from_str( - "16513958012405406860890342996091255867910990589443610357743227675107758695101", - ) - .unwrap(), - Fq::from_str( - "24764429351173766080138047602436205744310671344674490826288279531917797263231", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8256258316375000496541664568891934707113720493937218096466691600593595285909", - ) - .unwrap(), - Fq::from_str( - "26919625894863883593081175799908601863265420311251948374988589188905317081443", - ) - .unwrap(), - Fq::from_str( - "10135851848127171199130812615581006825969108287418884763125596866448544567342", - ) - .unwrap(), - ], - [ - Fq::from_str( - "17567146349912867622479843655652582453162587996421871126612027345809646551661", - ) - .unwrap(), - Fq::from_str( - "2524802431860351616270075327416865184018211992251290134350377936184047953453", - ) - .unwrap(), - Fq::from_str( - "3417609143162661859785838333493682460709943782149216513733553607075915176256", - ) - .unwrap(), - ], - [ - Fq::from_str( - "6906455011502599710165862205505812668908382042647994457156780865092846286493", - ) - .unwrap(), - Fq::from_str( - "21042097659487317081899343674473811663642293019125869396575405454328274948985", - ) - .unwrap(), - Fq::from_str( - "25222370053690749913129090298406788520061040938312366403907461864202905656238", - ) - .unwrap(), - ], - [ - Fq::from_str( - "18933201791079410639949505893100361911334261775545573219434897335758052335005", - ) - .unwrap(), - Fq::from_str( - "14503331557348715387048413780116585195932777696828173626366829282421027153184", - ) - .unwrap(), - Fq::from_str( - "3558781473325529402549318082942465709639711182863041375748599816583729962116", - ) - .unwrap(), - ], - [ - Fq::from_str( - "23932570601084008621895097434501731960424360312878373523779451810455362953625", - ) - .unwrap(), - Fq::from_str( - "13286131463754478912858022007443470896920464302917391606059553157137090717219", - ) - .unwrap(), - Fq::from_str( - "9969435194445819847988134248075866286921574284754991873902788928171429847506", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10821551500865029673311799086099720530496516676117927814621168667836737594374", - ) - .unwrap(), - Fq::from_str( - "57689402905128519605376551862931564078571458212398163192591670282543962941", - ) - .unwrap(), - Fq::from_str( - "4484359679395800410695081358212522306960518636189521201445105538223906998486", - ) - .unwrap(), - ], - ], - } -} - -/// the fq sponge params -pub fn static_fq_params() -> &'static ArithmeticSpongeParams { - static PARAMS: Lazy> = Lazy::new(params); - &PARAMS -} diff --git a/ledger/src/poseidon/mod.rs b/ledger/src/poseidon/mod.rs deleted file mode 100644 index 48ebf59fb3..0000000000 --- a/ledger/src/poseidon/mod.rs +++ /dev/null @@ -1,1940 +0,0 @@ -// use crate::constants::SpongeConstants; -// use crate::permutation::{full_round, poseidon_block_cipher}; -use ark_ff::Field; -use mina_curves::pasta::Fq; -use mina_hasher::Fp; -use once_cell::sync::Lazy; - -use std::str::FromStr; - -#[cfg(not(target_family = "wasm"))] -mod fp; - -mod fq_params; - -pub use fq_params::*; - -pub fn make_params() -> ArithmeticSpongeParams { - ArithmeticSpongeParams { - mds: [ - [ - Fp::from_str( - "12035446894107573964500871153637039653510326950134440362813193268448863222019", - ) - .unwrap(), - Fp::from_str( - "25461374787957152039031444204194007219326765802730624564074257060397341542093", - ) - .unwrap(), - Fp::from_str( - "27667907157110496066452777015908813333407980290333709698851344970789663080149", - ) - .unwrap(), - ], - [ - Fp::from_str( - "4491931056866994439025447213644536587424785196363427220456343191847333476930", - ) - .unwrap(), - Fp::from_str( - "14743631939509747387607291926699970421064627808101543132147270746750887019919", - ) - .unwrap(), - Fp::from_str( - "9448400033389617131295304336481030167723486090288313334230651810071857784477", - ) - .unwrap(), - ], - [ - Fp::from_str( - "10525578725509990281643336361904863911009900817790387635342941550657754064843", - ) - .unwrap(), - Fp::from_str( - "27437632000253211280915908546961303399777448677029255413769125486614773776695", - ) - .unwrap(), - Fp::from_str( - "27566319851776897085443681456689352477426926500749993803132851225169606086988", - ) - .unwrap(), - ], - ], - - round_constants: [ - [ - Fp::from_str( - "21155079691556475130150866428468322463125560312786319980770950159250751855431", - ) - .unwrap(), - Fp::from_str( - "16883442198399350202652499677723930673110172289234921799701652810789093522349", - ) - .unwrap(), - Fp::from_str( - "17030687036425314703519085065002231920937594822150793091243263847382891822670", - ) - .unwrap(), - ], - [ - Fp::from_str( - "25216718237129482752721276445368692059997901880654047883630276346421457427360", - ) - .unwrap(), - Fp::from_str( - "9054264347380455706540423067244764093107767235485930776517975315876127782582", - ) - .unwrap(), - Fp::from_str( - "26439087121446593160953570192891907825526260324480347638727375735543609856888", - ) - .unwrap(), - ], - [ - Fp::from_str( - "15251000790817261169639394496851831733819930596125214313084182526610855787494", - ) - .unwrap(), - Fp::from_str( - "10861916012597714684433535077722887124099023163589869801449218212493070551767", - ) - .unwrap(), - Fp::from_str( - "18597653523270601187312528478986388028263730767495975370566527202946430104139", - ) - .unwrap(), - ], - [ - Fp::from_str( - "15831416454198644276563319006805490049460322229057756462580029181847589006611", - ) - .unwrap(), - Fp::from_str( - "15171856919255965617705854914448645702014039524159471542852132430360867202292", - ) - .unwrap(), - Fp::from_str( - "15488495958879593647482715143904752785889816789652405888927117106448507625751", - ) - .unwrap(), - ], - [ - Fp::from_str( - "19039802679983063488134304670998725949842655199289961967801223969839823940152", - ) - .unwrap(), - Fp::from_str( - "4720101937153217036737330058775388037616286510783561045464678919473230044408", - ) - .unwrap(), - Fp::from_str( - "10226318327254973427513859412126640040910264416718766418164893837597674300190", - ) - .unwrap(), - ], - [ - Fp::from_str( - "20878756131129218406920515859235137275859844638301967889441262030146031838819", - ) - .unwrap(), - Fp::from_str( - "7178475685651744631172532830973371642652029385893667810726019303466125436953", - ) - .unwrap(), - Fp::from_str( - "1996970955918516145107673266490486752153434673064635795711751450164177339618", - ) - .unwrap(), - ], - [ - Fp::from_str( - "15205545916434157464929420145756897321482314798910153575340430817222504672630", - ) - .unwrap(), - Fp::from_str( - "25660296961552699573824264215804279051322332899472350724416657386062327210698", - ) - .unwrap(), - Fp::from_str( - "13842611741937412200312851417353455040950878279339067816479233688850376089318", - ) - .unwrap(), - ], - [ - Fp::from_str( - "1383799642177300432144836486981606294838630135265094078921115713566691160459", - ) - .unwrap(), - Fp::from_str( - "1135532281155277588005319334542025976079676424839948500020664227027300010929", - ) - .unwrap(), - Fp::from_str( - "4384117336930380014868572224801371377488688194169758696438185377724744869360", - ) - .unwrap(), - ], - [ - Fp::from_str( - "21725577575710270071808882335900370909424604447083353471892004026180492193649", - ) - .unwrap(), - Fp::from_str( - "676128913284806802699862508051022306366147359505124346651466289788974059668", - ) - .unwrap(), - Fp::from_str( - "25186611339598418732666781049829183886812651492845008333418424746493100589207", - ) - .unwrap(), - ], - [ - Fp::from_str( - "10402240124664763733060094237696964473609580414190944671778761753887884341073", - ) - .unwrap(), - Fp::from_str( - "11918307118590866200687906627767559273324023585642003803337447146531313172441", - ) - .unwrap(), - Fp::from_str( - "16895677254395661024186292503536662354181715337630376909778003268311296637301", - ) - .unwrap(), - ], - [ - Fp::from_str( - "23818602699032741669874498456696325705498383130221297580399035778119213224810", - ) - .unwrap(), - Fp::from_str( - "4285193711150023248690088154344086684336247475445482883105661485741762600154", - ) - .unwrap(), - Fp::from_str( - "19133204443389422404056150665863951250222934590192266371578950735825153238612", - ) - .unwrap(), - ], - [ - Fp::from_str( - "5515589673266504033533906836494002702866463791762187140099560583198974233395", - ) - .unwrap(), - Fp::from_str( - "11830435563729472715615302060564876527985621376031612798386367965451821182352", - ) - .unwrap(), - Fp::from_str( - "7510711479224915247011074129666445216001563200717943545636462819681638560128", - ) - .unwrap(), - ], - [ - Fp::from_str( - "24694843201907722940091503626731830056550128225297370217610328578733387733444", - ) - .unwrap(), - Fp::from_str( - "27361655066973784653563425664091383058914302579694897188019422193564924110528", - ) - .unwrap(), - Fp::from_str( - "21606788186194534241166833954371013788633495786419718955480491478044413102713", - ) - .unwrap(), - ], - [ - Fp::from_str( - "19934060063390905409309407607814787335159021816537006003398035237707924006757", - ) - .unwrap(), - Fp::from_str( - "8495813630060004961768092461554180468161254914257386012937942498774724649553", - ) - .unwrap(), - Fp::from_str( - "27524960680529762202005330464726908693944660961000958842417927307941561848461", - ) - .unwrap(), - ], - [ - Fp::from_str( - "15178481650950399259757805400615635703086255035073919114667254549690862896985", - ) - .unwrap(), - Fp::from_str( - "16164780354695672259791105197274509251141405713012804937107314962551600380870", - ) - .unwrap(), - Fp::from_str( - "10529167793600778056702353412758954281652843049850979705476598375597148191979", - ) - .unwrap(), - ], - [ - Fp::from_str( - "721141070179074082553302896292167103755384741083338957818644728290501449040", - ) - .unwrap(), - Fp::from_str( - "22044408985956234023934090378372374883099115753118261312473550998188148912041", - ) - .unwrap(), - Fp::from_str( - "27068254103241989852888872162525066148367014691482601147536314217249046186315", - ) - .unwrap(), - ], - [ - Fp::from_str( - "3880429241956357176819112098792744584376727450211873998699580893624868748961", - ) - .unwrap(), - Fp::from_str( - "17387097125522937623262508065966749501583017524609697127088211568136333655623", - ) - .unwrap(), - Fp::from_str( - "6256814421247770895467770393029354017922744712896100913895513234184920631289", - ) - .unwrap(), - ], - [ - Fp::from_str( - "2942627347777337187690939671601251987500285937340386328746818861972711408579", - ) - .unwrap(), - Fp::from_str( - "24031654937764287280548628128490074801809101323243546313826173430897408945397", - ) - .unwrap(), - Fp::from_str( - "14401457902976567713827506689641442844921449636054278900045849050301331732143", - ) - .unwrap(), - ], - [ - Fp::from_str( - "20170632877385406450742199836933900257692624353889848352407590794211839130727", - ) - .unwrap(), - Fp::from_str( - "24056496193857444725324410428861722338174099794084586764867109123681727290181", - ) - .unwrap(), - Fp::from_str( - "11257913009612703357266904349759250619633397075667824800196659858304604714965", - ) - .unwrap(), - ], - [ - Fp::from_str( - "22228158921984425749199071461510152694025757871561406897041788037116931009246", - ) - .unwrap(), - Fp::from_str( - "9152163378317846541430311327336774331416267016980485920222768197583559318682", - ) - .unwrap(), - Fp::from_str( - "13906695403538884432896105059360907560653506400343268230130536740148070289175", - ) - .unwrap(), - ], - [ - Fp::from_str( - "7220714562509721437034241786731185291972496952091254931195414855962344025067", - ) - .unwrap(), - Fp::from_str( - "27608867305903811397208862801981345878179337369367554478205559689592889691927", - ) - .unwrap(), - Fp::from_str( - "13288465747219756218882697408422850918209170830515545272152965967042670763153", - ) - .unwrap(), - ], - [ - Fp::from_str( - "8251343892709140154567051772980662609566359215743613773155065627504813327653", - ) - .unwrap(), - Fp::from_str( - "22035238365102171608166944627493632660244312563934708756134297161332908879090", - ) - .unwrap(), - Fp::from_str( - "13560937766273321037807329177749403409731524715067067740487246745322577571823", - ) - .unwrap(), - ], - [ - Fp::from_str( - "21652518608959234550262559135285358020552897349934571164032339186996805408040", - ) - .unwrap(), - Fp::from_str( - "22479086963324173427634460342145551255011746993910136574926173581069603086891", - ) - .unwrap(), - Fp::from_str( - "13676501958531751140966255121288182631772843001727158043704693838707387130095", - ) - .unwrap(), - ], - [ - Fp::from_str( - "5680310394102577950568930199056707827608275306479994663197187031893244826674", - ) - .unwrap(), - Fp::from_str( - "25125360450906166639190392763071557410047335755341060350879819485506243289998", - ) - .unwrap(), - Fp::from_str( - "22659254028501616785029594492374243581602744364859762239504348429834224676676", - ) - .unwrap(), - ], - [ - Fp::from_str( - "23101411405087512171421838856759448177512679869882987631073569441496722536782", - ) - .unwrap(), - Fp::from_str( - "24149774013240355952057123660656464942409328637280437515964899830988178868108", - ) - .unwrap(), - Fp::from_str( - "5782097512368226173095183217893826020351125522160843964147125728530147423065", - ) - .unwrap(), - ], - [ - Fp::from_str( - "13540762114500083869920564649399977644344247485313990448129838910231204868111", - ) - .unwrap(), - Fp::from_str( - "20421637734328811337527547703833013277831804985438407401987624070721139913982", - ) - .unwrap(), - Fp::from_str( - "7742664118615900772129122541139124149525273579639574972380600206383923500701", - ) - .unwrap(), - ], - [ - Fp::from_str( - "1109643801053963021778418773196543643970146666329661268825691230294798976318", - ) - .unwrap(), - Fp::from_str( - "16580663920817053843121063692728699890952505074386761779275436996241901223840", - ) - .unwrap(), - Fp::from_str( - "14638514680222429058240285918830106208025229459346033470787111294847121792366", - ) - .unwrap(), - ], - [ - Fp::from_str( - "17080385857812672649489217965285727739557573467014392822992021264701563205891", - ) - .unwrap(), - Fp::from_str( - "26176268111736737558502775993925696791974738793095023824029827577569530708665", - ) - .unwrap(), - Fp::from_str( - "4382756253392449071896813428140986330161215829425086284611219278674857536001", - ) - .unwrap(), - ], - [ - Fp::from_str( - "13934033814940585315406666445960471293638427404971553891617533231178815348902", - ) - .unwrap(), - Fp::from_str( - "27054912732979753314774418228399230433963143177662848084045249524271046173121", - ) - .unwrap(), - Fp::from_str( - "28916070403698593376490976676534962592542013020010643734621202484860041243391", - ) - .unwrap(), - ], - [ - Fp::from_str( - "24820015636966360150164458094894587765384135259446295278101998130934963922381", - ) - .unwrap(), - Fp::from_str( - "7969535238488580655870884015145760954416088335296905520306227531221721881868", - ) - .unwrap(), - Fp::from_str( - "7690547696740080985104189563436871930607055124031711216224219523236060212249", - ) - .unwrap(), - ], - [ - Fp::from_str( - "9712576468091272384496248353414290908377825697488757134833205246106605867289", - ) - .unwrap(), - Fp::from_str( - "12148698031438398980683630141370402088785182722473169207262735228500190477924", - ) - .unwrap(), - Fp::from_str( - "14359657643133476969781351728574842164124292705609900285041476162075031948227", - ) - .unwrap(), - ], - [ - Fp::from_str( - "23563839965372067275137992801035780013422228997724286060975035719045352435470", - ) - .unwrap(), - Fp::from_str( - "4184634822776323233231956802962638484057536837393405750680645555481330909086", - ) - .unwrap(), - Fp::from_str( - "16249511905185772125762038789038193114431085603985079639889795722501216492487", - ) - .unwrap(), - ], - [ - Fp::from_str( - "11001863048692031559800673473526311616702863826063550559568315794438941516621", - ) - .unwrap(), - Fp::from_str( - "4702354107983530219070178410740869035350641284373933887080161024348425080464", - ) - .unwrap(), - Fp::from_str( - "23751680507533064238793742311430343910720206725883441625894258483004979501613", - ) - .unwrap(), - ], - [ - Fp::from_str( - "28670526516158451470169873496541739545860177757793329093045522432279094518766", - ) - .unwrap(), - Fp::from_str( - "3568312993091537758218792253361873752799472566055209125947589819564395417072", - ) - .unwrap(), - Fp::from_str( - "1819755756343439646550062754332039103654718693246396323207323333948654200950", - ) - .unwrap(), - ], - [ - Fp::from_str( - "5372129954699791301953948907349887257752247843844511069896766784624930478273", - ) - .unwrap(), - Fp::from_str( - "17512156688034945920605615850550150476471921176481039715733979181538491476080", - ) - .unwrap(), - Fp::from_str( - "25777105342317622165159064911913148785971147228777677435200128966844208883059", - ) - .unwrap(), - ], - [ - Fp::from_str( - "25350392006158741749134238306326265756085455157012701586003300872637887157982", - ) - .unwrap(), - Fp::from_str( - "20096724945283767296886159120145376967480397366990493578897615204296873954844", - ) - .unwrap(), - Fp::from_str( - "8063283381910110762785892100479219642751540456251198202214433355775540036851", - ) - .unwrap(), - ], - [ - Fp::from_str( - "4393613870462297385565277757207010824900723217720226130342463666351557475823", - ) - .unwrap(), - Fp::from_str( - "9874972555132910032057499689351411450892722671352476280351715757363137891038", - ) - .unwrap(), - Fp::from_str( - "23590926474329902351439438151596866311245682682435235170001347511997242904868", - ) - .unwrap(), - ], - [ - Fp::from_str( - "17723373371137275859467518615551278584842947963894791032296774955869958211070", - ) - .unwrap(), - Fp::from_str( - "2350345015303336966039836492267992193191479606566494799781846958620636621159", - ) - .unwrap(), - Fp::from_str( - "27755207882790211140683010581856487965587066971982625511152297537534623405016", - ) - .unwrap(), - ], - [ - Fp::from_str( - "6584607987789185408123601849106260907671314994378225066806060862710814193906", - ) - .unwrap(), - Fp::from_str( - "609759108847171587253578490536519506369136135254150754300671591987320319770", - ) - .unwrap(), - Fp::from_str( - "28435187585965602110074342250910608316032945187476441868666714022529803033083", - ) - .unwrap(), - ], - [ - Fp::from_str( - "16016664911651770663938916450245705908287192964254704641717751103464322455303", - ) - .unwrap(), - Fp::from_str( - "17551273293154696089066968171579395800922204266630874071186322718903959339163", - ) - .unwrap(), - Fp::from_str( - "20414195497994754529479032467015716938594722029047207834858832838081413050198", - ) - .unwrap(), - ], - [ - Fp::from_str( - "19773307918850685463180290966774465805537520595602496529624568184993487593855", - ) - .unwrap(), - Fp::from_str( - "24598603838812162820757838364185126333280131847747737533989799467867231166980", - ) - .unwrap(), - Fp::from_str( - "11040972566103463398651864390163813377135738019556270484707889323659789290225", - ) - .unwrap(), - ], - [ - Fp::from_str( - "5189242080957784038860188184443287562488963023922086723850863987437818393811", - ) - .unwrap(), - Fp::from_str( - "1435203288979376557721239239445613396009633263160237764653161500252258220144", - ) - .unwrap(), - Fp::from_str( - "13066591163578079667911016543985168493088721636164837520689376346534152547210", - ) - .unwrap(), - ], - [ - Fp::from_str( - "17345901407013599418148210465150865782628422047458024807490502489711252831342", - ) - .unwrap(), - Fp::from_str( - "22139633362249671900128029132387275539363684188353969065288495002671733200348", - ) - .unwrap(), - Fp::from_str( - "1061056418502836172283188490483332922126033656372467737207927075184389487061", - ) - .unwrap(), - ], - [ - Fp::from_str( - "10241738906190857416046229928455551829189196941239601756375665129874835232299", - ) - .unwrap(), - Fp::from_str( - "27808033332417845112292408673209999320983657696373938259351951416571545364415", - ) - .unwrap(), - Fp::from_str( - "18820154989873674261497645724903918046694142479240549687085662625471577737140", - ) - .unwrap(), - ], - [ - Fp::from_str( - "7983688435214640842673294735439196010654951226956101271763849527529940619307", - ) - .unwrap(), - Fp::from_str( - "17067928657801807648925755556866676899145460770352731818062909643149568271566", - ) - .unwrap(), - Fp::from_str( - "24472070825156236829515738091791182856425635433388202153358580534810244942762", - ) - .unwrap(), - ], - [ - Fp::from_str( - "25752201169361795911258625731016717414310986450004737514595241038036936283227", - ) - .unwrap(), - Fp::from_str( - "26041505376284666160132119888949817249574689146924196064963008712979256107535", - ) - .unwrap(), - Fp::from_str( - "23977050489096115210391718599021827780049209314283111721864956071820102846008", - ) - .unwrap(), - ], - [ - Fp::from_str( - "26678257097278788410676026718736087312816016749016738933942134600725962413805", - ) - .unwrap(), - Fp::from_str( - "10480026985951498884090911619636977502506079971893083605102044931823547311729", - ) - .unwrap(), - Fp::from_str( - "21126631300593007055117122830961273871167754554670317425822083333557535463396", - ) - .unwrap(), - ], - [ - Fp::from_str( - "1564862894215434177641156287699106659379648851457681469848362532131406827573", - ) - .unwrap(), - Fp::from_str( - "13247162472821152334486419054854847522301612781818744556576865965657773174584", - ) - .unwrap(), - Fp::from_str( - "8673615954922496961704442777870253767001276027366984739283715623634850885984", - ) - .unwrap(), - ], - [ - Fp::from_str( - "2794525076937490807476666942602262298677291735723129868457629508555429470085", - ) - .unwrap(), - Fp::from_str( - "4656175953888995612264371467596648522808911819700660048695373348629527757049", - ) - .unwrap(), - Fp::from_str( - "23221574237857660318443567292601561932489621919104226163978909845174616477329", - ) - .unwrap(), - ], - [ - Fp::from_str( - "1878392460078272317716114458784636517603142716091316893054365153068227117145", - ) - .unwrap(), - Fp::from_str( - "2370412714505757731457251173604396662292063533194555369091306667486647634097", - ) - .unwrap(), - Fp::from_str( - "17409784861870189930766639925394191888667317762328427589153989811980152373276", - ) - .unwrap(), - ], - [ - Fp::from_str( - "25869136641898166514111941708608048269584233242773814014385564101168774293194", - ) - .unwrap(), - Fp::from_str( - "11361209360311194794795494027949518465383235799633128250259863567683341091323", - ) - .unwrap(), - Fp::from_str( - "14913258820718821235077379851098720071902170702113538811112331615559409988569", - ) - .unwrap(), - ], - [ - Fp::from_str( - "12957012022018304419868287033513141736995211906682903915897515954290678373899", - ) - .unwrap(), - Fp::from_str( - "17128889547450684566010972445328859295804027707361763477802050112063630550300", - ) - .unwrap(), - Fp::from_str( - "23329219085372232771288306767242735245018143857623151155581182779769305489903", - ) - .unwrap(), - ], - [ - Fp::from_str( - "1607741027962933685476527275858938699728586794398382348454736018784568853937", - ) - .unwrap(), - Fp::from_str( - "2611953825405141009309433982109911976923326848135736099261873796908057448476", - ) - .unwrap(), - Fp::from_str( - "7372230383134982628913227482618052530364724821976589156840317933676130378411", - ) - .unwrap(), - ], - [ - Fp::from_str( - "20203606758501212620842735123770014952499754751430660463060696990317556818571", - ) - .unwrap(), - Fp::from_str( - "4678361398979174017885631008335559529633853759463947250620930343087749944307", - ) - .unwrap(), - Fp::from_str( - "27176462634198471376002287271754121925750749676999036165457559387195124025594", - ) - .unwrap(), - ], - [ - Fp::from_str( - "6361981813552614697928697527332318530502852015189048838072565811230204474643", - ) - .unwrap(), - Fp::from_str( - "13815234633287489023151647353581705241145927054858922281829444557905946323248", - ) - .unwrap(), - Fp::from_str( - "10888828634279127981352133512429657747610298502219125571406085952954136470354", - ) - .unwrap(), - ], - ], - } -} - -/// the fp sponge params -pub fn static_params() -> &'static ArithmeticSpongeParams { - static PARAMS: Lazy> = Lazy::new(make_params); - &PARAMS -} - -pub fn make_fq_params() -> ArithmeticSpongeParams { - ArithmeticSpongeParams { - mds: [ - [ - Fq::from_str( - "28115781186772277486790024060542467295096710153315236019619365740021995624782", - ) - .unwrap(), - Fq::from_str( - "22098002279041163367053200604969603243328318626084412751290336872362628294144", - ) - .unwrap(), - Fq::from_str( - "10518156075882958317589806716220047551309200159506906232124952575033472931386", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8515206633865386306014865142947895502833797732365705727001733785057042819852", - ) - .unwrap(), - Fq::from_str( - "19310731234716792175834594131802557577955166208124819468043130037927500684373", - ) - .unwrap(), - Fq::from_str( - "361439796332338311597104753147071943681730695313819021679602959964518909239", - ) - .unwrap(), - ], - [ - Fq::from_str( - "2193808570710678216879007026210418088296432071066284289131688133644970611483", - ) - .unwrap(), - Fq::from_str( - "1201496953174589855481629688627002262719699487577300614284420648015658009380", - ) - .unwrap(), - Fq::from_str( - "11619800255560837597192574795389782851917036920101027584480912719351481334717", - ) - .unwrap(), - ], - ], - round_constants: [ - [ - Fq::from_str( - "2517640872121921965298496967863234221143680281046699148760560696057284005606", - ) - .unwrap(), - Fq::from_str( - "3391756047431116221709518926936538303706203177575259437741546230828058541679", - ) - .unwrap(), - Fq::from_str( - "28193080211857729746868575888309975056941007202713113547154010421664334143056", - ) - .unwrap(), - ], - [ - Fq::from_str( - "25261619184426186938919514618416881383323154981235406731208902193655587998749", - ) - .unwrap(), - Fq::from_str( - "5438499261516835502981531641588657477212528137520578797088407969732830437134", - ) - .unwrap(), - Fq::from_str( - "1447697894671779324954748568939217281372628544919576009518449387265606369859", - ) - .unwrap(), - ], - [ - Fq::from_str( - "5035532530235542599906399941203951970682478985022204457211063504597080640029", - ) - .unwrap(), - Fq::from_str( - "18548939393800290417015907795270784249198528773378593112394621615021029911007", - ) - .unwrap(), - Fq::from_str( - "28314657632459005492203969796973258399484591559931227050853551342156833947891", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10075465805557971120845970058070916255338843492716768289922460436606689369477", - ) - .unwrap(), - Fq::from_str( - "21985996556868691161386211003270106475915714625334030557267947035839814254081", - ) - .unwrap(), - Fq::from_str( - "9778523497398309788873186849997676949503189428912377745814036481347657299161", - ) - .unwrap(), - ], - [ - Fq::from_str( - "6085447467925843146276340167082679235758707259098174769103982431882228334038", - ) - .unwrap(), - Fq::from_str( - "11214803418623679719680560978819619149235769633101428825693192995405955507848", - ) - .unwrap(), - Fq::from_str( - "20585482519401972421539035665320299097144487427998598740316244173221216198246", - ) - .unwrap(), - ], - [ - Fq::from_str( - "18602266896623204184748247002001496873223612100325866696399863661914256384486", - ) - .unwrap(), - Fq::from_str( - "22165919841309962137671309308234475433816142848229812860682345190836583925843", - ) - .unwrap(), - Fq::from_str( - "22833505632200982123686653495190412951871851216487329681987951602744930627412", - ) - .unwrap(), - ], - [ - Fq::from_str( - "200996541962081036547810490655955282117589336000744078845964972887355639644", - ) - .unwrap(), - Fq::from_str( - "17159390488590225463405148524511348095493761844950655304775985535830170165304", - ) - .unwrap(), - Fq::from_str( - "7519689807382250126180254188667761476713509751388558140260305473388567529705", - ) - .unwrap(), - ], - [ - Fq::from_str( - "14159331841037307097148990917607709903712709092721125605507719995418592745663", - ) - .unwrap(), - Fq::from_str( - "10490695046555645615062072066940833278139280813429718770298136076375411280286", - ) - .unwrap(), - Fq::from_str( - "9996921069626538041923613626115903019578182147993504053879837245826104687293", - ) - .unwrap(), - ], - [ - Fq::from_str( - "28009241574980093348462093077828465154604666812509186537490618830383877236685", - ) - .unwrap(), - Fq::from_str( - "18925279443828804264179873719494108834579217607847079902207023181925588871175", - ) - .unwrap(), - Fq::from_str( - "13126164514615718686767880517156253918404905174962666942976286681458411835722", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1125667389564136291825905670957082668987611691949011617627091942772124917554", - ) - .unwrap(), - Fq::from_str( - "12737072162917928935765906421286553437026542524142430058538254259863452556200", - ) - .unwrap(), - Fq::from_str( - "9855113244149548216327019561589719324434080884827484555441182992249251832158", - ) - .unwrap(), - ], - [ - Fq::from_str( - "6006604346195593001833550983798183088851044846011297061071167569148810544010", - ) - .unwrap(), - Fq::from_str( - "23783465709464699444911580329342599880163107932561352210466223087637763994288", - ) - .unwrap(), - Fq::from_str( - "1581060363083815351710754851350813999229829634252940169154424073664057276774", - ) - .unwrap(), - ], - [ - Fq::from_str( - "24121961545310887440574053281799796355427122479626872394472157625455666323022", - ) - .unwrap(), - Fq::from_str( - "23925781309638869606256007860000699567158045595326122474217734988331349678475", - ) - .unwrap(), - Fq::from_str( - "433512980570318160778040929743715681206456334448542248765142091911433454703", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8080307140515367021419180108267113624095868360927897204642243727009503935719", - ) - .unwrap(), - Fq::from_str( - "13661807750191096117929173962837770733539092996971801228126331071941306856508", - ) - .unwrap(), - Fq::from_str( - "9268394414065063505331314418649987795374055416089324253185088859000252370756", - ) - .unwrap(), - ], - [ - Fq::from_str( - "22374115023493407761095751712373350824513305398485824175669182288521610150311", - ) - .unwrap(), - Fq::from_str( - "22951274634403942446739133926874770994604864227598567536319143390467218980824", - ) - .unwrap(), - Fq::from_str( - "21411532836345163980832919797897483979345524322135010935120723250070247464549", - ) - .unwrap(), - ], - [ - Fq::from_str( - "20688285497159372157224857370703211924056803904697620218749985029000049442943", - ) - .unwrap(), - Fq::from_str( - "8350087190167057556241775495760369408781696125331535735138679647687106863977", - ) - .unwrap(), - Fq::from_str( - "13485893160159637778707269611856683957779710980787754997470728774769162419576", - ) - .unwrap(), - ], - [ - Fq::from_str( - "4621792784192688819920303666439776744566536330750316034321950771579978771021", - ) - .unwrap(), - Fq::from_str( - "13900656491552343190424687336475573267660717627286734246676255663734655019912", - ) - .unwrap(), - Fq::from_str( - "16577037405341365304416318048187907895286388691199320947077947552959834207823", - ) - .unwrap(), - ], - [ - Fq::from_str( - "17453637937712580666297652202332273322112052411250919589546137386514183913993", - ) - .unwrap(), - Fq::from_str( - "9852736110707561006399582579453396957225552488023642073454517393228764176471", - ) - .unwrap(), - Fq::from_str( - "8053970357622019747109700798952789019805031210730923951116580579194625334710", - ) - .unwrap(), - ], - [ - Fq::from_str( - "14566849926060034944494603512439278530775668595134329897253012222562109882008", - ) - .unwrap(), - Fq::from_str( - "8863944349051942080060073891691580009950648437676309749771884964336231381737", - ) - .unwrap(), - Fq::from_str( - "16455762285584757654310476505019438984453107876908065440396394186006196612077", - ) - .unwrap(), - ], - [ - Fq::from_str( - "28098375311516838082882166381119795701982164671360574802728073046992978741339", - ) - .unwrap(), - Fq::from_str( - "13538346067341652694825445642847479918140731375902310280683284825070643960891", - ) - .unwrap(), - Fq::from_str( - "18313412784975078534612748781201087502203257054025866271209086293337241477805", - ) - .unwrap(), - ], - [ - Fq::from_str( - "24807061345703288899043018750567607387907450632666147403804744880717736838940", - ) - .unwrap(), - Fq::from_str( - "16638378638176552952794487891875614248110181610295183306789394461536640085108", - ) - .unwrap(), - Fq::from_str( - "2342874860138849081032934096750004917991517717553229739958552529472431319656", - ) - .unwrap(), - ], - [ - Fq::from_str( - "21631810094765090996871180483650934431972930909326270651252393395613356531282", - ) - .unwrap(), - Fq::from_str( - "2220759912186713489010197903069023809260408491503960321105305330086947471014", - ) - .unwrap(), - Fq::from_str( - "14815764944505758746761442212662459585220143243155504464852948007238083120696", - ) - .unwrap(), - ], - [ - Fq::from_str( - "23947619952183462858644581465494050309407721428302029371055887418452994318961", - ) - .unwrap(), - Fq::from_str( - "25035254658153233628169609451068923631269927394392748023889572264723092874720", - ) - .unwrap(), - Fq::from_str( - "17468020412163678868776493601957969748197290347006692843306595815987772942732", - ) - .unwrap(), - ], - [ - Fq::from_str( - "15262198027618900223004625662874755104828479630165814039838611768431063172994", - ) - .unwrap(), - Fq::from_str( - "25161066724266754383358798644805908588326959881061318668106454787543611445887", - ) - .unwrap(), - Fq::from_str( - "2454250001039770891411267760383268680504653332090622148533496270387793031332", - ) - .unwrap(), - ], - [ - Fq::from_str( - "9171946491887082474979985164918822959719377078284664312866368737511724712644", - ) - .unwrap(), - Fq::from_str( - "6672870238005411132577302023934139592378291207852994424857452575898007687159", - ) - .unwrap(), - Fq::from_str( - "2950400608762766076731526167833938554190979516192019010641815746350334547745", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10653725154501691589476837895400001173933804810435931645261606197625601363132", - ) - .unwrap(), - Fq::from_str( - "12717400214508961810851553873706609743505640660238109459222577386574996883747", - ) - .unwrap(), - Fq::from_str( - "5871058785976817081042949511195036111847495052209270758342334312740290470200", - ) - .unwrap(), - ], - [ - Fq::from_str( - "18192562665205900830717234913238180302424621739145466326708104656354353538015", - ) - .unwrap(), - Fq::from_str( - "19946412409172091711185698839696950657650658896270607012902209489827790455314", - ) - .unwrap(), - Fq::from_str( - "21997416257528392077410699901606794827305154904508120972585193876767785262539", - ) - .unwrap(), - ], - [ - Fq::from_str( - "16525092684784199198745517563091041705366544303388462641935777835264970071331", - ) - .unwrap(), - Fq::from_str( - "27613372589672512522307803997948488817865025374001297632527692577079750053456", - ) - .unwrap(), - Fq::from_str( - "23369674747888778238616865774843237791546925005553032792584302158017141634655", - ) - .unwrap(), - ], - [ - Fq::from_str( - "11012136308159330675912474383855146192700147583104742924419195363346115019405", - ) - .unwrap(), - Fq::from_str( - "20632243971343595216801828590185617698839041744000918292113739726624680548813", - ) - .unwrap(), - Fq::from_str( - "10530371852841765918702282883445676639977895775479854136871270050807595649710", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1610594053831245596683250788274018471388810111366046583216577135605955718023", - ) - .unwrap(), - Fq::from_str( - "452300846172044702598793611907955884294868639769163388132276731316720796255", - ) - .unwrap(), - Fq::from_str( - "22297945145153422883128810575530182077542612397826351322358420927950400316504", - ) - .unwrap(), - ], - [ - Fq::from_str( - "28212510899948152845929142163236606049756849316851154583029383581129293825706", - ) - .unwrap(), - Fq::from_str( - "28325924586146971645663587791728624896861517146549428987043066595915712075981", - ) - .unwrap(), - Fq::from_str( - "23489013325315178311518261165509151135555509351661386106070231815049642443022", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10150108696154604591036176090028652090941375062280095655463112192524823306544", - ) - .unwrap(), - Fq::from_str( - "14935856239824547404885450872472169780177654619496758596151670953532153419587", - ) - .unwrap(), - Fq::from_str( - "4367251608666794961207658726914177158125339342277880902441218521648798930454", - ) - .unwrap(), - ], - [ - Fq::from_str( - "14278046449956534912766622635951826857049583276976844525135170835571509013020", - ) - .unwrap(), - Fq::from_str( - "11627801940273881243235293875277734806211947530882079339115454640100174268255", - ) - .unwrap(), - Fq::from_str( - "22853853581419894582873479603685652928885253184240650995805892818180355600894", - ) - .unwrap(), - ], - [ - Fq::from_str( - "4405193089432137585625363585733613667088817369599257533888439029942466720878", - ) - .unwrap(), - Fq::from_str( - "26434497741746827048559732407319982377645052620918789373329661707603241810667", - ) - .unwrap(), - Fq::from_str( - "23558650878002025381506445692526977061352711282820117441110868042756853707843", - ) - .unwrap(), - ], - [ - Fq::from_str( - "27427423077748345654234924309581695092179468167973406115643356520054395647078", - ) - .unwrap(), - Fq::from_str( - "17585801825757985265979208086560185342609289319992678737491966299829354657891", - ) - .unwrap(), - Fq::from_str( - "22079131836316223121286612953926945430480043835170303484162677394496378207190", - ) - .unwrap(), - ], - [ - Fq::from_str( - "20126865597655889981803452476686954944892814234259869552204215672627920656068", - ) - .unwrap(), - Fq::from_str( - "5591585339015997308682985123056479221565470335707041924016523106405300562835", - ) - .unwrap(), - Fq::from_str( - "9422316572086279209843572429137982927615080330725918371521370800874341571474", - ) - .unwrap(), - ], - [ - Fq::from_str( - "2735677349719528139570614238939713941030373684882307164259316901880218894412", - ) - .unwrap(), - Fq::from_str( - "16229147459127626384090303399894157248853232127961182470501666316464149067069", - ) - .unwrap(), - Fq::from_str( - "17151067888069760812629817914442472623785916486309268828873486698948911058517", - ) - .unwrap(), - ], - [ - Fq::from_str( - "13833972862865550568348750465964022581895521701070662509936215512761615491351", - ) - .unwrap(), - Fq::from_str( - "9624679817699048440664645568701817641311119158936258215534754849666144699339", - ) - .unwrap(), - Fq::from_str( - "10273179847163882031630140477902608240997857384703412878925192706057610103613", - ) - .unwrap(), - ], - [ - Fq::from_str( - "3172037826021850467928085880043492158321918352296515787555947245998877188849", - ) - .unwrap(), - Fq::from_str( - "28890802281119993101506497911757988639840653958256859430239635494708187190915", - ) - .unwrap(), - Fq::from_str( - "23496953773368274731821824281559682992786773767847557735733251263969009271239", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1509044982655321910215442389040863370827049078919961070795919190828975736187", - ) - .unwrap(), - Fq::from_str( - "13927172650979098916742472053302036482743492746437467103459483008024082210879", - ) - .unwrap(), - Fq::from_str( - "17248379591027039069313293591621091031164062825086122980769287846951363066520", - ) - .unwrap(), - ], - [ - Fq::from_str( - "11350333545134487336540967650634077894516131586708748380417042089147896079201", - ) - .unwrap(), - Fq::from_str( - "639497848254405996993150855123515463224731962182127668267769103213580096582", - ) - .unwrap(), - Fq::from_str( - "24528361599642320451530127347946798949257664936307333999618279589325586618880", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8217015496508457685301448884203977810298711070026260090660268003968421268717", - ) - .unwrap(), - Fq::from_str( - "6703444480721420507060701216472376128524677965704475494357937059812166295103", - ) - .unwrap(), - Fq::from_str( - "8051365375874262471960241848873604339195556527603956582828833313772444122472", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10412735174026641936105532807659667596947675372330827493649954160029449767122", - ) - .unwrap(), - Fq::from_str( - "8447576362386697729021229138353952824970707645851763166490398451107606293885", - ) - .unwrap(), - Fq::from_str( - "4802965296970904162106502573136505305073730277702271660292532219583823320181", - ) - .unwrap(), - ], - [ - Fq::from_str( - "3244354881334856885788568976540712586633556478250043997221528214026130052269", - ) - .unwrap(), - Fq::from_str( - "817270901440592571623549787267103386561304980129799240746702119063425010300", - ) - .unwrap(), - Fq::from_str( - "6566338353152134577893356938981496347522747926131278635019050445923229718029", - ) - .unwrap(), - ], - [ - Fq::from_str( - "4854521709622003124815206874897232905514824969466266873443062691298769768277", - ) - .unwrap(), - Fq::from_str( - "12830134034124699064152980183243986699241944691238427861184919962819448276943", - ) - .unwrap(), - Fq::from_str( - "24309439157688106320977023683093060719537142150089588950480669629964661236785", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1853791709949511636795588377016980571084333441972847324139062389997895453872", - ) - .unwrap(), - Fq::from_str( - "11399505004623970417786749745036397690793259153591025248188283534764565207306", - ) - .unwrap(), - Fq::from_str( - "6280235834578097246976697944083887557501831809932305676532914637669922657807", - ) - .unwrap(), - ], - [ - Fq::from_str( - "1516294190187225192808636261678393666537186816904214776860202535671714230097", - ) - .unwrap(), - Fq::from_str( - "5835813607391397757416951433662507638966861369364000865214031356023042341328", - ) - .unwrap(), - Fq::from_str( - "25777313996516799380163546628133415256678997511953860435781885414872422583905", - ) - .unwrap(), - ], - [ - Fq::from_str( - "9749298878960864917089442034293906589697892682402070689770627645324414273893", - ) - .unwrap(), - Fq::from_str( - "19986612197193695239708718365565978831607994386509967951279410162135133793419", - ) - .unwrap(), - Fq::from_str( - "5020585421647265067890838871263925730422335215511670656851726444447972642755", - ) - .unwrap(), - ], - [ - Fq::from_str( - "7256822974971238434100017358319972368738353570339258522235883585691301791128", - ) - .unwrap(), - Fq::from_str( - "9789139064283320903202623693175751994730652446378861671859478926598420184293", - ) - .unwrap(), - Fq::from_str( - "19283468246375057076525422714896652730563534118070235174488237489890270899533", - ) - .unwrap(), - ], - [ - Fq::from_str( - "11487321478704551489982188818171823402443882145686911658585221913500937481156", - ) - .unwrap(), - Fq::from_str( - "16513958012405406860890342996091255867910990589443610357743227675107758695101", - ) - .unwrap(), - Fq::from_str( - "24764429351173766080138047602436205744310671344674490826288279531917797263231", - ) - .unwrap(), - ], - [ - Fq::from_str( - "8256258316375000496541664568891934707113720493937218096466691600593595285909", - ) - .unwrap(), - Fq::from_str( - "26919625894863883593081175799908601863265420311251948374988589188905317081443", - ) - .unwrap(), - Fq::from_str( - "10135851848127171199130812615581006825969108287418884763125596866448544567342", - ) - .unwrap(), - ], - [ - Fq::from_str( - "17567146349912867622479843655652582453162587996421871126612027345809646551661", - ) - .unwrap(), - Fq::from_str( - "2524802431860351616270075327416865184018211992251290134350377936184047953453", - ) - .unwrap(), - Fq::from_str( - "3417609143162661859785838333493682460709943782149216513733553607075915176256", - ) - .unwrap(), - ], - [ - Fq::from_str( - "6906455011502599710165862205505812668908382042647994457156780865092846286493", - ) - .unwrap(), - Fq::from_str( - "21042097659487317081899343674473811663642293019125869396575405454328274948985", - ) - .unwrap(), - Fq::from_str( - "25222370053690749913129090298406788520061040938312366403907461864202905656238", - ) - .unwrap(), - ], - [ - Fq::from_str( - "18933201791079410639949505893100361911334261775545573219434897335758052335005", - ) - .unwrap(), - Fq::from_str( - "14503331557348715387048413780116585195932777696828173626366829282421027153184", - ) - .unwrap(), - Fq::from_str( - "3558781473325529402549318082942465709639711182863041375748599816583729962116", - ) - .unwrap(), - ], - [ - Fq::from_str( - "23932570601084008621895097434501731960424360312878373523779451810455362953625", - ) - .unwrap(), - Fq::from_str( - "13286131463754478912858022007443470896920464302917391606059553157137090717219", - ) - .unwrap(), - Fq::from_str( - "9969435194445819847988134248075866286921574284754991873902788928171429847506", - ) - .unwrap(), - ], - [ - Fq::from_str( - "10821551500865029673311799086099720530496516676117927814621168667836737594374", - ) - .unwrap(), - Fq::from_str( - "57689402905128519605376551862931564078571458212398163192591670282543962941", - ) - .unwrap(), - Fq::from_str( - "4484359679395800410695081358212522306960518636189521201445105538223906998486", - ) - .unwrap(), - ], - ], - } -} - -/// the fp sponge params -pub fn static_fq_params() -> &'static ArithmeticSpongeParams { - static PARAMS: Lazy> = Lazy::new(make_fq_params); - &PARAMS -} - -pub trait SpongeConstants { - const SPONGE_CAPACITY: usize = 1; - const SPONGE_WIDTH: usize = 3; - const SPONGE_RATE: usize = 2; - const PERM_ROUNDS_FULL: usize; - const PERM_ROUNDS_PARTIAL: usize; - const PERM_HALF_ROUNDS_FULL: usize; - const PERM_SBOX: u32; - const PERM_FULL_MDS: bool; - const PERM_INITIAL_ARK: bool; -} - -// #[derive(Clone)] -// pub struct PlonkSpongeConstantsLegacy {} - -// impl SpongeConstants for PlonkSpongeConstantsLegacy { -// const SPONGE_CAPACITY: usize = 1; -// const SPONGE_WIDTH: usize = 3; -// const SPONGE_RATE: usize = 2; -// const PERM_ROUNDS_FULL: usize = 63; -// const PERM_ROUNDS_PARTIAL: usize = 0; -// const PERM_HALF_ROUNDS_FULL: usize = 0; -// const PERM_SBOX: u32 = 5; -// const PERM_FULL_MDS: bool = true; -// const PERM_INITIAL_ARK: bool = true; -// } - -#[derive(Clone)] -pub struct PlonkSpongeConstantsKimchi {} - -impl SpongeConstants for PlonkSpongeConstantsKimchi { - const SPONGE_CAPACITY: usize = 1; - const SPONGE_WIDTH: usize = 3; - const SPONGE_RATE: usize = 2; - const PERM_ROUNDS_FULL: usize = 55; - const PERM_ROUNDS_PARTIAL: usize = 0; - const PERM_HALF_ROUNDS_FULL: usize = 0; - const PERM_SBOX: u32 = 7; - const PERM_FULL_MDS: bool = true; - const PERM_INITIAL_ARK: bool = false; -} - -#[allow(clippy::extra_unused_type_parameters)] -#[inline(never)] -fn apply_mds_matrix( - params: &ArithmeticSpongeParams, - state: &[F], -) -> [F; 3] { - let mut new_state = [F::zero(); 3]; - - for (i, sub_params) in params.mds.iter().enumerate() { - for (state, param) in state.iter().zip(sub_params) { - new_state[i].add_assign(*param * state); - } - } - - new_state -} - -#[inline(never)] -pub fn full_round( - params: &ArithmeticSpongeParams, - state: &mut [F; 3], - r: usize, -) { - for state_i in state.iter_mut() { - *state_i = sbox::(*state_i); - } - *state = apply_mds_matrix::(params, state); - for (i, x) in params.round_constants[r].iter().enumerate() { - state[i].add_assign(x); - } -} - -#[inline(never)] -pub fn poseidon_block_cipher( - params: &ArithmeticSpongeParams, - state: &mut [F; 3], -) { - for r in 0..SC::PERM_ROUNDS_FULL { - full_round::(params, state, r); - } -} - -/// Cryptographic sponge interface - for hashing an arbitrary amount of -/// data into one or more field elements -pub trait Sponge { - /// Create a new cryptographic sponge using arithmetic sponge `params` - fn new(params: &'static ArithmeticSpongeParams) -> Self; - - /// Absorb an array of field elements `x` - fn absorb(&mut self, x: &[Input]); - - /// Squeeze an output from the sponge - fn squeeze(&mut self) -> Digest; - - /// Reset the sponge back to its initial state (as if it were just created) - fn reset(&mut self); -} - -#[inline(never)] -pub fn sbox(mut x: F) -> F { - // Faster than calling x.pow(SC::PERM_SBOX) - let a = x; - for _ in 0..SC::PERM_SBOX - 1 { - x.mul_assign(a); - } - x - - // x.pow([SC::PERM_SBOX as u64]) -} - -#[derive(Clone, Debug)] -pub enum SpongeState { - Absorbed(usize), - Squeezed(usize), -} - -// #[derive(Clone, Serialize, Deserialize, Debug)] -pub struct ArithmeticSpongeParams { - pub round_constants: [[F; 3]; 55], - pub mds: [[F; 3]; 3], - // pub mds: Vec>, -} - -pub trait SpongeParamsForField { - fn get_params() -> &'static ArithmeticSpongeParams; - /// Params from `proof-systems` - /// TODO: Dedup this - fn get_params2() -> &'static mina_poseidon::poseidon::ArithmeticSpongeParams; -} - -impl SpongeParamsForField for Fp { - fn get_params() -> &'static ArithmeticSpongeParams { - static_params() - } - - fn get_params2() -> &'static mina_poseidon::poseidon::ArithmeticSpongeParams { - use mina_poseidon::pasta::fp_kimchi::static_params; - static_params() - } -} - -impl SpongeParamsForField for Fq { - fn get_params() -> &'static ArithmeticSpongeParams { - static_fq_params() - } - - fn get_params2() -> &'static mina_poseidon::poseidon::ArithmeticSpongeParams { - use mina_poseidon::pasta::fq_kimchi::static_params; - static_params() - } -} - -#[derive(Clone)] -pub struct ArithmeticSponge { - pub sponge_state: SpongeState, - rate: usize, - // TODO(mimoo: an array enforcing the width is better no? or at least an assert somewhere) - pub state: [F; 3], - // pub state: Vec, - // tmp_state: Vec>, - params: &'static ArithmeticSpongeParams, - pub constants: std::marker::PhantomData, -} - -impl ArithmeticSponge { - #[inline(never)] - pub fn full_round(&mut self, r: usize) { - full_round::(self.params, &mut self.state, r); - } - - #[inline(never)] - fn poseidon_block_cipher(&mut self) { - poseidon_block_cipher::(self.params, &mut self.state); - } -} - -impl Sponge for ArithmeticSponge { - fn new(params: &'static ArithmeticSpongeParams) -> ArithmeticSponge { - // let capacity = SC::SPONGE_CAPACITY; - let rate = SC::SPONGE_RATE; - - // let mut state = Vec::with_capacity(capacity + rate); - - // for _ in 0..(capacity + rate) { - // state.push(F::zero()); - // } - - // let mut tmp_state = Vec::with_capacity(16); - - ArithmeticSponge { - state: [F::zero(); 3], - rate, - sponge_state: SpongeState::Absorbed(0), - params, - constants: std::marker::PhantomData, - // tmp_state, - } - } - - #[inline(never)] - fn absorb(&mut self, x: &[F]) { - if x.is_empty() { - // Same as the loop below but doesn't add `x` - match self.sponge_state { - SpongeState::Absorbed(n) => { - if n == self.rate { - self.poseidon_block_cipher(); - self.sponge_state = SpongeState::Absorbed(1); - } else { - self.sponge_state = SpongeState::Absorbed(n + 1); - } - } - SpongeState::Squeezed(_n) => { - self.sponge_state = SpongeState::Absorbed(1); - } - } - return; - } - for x in x.iter() { - match self.sponge_state { - SpongeState::Absorbed(n) => { - if n == self.rate { - self.poseidon_block_cipher(); - self.sponge_state = SpongeState::Absorbed(1); - self.state[0].add_assign(x); - } else { - self.sponge_state = SpongeState::Absorbed(n + 1); - self.state[n].add_assign(x); - } - } - SpongeState::Squeezed(_n) => { - self.state[0].add_assign(x); - self.sponge_state = SpongeState::Absorbed(1); - } - } - } - } - - #[inline(never)] - fn squeeze(&mut self) -> F { - // assert_eq!(self.state.len(), 3); - // elog!("NSTATE={:?}", self.state.len()); - match self.sponge_state { - SpongeState::Squeezed(n) => { - if n == self.rate { - self.poseidon_block_cipher(); - self.sponge_state = SpongeState::Squeezed(1); - self.state[0] - } else { - self.sponge_state = SpongeState::Squeezed(n + 1); - self.state[n] - } - } - SpongeState::Absorbed(_n) => { - self.poseidon_block_cipher(); - self.sponge_state = SpongeState::Squeezed(1); - self.state[0] - } - } - } - - fn reset(&mut self) { - todo!() - // self.state = vec![F::zero(); self.state.len()]; - // self.sponge_state = SpongeState::Absorbed(0); - } -} diff --git a/ledger/src/proofs/block.rs b/ledger/src/proofs/block.rs index 9a7f229ccb..0e9e095ecf 100644 --- a/ledger/src/proofs/block.rs +++ b/ledger/src/proofs/block.rs @@ -6,6 +6,10 @@ use mina_curves::pasta::Fq; use mina_hasher::Fp; use mina_p2p_messages::v2; use openmina_core::constants::{constraint_constants, ForkConstants}; +use poseidon::hash::{ + params::{MINA_PROTO_STATE, MINA_PROTO_STATE_BODY}, + Inputs, +}; use crate::{ dummy, @@ -35,7 +39,8 @@ use crate::{ transaction_logic::protocol_state::{EpochLedger, ProtocolStateView}, }, staged_ledger::hash::StagedLedgerHash, - Inputs, ToInputs, + zkapps::intefaces::{SignedAmountBranchParam, SignedAmountInterface}, + ToInputs, }; use super::{ @@ -219,12 +224,12 @@ fn checked_hash_protocol_state( let mut inputs = Inputs::new(); body.to_inputs(&mut inputs); - let body_hash = checked_hash("MinaProtoStateBody", &inputs.to_fields(), w); + let body_hash = checked_hash(&MINA_PROTO_STATE_BODY, &inputs.to_fields(), w); let mut inputs = Inputs::new(); inputs.append_field(*previous_state_hash); inputs.append_field(body_hash); - let hash = checked_hash("MinaProtoState", &inputs.to_fields(), w); + let hash = checked_hash(&MINA_PROTO_STATE, &inputs.to_fields(), w); Ok((hash, body_hash)) } @@ -241,12 +246,12 @@ fn checked_hash_protocol_state2( let mut inputs = Inputs::new(); body.to_inputs(&mut inputs); - let body_hash = checked_hash("MinaProtoStateBody", &inputs.to_fields(), w); + let body_hash = checked_hash(&MINA_PROTO_STATE_BODY, &inputs.to_fields(), w); let mut inputs = Inputs::new(); inputs.append_field(*previous_state_hash); inputs.append_field(body_hash); - let hash = checked_hash("MinaProtoState", &inputs.to_fields(), w); + let hash = checked_hash(&MINA_PROTO_STATE, &inputs.to_fields(), w); (hash, body_hash) } @@ -311,17 +316,17 @@ mod floating_point { } const COEFFICIENTS: [(Sgn, BigInteger256); 11] = [ - (Sgn::Pos, BigInteger256::new([405058, 0, 0, 0])), - (Sgn::Neg, BigInteger256::new([1007582, 0, 0, 0])), - (Sgn::Pos, BigInteger256::new([465602, 0, 0, 0])), - (Sgn::Neg, BigInteger256::new([161365, 0, 0, 0])), - (Sgn::Pos, BigInteger256::new([44739, 0, 0, 0])), - (Sgn::Neg, BigInteger256::new([10337, 0, 0, 0])), - (Sgn::Pos, BigInteger256::new([2047, 0, 0, 0])), - (Sgn::Neg, BigInteger256::new([354, 0, 0, 0])), - (Sgn::Pos, BigInteger256::new([54, 0, 0, 0])), - (Sgn::Neg, BigInteger256::new([7, 0, 0, 0])), - (Sgn::Pos, BigInteger256::new([0, 0, 0, 0])), + (Sgn::Pos, BigInteger256::from_64x4([405058, 0, 0, 0])), + (Sgn::Neg, BigInteger256::from_64x4([1007582, 0, 0, 0])), + (Sgn::Pos, BigInteger256::from_64x4([465602, 0, 0, 0])), + (Sgn::Neg, BigInteger256::from_64x4([161365, 0, 0, 0])), + (Sgn::Pos, BigInteger256::from_64x4([44739, 0, 0, 0])), + (Sgn::Neg, BigInteger256::from_64x4([10337, 0, 0, 0])), + (Sgn::Pos, BigInteger256::from_64x4([2047, 0, 0, 0])), + (Sgn::Neg, BigInteger256::from_64x4([354, 0, 0, 0])), + (Sgn::Pos, BigInteger256::from_64x4([54, 0, 0, 0])), + (Sgn::Neg, BigInteger256::from_64x4([7, 0, 0, 0])), + (Sgn::Pos, BigInteger256::from_64x4([0, 0, 0, 0])), ]; pub struct Params { @@ -606,6 +611,7 @@ mod vrf { use std::ops::Neg; use mina_signer::{CompressedPubKey, PubKey}; + use poseidon::hash::params::{MINA_VRF_MESSAGE, MINA_VRF_OUTPUT}; use crate::{ checked_verify_merkle_path, @@ -618,7 +624,7 @@ mod vrf { }, scan_state::currency::{Amount, Balance}, sparse_ledger::SparseLedger, - AccountIndex, Address, + AccountIndex, Address, AppendToInputs, }; use super::*; @@ -630,7 +636,7 @@ mod vrf { delegator_bits: [bool; 35], } - impl<'a> ToInputs for Message<'a> { + impl ToInputs for Message<'_> { fn to_inputs(&self, inputs: &mut Inputs) { let Self { global_slot, @@ -649,7 +655,7 @@ mod vrf { fn hash_to_group(m: &Message, w: &mut Witness) -> GroupAffine { let inputs = m.to_inputs_owned().to_fields(); - let hash = checked_hash("MinaVrfMessage", &inputs, w); + let hash = checked_hash(&MINA_VRF_MESSAGE, &inputs, w); crate::proofs::group_map::to_group(hash, w) } @@ -685,7 +691,7 @@ mod vrf { inputs.append_field(x); inputs.append_field(y); - checked_hash("MinaVrfOutput", &inputs.to_fields(), w) + checked_hash(&MINA_VRF_OUTPUT, &inputs.to_fields(), w) } fn eval_and_check_public_key( @@ -814,6 +820,7 @@ mod vrf { pub mod consensus { use ark_ff::Zero; use mina_signer::CompressedPubKey; + use poseidon::hash::params::MINA_EPOCH_SEED; use super::{vrf::VRF_OUTPUT_NBITS, *}; use crate::{ @@ -1375,7 +1382,6 @@ pub mod consensus { let (new_total_currency, _overflow) = { let total_currency: Amount = previous_state.total_currency; - w.exists(supply_increase.force_value()); total_currency .to_checked() .add_signed_flagged(supply_increase, w) @@ -1396,7 +1402,7 @@ pub mod consensus { }; fn epoch_seed_update_var(seed: Fp, vrf_result: Fp, w: &mut Witness) -> Fp { - checked_hash("MinaEpochSeed", &[seed, vrf_result], w) + checked_hash(&MINA_EPOCH_SEED, &[seed, vrf_result], w) } let next_epoch_data = { @@ -1638,10 +1644,14 @@ fn block_main<'a>( txn_statement_ledger_hashes_equal(s1, &s2, w) }; - let supply_increase = w.exists_no_check(match txn_stmt_ledger_hashes_didn_t_change { - Boolean::True => CheckedSigned::zero(), - Boolean::False => txn_snark.supply_increase.to_checked(), - }); + let supply_increase = CheckedSigned::on_if( + txn_stmt_ledger_hashes_didn_t_change.var(), + SignedAmountBranchParam { + on_true: &CheckedSigned::zero(), + on_false: &txn_snark.supply_increase.to_checked(), + }, + w, + ); let (updated_consensus_state, consensus_state) = consensus::next_state_checked( previous_state, @@ -1845,7 +1855,7 @@ pub(super) fn generate_block_proof( } = params; let (txn_snark_statement, txn_snark_proof) = - ledger_proof_opt(ledger_proof.as_ref(), next_state)?; + ledger_proof_opt(ledger_proof.as_deref(), next_state)?; let prev_state_proof = &chain.proof; let (new_state_hash, previous_proof_statements) = block_main( diff --git a/ledger/src/proofs/circuit_blobs.rs b/ledger/src/proofs/circuit_blobs.rs index ed89c42852..18558061ef 100644 --- a/ledger/src/proofs/circuit_blobs.rs +++ b/ledger/src/proofs/circuit_blobs.rs @@ -1,43 +1,5 @@ use std::path::Path; -#[cfg(target_family = "wasm")] -mod http { - use openmina_core::thread; - use wasm_bindgen::prelude::*; - - fn to_io_err(err: JsValue) -> std::io::Error { - std::io::Error::new(std::io::ErrorKind::Other, format!("{err:?}")) - } - - async fn _get_bytes(url: String) -> std::io::Result> { - use wasm_bindgen_futures::JsFuture; - use web_sys::Response; - - // let window = js_sys::global().dyn_into::().unwrap(); - let window = web_sys::window().unwrap(); - - let resp_value = JsFuture::from(window.fetch_with_str(&url)) - .await - .map_err(to_io_err)?; - - assert!(resp_value.is_instance_of::()); - let resp: Response = resp_value.dyn_into().unwrap(); - let js = JsFuture::from(resp.array_buffer().map_err(to_io_err)?) - .await - .map_err(to_io_err)?; - Ok(js_sys::Uint8Array::new(&js).to_vec()) - } - - pub async fn get_bytes(url: &str) -> std::io::Result> { - let url = url.to_owned(); - if thread::is_web_worker_thread() { - thread::run_async_fn_in_main_thread(move || _get_bytes(url)).await.expect("failed to run task in the main thread! Maybe main thread crashed or not initialized?") - } else { - _get_bytes(url).await - } - } -} - #[cfg(not(target_family = "wasm"))] pub fn home_base_dir() -> Option { let mut path = std::path::PathBuf::from(std::env::var("HOME").ok()?); @@ -53,7 +15,7 @@ fn git_release_url(filename: &impl AsRef) -> String { } #[cfg(not(target_family = "wasm"))] -pub fn fetch(filename: &impl AsRef) -> std::io::Result> { +pub fn fetch_blocking(filename: &impl AsRef) -> std::io::Result> { use std::path::PathBuf; fn try_base_dir>(base_dir: P, filename: &impl AsRef) -> Option { @@ -121,6 +83,14 @@ pub async fn fetch(filename: &impl AsRef) -> std::io::Result> { let prefix = option_env!("CIRCUIT_BLOBS_HTTP_PREFIX").unwrap_or("/assets/webnode/circuit-blobs"); let url = format!("{prefix}/{}", filename.as_ref().to_str().unwrap()); - http::get_bytes(&url).await + openmina_core::http::get_bytes(&url).await // http::get_bytes(&git_release_url(filename)).await } + +#[cfg(target_family = "wasm")] +pub fn fetch_blocking(filename: &impl AsRef) -> std::io::Result> { + let prefix = + option_env!("CIRCUIT_BLOBS_HTTP_PREFIX").unwrap_or("/assets/webnode/circuit-blobs"); + let url = format!("{prefix}/{}", filename.as_ref().to_str().unwrap()); + openmina_core::http::get_bytes_blocking(&url) +} diff --git a/ledger/src/proofs/field.rs b/ledger/src/proofs/field.rs index c821185663..2588b007ae 100644 --- a/ledger/src/proofs/field.rs +++ b/ledger/src/proofs/field.rs @@ -12,7 +12,7 @@ use mina_curves::pasta::{ use mina_hasher::Fp; use mina_poseidon::{constants::PlonkSpongeConstantsKimchi, sponge::DefaultFqSponge}; -use crate::SpongeParamsForField; +use poseidon::SpongeParamsForField; use super::{ public_input::plonk_checks::{self, ShiftedValue}, diff --git a/ledger/src/proofs/group_map.rs b/ledger/src/proofs/group_map.rs index 013b54c544..04f0f46e91 100644 --- a/ledger/src/proofs/group_map.rs +++ b/ledger/src/proofs/group_map.rs @@ -109,7 +109,10 @@ fn sqrt_exn(x: F, w: &mut Witness) -> F { } fn is_square(x: F) -> bool { - let s = x.pow(F::Params::MODULUS_MINUS_ONE_DIV_TWO); + use ark_ff::BigInteger; + + let modulus_minus_one_div_two = F::Params::MODULUS_MINUS_ONE_DIV_TWO.to_64x4(); + let s = x.pow(modulus_minus_one_div_two); s.is_zero() || s.is_one() } diff --git a/ledger/src/proofs/numbers/currency.rs b/ledger/src/proofs/numbers/currency.rs index cd2928e24a..388d558990 100644 --- a/ledger/src/proofs/numbers/currency.rs +++ b/ledger/src/proofs/numbers/currency.rs @@ -1,4 +1,7 @@ -use crate::scan_state::currency::{self, Amount, Balance, Fee, Magnitude, MinMax, Sgn, Signed}; +use crate::{ + proofs::field::CircuitVar, + scan_state::currency::{self, Amount, Balance, Fee, Magnitude, MinMax, Sgn, Signed}, +}; use std::{cell::Cell, cmp::Ordering::Less}; use crate::proofs::{ @@ -11,6 +14,7 @@ use crate::ToInputs; use super::common::{range_check, ForZkappCheck}; +#[derive(Debug)] pub enum RangeCheckFlaggedKind { Add, Sub, @@ -42,7 +46,7 @@ where T: CheckedCurrency, { pub magnitude: T, - pub sgn: Sgn, + pub sgn: CircuitVar, pub value: Cell>, } @@ -65,7 +69,7 @@ where F: FieldWitness + std::fmt::Debug, T: CheckedCurrency + std::fmt::Debug, { - pub fn create(magnitude: T, sgn: Sgn, value: Option) -> Self { + pub fn create(magnitude: T, sgn: CircuitVar, value: Option) -> Self { Self { magnitude, sgn, @@ -77,32 +81,46 @@ where let value = magnitude.to_field(); Self { magnitude, - sgn: Sgn::Pos, + sgn: CircuitVar::Constant(Sgn::Pos), value: Cell::new(Some(value)), } } pub fn zero() -> Self { - Self::of_unsigned(T::zero()) + Self { + magnitude: T::zero(), + sgn: CircuitVar::Constant(Sgn::Pos), + value: Cell::new(None), + } + } + + // https://github.com/MinaProtocol/mina/blob/ca9c8c86aa21d3c346d28ea0be7ad4cb0c22bf7f/src/lib/transaction_snark/transaction_snark.ml#L1891-L1892 + // https://github.com/MinaProtocol/mina/blob/ca9c8c86aa21d3c346d28ea0be7ad4cb0c22bf7f/src/lib/currency/currency.ml#L579 + pub fn constant_zero() -> Self { + Self { + magnitude: T::zero(), + sgn: CircuitVar::Constant(Sgn::Pos), + value: Cell::new(Some(T::zero().to_field())), + } } pub fn negate(self) -> Self { Self { magnitude: self.magnitude, - sgn: self.sgn.negate(), + sgn: self.sgn.map(|sgn| sgn.negate()), value: Cell::new(self.value.get().map(|f| f.neg())), } } pub fn is_neg(&self) -> Boolean { - match self.sgn { + match self.sgn.value() { Sgn::Pos => Boolean::False, Sgn::Neg => Boolean::True, } } pub fn is_pos(&self) -> Boolean { - match self.sgn { + match self.sgn.value() { Sgn::Pos => Boolean::True, Sgn::Neg => Boolean::False, } @@ -112,7 +130,7 @@ where match self.value.get() { Some(x) => x, None => { - let sgn: F = self.sgn.to_field(); + let sgn: F = self.sgn.value().to_field(); let magnitude: F = self.magnitude.to_field(); let value = w.exists_no_check(magnitude * sgn); self.value.replace(Some(value)); @@ -125,7 +143,7 @@ where match self.value.get() { Some(x) => x, None => { - let sgn: F = self.sgn.to_field(); + let sgn: F = self.sgn.value().to_field(); let magnitude: F = self.magnitude.to_field(); magnitude * sgn } @@ -136,7 +154,7 @@ where match self.value.get() { Some(_) => {} None => { - let sgn: F = self.sgn.to_field(); + let sgn: F = self.sgn.value().to_field(); let magnitude: F = self.magnitude.to_field(); self.value.replace(Some(magnitude * sgn)); } @@ -150,7 +168,7 @@ where fn unchecked(&self) -> currency::Signed { currency::Signed { magnitude: self.magnitude.to_inner(), - sgn: self.sgn, + sgn: *self.sgn.value(), } } @@ -185,7 +203,7 @@ where let res = Self { magnitude: res_magnitude, - sgn, + sgn: CircuitVar::Var(sgn), value: Cell::new(Some(res_value)), }; (res, overflow) @@ -211,19 +229,22 @@ where range_check::(magnitude, w); - Self::create(T::from_field(magnitude), sgn, Some(res_value)) + Self::create( + T::from_field(magnitude), + CircuitVar::Var(sgn), + Some(res_value), + ) } pub fn equal(&self, other: &Self, w: &mut Witness) -> Boolean { - // We decompose this way because of OCaml evaluation order - let t2 = other.value(w); let t1 = self.value(w); + let t2 = other.value(w); field::equal(t1, t2, w) } pub fn const_equal(&self, other: &Self, w: &mut Witness) -> Boolean { - let t2 = other.value(w); let t1 = self.value(w); + let t2 = other.value(w); field::equal(t1, t2, w) } } @@ -482,7 +503,7 @@ macro_rules! impl_currency { } impl ToInputs for $name { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) { self.to_inner().to_inputs(inputs) } } @@ -497,7 +518,7 @@ macro_rules! impl_currency { pub fn to_checked(&self) -> CheckedSigned> { CheckedSigned { magnitude: self.magnitude.to_checked(), - sgn: self.sgn, + sgn: CircuitVar::Var(self.sgn), value: Cell::new(None), } } diff --git a/ledger/src/proofs/numbers/nat.rs b/ledger/src/proofs/numbers/nat.rs index 8fba155935..9965c43450 100644 --- a/ledger/src/proofs/numbers/nat.rs +++ b/ledger/src/proofs/numbers/nat.rs @@ -255,7 +255,7 @@ macro_rules! impl_nat { } impl ToInputs for $name { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut ::poseidon::hash::Inputs) { self.to_inner().to_inputs(inputs) } } diff --git a/ledger/src/proofs/opt_sponge.rs b/ledger/src/proofs/opt_sponge.rs index 20596c0b1e..f9dd6044be 100644 --- a/ledger/src/proofs/opt_sponge.rs +++ b/ledger/src/proofs/opt_sponge.rs @@ -1,4 +1,4 @@ -use crate::ArithmeticSpongeParams; +use poseidon::SpongeParams; use super::{ field::{field, Boolean, CircuitVar, FieldWitness}, @@ -20,7 +20,7 @@ pub enum SpongeState { pub struct OptSponge { pub state: [F; M], - params: &'static ArithmeticSpongeParams, + params: &'static SpongeParams, needs_final_permute_if_empty: bool, pub sponge_state: SpongeState, } @@ -48,13 +48,13 @@ impl OptSponge { } = sponge; match sponge_state { - mina_poseidon::poseidon::SpongeState::Squeezed(n) => Self { + ::poseidon::SpongeState::Squeezed(n) => Self { state, params: F::get_params(), needs_final_permute_if_empty: true, sponge_state: SpongeState::Squeezed(n), }, - mina_poseidon::poseidon::SpongeState::Absorbed(n) => { + ::poseidon::SpongeState::Absorbed(n) => { let abs = |i: Boolean| Self { state, params: F::get_params(), @@ -162,7 +162,7 @@ where struct ConsumeParams<'a, F: FieldWitness> { needs_final_permute_if_empty: bool, start_pos: CircuitVar, - params: &'static ArithmeticSpongeParams, + params: &'static SpongeParams, input: &'a [(CircuitVar, F)], state: [F; 3], } @@ -268,7 +268,7 @@ fn consume(params: ConsumeParams, w: &mut Witness) -> [F; fn block_cipher( mut state: [F; M], - params: &ArithmeticSpongeParams, + params: &SpongeParams, w: &mut Witness, ) -> [F; M] { w.exists(state); @@ -281,7 +281,7 @@ fn block_cipher( fn full_round( state: &mut [F; M], r: usize, - params: &ArithmeticSpongeParams, + params: &SpongeParams, w: &mut Witness, ) { for state_i in state.iter_mut() { @@ -301,7 +301,7 @@ fn sbox(x: F) -> F { res * x } -fn apply_mds_matrix(params: &ArithmeticSpongeParams, state: &[F; 3]) -> [F; 3] { +fn apply_mds_matrix(params: &SpongeParams, state: &[F; 3]) -> [F; 3] { std::array::from_fn(|i| { state .iter() diff --git a/ledger/src/proofs/provers.rs b/ledger/src/proofs/provers.rs index 1e1da34c12..3afd8aa71c 100644 --- a/ledger/src/proofs/provers.rs +++ b/ledger/src/proofs/provers.rs @@ -50,23 +50,13 @@ fn decode_gates_file( Ok(data.gates) } -#[cfg(not(target_family = "wasm"))] fn read_gates_file( filename: &impl AsRef, ) -> std::io::Result>> { - let bytes = circuit_blobs::fetch(filename)?; + let bytes = circuit_blobs::fetch_blocking(filename)?; decode_gates_file(bytes.as_slice()) } -#[cfg(target_family = "wasm")] -async fn read_gates_file( - filepath: &impl AsRef, -) -> std::io::Result>> { - let resp = circuit_blobs::fetch(filepath).await?; - decode_gates_file(&mut resp.as_slice()) -} - -#[cfg(not(target_family = "wasm"))] fn make_gates( filename: &str, ) -> ( @@ -88,30 +78,6 @@ fn make_gates( (internal_vars_path, rows_rev_path, gates) } -#[cfg(target_family = "wasm")] -async fn make_gates( - filename: &str, -) -> ( - HashMap, Option)>, - Vec>>, - Vec>, -) { - let circuits_config = openmina_core::NetworkConfig::global().circuits_config; - let base_dir = Path::new(circuits_config.directory_name); - - let internal_vars_path = base_dir.join(format!("{}_internal_vars.bin", filename)); - let rows_rev_path = base_dir.join(format!("{}_rows_rev.bin", filename)); - let gates_path = base_dir.join(format!("{}_gates.json", filename)); - - let gates: Vec> = read_gates_file(&gates_path).await.unwrap(); - let (internal_vars_path, rows_rev_path) = - read_constraints_data::(&internal_vars_path, &rows_rev_path) - .await - .unwrap(); - - (internal_vars_path, rows_rev_path, gates) -} - macro_rules! get_or_make { ($constant: ident, $type: ty, $filename: expr) => {{ get_or_make!($constant, $type, None, $filename) @@ -121,13 +87,7 @@ macro_rules! get_or_make { return prover.clone(); } - let (internal_vars, rows_rev, gates) = { - #[cfg(not(target_family = "wasm"))] - let res = make_gates($filename); - #[cfg(target_family = "wasm")] - let res = make_gates($filename).await; - res - }; + let (internal_vars, rows_rev, gates) = make_gates($filename); let index = make_prover_index::<$type, _>(gates, $verifier_index); let prover = Prover { @@ -153,7 +113,6 @@ fn default_circuits_config() -> &'static CircuitsConfig { openmina_core::NetworkConfig::global().circuits_config } -#[cfg(not(target_family = "wasm"))] mod prover_makers { use super::*; @@ -274,128 +233,6 @@ mod prover_makers { } } -#[cfg(target_family = "wasm")] -mod prover_makers { - use super::*; - - async fn get_or_make_tx_step_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - TX_STEP_PROVER, - StepTransactionProof, - config.step_transaction_gates - ) - } - async fn get_or_make_tx_wrap_prover( - config: &CircuitsConfig, - verifier_index: Option, - ) -> Arc> { - get_or_make!( - TX_WRAP_PROVER, - WrapTransactionProof, - verifier_index.map(Into::into), - config.wrap_transaction_gates - ) - } - async fn get_or_make_merge_step_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!(MERGE_STEP_PROVER, StepMergeProof, config.step_merge_gates) - } - async fn get_or_make_block_step_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - BLOCK_STEP_PROVER, - StepBlockProof, - config.step_blockchain_gates - ) - } - async fn get_or_make_block_wrap_prover( - config: &CircuitsConfig, - verifier_index: Option, - ) -> Arc> { - get_or_make!( - BLOCK_WRAP_PROVER, - WrapBlockProof, - verifier_index.map(Into::into), - config.wrap_blockchain_gates - ) - } - async fn get_or_make_zkapp_step_opt_signed_opt_signed_prover( - config: &CircuitsConfig, - ) -> Arc> { - get_or_make!( - ZKAPP_STEP_OPT_SIGNED_OPT_SIGNED_PROVER, - StepZkappOptSignedOptSignedProof, - config.step_transaction_opt_signed_opt_signed_gates - ) - } - async fn get_or_make_zkapp_step_opt_signed_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - ZKAPP_STEP_OPT_SIGNED_PROVER, - StepZkappOptSignedProof, - config.step_transaction_opt_signed_gates - ) - } - async fn get_or_make_zkapp_step_proof_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - ZKAPP_STEP_PROOF_PROVER, - StepZkappProvedProof, - config.step_transaction_proved_gates - ) - } - - impl BlockProver { - pub async fn make( - block_verifier_index: Option, - tx_verifier_index: Option, - ) -> Self { - let config = default_circuits_config(); - let block_step_prover = get_or_make_block_step_prover(config).await; - let block_wrap_prover = - get_or_make_block_wrap_prover(config, block_verifier_index).await; - let tx_wrap_prover = get_or_make_tx_wrap_prover(config, tx_verifier_index).await; - - Self { - block_step_prover, - block_wrap_prover, - tx_wrap_prover, - } - } - } - - impl TransactionProver { - pub async fn make(tx_verifier_index: Option) -> Self { - let config = default_circuits_config(); - let tx_step_prover = get_or_make_tx_step_prover(config).await; - let tx_wrap_prover = get_or_make_tx_wrap_prover(config, tx_verifier_index).await; - let merge_step_prover = get_or_make_merge_step_prover(config).await; - - Self { - tx_step_prover, - tx_wrap_prover, - merge_step_prover, - } - } - } - - impl ZkappProver { - pub async fn make(tx_verifier_index: Option) -> Self { - let config = default_circuits_config(); - let tx_wrap_prover = get_or_make_tx_wrap_prover(config, tx_verifier_index).await; - let merge_step_prover = get_or_make_merge_step_prover(config).await; - let step_opt_signed_opt_signed_prover = - get_or_make_zkapp_step_opt_signed_opt_signed_prover(config).await; - let step_opt_signed_prover = get_or_make_zkapp_step_opt_signed_prover(config).await; - let step_proof_prover = get_or_make_zkapp_step_proof_prover(config).await; - - Self { - tx_wrap_prover, - merge_step_prover, - step_opt_signed_opt_signed_prover, - step_opt_signed_prover, - step_proof_prover, - } - } - } -} - #[derive(Clone)] pub struct BlockProver { pub block_step_prover: Arc>, @@ -419,6 +256,18 @@ pub struct ZkappProver { pub step_proof_prover: Arc>, } +impl BlockProver { + /// Called must make sure that before calling this, + /// `BlockProver::make` call was made. + pub fn get_once_made() -> Self { + Self { + block_step_prover: BLOCK_STEP_PROVER.wait().clone(), + block_wrap_prover: BLOCK_WRAP_PROVER.wait().clone(), + tx_wrap_prover: TX_WRAP_PROVER.wait().clone(), + } + } +} + fn decode_constraints_data( mut internal_vars: &[u8], mut rows_rev: &[u8], @@ -471,31 +320,15 @@ fn decode_constraints_data( Some((internal_vars, rows_rev)) } -#[cfg(not(target_family = "wasm"))] fn read_constraints_data( internal_vars_path: &Path, rows_rev_path: &Path, ) -> Option<(InternalVars, Vec>>)> { // ((Fp.t * V.t) list * Fp.t option) - let internal_vars = circuit_blobs::fetch(&internal_vars_path).ok()?; + let internal_vars = circuit_blobs::fetch_blocking(&internal_vars_path).ok()?; // V.t option array list - let rows_rev = circuit_blobs::fetch(&rows_rev_path).ok()?; + let rows_rev = circuit_blobs::fetch_blocking(&rows_rev_path).ok()?; decode_constraints_data(internal_vars.as_slice(), rows_rev.as_slice()) } - -#[cfg(target_family = "wasm")] -async fn read_constraints_data( - internal_vars_path: &Path, - rows_rev_path: &Path, -) -> Option<(InternalVars, Vec>>)> { - // ((Fp.t * V.t) list * Fp.t option) - let internal_vars = circuit_blobs::fetch(&internal_vars_path).await.ok()?; - - // V.t option array list - let rows_rev = circuit_blobs::fetch(&rows_rev_path).await.ok()?; - // std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("rows_rev.bin")).unwrap(); - - decode_constraints_data(internal_vars.as_ref(), rows_rev.as_ref()) -} diff --git a/ledger/src/proofs/public_input/messages.rs b/ledger/src/proofs/public_input/messages.rs index 378e3994b8..480b75a9a3 100644 --- a/ledger/src/proofs/public_input/messages.rs +++ b/ledger/src/proofs/public_input/messages.rs @@ -10,7 +10,7 @@ use mina_curves::{pasta::Fq, pasta::Pallas}; use mina_hasher::Fp; use poly_commitment::PolyComm; -use crate::hash::hash_fields; +use poseidon::hash::hash_fields; impl<'a> From<&'a VerifierIndex> for PlonkVerificationKeyEvals { fn from(verifier_index: &'a VerifierIndex) -> Self { @@ -58,7 +58,7 @@ impl MessagesForNextWrapProof { let field: Fq = hash_fields(&fields); let bigint: BigInteger256 = field.into_repr(); - bigint.0 + bigint.to_64x4() } pub fn hash_checked(&self, w: &mut Witness) -> [u64; 4] { @@ -66,7 +66,7 @@ impl MessagesForNextWrapProof { let field: Fq = checked_hash2(&fields, w); let bigint: BigInteger256 = field.into_repr(); - bigint.0 + bigint.to_64x4() } // TODO: De-duplicate with above @@ -75,7 +75,7 @@ impl MessagesForNextWrapProof { let field: Fq = crate::proofs::transaction::checked_hash3(&fields, w); let bigint: BigInteger256 = field.into_repr(); - bigint.0 + bigint.to_64x4() } /// Implementation of `to_field_elements` @@ -152,7 +152,7 @@ where let field: Fp = hash_fields(&fields); let bigint: BigInteger256 = field.into_repr(); - bigint.0 + bigint.to_64x4() } /// Implementation of `to_field_elements` diff --git a/ledger/src/proofs/public_input/plonk_checks.rs b/ledger/src/proofs/public_input/plonk_checks.rs index ea34e8f0ab..acf4098b95 100644 --- a/ledger/src/proofs/public_input/plonk_checks.rs +++ b/ledger/src/proofs/public_input/plonk_checks.rs @@ -253,15 +253,15 @@ pub fn derive_plonk( let zkp = env.zk_polynomial; let powers_of_alpha = powers_of_alpha(*alpha); let alpha_pow = |i: usize| powers_of_alpha[i]; - let w0 = evals.w.map(|point| point.fst()); + let w0 = evals.w.map(|point| point.zeta); let beta = *beta; let gamma = *gamma; // https://github.com/MinaProtocol/mina/blob/0b63498e271575dbffe2b31f3ab8be293490b1ac/src/lib/pickles/plonk_checks/plonk_checks.ml#L397 let perm = evals.s.iter().enumerate().fold( - evals.z.snd() * beta * alpha_pow(PERM_ALPHA0) * zkp, - |accum, (index, elem)| accum * (gamma + (beta * elem.fst()) + w0[index]), + evals.z.zeta_omega * beta * alpha_pow(PERM_ALPHA0) * zkp, + |accum, (index, elem)| accum * (gamma + (beta * elem.zeta) + w0[index]), ); let perm = -perm; @@ -302,16 +302,16 @@ pub fn derive_plonk_checked( let zkp = env.zk_polynomial; let powers_of_alpha = powers_of_alpha(minimal.alpha); let alpha_pow = |i: usize| powers_of_alpha[i]; - let w0 = evals.w.map(|point| point.fst()); + let w0 = evals.w.map(|point| point.zeta); let beta = minimal.beta; let gamma = minimal.gamma; let perm = evals.s.iter().enumerate().fold( - field::muls(&[evals.z.snd(), beta, alpha_pow(PERM_ALPHA0), zkp], w), + field::muls(&[evals.z.zeta_omega, beta, alpha_pow(PERM_ALPHA0), zkp], w), |accum, (index, elem)| { // We decompose this way because of OCaml evaluation order - let beta_elem = field::mul(beta, elem.fst(), w); + let beta_elem = field::mul(beta, elem.zeta, w); field::mul(accum, gamma + beta_elem + w0[index], w) }, ); @@ -385,7 +385,7 @@ pub fn ft_eval0( ) -> F { const PLONK_TYPES_PERMUTS_MINUS_1_N: usize = 6; - let e0_s: Vec<_> = evals.s.iter().map(|s| s.fst()).collect(); + let e0_s: Vec<_> = evals.s.iter().map(|s| s.zeta).collect(); let zkp = env.zk_polynomial; let powers_of_alpha = powers_of_alpha(minimal.alpha); let alpha_pow = |i: usize| powers_of_alpha[i]; @@ -405,12 +405,12 @@ pub fn ft_eval0( }) .unwrap(); // Never fail, `p_eval0` is non-empty - let w0: Vec<_> = evals.w.iter().map(|w| w.fst()).collect(); + let w0: Vec<_> = evals.w.iter().map(|w| w.zeta).collect(); let ft_eval0 = { let a0 = alpha_pow(PERM_ALPHA0); let w_n = w0[PLONK_TYPES_PERMUTS_MINUS_1_N]; - let init = (w_n + minimal.gamma) * evals.z.snd() * a0 * zkp; + let init = (w_n + minimal.gamma) * evals.z.zeta_omega * a0 * zkp; e0_s.iter().enumerate().fold(init, |acc, (i, s)| { ((minimal.beta * s) + w0[i] + minimal.gamma) * acc }) @@ -421,14 +421,14 @@ pub fn ft_eval0( let ft_eval0 = ft_eval0 - shifts.iter().enumerate().fold( - alpha_pow(PERM_ALPHA0) * zkp * evals.z.fst(), + alpha_pow(PERM_ALPHA0) * zkp * evals.z.zeta, |acc, (i, s)| acc * (minimal.gamma + (minimal.beta * minimal.zeta * s) + w0[i]), ); let nominator = (zeta1m1 * alpha_pow(PERM_ALPHA0 + 1) * (minimal.zeta - env.omega_to_minus_zk_rows) + (zeta1m1 * alpha_pow(PERM_ALPHA0 + 2) * (minimal.zeta - F::one()))) - * (F::one() - evals.z.fst()); + * (F::one() - evals.z.zeta); let denominator = (minimal.zeta - env.omega_to_minus_zk_rows) * (minimal.zeta - F::one()); let ft_eval0 = ft_eval0 + (nominator / denominator); @@ -873,7 +873,7 @@ pub fn ft_eval0_checked( ) -> F { const PLONK_TYPES_PERMUTS_MINUS_1_N: usize = 6; - let e0_s: Vec<_> = evals.s.iter().map(|s| s.fst()).collect(); + let e0_s: Vec<_> = evals.s.iter().map(|s| s.zeta).collect(); let zkp = env.zk_polynomial; let powers_of_alpha = powers_of_alpha(minimal.alpha); let alpha_pow = |i: usize| powers_of_alpha[i]; @@ -890,12 +890,12 @@ pub fn ft_eval0_checked( } }) .unwrap(); // Never fail, `p_eval0` is non-empty - let w0: Vec<_> = evals.w.iter().map(|w| w.fst()).collect(); + let w0: Vec<_> = evals.w.iter().map(|w| w.zeta).collect(); let ft_eval0 = { let a0 = alpha_pow(PERM_ALPHA0); let w_n = w0[PLONK_TYPES_PERMUTS_MINUS_1_N]; - let init = field::muls(&[(w_n + minimal.gamma), evals.z.snd(), a0, zkp], w); + let init = field::muls(&[(w_n + minimal.gamma), evals.z.zeta_omega, a0, zkp], w); e0_s.iter().enumerate().fold(init, |acc, (i, s)| { // We decompose this way because of OCaml evaluation order let beta_s = field::mul(minimal.beta, *s, w); @@ -908,7 +908,7 @@ pub fn ft_eval0_checked( let ft_eval0 = ft_eval0 - shifts.iter().enumerate().fold( - field::muls(&[alpha_pow(PERM_ALPHA0), zkp, evals.z.fst()], w), + field::muls(&[alpha_pow(PERM_ALPHA0), zkp, evals.z.zeta], w), |acc, (i, s)| { let beta_zeta = field::mul(minimal.beta, minimal.zeta, w); field::mul(acc, minimal.gamma + (beta_zeta * s) + w0[i], w) @@ -932,7 +932,7 @@ pub fn ft_eval0_checked( ], w, ); - let nominator = field::mul(a + b, F::one() - evals.z.fst(), w); + let nominator = field::mul(a + b, F::one() - evals.z.zeta, w); let denominator = field::mul( minimal.zeta - env.omega_to_minus_zk_rows, diff --git a/ledger/src/proofs/public_input/protocol_state.rs b/ledger/src/proofs/public_input/protocol_state.rs index b413dedbfe..c414f1ae14 100644 --- a/ledger/src/proofs/public_input/protocol_state.rs +++ b/ledger/src/proofs/public_input/protocol_state.rs @@ -2,14 +2,14 @@ use mina_hasher::Fp; use mina_p2p_messages::v2::MinaBaseProtocolConstantsCheckedValueStableV1; use crate::{ - hash::Inputs, proofs::block::{ consensus::{CheckedConsensusState, ConsensusState}, ProtocolStateBody, }, scan_state::transaction_logic::protocol_state::{EpochData, EpochLedger}, - ToInputs, + AppendToInputs as _, ToInputs, }; +use poseidon::hash::Inputs; impl ToInputs for crate::proofs::block::BlockchainState { fn to_inputs(&self, inputs: &mut Inputs) { diff --git a/ledger/src/proofs/public_input/scalar_challenge.rs b/ledger/src/proofs/public_input/scalar_challenge.rs index 8ed00dfb3b..22933acff7 100644 --- a/ledger/src/proofs/public_input/scalar_challenge.rs +++ b/ledger/src/proofs/public_input/scalar_challenge.rs @@ -12,7 +12,8 @@ pub struct ScalarChallenge { impl From for ScalarChallenge { fn from(value: F) -> Self { let bigint: BigInteger256 = value.into(); - Self::new(bigint.0[0], bigint.0[1]) + let bigint = bigint.to_64x4(); + Self::new(bigint[0], bigint[1]) } } diff --git a/ledger/src/proofs/step.rs b/ledger/src/proofs/step.rs index 0ab038072f..acb3613160 100644 --- a/ledger/src/proofs/step.rs +++ b/ledger/src/proofs/step.rs @@ -16,7 +16,6 @@ use crate::{ }, }, verifier::{get_srs, get_srs_mut}, - SpongeParamsForField, }; use ark_ff::{fields::arithmetic::InvalidBigInt, BigInteger256, One, Zero}; use ark_poly::{ @@ -682,7 +681,7 @@ pub mod step_verifier { sponge.absorb(zeta_omega, w); // TODO: Does it panic in OCaml ? - use mina_poseidon::poseidon::SpongeState::{Absorbed, Squeezed}; + use ::poseidon::SpongeState::{Absorbed, Squeezed}; match (sponge_state_before, &sponge.sponge_state) { (Absorbed(x), Absorbed(y)) => assert_eq!(x, *y), (Squeezed(x), Squeezed(y)) => assert_eq!(x, *y), @@ -714,8 +713,8 @@ pub mod step_verifier { let r = to_field_checked::(r_actual, endo, w); let to_bytes = |f: Fp| { - let BigInteger256([a, b, c, d]) = f.into(); - [a, b, c, d] + let bigint: BigInteger256 = f.into(); + bigint.to_64x4() }; let plonk_mininal = PlonkMinimal:: { @@ -1064,7 +1063,8 @@ pub mod step_verifier { let s_parts = w.exists({ // TODO: Here `s` is a `F` but needs to be read as a `F::Scalar` let bigint: BigInteger256 = s.into(); - let s_odd = bigint.0[0] & 1 != 0; + let bigint = bigint.to_64x4(); + let s_odd = bigint[0] & 1 != 0; let v = if s_odd { s - F2::one() } else { s }; // TODO: Remove this ugly hack let v: BigInteger256 = (v / F2::from(2u64)).into(); @@ -1916,8 +1916,8 @@ fn verify_one( } fn to_bytes(f: Fp) -> [u64; 4] { - let BigInteger256([a, b, c, d]): BigInteger256 = f.into(); - [a, b, c, d] + let bigint: BigInteger256 = f.into(); + bigint.to_64x4() } fn to_4limbs(v: [u64; 2]) -> [u64; 4] { @@ -2011,19 +2011,14 @@ pub fn expand_deferred(params: ExpandDeferredParams) -> Result::new( - crate::static_params(), - ); + let mut sponge = poseidon::Sponge::::default(); for old_bulletproof_challenges in &old_bulletproof_challenges { sponge.absorb(old_bulletproof_challenges); } sponge.squeeze() }; - use mina_poseidon::FqSponge; - - let mut sponge = ::FqSponge::new(Fp::get_params2()); + let mut sponge = poseidon::FqSponge::default(); sponge.absorb_fq(&[four_u64_to_field( &proof_state.sponge_digest_before_evaluations, )?]); @@ -2035,10 +2030,10 @@ pub fn expand_deferred(params: ExpandDeferredParams) -> Result Result Result Result Result for Box { voting_for.to_field_elements(fields); timing.to_field_elements(fields); permissions.to_field_elements(fields); - MyCow::borrow_or_default(zkapp) - .hash() - .to_field_elements(fields); + match zkapp.as_ref() { + Some(zkapp) => zkapp.hash(), + None => default_zkapp_hash(), + } + .to_field_elements(fields); } } @@ -799,13 +803,6 @@ impl> ToFieldElements for &T { } } -impl> ToFieldElements for CheckedSigned { - fn to_field_elements(&self, fields: &mut Vec) { - self.sgn.to_field_elements(fields); - self.magnitude.to_field_elements(fields); - } -} - impl ToFieldElements for InnerCurve { fn to_field_elements(&self, fields: &mut Vec) { let GroupAffine:: { x, y, .. } = self.to_affine(); @@ -826,6 +823,15 @@ impl ToFieldElements for CircuitVar { } } +impl ToFieldElements for CircuitVar { + fn to_field_elements(&self, fields: &mut Vec) { + match self { + CircuitVar::Constant(_) => (), + CircuitVar::Var(var) => var.to_field_elements(fields), + } + } +} + impl ToFieldElements for StepMainStatement { fn to_field_elements(&self, fields: &mut Vec) { let Self { diff --git a/ledger/src/proofs/transaction.rs b/ledger/src/proofs/transaction.rs index 2ba60c89b4..4d7c0a459f 100644 --- a/ledger/src/proofs/transaction.rs +++ b/ledger/src/proofs/transaction.rs @@ -40,7 +40,7 @@ use crate::{ transaction_logic::{local_state::LocalState, transaction_union_payload}, }, verifier::get_srs_mut, - Account, MyCow, ReceiptChainHash, SpongeParamsForField, TimingAsRecord, TokenId, TokenSymbol, + Account, AppendToInputs, MyCow, ReceiptChainHash, TimingAsRecord, TokenId, TokenSymbol, }; use super::{ @@ -67,7 +67,7 @@ pub trait Check { struct FieldBitsIterator { index: usize, - bigint: BigInteger256, + bigint: [u64; 4], } impl Iterator for FieldBitsIterator { @@ -80,13 +80,17 @@ impl Iterator for FieldBitsIterator { let limb_index = index / 64; let bit_index = index % 64; - let limb = self.bigint.0.get(limb_index)?; + let limb = self.bigint.get(limb_index)?; Some(limb & (1 << bit_index) != 0) } } pub fn bigint_to_bits(bigint: BigInteger256) -> [bool; NBITS] { - let mut bits = FieldBitsIterator { index: 0, bigint }.take(NBITS); + let mut bits = FieldBitsIterator { + index: 0, + bigint: bigint.to_64x4(), + } + .take(NBITS); std::array::from_fn(|_| bits.next().unwrap()) } @@ -100,7 +104,12 @@ where /// Difference with `bigint_to_bits`: the number of bits isn't a constant fn bigint_to_bits2(bigint: BigInteger256, nbits: usize) -> Box<[bool]> { - FieldBitsIterator { index: 0, bigint }.take(nbits).collect() + FieldBitsIterator { + index: 0, + bigint: bigint.to_64x4(), + } + .take(nbits) + .collect() } /// Difference with `field_to_bits`: the number of bits isn't a constant @@ -692,7 +701,7 @@ impl PlonkVerificationKeyEvals { } impl crate::ToInputs for PlonkVerificationKeyEvals { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut ::poseidon::hash::Inputs) { let Self { sigma, coefficients, @@ -1220,7 +1229,7 @@ impl std::fmt::Debug for InnerCurve { } impl crate::ToInputs for InnerCurve { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut ::poseidon::hash::Inputs) { let GroupAffine:: { x, y, .. } = self.to_affine(); inputs.append_field(x); inputs.append_field(y); @@ -1250,6 +1259,7 @@ impl InnerCurve { S: Into, { let scale: BigInteger256 = scale.into(); + let scale = scale.to_64x4(); Self { inner: self.inner.mul(scale), } @@ -1514,6 +1524,7 @@ pub mod legacy_input { use crate::scan_state::transaction_logic::transaction_union_payload::{ Body, Common, TransactionUnionPayload, }; + use ::poseidon::hash::legacy; use super::*; @@ -1552,68 +1563,15 @@ pub mod legacy_input { } pub trait CheckedLegacyInput { - fn to_checked_legacy_input(&self, inputs: &mut LegacyInput, w: &mut Witness); + fn to_checked_legacy_input(&self, inputs: &mut legacy::Inputs, w: &mut Witness); - fn to_checked_legacy_input_owned(&self, w: &mut Witness) -> LegacyInput { - let mut inputs = LegacyInput::new(); + fn to_checked_legacy_input_owned(&self, w: &mut Witness) -> legacy::Inputs { + let mut inputs = legacy::Inputs::new(); self.to_checked_legacy_input(&mut inputs, w); inputs } } - #[derive(Clone, Debug)] - pub struct LegacyInput { - fields: Vec, - bits: Vec, - } - - impl Default for LegacyInput { - fn default() -> Self { - Self::new() - } - } - - impl LegacyInput { - pub fn new() -> Self { - Self { - fields: Vec::with_capacity(256), - bits: Vec::with_capacity(1024), - } - } - - pub fn append_bit(&mut self, bit: bool) { - self.bits.push(bit); - } - - pub fn append_bits(&mut self, bits: &[bool]) { - self.bits.extend(bits); - } - - pub fn append_field(&mut self, field: F) { - self.fields.push(field); - } - - pub fn to_fields(mut self) -> Vec { - const NBITS: usize = 255 - 1; - - self.fields.reserve(self.bits.len() / NBITS); - self.fields.extend(self.bits.chunks(NBITS).map(|bits| { - assert!(bits.len() <= NBITS); - - let mut field = [0u64; 4]; - - for (index, bit) in bits.iter().enumerate() { - let limb_index = index / 64; - let bit_index = index % 64; - field[limb_index] |= (*bit as u64) << bit_index; - } - - F::try_from(BigInteger256::new(field)).unwrap() // Never fail - })); - self.fields - } - } - const LEGACY_DEFAULT_TOKEN: [bool; 64] = { let mut default = [false; 64]; default[0] = true; @@ -1621,7 +1579,7 @@ pub mod legacy_input { }; impl CheckedLegacyInput for TransactionUnionPayload { - fn to_checked_legacy_input(&self, inputs: &mut LegacyInput, w: &mut Witness) { + fn to_checked_legacy_input(&self, inputs: &mut legacy::Inputs, w: &mut Witness) { let Self { common: Common { @@ -1673,8 +1631,7 @@ pub mod legacy_input { pub mod poseidon { use std::marker::PhantomData; - use mina_poseidon::constants::SpongeConstants; - use mina_poseidon::poseidon::{ArithmeticSpongeParams, SpongeState}; + use ::poseidon::{PlonkSpongeConstantsKimchi, SpongeConstants, SpongeParams, SpongeState}; use super::*; @@ -1682,7 +1639,7 @@ pub mod poseidon { pub struct Sponge { pub state: [F; 3], pub sponge_state: SpongeState, - params: &'static ArithmeticSpongeParams, + params: &'static SpongeParams, nabsorb: usize, _constants: PhantomData, } @@ -1702,7 +1659,7 @@ pub mod poseidon { F: FieldWitness, C: SpongeConstants, { - pub fn new_with_state(state: [F; 3], params: &'static ArithmeticSpongeParams) -> Self { + pub fn new_with_state_params(state: [F; 3], params: &'static SpongeParams) -> Self { Self { state, sponge_state: SpongeState::Absorbed(0), @@ -1712,8 +1669,12 @@ pub mod poseidon { } } + pub fn new_with_state(state: [F; 3]) -> Self { + Self::new_with_state_params(state, F::get_params()) + } + pub fn new() -> Self { - Self::new_with_state([F::zero(); 3], F::get_params2()) + Self::new_with_state([F::zero(); 3]) } fn absorb_empty(&mut self, w: &mut Witness) { @@ -1955,7 +1916,7 @@ pub mod poseidon { } fn apply_mds_matrix( - params: &ArithmeticSpongeParams, + params: &SpongeParams, state: &[F; 3], ) -> [F; 3] { if C::PERM_FULL_MDS { @@ -2285,18 +2246,21 @@ pub mod transaction_snark { transaction_logic::{checked_cons_signed_command_payload, Coinbase}, }, sparse_ledger::SparseLedger, - AccountId, Inputs, PermissionTo, PermsConst, Timing, TimingAsRecordChecked, ToInputs, + zkapps::intefaces::{SignedAmountBranchParam, SignedAmountInterface}, + AccountId, PermissionTo, PermsConst, Timing, TimingAsRecordChecked, ToInputs, }; + use ::poseidon::hash::{params::MINA_PROTO_STATE_BODY, Inputs, LazyParam}; use ark_ff::Zero; use crate::scan_state::{ currency, transaction_logic::transaction_union_payload::{TransactionUnion, TransactionUnionPayload}, }; + use ::poseidon::hash::legacy; use mina_signer::Signature; use openmina_core::constants::constraint_constants; - use super::{legacy_input::LegacyInput, *}; + use super::*; mod user_command_failure { use crate::scan_state::{ @@ -2559,65 +2523,37 @@ pub mod transaction_snark { } } - pub fn checked_legacy_hash(param: &str, inputs: LegacyInput, w: &mut Witness) -> Fp { - use mina_poseidon::constants::PlonkSpongeConstantsLegacy as Constants; - use mina_poseidon::pasta::fp_legacy::static_params; - - // We hash the parameter first, without introducing values to the witness - let initial_state: [Fp; 3] = { - use mina_poseidon::poseidon::ArithmeticSponge; - use mina_poseidon::poseidon::Sponge; - - let mut sponge = ArithmeticSponge::::new(static_params()); - sponge.absorb(&[crate::param_to_field(param)]); - sponge.squeeze(); - sponge.state.try_into().unwrap() - }; + pub fn checked_legacy_hash( + param: &LazyParam, + inputs: legacy::Inputs, + w: &mut Witness, + ) -> Fp { + use ::poseidon::fp_legacy::params; + use ::poseidon::PlonkSpongeConstantsLegacy as Constants; + let initial_state: [Fp; 3] = param.state(); let mut sponge = - poseidon::Sponge::::new_with_state(initial_state, static_params()); + poseidon::Sponge::::new_with_state_params(initial_state, params()); sponge.absorb(&inputs.to_fields(), w); sponge.squeeze(w) } - pub fn checked_hash(param: &str, inputs: &[Fp], w: &mut Witness) -> Fp { - // We hash the parameter first, without introducing values to the witness - let initial_state: [Fp; 3] = { - use crate::{param_to_field, ArithmeticSponge, PlonkSpongeConstantsKimchi, Sponge}; - - let mut sponge = - ArithmeticSponge::::new(Fp::get_params()); - sponge.absorb(&[param_to_field(param)]); - sponge.squeeze(); - sponge.state - }; - - // dbg!(inputs); - - let mut sponge = poseidon::Sponge::::new_with_state(initial_state, Fp::get_params2()); + pub fn checked_hash(param: &LazyParam, inputs: &[Fp], w: &mut Witness) -> Fp { + let initial_state: [Fp; 3] = param.state(); + let mut sponge = poseidon::Sponge::::new_with_state(initial_state); sponge.absorb(inputs, w); sponge.squeeze(w) } - pub fn checked_hash3(param: &str, inputs: &[Fp], w: &mut Witness) -> Fp { - // We hash the parameter first, without introducing values to the witness - let initial_state: [Fp; 3] = { - use crate::{param_to_field, ArithmeticSponge, PlonkSpongeConstantsKimchi, Sponge}; - - let mut sponge = - ArithmeticSponge::::new(Fp::get_params()); - sponge.absorb(&[param_to_field(param)]); - sponge.squeeze(); - sponge.state - }; - - let mut sponge = poseidon::Sponge::::new_with_state(initial_state, Fp::get_params2()); + pub fn checked_hash3(param: &LazyParam, inputs: &[Fp], w: &mut Witness) -> Fp { + let initial_state: [Fp; 3] = param.state(); + let mut sponge = poseidon::Sponge::::new_with_state(initial_state); sponge.absorb3(inputs, w); sponge.squeeze(w) } fn checked_legacy_signature_hash( - mut inputs: LegacyInput, + mut inputs: legacy::Inputs, signer: &PubKey, signature: &Signature, w: &mut Witness, @@ -2628,7 +2564,7 @@ pub mod transaction_snark { inputs.append_field(*px); inputs.append_field(*py); inputs.append_field(*rx); - let signature_prefix = openmina_core::NetworkConfig::global().signature_prefix; + let signature_prefix = openmina_core::NetworkConfig::global().legacy_signature_prefix; let hash = checked_legacy_hash(signature_prefix, inputs, w); w.exists(field_to_bits::<_, 255>(hash)) @@ -2638,7 +2574,7 @@ pub mod transaction_snark { shifted: &InnerCurve, signer: &PubKey, signature: &Signature, - inputs: LegacyInput, + inputs: legacy::Inputs, w: &mut Witness, ) -> Boolean { let hash = checked_legacy_signature_hash(inputs, signer, signature, w); @@ -2920,7 +2856,7 @@ pub mod transaction_snark { fee_payer.checked_equal(&source, w); current_global_slot.lte(&payload.common.valid_until.to_checked(), w); - let state_body_hash = state_body.checked_hash_with_param("MinaProtoStateBody", w); + let state_body_hash = state_body.checked_hash_with_param(&MINA_PROTO_STATE_BODY, w); let pending_coinbase_stack_with_state = pending_coinbase_init.checked_push_state(state_body_hash, current_global_slot, w); @@ -3080,7 +3016,11 @@ pub mod transaction_snark { Boolean::True => Sgn::Neg, Boolean::False => Sgn::Pos, }; - CheckedSigned::create(CheckedAmount::of_fee(&fee), sgn, None) + CheckedSigned::create( + CheckedAmount::of_fee(&fee), + CircuitVar::Constant(sgn), + None, + ) }; let account_creation_fee = { @@ -3090,7 +3030,7 @@ pub mod transaction_snark { } else { CheckedAmount::zero() }; - CheckedSigned::create(magnitude, Sgn::Neg, None) + CheckedSigned::create(magnitude, CircuitVar::Constant(Sgn::Neg), None) }; new_account_fees = account_creation_fee.clone(); @@ -3113,7 +3053,7 @@ pub mod transaction_snark { let txn_global_slot = current_global_slot; let timing = { - let txn_amount = w.exists_no_check(match amount.sgn { + let txn_amount = w.exists_no_check(match amount.sgn.value() { Sgn::Neg => amount.magnitude, Sgn::Pos => CheckedAmount::zero(), }); @@ -3569,7 +3509,10 @@ pub mod transaction_snark { let (fee_transfer_excess, fee_transfer_excess_overflowed) = { let (magnitude, overflow) = payload.body.amount.to_checked().add_flagged(&amount_fee, w); - (CheckedSigned::create(magnitude, Sgn::Neg, None), overflow) + ( + CheckedSigned::create(magnitude, CircuitVar::Constant(Sgn::Neg), None), + overflow, + ) }; Boolean::assert_any( @@ -3577,37 +3520,48 @@ pub mod transaction_snark { w, ); - let value = match is_fee_transfer { - Boolean::True => fee_transfer_excess, - Boolean::False => user_command_excess, - }; - w.exists_no_check(value.magnitude); - value + CheckedSigned::on_if( + is_fee_transfer.var(), + SignedAmountBranchParam { + on_true: &fee_transfer_excess, + on_false: &user_command_excess, + }, + w, + ) }; - w.exists_no_check(match is_coinbase { - Boolean::True => then_value, - Boolean::False => else_value, - }) + CheckedSigned::on_if( + is_coinbase.var(), + SignedAmountBranchParam { + on_true: &then_value, + on_false: &else_value, + }, + w, + ) }; let supply_increase = { - let expected_supply_increase = match is_coinbase { - Boolean::True => CheckedSigned::of_unsigned(payload.body.amount.to_checked()), - Boolean::False => CheckedSigned::of_unsigned(CheckedAmount::zero()), - }; - w.exists_no_check(expected_supply_increase.magnitude); - w.exists_no_check(expected_supply_increase.magnitude); + let expected_supply_increase = CheckedSigned::on_if( + is_coinbase.var(), + SignedAmountBranchParam { + on_true: &CheckedSigned::of_unsigned(payload.body.amount.to_checked()), + on_false: &CheckedSigned::of_unsigned(CheckedAmount::zero()), + }, + w, + ); let (amt0, _overflow0) = expected_supply_increase .add_flagged(&CheckedSigned::of_unsigned(burned_tokens).negate(), w); - let new_account_fees_total = w.exists_no_check(match user_command_fails { - Boolean::True => zero_fee, - Boolean::False => new_account_fees, - }); + let new_account_fees_total = CheckedSigned::on_if( + user_command_fails.var(), + SignedAmountBranchParam { + on_true: &zero_fee, + on_false: &new_account_fees, + }, + w, + ); - w.exists(new_account_fees_total.force_value()); // Made in the `add_flagged` call let (amt, _overflow) = amt0.add_flagged(&new_account_fees_total, w); amt @@ -3723,7 +3677,7 @@ pub fn messages_for_next_wrap_proof_padding() -> Fp { old_bulletproof_challenges: vec![], // Filled with padding, in `hash()` below }; let hash: [u64; 4] = msg.hash(); - Fp::try_from(BigInteger256(hash)).unwrap() // Never fail + Fp::try_from(BigInteger256::from_64x4(hash)).unwrap() // Never fail }) } @@ -3787,10 +3741,10 @@ impl MessagesForNextStepProof<'_> { /// https://github.com/MinaProtocol/mina/blob/32a91613c388a71f875581ad72276e762242f802/src/lib/pickles/common.ml#L33 pub fn hash(&self) -> [u64; 4] { let fields: Vec = self.to_fields(); - let field: Fp = crate::hash_fields(&fields); + let field: Fp = ::poseidon::hash::hash_fields(&fields); let bigint: BigInteger256 = field.into_repr(); - bigint.0 + bigint.to_64x4() } /// Implementation of `to_field_elements` @@ -4184,6 +4138,7 @@ mod tests_with_wasm { pub(super) mod tests { use std::path::Path; + use ::poseidon::hash::params::MINA_ZKAPP_EVENT; use mina_p2p_messages::binprot::{ self, macros::{BinProtRead, BinProtWrite}, @@ -4510,11 +4465,11 @@ pub(super) mod tests { "6963060754718463299978089777716994949151371320681588566338620419071140958308"; let mut w = Witness::empty(); - let hash = transaction_snark::checked_hash("MinaZkappEvent", &[], &mut w); + let hash = transaction_snark::checked_hash(&MINA_ZKAPP_EVENT, &[], &mut w); assert_eq!(hash, Fp::from_str(EXPECTED).unwrap()); let mut w = Witness::empty(); - let hash = transaction_snark::checked_hash3("MinaZkappEvent", &[], &mut w); + let hash = transaction_snark::checked_hash3(&MINA_ZKAPP_EVENT, &[], &mut w); assert_eq!(hash, Fp::from_str(EXPECTED).unwrap()); } diff --git a/ledger/src/proofs/util.rs b/ledger/src/proofs/util.rs index e41613a40c..ce9bc6f9e3 100644 --- a/ledger/src/proofs/util.rs +++ b/ledger/src/proofs/util.rs @@ -62,7 +62,7 @@ where let mut bigint: [u64; 4] = [0; 4]; bigint[..4].copy_from_slice(v); - let bigint = BigInteger256(bigint); + let bigint = BigInteger256::from_64x4(bigint); F::try_from(bigint) } @@ -73,7 +73,7 @@ where let mut bigint: [u64; 4] = [0; 4]; bigint[..2].copy_from_slice(v); - let bigint = BigInteger256(bigint); + let bigint = BigInteger256::from_64x4(bigint); F::try_from(bigint).unwrap() // Never fail with 2 limbs } diff --git a/ledger/src/proofs/verifiers.rs b/ledger/src/proofs/verifiers.rs index 3ede6d9be7..a0acd68096 100644 --- a/ledger/src/proofs/verifiers.rs +++ b/ledger/src/proofs/verifiers.rs @@ -71,7 +71,7 @@ fn cache_path(kind: Kind) -> Option { macro_rules! read_cache { ($kind: expr, $digest: expr) => {{ #[cfg(not(target_family = "wasm"))] - let data = super::circuit_blobs::fetch(&cache_filename($kind)) + let data = super::circuit_blobs::fetch_blocking(&cache_filename($kind)) .context("fetching verifier index failed")?; #[cfg(target_family = "wasm")] let data = super::circuit_blobs::fetch(&cache_filename($kind)) diff --git a/ledger/src/proofs/wrap.rs b/ledger/src/proofs/wrap.rs index 13fbf620c7..d59c18efb3 100644 --- a/ledger/src/proofs/wrap.rs +++ b/ledger/src/proofs/wrap.rs @@ -339,7 +339,8 @@ fn deferred_values(params: DeferredValuesParams) -> DeferredValuesAndHints { let zeta = oracle.zeta(); let to_bytes = |f: Fp| { - let BigInteger256([a, b, c, d]): BigInteger256 = f.into(); + let bigint: BigInteger256 = f.into(); + let [a, b, c, d] = bigint.to_64x4(); assert_eq!([c, d], [0, 0]); [a, b] }; @@ -499,17 +500,18 @@ fn make_public_input( unfinalized_proofs.to_field_elements(&mut fields); } - let to_fp = |v: [u64; 4]| Fp::try_from(BigInteger256(v)).unwrap(); // Never fail, `messages_for_next_step_proof_hash` was a `Fp` + let to_fp = |v: [u64; 4]| Fp::try_from(BigInteger256::from_64x4(v)).unwrap(); // Never fail, `messages_for_next_step_proof_hash` was a `Fp` to_fp(messages_for_next_step_proof_hash).to_field_elements(&mut fields); // `messages_for_next_wrap_proof_hash` were `Fq` previously, so we have to // build a `Fp` from them with care: they can overflow let to_fp = |v: [u64; 4]| { - match Fp::try_from(BigInteger256(v)) { + match Fp::try_from(BigInteger256::from_64x4(v)) { Ok(fp) => fp, // fast-path: we get the `Fp` without modulo/reducing Err(_) => { // slow path: we build the `Fp` bit by bit, so it will reduce it - let bits = crate::proofs::transaction::bigint_to_bits::<255>(BigInteger256(v)); + let bits = + crate::proofs::transaction::bigint_to_bits::<255>(BigInteger256::from_64x4(v)); super::util::field_of_bits(&bits) } } @@ -683,7 +685,7 @@ pub fn wrap( prover_index: step_prover_index, }); - let to_fq = |[a, b]: [u64; 2]| Fq::try_from(BigInteger256([a, b, 0, 0])).unwrap(); // Never fail with 2 limbs + let to_fq = |[a, b]: [u64; 2]| Fq::try_from(BigInteger256::from_64x4([a, b, 0, 0])).unwrap(); // Never fail with 2 limbs let to_fqs = |v: &[[u64; 2]]| v.iter().copied().map(to_fq).collect::>(); let messages_for_next_wrap_proof = MessagesForNextWrapProof { @@ -761,7 +763,7 @@ pub fn wrap( .proof_state .sponge_digest_before_evaluations .into(); - bigint.0 + bigint.to_64x4() }, messages_for_next_wrap_proof: messages_for_next_wrap_proof_prepared.hash(), }, @@ -1563,7 +1565,8 @@ pub mod wrap_verifier { let (_, endo) = endos::(); let (lo, hi): (F, F) = w.exists({ - let BigInteger256([a, b, c, d]) = f.into(); + let bigint: BigInteger256 = f.into(); + let [a, b, c, d] = bigint.to_64x4(); (two_u64_to_field(&[a, b]), two_u64_to_field(&[c, d])) }); @@ -1672,7 +1675,8 @@ pub mod wrap_verifier { let r = to_field_checked::(r_actual, endo, w); let to_bytes = |f: Fq| { - let BigInteger256([a, b, c, d]) = f.into(); + let bigint: BigInteger256 = f.into(); + let [a, b, c, d] = bigint.to_64x4(); [a, b, c, d] }; @@ -1848,7 +1852,8 @@ pub mod wrap_verifier { pub const OPS_BITS_PER_CHUNK: usize = 5; pub fn chunks_needed(num_bits: usize) -> usize { - (num_bits + (OPS_BITS_PER_CHUNK - 1)) / OPS_BITS_PER_CHUNK + // (num_bits + (OPS_BITS_PER_CHUNK - 1)) / OPS_BITS_PER_CHUNK + num_bits.div_ceil(OPS_BITS_PER_CHUNK) } fn lagrange_with_correction( @@ -1966,7 +1971,8 @@ pub mod wrap_verifier { let s_parts = w.exists({ // TODO: Here `s` is a `F` but needs to be read as a `F::Scalar` let bigint: BigInteger256 = s.into(); - let s_odd = bigint.0[0] & 1 != 0; + let bigint: [u64; 4] = bigint.to_64x4(); + let s_odd = bigint[0] & 1 != 0; let v = if s_odd { s - F::one() } else { s }; (v / F::from(2u64), s_odd.to_boolean()) }); @@ -2429,14 +2435,13 @@ pub mod wrap_verifier { let mut sponge = { use crate::proofs::opt_sponge::SpongeState as OptSpongeState; - use mina_poseidon::pasta::fq_kimchi::static_params; - use mina_poseidon::poseidon::SpongeState; + use ::poseidon::SpongeState; let OptSpongeState::Squeezed(n_squeezed) = sponge.sponge_state else { // We just called `sample_scalar` panic!("OCaml panics too") }; - let mut sponge = Sponge::::new_with_state(sponge.state, static_params()); + let mut sponge = Sponge::::new_with_state(sponge.state); sponge.sponge_state = SpongeState::Squeezed(n_squeezed); sponge }; @@ -2758,8 +2763,9 @@ fn pack_statement( fn split_field(x: Fq, w: &mut Witness) -> (Fq, Boolean) { let n: BigInteger256 = x.into(); + let n: [u64; 4] = n.to_64x4(); - let is_odd = n.0[0] & 1 != 0; + let is_odd = n[0] & 1 != 0; let y = if is_odd { x - Fq::one() } else { x }; let y = y / Fq::from(2u64); diff --git a/ledger/src/proofs/zkapp.rs b/ledger/src/proofs/zkapp.rs index a66891a986..b83ed0a487 100644 --- a/ledger/src/proofs/zkapp.rs +++ b/ledger/src/proofs/zkapp.rs @@ -5,9 +5,12 @@ use kimchi::proof::PointEvaluations; use mina_curves::pasta::Fq; use mina_hasher::Fp; use mina_p2p_messages::v2; +use poseidon::hash::{ + hash_with_kimchi, + params::{MINA_ACCOUNT_UPDATE_CONS, MINA_PROTO_STATE_BODY}, +}; use crate::{ - hash_with_kimchi, proofs::{ constants::{ make_step_zkapp_data, StepMergeProof, StepZkappOptSignedOptSignedProof, @@ -37,10 +40,7 @@ use crate::{ LocalState, LocalStateEnv, LocalStateSkeleton, StackFrame, StackFrameChecked, }, protocol_state::{protocol_state_body_view, GlobalStateSkeleton}, - zkapp_command::{ - AccountUpdate, CallForest, Control, WithHash, ZkAppCommand, - ACCOUNT_UPDATE_CONS_HASH_PARAM, - }, + zkapp_command::{AccountUpdate, CallForest, Control, WithHash, ZkAppCommand}, zkapp_statement::{TransactionCommitment, ZkappStatement}, TransactionFailure, }, @@ -407,7 +407,7 @@ fn accumulate_call_stack_hashes( 0, WithStackHash { elt: f.clone(), - stack_hash: hash_with_kimchi(ACCOUNT_UPDATE_CONS_HASH_PARAM, &[h_f, h_tl]), + stack_hash: hash_with_kimchi(&MINA_ACCOUNT_UPDATE_CONS, &[h_f, h_tl]), }, ); @@ -961,7 +961,7 @@ fn check_protocol_state(params: CheckProtocolStateParams, w: &mut Witness) { state_body, } = params; - let state_body_hash = state_body.checked_hash_with_param("MinaProtoStateBody", w); + let state_body_hash = state_body.checked_hash_with_param(&MINA_PROTO_STATE_BODY, w); let global_slot = block_global_slot; let computed_pending_coinbase_stack_after = pending_coinbase_stack_init.checked_push_state(state_body_hash, global_slot, w); @@ -1075,8 +1075,8 @@ fn zkapp_main( ledger: witness.global_second_pass_ledger.clone(), hash: statement.source.second_pass_ledger, }, - fee_excess: CheckedSigned::zero(), - supply_increase: CheckedSigned::zero(), + fee_excess: CheckedSigned::constant_zero(), + supply_increase: CheckedSigned::constant_zero(), protocol_state: state_body.view(), block_global_slot, }; @@ -1504,7 +1504,8 @@ impl From<&WrapProof> for v2::PicklesProofProofsVerified2ReprStableV2 { v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { prechallenge: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { inner: { - let BigInteger256(bigint) = bulletproof_challenges[i].into(); + let bigint: BigInteger256 = bulletproof_challenges[i].into(); + let bigint = bigint.to_64x4(); PaddedSeq([v2::LimbVectorConstantHex64StableV1(bigint[0].into()), v2::LimbVectorConstantHex64StableV1(bigint[1].into())]) }, }, @@ -1515,8 +1516,9 @@ impl From<&WrapProof> for v2::PicklesProofProofsVerified2ReprStableV2 { }, sponge_digest_before_evaluations: v2::CompositionTypesDigestConstantStableV1({ - let BigInteger256(bigint) = + let bigint: BigInteger256 = (*sponge_digest_before_evaluations).into(); + let bigint = bigint.to_64x4(); PaddedSeq( bigint .each_ref() @@ -1535,7 +1537,8 @@ impl From<&WrapProof> for v2::PicklesProofProofsVerified2ReprStableV2 { PaddedSeq(array::from_fn(|j| v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { prechallenge: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { inner: { - let BigInteger256(bigint) = old_bulletproof_challenges[i][j].into(); + let bigint: BigInteger256 = old_bulletproof_challenges[i][j].into(); + let bigint = bigint.to_64x4(); PaddedSeq([v2::LimbVectorConstantHex64StableV1(bigint[0].into()), v2::LimbVectorConstantHex64StableV1(bigint[1].into())]) }, }, @@ -1562,7 +1565,8 @@ impl From<&WrapProof> for v2::PicklesProofProofsVerified2ReprStableV2 { PaddedSeq(array::from_fn(|i| v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { prechallenge: v2::PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { inner: { - let BigInteger256(bigint) = v[i].into(); + let bigint: BigInteger256 = v[i].into(); + let bigint = bigint.to_64x4(); PaddedSeq([v2::LimbVectorConstantHex64StableV1(bigint[0].into()), v2::LimbVectorConstantHex64StableV1(bigint[1].into())]) }, }, diff --git a/ledger/src/scan_state/currency.rs b/ledger/src/scan_state/currency.rs index 282ad479bc..badd586298 100644 --- a/ledger/src/scan_state/currency.rs +++ b/ledger/src/scan_state/currency.rs @@ -448,7 +448,7 @@ macro_rules! impl_number { fn of_field(field: F) -> Self { let amount: BigInteger256 = field.into(); - let amount: $inner = amount.0[0].try_into().unwrap(); + let amount: $inner = amount.to_64x4()[0].try_into().unwrap(); Self::$from_name(amount) } @@ -545,7 +545,7 @@ macro_rules! impl_number { } impl crate::ToInputs for $name { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) { inputs.$append_name(self.0); } } diff --git a/ledger/src/scan_state/fee_excess.rs b/ledger/src/scan_state/fee_excess.rs index c82bce9e7f..06cfe0b904 100644 --- a/ledger/src/scan_state/fee_excess.rs +++ b/ledger/src/scan_state/fee_excess.rs @@ -33,6 +33,7 @@ use ark_ff::{BigInteger, BigInteger256, Zero}; use mina_hasher::Fp; +use poseidon::hash::Inputs; use crate::{ proofs::{ @@ -40,7 +41,7 @@ use crate::{ numbers::currency::{CheckedFee, CheckedSigned}, witness::Witness, }, - ToInputs, TokenId, + AppendToInputs, ToInputs, TokenId, }; use super::{ @@ -66,7 +67,7 @@ pub struct CheckedFeeExcess { impl ToInputs for FeeExcess { /// https://github.com/MinaProtocol/mina/blob/4e0b324912017c3ff576704ee397ade3d9bda412/src/lib/mina_base/fee_excess.ml#L162 - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut Inputs) { let Self { fee_token_l, fee_excess_l, @@ -318,7 +319,7 @@ impl FeeExcess { let bigint: BigInteger256 = excess.into(); let is_neg = bigint.get_bit(255 - 1); let sgn = if is_neg { Sgn::Neg } else { Sgn::Pos }; - let magnitude = Fee::from_u64(bigint.0[0]); + let magnitude = Fee::from_u64(bigint.to_64x4()[0]); Signed::create(magnitude, sgn) }; diff --git a/ledger/src/scan_state/parallel_scan.rs b/ledger/src/scan_state/parallel_scan.rs index 1d3e081211..8ee442fcc4 100644 --- a/ledger/src/scan_state/parallel_scan.rs +++ b/ledger/src/scan_state/parallel_scan.rs @@ -422,7 +422,7 @@ where Merge(&'a merge::Merge), } - impl<'a, B, M> Debug for BaseOrMerge<'a, B, M> + impl Debug for BaseOrMerge<'_, B, M> where B: Debug, M: Debug, @@ -818,7 +818,7 @@ pub struct JobValueWithIndex<'a> { pub job: JobValue<'a>, } -impl<'a> JobValueWithIndex<'a> { +impl JobValueWithIndex<'_> { pub fn index(&self) -> usize { self.index } diff --git a/ledger/src/scan_state/pending_coinbase.rs b/ledger/src/scan_state/pending_coinbase.rs index de8aeb6cdd..c77eb89993 100644 --- a/ledger/src/scan_state/pending_coinbase.rs +++ b/ledger/src/scan_state/pending_coinbase.rs @@ -24,10 +24,16 @@ use ark_ff::{fields::arithmetic::InvalidBigInt, Zero}; use mina_hasher::Fp; use mina_signer::CompressedPubKey; use openmina_core::constants::constraint_constants; +use poseidon::hash::{ + hash_noinputs, hash_with_kimchi, + params::{ + get_coinbase_param_for_height, COINBASE_STACK, MINA_PROTO_STATE, NO_INPUT_COINBASE_STACK, + }, + Inputs, +}; use sha2::{Digest, Sha256}; use crate::{ - hash_noinputs, hash_with_kimchi, proofs::{ field::{field, Boolean}, numbers::{ @@ -38,7 +44,7 @@ use crate::{ witness::Witness, }, staged_ledger::hash::PendingCoinbaseAux, - Address, Inputs, MerklePath, ToInputs, + Address, AppendToInputs as _, MerklePath, ToInputs, }; use self::merkle_tree::MiniMerkleTree; @@ -149,7 +155,7 @@ impl CoinbaseStack { inputs.append(&CoinbaseData::of_coinbase(cb)); inputs.append_field(self.0); - let hash = hash_with_kimchi("CoinbaseStack", &inputs.to_fields()); + let hash = hash_with_kimchi(&COINBASE_STACK, &inputs.to_fields()); Self(hash) } @@ -159,7 +165,7 @@ impl CoinbaseStack { inputs.append(&CoinbaseData::of_coinbase(cb)); inputs.append_field(self.0); - let hash = checked_hash("CoinbaseStack", &inputs.to_fields(), w); + let hash = checked_hash(&COINBASE_STACK, &inputs.to_fields(), w); Self(hash) } @@ -173,7 +179,7 @@ impl CoinbaseStack { /// https://github.com/MinaProtocol/mina/blob/2ee6e004ba8c6a0541056076aab22ea162f7eb3a/src/lib/mina_base/pending_coinbase.ml#L188 pub fn empty() -> Self { - Self(hash_noinputs("CoinbaseStack")) + Self(hash_noinputs(&NO_INPUT_COINBASE_STACK)) } /// Used for tests/debug only @@ -220,7 +226,7 @@ impl StateStack { inputs.append_field(state_body_hash); inputs.append_field(global_slot.to_field()); - let hash = hash_with_kimchi("MinaProtoState", &inputs.to_fields()); + let hash = hash_with_kimchi(&MINA_PROTO_STATE, &inputs.to_fields()); Self { init: self.init, @@ -240,7 +246,7 @@ impl StateStack { inputs.append_field(state_body_hash); inputs.append_field(global_slot.to_field()); - let hash = checked_hash("MinaProtoState", &inputs.to_fields(), w); + let hash = checked_hash(&MINA_PROTO_STATE, &inputs.to_fields(), w); Self { init: self.init, @@ -451,7 +457,7 @@ impl Stack { } fn hash_var(&self, w: &mut Witness) -> Fp { - checked_hash("CoinbaseStack", &self.to_inputs_owned().to_fields(), w) + checked_hash(&COINBASE_STACK, &self.to_inputs_owned().to_fields(), w) } } @@ -474,13 +480,12 @@ impl merkle_tree::TreeHasher for StackHasher { inputs.append_field(value.state.init); inputs.append_field(value.state.curr); - hash_with_kimchi("CoinbaseStack", &inputs.to_fields()) + hash_with_kimchi(&COINBASE_STACK, &inputs.to_fields()) } - fn merge_hash(depth: usize, left: Fp, right: Fp) -> Fp { - let param = format!("MinaCbMklTree{:03}", depth); - - crate::hash::hash_with_kimchi(param.as_str(), &[left, right]) + fn merge_hash(height: usize, left: Fp, right: Fp) -> Fp { + let param = get_coinbase_param_for_height(height); + poseidon::hash::hash_with_kimchi(param, &[left, right]) } fn empty_value() -> Stack { @@ -875,22 +880,19 @@ pub fn checked_verify_merkle_path( w: &mut Witness, ) -> Fp { let account_hash = account.hash_var(w); - let mut param = String::with_capacity(16); let hash = merkle_path .iter() .enumerate() - .fold(account_hash, |accum, (depth, path)| { + .fold(account_hash, |accum, (height, path)| { let hashes = match path { MerklePath::Left(right) => [accum, *right], MerklePath::Right(left) => [*left, accum], }; - - param.clear(); - write!(&mut param, "MinaCbMklTree{:03}", depth).unwrap(); - w.exists(hashes); - checked_hash(param.as_str(), &hashes, w) + + let param = get_coinbase_param_for_height(height); + checked_hash(param, &hashes, w) }); hash diff --git a/ledger/src/scan_state/protocol_state.rs b/ledger/src/scan_state/protocol_state.rs index dba137bb6d..4d94c89cf0 100644 --- a/ledger/src/scan_state/protocol_state.rs +++ b/ledger/src/scan_state/protocol_state.rs @@ -1,9 +1,10 @@ use mina_hasher::Fp; -use crate::{ - hash::{hash_with_kimchi, Inputs}, - proofs::block::ProtocolState, - ToInputs, +use crate::{proofs::block::ProtocolState, ToInputs}; +use poseidon::hash::{ + hash_with_kimchi, + params::{MINA_PROTO_STATE, MINA_PROTO_STATE_BODY}, + Inputs, }; pub trait MinaHash { @@ -12,7 +13,7 @@ pub trait MinaHash { impl MinaHash for crate::proofs::block::ProtocolStateBody { fn hash(&self) -> Fp { - self.hash_with_param("MinaProtoStateBody") + self.hash_with_param(&MINA_PROTO_STATE_BODY) } } @@ -22,7 +23,7 @@ pub fn hashes_abstract(previous_state_hash: Fp, body_hash: Fp) -> Fp { inputs.append_field(previous_state_hash); inputs.append_field(body_hash); - hash_with_kimchi("MinaProtoState", &inputs.to_fields()) + hash_with_kimchi(&MINA_PROTO_STATE, &inputs.to_fields()) } impl ProtocolState { diff --git a/ledger/src/scan_state/scan_state.rs b/ledger/src/scan_state/scan_state.rs index e41c041749..9dacfc9abd 100644 --- a/ledger/src/scan_state/scan_state.rs +++ b/ledger/src/scan_state/scan_state.rs @@ -112,10 +112,11 @@ pub mod transaction_snark { }, sparse_ledger::SparseLedger, staged_ledger::hash::OCamlString, - Inputs, ToInputs, + AppendToInputs as _, ToInputs, }; use super::Fee; + use poseidon::hash::Inputs; pub type LedgerHash = Fp; @@ -393,7 +394,7 @@ pub mod transaction_snark { impl ToInputs for Statement { /// https://github.com/MinaProtocol/mina/blob/4e0b324912017c3ff576704ee397ade3d9bda412/src/lib/mina_state/snarked_ledger_state.ml#L263 - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut Inputs) { let Self { source, target, @@ -2364,7 +2365,7 @@ impl ScanState { } } -pub fn group_list<'a, F, T, R>(slice: &'a [T], fun: F) -> impl Iterator> + '_ +pub fn group_list<'a, F, T, R>(slice: &'a [T], fun: F) -> impl Iterator> + 'a where F: Fn(&'a T) -> R + 'a, { diff --git a/ledger/src/scan_state/transaction_logic.rs b/ledger/src/scan_state/transaction_logic.rs index 0369c2ada2..b1c21037f3 100644 --- a/ledger/src/scan_state/transaction_logic.rs +++ b/ledger/src/scan_state/transaction_logic.rs @@ -4,26 +4,28 @@ use std::fmt::Display; use ark_ff::fields::arithmetic::InvalidBigInt; use ark_ff::Zero; use itertools::{FoldWhile, Itertools}; -use mina_hasher::{create_kimchi, Fp}; +use mina_hasher::{Fp, Hashable, ROInput}; use mina_p2p_messages::binprot; use mina_p2p_messages::v2::{MinaBaseUserCommandStableV2, MinaTransactionTransactionStableV2}; use mina_signer::CompressedPubKey; +use mina_signer::NetworkId; use openmina_core::constants::ConstraintConstants; use openmina_macros::SerdeYojsonEnum; +use poseidon::hash::params::{CODA_RECEIPT_UC, MINA_ZKAPP_MEMO}; +use poseidon::hash::{hash_noinputs, hash_with_kimchi, Inputs}; use crate::proofs::witness::Witness; use crate::scan_state::transaction_logic::transaction_partially_applied::FullyApplied; use crate::scan_state::transaction_logic::zkapp_command::MaybeWithStatus; use crate::zkapps::non_snark::{LedgerNonSnark, ZkappNonSnark}; -use crate::{ - hash_with_kimchi, zkapps, AccountIdOrderable, BaseLedger, ControlTag, Inputs, - VerificationKeyWire, -}; use crate::{ scan_state::transaction_logic::transaction_applied::{CommandApplied, Varying}, sparse_ledger::{LedgerIntf, SparseLedger}, Account, AccountId, ReceiptChainHash, Timing, TokenId, }; +use crate::{ + zkapps, AccountIdOrderable, AppendToInputs, BaseLedger, ControlTag, VerificationKeyWire, +}; use self::zkapp_command::AccessedOrNot; use self::{ @@ -620,31 +622,12 @@ impl Memo { } pub fn hash(&self) -> Fp { - use mina_hasher::ROInput as LegacyInput; - - let inputs = LegacyInput::new(); - let inputs = inputs.append_bytes(&self.0); - - use mina_hasher::{Hashable, Hasher, ROInput}; - - #[derive(Clone)] - struct MyInput(LegacyInput); - - impl Hashable for MyInput { - type D = (); - - fn to_roinput(&self) -> ROInput { - self.0.clone() - } - - fn domain_string(_: Self::D) -> Option { - Some("MinaZkappMemo".to_string()) - } - } + use ::poseidon::hash::{hash_with_kimchi, legacy}; - let mut hasher = create_kimchi::(()); - hasher.update(&MyInput(inputs)); - hasher.digest() + // For some reason we are mixing legacy inputs and "new" hashing + let mut inputs = legacy::Inputs::new(); + inputs.append_bytes(&self.0); + hash_with_kimchi(&MINA_ZKAPP_MEMO, &inputs.to_fields()) } pub fn as_slice(&self) -> &[u8] { @@ -923,10 +906,14 @@ pub mod zkapp_command { use ark_ff::UniformRand; use mina_p2p_messages::v2::MinaBaseZkappCommandTStableV1WireStableV1AccountUpdatesA; use mina_signer::Signature; + use poseidon::hash::params::{ + MINA_ACCOUNT_UPDATE_CONS, MINA_ACCOUNT_UPDATE_NODE, MINA_ZKAPP_EVENT, MINA_ZKAPP_EVENTS, + MINA_ZKAPP_SEQ_EVENTS, NO_INPUT_MINA_ZKAPP_ACTIONS_EMPTY, NO_INPUT_MINA_ZKAPP_EVENTS_EMPTY, + }; use rand::{seq::SliceRandom, Rng}; use crate::{ - dummy, gen_compressed, gen_keypair, hash_noinputs, + dummy, gen_compressed, gen_keypair, proofs::{ field::{Boolean, ToBoolean}, to_field_elements::ToFieldElements, @@ -951,7 +938,7 @@ pub mod zkapp_command { Self(Vec::new()) } pub fn hash(&self) -> Fp { - hash_with_kimchi("MinaZkappEvent", &self.0[..]) + hash_with_kimchi(&MINA_ZKAPP_EVENT, &self.0[..]) } pub fn len(&self) -> usize { let Self(list) = self; @@ -981,21 +968,27 @@ pub mod zkapp_command { .collect() } + use poseidon::hash::LazyParam; + /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/zkapp_account.ml#L23 pub trait MakeEvents { - const SALT_PHRASE: &'static str; - const HASH_PREFIX: &'static str; const DERIVER_NAME: (); // Unused here for now + fn get_salt_phrase() -> &'static LazyParam; + fn get_hash_prefix() -> &'static LazyParam; fn events(&self) -> &[Event]; fn empty_hash() -> Fp; } /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/zkapp_account.ml#L100 impl MakeEvents for Events { - const SALT_PHRASE: &'static str = "MinaZkappEventsEmpty"; - const HASH_PREFIX: &'static str = "MinaZkappEvents"; const DERIVER_NAME: () = (); + fn get_salt_phrase() -> &'static LazyParam { + &NO_INPUT_MINA_ZKAPP_EVENTS_EMPTY + } + fn get_hash_prefix() -> &'static poseidon::hash::LazyParam { + &MINA_ZKAPP_EVENTS + } fn events(&self) -> &[Event] { self.0.as_slice() } @@ -1006,9 +999,13 @@ pub mod zkapp_command { /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/zkapp_account.ml#L156 impl MakeEvents for Actions { - const SALT_PHRASE: &'static str = "MinaZkappActionsEmpty"; - const HASH_PREFIX: &'static str = "MinaZkappSeqEvents"; const DERIVER_NAME: () = (); + fn get_salt_phrase() -> &'static LazyParam { + &NO_INPUT_MINA_ZKAPP_ACTIONS_EMPTY + } + fn get_hash_prefix() -> &'static poseidon::hash::LazyParam { + &MINA_ZKAPP_SEQ_EVENTS + } fn events(&self) -> &[Event] { self.0.as_slice() } @@ -1022,10 +1019,10 @@ pub mod zkapp_command { where E: MakeEvents, { - let init = hash_noinputs(E::SALT_PHRASE); + let init = hash_noinputs(E::get_salt_phrase()); e.events().iter().rfold(init, |accum, elem| { - hash_with_kimchi(E::HASH_PREFIX, &[accum, elem.hash()]) + hash_with_kimchi(E::get_hash_prefix(), &[accum, elem.hash()]) }) } @@ -1183,17 +1180,17 @@ pub mod zkapp_command { } pub fn push_event(acc: Fp, event: Event) -> Fp { - hash_with_kimchi(Self::HASH_PREFIX, &[acc, event.hash()]) + hash_with_kimchi(Self::get_hash_prefix(), &[acc, event.hash()]) } pub fn push_events(&self, acc: Fp) -> Fp { let hash = self .0 .iter() - .rfold(hash_noinputs(Self::SALT_PHRASE), |acc, e| { + .rfold(hash_noinputs(Self::get_salt_phrase()), |acc, e| { Self::push_event(acc, e.clone()) }); - hash_with_kimchi(Self::HASH_PREFIX, &[acc, hash]) + hash_with_kimchi(Self::get_hash_prefix(), &[acc, hash]) } } @@ -1207,17 +1204,17 @@ pub mod zkapp_command { } pub fn push_event(acc: Fp, event: Event) -> Fp { - hash_with_kimchi(Self::HASH_PREFIX, &[acc, event.hash()]) + hash_with_kimchi(Self::get_hash_prefix(), &[acc, event.hash()]) } pub fn push_events(&self, acc: Fp) -> Fp { let hash = self .0 .iter() - .rfold(hash_noinputs(Self::SALT_PHRASE), |acc, e| { + .rfold(hash_noinputs(Self::get_salt_phrase()), |acc, e| { Self::push_event(acc, e.clone()) }); - hash_with_kimchi(Self::HASH_PREFIX, &[acc, hash]) + hash_with_kimchi(Self::get_hash_prefix(), &[acc, hash]) } } @@ -3136,8 +3133,6 @@ pub mod zkapp_command { } impl Tree { - pub const HASH_PARAM: &'static str = "MinaAcctUpdateNode"; - // TODO: Cache this result somewhere ? pub fn digest(&self) -> Fp { let stack_hash = match self.calls.0.first() { @@ -3145,7 +3140,10 @@ pub mod zkapp_command { None => Fp::zero(), }; let account_update_digest = self.account_update_digest.get().unwrap(); - hash_with_kimchi(Self::HASH_PARAM, &[account_update_digest, stack_hash]) + hash_with_kimchi( + &MINA_ACCOUNT_UPDATE_NODE, + &[account_update_digest, stack_hash], + ) } fn fold(&self, init: Vec, f: &mut F) -> Vec @@ -3186,8 +3184,6 @@ pub mod zkapp_command { this: TokenId, } - pub const ACCOUNT_UPDATE_CONS_HASH_PARAM: &str = "MinaAcctUpdateCons"; - pub trait AccountUpdateRef { fn account_update_ref(&self) -> &AccountUpdate; } @@ -3258,7 +3254,7 @@ pub mod zkapp_command { let hash = tree.digest(); let h_tl = self.hash(); - let stack_hash = hash_with_kimchi(ACCOUNT_UPDATE_CONS_HASH_PARAM, &[hash, h_tl]); + let stack_hash = hash_with_kimchi(&MINA_ACCOUNT_UPDATE_CONS, &[hash, h_tl]); let node = WithStackHash:: { elt: tree, stack_hash: MutableFp::new(stack_hash), @@ -3458,7 +3454,7 @@ pub mod zkapp_command { pub fn accumulate_hashes(&self) { /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/zkapp_command.ml#L293 fn cons(hash: Fp, h_tl: Fp) -> Fp { - hash_with_kimchi(ACCOUNT_UPDATE_CONS_HASH_PARAM, &[hash, h_tl]) + hash_with_kimchi(&MINA_ACCOUNT_UPDATE_CONS, &[hash, h_tl]) } /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/zkapp_command.ml#L561 @@ -4191,8 +4187,10 @@ pub mod zkapp_command { } pub mod zkapp_statement { + use poseidon::hash::params::MINA_ACCOUNT_UPDATE_CONS; + use super::{ - zkapp_command::{CallForest, Tree, ACCOUNT_UPDATE_CONS_HASH_PARAM}, + zkapp_command::{CallForest, Tree}, *, }; @@ -4208,7 +4206,7 @@ pub mod zkapp_statement { /// https://github.com/MinaProtocol/mina/blob/3753a8593cc1577bcf4da16620daf9946d88e8e5/src/lib/mina_base/zkapp_command.ml#L1368 pub fn create_complete(&self, memo_hash: Fp, fee_payer_hash: Fp) -> Self { Self(hash_with_kimchi( - ACCOUNT_UPDATE_CONS_HASH_PARAM, + &MINA_ACCOUNT_UPDATE_CONS, &[memo_hash, fee_payer_hash, self.0], )) } @@ -4218,18 +4216,19 @@ pub mod zkapp_statement { } } - impl mina_hasher::Hashable for TransactionCommitment { - type D = mina_signer::NetworkId; + impl Hashable for TransactionCommitment { + type D = NetworkId; - fn to_roinput(&self) -> mina_hasher::ROInput { - mina_hasher::ROInput::new().append_field(self.0) + fn to_roinput(&self) -> ROInput { + let mut roi = ROInput::new(); + roi = roi.append_field(self.0); + roi } - fn domain_string(network_id: mina_signer::NetworkId) -> Option { - // Domain strings must have length <= 20 + fn domain_string(network_id: NetworkId) -> Option { match network_id { - mina_signer::NetworkId::MAINNET => "MinaSignatureMainnet", - mina_signer::NetworkId::TESTNET => "CodaSignature", + NetworkId::MAINNET => openmina_core::network::mainnet::SIGNATURE_PREFIX, + NetworkId::TESTNET => openmina_core::network::devnet::SIGNATURE_PREFIX, } .to_string() .into() @@ -4289,7 +4288,6 @@ pub mod verifiable { use std::ops::Neg; use ark_ff::{BigInteger, PrimeField}; - use mina_signer::Signer; use super::*; @@ -4328,13 +4326,7 @@ pub mod verifiable { let payload = TransactionUnionPayload::of_user_command_payload(payload); let pubkey = compressed_to_pubkey(pubkey); - let network_id = match openmina_core::NetworkConfig::global().network_id { - openmina_core::network::NetworkId::TESTNET => mina_signer::NetworkId::TESTNET, - openmina_core::network::NetworkId::MAINNET => mina_signer::NetworkId::MAINNET, - }; - let mut signer = mina_signer::create_legacy(network_id); - - if signer.verify(signature, &pubkey, &payload) { + if crate::verifier::common::legacy_verify_signature(signature, &pubkey, &payload) { Ok(valid::UserCommand::SignedCommand(cmd)) } else { Err(cmd) @@ -5122,6 +5114,8 @@ pub mod protocol_state { pub mod local_state { use std::{cell::RefCell, rc::Rc}; + use poseidon::hash::params::MINA_ACCOUNT_UPDATE_STACK_FRAME; + use crate::{ proofs::{ field::{field, Boolean, ToBoolean}, @@ -5328,7 +5322,7 @@ pub mod local_state { }; inputs.append_field(field); - hash_with_kimchi("MinaAcctUpdStckFrm", &inputs.to_fields()) + hash_with_kimchi(&MINA_ACCOUNT_UPDATE_STACK_FRAME, &inputs.to_fields()) } pub fn digest(&self) -> Fp { @@ -5373,10 +5367,10 @@ pub mod local_state { if self.is_default { use crate::proofs::transaction::transaction_snark::checked_hash3; - checked_hash3("MinaAcctUpdStckFrm", &fields, w) + checked_hash3(&MINA_ACCOUNT_UPDATE_STACK_FRAME, &fields, w) } else { use crate::proofs::transaction::transaction_snark::checked_hash; - checked_hash("MinaAcctUpdStckFrm", &fields, w) + checked_hash(&MINA_ACCOUNT_UPDATE_STACK_FRAME, &fields, w) } } } @@ -5434,10 +5428,10 @@ pub mod local_state { } } - /// NOTE: It looks like there are different instances of the polymorphic LocalEnv type - /// One with concrete types for the stack frame, call stack, and ledger. Created from the Env - /// And the other with their hashes. To differentiate them I renamed the first LocalStateEnv - /// Maybe a better solution is to keep the LocalState name and put it under a different module + // NOTE: It looks like there are different instances of the polymorphic LocalEnv type + // One with concrete types for the stack frame, call stack, and ledger. Created from the Env + // And the other with their hashes. To differentiate them I renamed the first LocalStateEnv + // Maybe a better solution is to keep the LocalState name and put it under a different module // pub type LocalStateEnv = LocalStateSkeleton< // L, // ledger // StackFrame, // stack_frame @@ -7279,8 +7273,8 @@ pub mod transaction_union_payload { arbitrary values different from the default token-id, for this we will extract the LS u64 of the token-id. */ - let fee_token_id = self.common.fee_token.0.into_repr().0[0]; - let token_id = self.body.token_id.0.into_repr().0[0]; + let fee_token_id = self.common.fee_token.0.into_repr().to_64x4()[0]; + let token_id = self.body.token_id.0.into_repr().to_64x4()[0]; let mut roi = LegacyInput::new() .append_field(self.common.fee_payer_pk.x) @@ -7353,32 +7347,32 @@ pub mod transaction_union_payload { } /// https://github.com/MinaProtocol/mina/blob/2ee6e004ba8c6a0541056076aab22ea162f7eb3a/src/lib/mina_base/transaction_union_payload.ml#L309 - pub fn to_input_legacy(&self) -> LegacyInput { - let mut roi = LegacyInput::new(); + pub fn to_input_legacy(&self) -> ::poseidon::hash::legacy::Inputs { + let mut roi = ::poseidon::hash::legacy::Inputs::new(); // Self.common { - roi = roi.append_u64(self.common.fee.0); + roi.append_u64(self.common.fee.0); // TokenId.default // https://github.com/MinaProtocol/mina/blob/2ee6e004ba8c6a0541056076aab22ea162f7eb3a/src/lib/mina_base/signed_command_payload.ml#L19 - roi = roi.append_bool(true); + roi.append_bool(true); for _ in 0..63 { - roi = roi.append_bool(false); + roi.append_bool(false); } // fee_payer_pk - roi = roi.append_field(self.common.fee_payer_pk.x); - roi = roi.append_bool(self.common.fee_payer_pk.is_odd); + roi.append_field(self.common.fee_payer_pk.x); + roi.append_bool(self.common.fee_payer_pk.is_odd); // nonce - roi = roi.append_u32(self.common.nonce.0); + roi.append_u32(self.common.nonce.0); // valid_until - roi = roi.append_u32(self.common.valid_until.0); + roi.append_u32(self.common.valid_until.0); // memo - roi = roi.append_bytes(&self.common.memo.0); + roi.append_bytes(&self.common.memo.0); } // Self.body @@ -7386,25 +7380,25 @@ pub mod transaction_union_payload { // tag let tag = self.body.tag.clone() as u8; for bit in [4, 2, 1] { - roi = roi.append_bool(tag & bit != 0); + roi.append_bool(tag & bit != 0); } // source_pk - roi = roi.append_field(self.body.source_pk.x); - roi = roi.append_bool(self.body.source_pk.is_odd); + roi.append_field(self.body.source_pk.x); + roi.append_bool(self.body.source_pk.is_odd); // receiver_pk - roi = roi.append_field(self.body.receiver_pk.x); - roi = roi.append_bool(self.body.receiver_pk.is_odd); + roi.append_field(self.body.receiver_pk.x); + roi.append_bool(self.body.receiver_pk.is_odd); // default token_id - roi = roi.append_u64(1); + roi.append_u64(1); // amount - roi = roi.append_u64(self.body.amount.0); + roi.append_u64(self.body.amount.0); // token_locked - roi = roi.append_bool(false); + roi.append_bool(false); } roi @@ -7541,35 +7535,18 @@ pub fn cons_signed_command_payload( command_payload: &SignedCommandPayload, last_receipt_chain_hash: ReceiptChainHash, ) -> ReceiptChainHash { - // Note: Not sure why the use the legacy way of hashing here + // Note: Not sure why they use the legacy way of hashing here - use mina_hasher::ROInput as LegacyInput; + use ::poseidon::hash::legacy; + let ReceiptChainHash(last_receipt_chain_hash) = last_receipt_chain_hash; let union = TransactionUnionPayload::of_user_command_payload(command_payload); - let inputs = union.to_input_legacy(); - let inputs = inputs.append_field(last_receipt_chain_hash.0); + let mut inputs = union.to_input_legacy(); + inputs.append_field(last_receipt_chain_hash); + let hash = legacy::hash_with_kimchi(&legacy::params::CODA_RECEIPT_UC, &inputs.to_fields()); - use mina_hasher::{create_legacy, Hashable, Hasher, ROInput}; - - #[derive(Clone)] - struct MyInput(LegacyInput); - - impl Hashable for MyInput { - type D = (); - - fn to_roinput(&self) -> ROInput { - self.0.clone() - } - - fn domain_string(_: Self::D) -> Option { - Some(ReceiptChainHash::HASH_PREFIX.to_string()) - } - } - - let mut hasher = create_legacy::(()); - hasher.update(&MyInput(inputs)); - ReceiptChainHash(hasher.digest()) + ReceiptChainHash(hash) } /// Returns the new `receipt_chain_hash` @@ -7580,11 +7557,12 @@ pub fn checked_cons_signed_command_payload( ) -> ReceiptChainHash { use crate::proofs::transaction::legacy_input::CheckedLegacyInput; use crate::proofs::transaction::transaction_snark::checked_legacy_hash; + use ::poseidon::hash::legacy; let mut inputs = payload.to_checked_legacy_input_owned(w); inputs.append_field(last_receipt_chain_hash.0); - let receipt_chain_hash = checked_legacy_hash(ReceiptChainHash::HASH_PREFIX, inputs, w); + let receipt_chain_hash = checked_legacy_hash(&legacy::params::CODA_RECEIPT_UC, inputs, w); ReceiptChainHash(receipt_chain_hash) } @@ -7605,10 +7583,7 @@ pub fn cons_zkapp_command_commitment( inputs.append_field(x.0); inputs.append(receipt_hash); - ReceiptChainHash(hash_with_kimchi( - ReceiptChainHash::HASH_PREFIX, - &inputs.to_fields(), - )) + ReceiptChainHash(hash_with_kimchi(&CODA_RECEIPT_UC, &inputs.to_fields())) } fn validate_nonces(txn_nonce: Nonce, account_nonce: Nonce) -> Result<(), String> { diff --git a/ledger/src/sparse_ledger/sparse_ledger_impl.rs b/ledger/src/sparse_ledger/sparse_ledger_impl.rs index 3667005bdc..963280c46a 100644 --- a/ledger/src/sparse_ledger/sparse_ledger_impl.rs +++ b/ledger/src/sparse_ledger/sparse_ledger_impl.rs @@ -1,11 +1,9 @@ -use std::{ - collections::{BTreeMap, HashMap, VecDeque}, - fmt::Write, -}; +use std::collections::{BTreeMap, HashMap, VecDeque}; use ark_ff::Zero; use mina_hasher::Fp; use mina_signer::CompressedPubKey; +use poseidon::hash::params::get_merkle_param_for_height; use crate::{ scan_state::{currency::Slot, transaction_logic::AccountState}, @@ -156,10 +154,9 @@ impl SparseLedgerImpl { let account_addr = addr.clone(); let mut current = account.hash(); - let mut param = String::with_capacity(16); // Go back from the account to root, to compute missing hashes - for (depth, path) in merkle_path.iter().enumerate() { + for (height, path) in merkle_path.iter().enumerate() { set_hash(addr.clone(), ¤t); let hashes = match path { @@ -167,10 +164,8 @@ impl SparseLedgerImpl { MerklePath::Right(left) => [*left, current], }; - param.clear(); - write!(&mut param, "MinaMklTree{:03}", depth).unwrap(); - - current = crate::hash::hash_with_kimchi(param.as_str(), &hashes); + let param = get_merkle_param_for_height(height); + current = poseidon::hash::hash_with_kimchi(param, &hashes); addr = addr.parent().unwrap(); } diff --git a/ledger/src/staged_ledger/hash.rs b/ledger/src/staged_ledger/hash.rs index 70d0cd3092..f649070908 100644 --- a/ledger/src/staged_ledger/hash.rs +++ b/ledger/src/staged_ledger/hash.rs @@ -1,8 +1,12 @@ use ark_ff::{PrimeField, ToBytes}; use mina_hasher::Fp; +use poseidon::hash::Inputs; use sha2::{Digest, Sha256}; -use crate::{proofs::field::FieldWitness, scan_state::pending_coinbase::PendingCoinbase, ToInputs}; +use crate::{ + proofs::field::FieldWitness, scan_state::pending_coinbase::PendingCoinbase, + AppendToInputs as _, ToInputs, +}; /// Convert to/from OCaml strings, such as /// "u~\218kzX\228$\027qG\239\135\255:\143\171\186\011\200P\243\163\135\223T>\017\172\254\1906" @@ -168,7 +172,7 @@ impl NonStark { impl ToInputs for NonStark { /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/staged_ledger_hash.ml#L193 - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut Inputs) { let digest = self.digest(); inputs.append_bytes(digest.as_slice()); } @@ -228,7 +232,7 @@ impl StagedLedgerHash { } impl ToInputs for StagedLedgerHash { - fn to_inputs(&self, inputs: &mut crate::Inputs) { + fn to_inputs(&self, inputs: &mut Inputs) { let Self { non_snark, pending_coinbase_hash, diff --git a/ledger/src/staged_ledger/staged_ledger.rs b/ledger/src/staged_ledger/staged_ledger.rs index 06af06cdff..a2527c0b4b 100644 --- a/ledger/src/staged_ledger/staged_ledger.rs +++ b/ledger/src/staged_ledger/staged_ledger.rs @@ -1692,7 +1692,7 @@ impl StagedLedger { } /// https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1781 - fn can_apply_supercharged_coinbase_exn( + pub fn can_apply_supercharged_coinbase_exn( winner: CompressedPubKey, epoch_ledger: &SparseLedger, global_slot: Slot, diff --git a/ledger/src/staged_ledger/validate_block.rs b/ledger/src/staged_ledger/validate_block.rs index 297fda3ab7..f10ff4745e 100644 --- a/ledger/src/staged_ledger/validate_block.rs +++ b/ledger/src/staged_ledger/validate_block.rs @@ -185,7 +185,8 @@ fn required_bitswap_block_count(max_block_size: usize, data_length: usize) -> us } else { let n1 = data_length - LINK_SIZE; let n2 = max_block_size - LINK_SIZE - 2; - (n1 + n2 - 1) / n2 + // (n1 + n2 - 1) / n2 + n1.div_ceil(n2) } } diff --git a/ledger/src/transaction_pool.rs b/ledger/src/transaction_pool.rs index 9e180fe63c..207c58c7a1 100644 --- a/ledger/src/transaction_pool.rs +++ b/ledger/src/transaction_pool.rs @@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize}; use std::{ borrow::{Borrow, Cow}, collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}, - sync::Arc, }; use itertools::Itertools; @@ -198,7 +197,7 @@ mod consensus { /// Fee increase required to replace a transaction. const REPLACE_FEE: Fee = Fee::of_nanomina_int_exn(1); -pub type ValidCommandWithHash = WithHash; +pub type ValidCommandWithHash = WithHash; pub mod diff { use super::*; @@ -611,7 +610,7 @@ pub struct IndexedPool { all_by_sender: HashMap, Amount)>, /// All transactions in the pool indexed by fee per weight unit. all_by_fee: HashMap>, - all_by_hash: HashMap, + all_by_hash: HashMap, /// Only transactions that have an expiry transactions_with_expiration: HashMap>, size: usize, @@ -672,6 +671,10 @@ impl IndexedPool { self.all_by_hash.contains_key(&cmd.hash) } + pub fn get(&self, hash: &v2::TransactionHash) -> Option<&ValidCommandWithHash> { + self.all_by_hash.get(hash) + } + fn check_expiry( &self, global_slot_since_genesis: Slot, @@ -1395,7 +1398,7 @@ impl IndexedPool { .unwrap(); // TODO: Check if OCaml compare using `hash` (order) - let txn = set.iter().min_by_key(|b| &*b.hash).cloned().unwrap(); + let txn = set.iter().min_by_key(|b| &b.hash).cloned().unwrap(); { set.remove(&txn); @@ -1456,7 +1459,7 @@ impl IndexedPool { .unwrap(); // TODO: Check if OCaml compare using `hash` (order) - let txn = set.iter().min_by_key(|b| &*b.hash).cloned().unwrap(); + let txn = set.iter().min_by_key(|b| &b.hash).cloned().unwrap(); { set.remove(&txn); @@ -1537,62 +1540,24 @@ fn currency_consumed(cmd: &UserCommand) -> Result { .ok_or(CommandError::InvalidCurrencyConsumed) } -type BlakeHash = Arc<[u8; 32]>; - pub mod transaction_hash { - use blake2::{ - digest::{Update, VariableOutput}, - Blake2bVar, - }; - use mina_signer::Signature; - - use crate::scan_state::transaction_logic::{ - signed_command::SignedCommand, zkapp_command::AccountUpdate, - }; - use super::*; pub fn hash_command(cmd: valid::UserCommand) -> ValidCommandWithHash { - use mina_p2p_messages::binprot::BinProtWrite; - - fn to_binprot, V: BinProtWrite>(v: T) -> Vec { - let value = v.into(); - let mut buffer = Vec::with_capacity(32 * 1024); - value.binprot_write(&mut buffer).unwrap(); - buffer - } - - let buffer: Vec = match &cmd { + let hash = match &cmd { valid::UserCommand::SignedCommand(cmd) => { - let mut cmd: SignedCommand = (**cmd).clone(); - cmd.signature = Signature::dummy(); - to_binprot::<_, v2::MinaBaseSignedCommandStableV2>(&cmd) + v2::MinaBaseSignedCommandStableV2::from(&**cmd) + .hash() + .unwrap() } valid::UserCommand::ZkAppCommand(cmd) => { - let mut cmd = cmd.clone().forget(); - cmd.fee_payer.authorization = Signature::dummy(); - cmd.account_updates = cmd.account_updates.map_to(|account_update| { - let dummy_auth = account_update.authorization.dummy(); - AccountUpdate { - authorization: dummy_auth, - ..account_update.clone() - } - }); - to_binprot::<_, v2::MinaBaseZkappCommandTStableV1WireStableV1>(&cmd) + let cmd = cmd.clone().forget(); + v2::MinaBaseZkappCommandTStableV1WireStableV1::from(&cmd) + .hash() + .unwrap() } }; - let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size"); - hasher.update(&buffer); - - let hash: Arc<[u8; 32]> = { - let mut buffer = [0; 32]; - hasher - .finalize_variable(&mut buffer) - .expect("Invalid buffer size"); // Never occur - Arc::from(buffer) - }; - WithHash { data: cmd, hash } } } @@ -1661,7 +1626,7 @@ impl TransactionPool { &mut self, global_slot_since_genesis: Slot, accounts: &BTreeMap, - ) -> Result<(), CommandError> { + ) -> Result, CommandError> { let dropped = self.pool.revalidate( global_slot_since_genesis, RevalidateKind::EntirePool, @@ -1695,7 +1660,7 @@ impl TransactionPool { ); } - Ok(()) + Ok(dropped) } fn has_sufficient_fee(&self, pool_max_size: usize, cmd: &valid::UserCommand) -> bool { @@ -1852,7 +1817,7 @@ impl TransactionPool { }; let (committed_commands, dropped_commit_conflicts): (Vec<_>, Vec<_>) = { - let command_hashes: HashSet = + let command_hashes: HashSet = new_commands.iter().map(|cmd| cmd.hash.clone()).collect(); dropped_commands @@ -1940,6 +1905,7 @@ impl TransactionPool { ApplyDecision, Vec, Vec<(ValidCommandWithHash, diff::Error)>, + HashSet, ), String, > { @@ -2026,13 +1992,13 @@ impl TransactionPool { .decrement_hashed(all_dropped_cmds.iter().copied()); }; - let dropped_for_add_hashes: HashSet<&BlakeHash> = + let dropped_for_add_hashes: HashSet<&v2::TransactionHash> = dropped_for_add.iter().map(|cmd| &cmd.hash).collect(); - let dropped_for_size_hashes: HashSet<&BlakeHash> = + let dropped_for_size_hashes: HashSet<&v2::TransactionHash> = dropped_for_size.iter().map(|cmd| &cmd.hash).collect(); - let all_dropped_cmd_hashes: HashSet<&BlakeHash> = dropped_for_add_hashes + let all_dropped_cmd_hashes: HashSet = dropped_for_add_hashes .union(&dropped_for_size_hashes) - .copied() + .map(|hash| (*hash).clone()) .collect(); // let locally_generated_dropped = all_dropped_cmds @@ -2078,7 +2044,7 @@ impl TransactionPool { ApplyDecision::Accept }; - Ok((decision, accepted, rejected)) + Ok((decision, accepted, rejected, all_dropped_cmd_hashes)) } pub fn unsafe_apply( @@ -2094,10 +2060,11 @@ impl TransactionPool { ApplyDecision, Vec, Vec<(ValidCommandWithHash, diff::Error)>, + HashSet, ), String, > { - let (decision, accepted, rejected) = self.apply( + let (decision, accepted, rejected, dropped) = self.apply( time, global_slot_since_genesis, current_global_slot, @@ -2105,7 +2072,7 @@ impl TransactionPool { accounts, is_sender_local, )?; - Ok((decision, accepted, rejected)) + Ok((decision, accepted, rejected, dropped)) } fn register_locally_generated(&mut self, time: redux::Timestamp, cmd: &ValidCommandWithHash) { diff --git a/ledger/src/tree_version.rs b/ledger/src/tree_version.rs index 192cc24dd4..69913c1b64 100644 --- a/ledger/src/tree_version.rs +++ b/ledger/src/tree_version.rs @@ -1,6 +1,7 @@ use std::{fmt::Debug, hash::Hash}; use mina_hasher::Fp; +use poseidon::hash::params::get_merkle_param_for_height; use crate::account::{get_legacy_hash_of, Account, AccountLegacy, TokenId, TokenIdLegacy}; @@ -24,9 +25,8 @@ impl TreeVersion for V2 { type TokenId = TokenId; fn hash_node(height: usize, left: Fp, right: Fp) -> Fp { - let param = format!("MinaMklTree{height:03}"); - - crate::hash::hash_with_kimchi(param.as_str(), &[left, right]) + let param = get_merkle_param_for_height(height); + poseidon::hash::hash_with_kimchi(param, &[left, right]) } fn hash_leaf(leaf: &Self::Account) -> Fp { diff --git a/ledger/src/util/backtrace.rs b/ledger/src/util/backtrace.rs index 658594600f..51193556df 100644 --- a/ledger/src/util/backtrace.rs +++ b/ledger/src/util/backtrace.rs @@ -16,7 +16,7 @@ pub fn short_backtrace() -> String { .map(|sym| (sym.filename(), sym.name(), sym.lineno())) .enumerate() .filter(|(_, (filename, name, _))| { - return !(filename + !(filename .map(|filename| { filename.ends_with("ledger/src/util/backtrace.rs") || filename @@ -34,7 +34,7 @@ pub fn short_backtrace() -> String { || name.starts_with("camlCamlinternalLazy__") || name.starts_with("camlO1trace__") // This belongs to the mina repo, but useless to us }) - .unwrap_or(false)); + .unwrap_or(false)) }) .take_while(|(_, (_, name, _))| { name.as_ref().and_then(|n| n.as_str()) != Some("caml_program") diff --git a/ledger/src/util/mod.rs b/ledger/src/util/mod.rs index 74c706328e..72a7baa77d 100644 --- a/ledger/src/util/mod.rs +++ b/ledger/src/util/mod.rs @@ -130,7 +130,7 @@ impl<'a, T> MyCow<'a, T> { } } -impl<'a, T> MyCow<'a, T> +impl MyCow<'_, T> where T: ToOwned, { @@ -142,7 +142,7 @@ where } } -impl<'a, T> std::ops::Deref for MyCow<'a, T> { +impl std::ops::Deref for MyCow<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -150,7 +150,7 @@ impl<'a, T> std::ops::Deref for MyCow<'a, T> { } } -impl<'a, T> AsRef for MyCow<'a, T> { +impl AsRef for MyCow<'_, T> { fn as_ref(&self) -> &T { match self { MyCow::Borrow(v) => v, @@ -159,7 +159,7 @@ impl<'a, T> AsRef for MyCow<'a, T> { } } -impl<'a, F, T> ToFieldElements for MyCow<'a, T> +impl ToFieldElements for MyCow<'_, T> where F: FieldWitness, T: ToFieldElements, @@ -176,7 +176,7 @@ pub enum MyCowMut<'a, T> { Own(T), } -impl<'a, T> std::ops::Deref for MyCowMut<'a, T> { +impl std::ops::Deref for MyCowMut<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -187,7 +187,7 @@ impl<'a, T> std::ops::Deref for MyCowMut<'a, T> { } } -impl<'a, T> std::ops::DerefMut for MyCowMut<'a, T> { +impl std::ops::DerefMut for MyCowMut<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { match self { MyCowMut::Borrow(v) => v, diff --git a/ledger/src/verifier/mod.rs b/ledger/src/verifier/mod.rs index e551bfe565..86a7f363d9 100644 --- a/ledger/src/verifier/mod.rs +++ b/ledger/src/verifier/mod.rs @@ -232,10 +232,12 @@ pub mod common { use mina_p2p_messages::v2::PicklesProofProofsVerifiedMaxStableV2; use mina_signer::{CompressedPubKey, PubKey, Signature}; + use poseidon::hash::hash_with_kimchi; use crate::{ - decompress_pk, hash_with_kimchi, + decompress_pk, scan_state::transaction_logic::{ + transaction_union_payload::TransactionUnionPayload, valid, verifiable, zkapp_command::{self, valid::of_verifiable, AccountUpdate}, zkapp_statement::{TransactionCommitment, ZkappStatement}, @@ -370,8 +372,7 @@ pub mod common { } } - /// Verify signature with new style (chunked inputs) - /// `mina_signer::verify` is using old one + /// Verify zkapp signature/statement with new style (chunked inputs) fn verify_signature( signature: &Signature, pubkey: &PubKey, @@ -400,4 +401,41 @@ pub mod common { let rv = rv.into_affine(); rv.y.into_repr().is_even() && rv.x == *rx } + + /// Verify signature with legacy style + pub fn legacy_verify_signature( + signature: &Signature, + pubkey: &PubKey, + msg: &TransactionUnionPayload, + ) -> bool { + use ::poseidon::hash::legacy; + use ark_ec::{AffineCurve, ProjectiveCurve}; + use ark_ff::{BigInteger, PrimeField, Zero}; + use mina_curves::pasta::Fq; + use mina_curves::pasta::Pallas; + use mina_signer::CurvePoint; + use std::ops::Neg; + + let Pallas { x, y, .. } = pubkey.point(); + let Signature { rx, s } = signature; + + let signature_prefix = openmina_core::NetworkConfig::global().legacy_signature_prefix; + + let mut inputs = msg.to_input_legacy(); + inputs.append_field(*x); + inputs.append_field(*y); + inputs.append_field(*rx); + + let hash = legacy::hash_with_kimchi(signature_prefix, &inputs.to_fields()); + let hash: Fq = Fq::try_from(hash.into_repr()).unwrap(); // Never fail, `Fq` is larger than `Fp` + + let sv: CurvePoint = CurvePoint::prime_subgroup_generator().mul(*s).into_affine(); + // Perform addition and infinity check in projective coordinates for performance + let rv = pubkey.point().mul(hash).neg().add_mixed(&sv); + if rv.is_zero() { + return false; + } + let rv = rv.into_affine(); + rv.y.into_repr().is_even() && rv.x == *rx + } } diff --git a/ledger/src/zkapps/intefaces.rs b/ledger/src/zkapps/intefaces.rs index ae8a9b9856..758767c06a 100644 --- a/ledger/src/zkapps/intefaces.rs +++ b/ledger/src/zkapps/intefaces.rs @@ -142,11 +142,7 @@ where fn negate(&self) -> Self; fn add_flagged(&self, other: &Self, w: &mut Self::W) -> (Self, Self::Bool); fn of_unsigned(unsigned: Self::Amount) -> Self; - fn on_if<'a>( - b: Self::Bool, - param: SignedAmountBranchParam<&'a Self>, - w: &mut Self::W, - ) -> &'a Self; + fn on_if(b: Self::Bool, param: SignedAmountBranchParam<&Self>, w: &mut Self::W) -> Self; } pub trait BalanceInterface @@ -226,6 +222,7 @@ pub struct StackFrameMakeParams<'a, Calls> { pub calls: &'a Calls, } +#[derive(Debug)] pub struct SignedAmountBranchParam { pub on_true: T, pub on_false: T, @@ -583,8 +580,7 @@ where >; type SignedAmount: SignedAmountInterface + std::fmt::Debug - + Clone - + ToFieldElements; + + Clone; type Amount: AmountInterface + Clone; type Balance: BalanceInterface< W = Self::WitnessGenerator, diff --git a/ledger/src/zkapps/non_snark.rs b/ledger/src/zkapps/non_snark.rs index 17a1ad4629..0679085304 100644 --- a/ledger/src/zkapps/non_snark.rs +++ b/ledger/src/zkapps/non_snark.rs @@ -401,15 +401,11 @@ impl SignedAmountInterface for Signed { fn of_unsigned(unsigned: Self::Amount) -> Self { Self::of_unsigned(unsigned) } - fn on_if<'a>( - b: Self::Bool, - param: SignedAmountBranchParam<&'a Self>, - w: &mut Self::W, - ) -> &'a Self { + fn on_if(b: Self::Bool, param: SignedAmountBranchParam<&Self>, w: &mut Self::W) -> Self { let SignedAmountBranchParam { on_true, on_false } = param; match b { - true => on_true, - false => on_false, + true => *on_true, + false => *on_false, } } } diff --git a/ledger/src/zkapps/snark.rs b/ledger/src/zkapps/snark.rs index 49806e3a60..748dc995b8 100644 --- a/ledger/src/zkapps/snark.rs +++ b/ledger/src/zkapps/snark.rs @@ -1,8 +1,16 @@ -use std::{fmt::Write, marker::PhantomData}; +use std::{cell::Cell, marker::PhantomData}; use ark_ff::Zero; use mina_hasher::Fp; use mina_signer::CompressedPubKey; +use poseidon::hash::{ + params::{ + get_merkle_param_for_height, CODA_RECEIPT_UC, MINA_ACCOUNT_UPDATE_CONS, + MINA_ACCOUNT_UPDATE_NODE, MINA_ACCOUNT_UPDATE_STACK_FRAME_CONS, MINA_DERIVE_TOKEN_ID, + MINA_SIDELOADED_VK, MINA_ZKAPP_ACCOUNT, MINA_ZKAPP_SEQ_EVENTS, + }, + Inputs, +}; use crate::{ checked_equal_compressed_key, checked_equal_compressed_key_const_and, @@ -27,8 +35,7 @@ use crate::{ local_state::{StackFrame, StackFrameChecked, StackFrameCheckedFrame, WithLazyHash}, zkapp_command::{ self, AccountUpdate, AccountUpdateSkeleton, CallForest, CheckAuthorizationResult, - ClosedInterval, OrIgnore, SetOrKeep, Tree, WithHash, - ACCOUNT_UPDATE_CONS_HASH_PARAM, + ClosedInterval, OrIgnore, SetOrKeep, WithHash, }, zkapp_statement::ZkappStatement, TimingValidation, TransactionFailure, @@ -36,8 +43,8 @@ use crate::{ }, sparse_ledger::SparseLedger, zkapps::zkapp_logic, - Account, AccountId, AuthRequired, AuthRequiredEncoded, Inputs, MyCow, ReceiptChainHash, - ToInputs, TokenId, VerificationKey, VerificationKeyWire, ZkAppAccount, TXN_VERSION_CURRENT, + Account, AccountId, AppendToInputs, AuthRequired, AuthRequiredEncoded, MyCow, ReceiptChainHash, + ToInputs, TokenId, VerificationKeyWire, ZkAppAccount, TXN_VERSION_CURRENT, }; use super::{ @@ -176,7 +183,7 @@ impl ZkappHandler for SnarkHandler { let account2 = account.clone(); let account = WithLazyHash::new(account, move |w: &mut Witness| { let zkapp = MyCow::borrow_or_default(&account2.zkapp); - zkapp.checked_hash_with_param(ZkAppAccount::HASH_PARAM, w); + zkapp.checked_hash_with_param(&MINA_ZKAPP_ACCOUNT, w); account2.checked_hash(w) }); account @@ -189,7 +196,7 @@ impl SignedAmountInterface for CheckedSigned> { type Amount = SnarkAmount; fn zero() -> Self { - CheckedSigned::zero() + CheckedSigned::of_unsigned( as CheckedCurrency>::zero()) } fn is_neg(&self) -> Self::Bool { CheckedSigned::is_neg(self).var() @@ -210,21 +217,32 @@ impl SignedAmountInterface for CheckedSigned> { fn of_unsigned(unsigned: Self::Amount) -> Self { Self::of_unsigned(unsigned) } - fn on_if<'a>( - b: Self::Bool, - param: SignedAmountBranchParam<&'a Self>, - w: &mut Self::W, - ) -> &'a Self { + fn on_if(b: Self::Bool, param: SignedAmountBranchParam<&Self>, w: &mut Self::W) -> Self { let SignedAmountBranchParam { on_true, on_false } = param; - let amount = w.exists_no_check(match b.as_boolean() { + let amount = match b.as_boolean() { Boolean::True => on_true, Boolean::False => on_false, - }); - if on_true.try_get_value().is_some() && on_false.try_get_value().is_some() { - w.exists_no_check(amount.force_value()); + }; + + // TODO: This should be moved in a `Sgn::on_if` + let sgn = match (on_true.sgn, on_false.sgn) { + (CircuitVar::Constant(_), CircuitVar::Constant(_)) => { + CircuitVar::Var(*amount.sgn.value()) + } + _ => CircuitVar::Var(w.exists_no_check(*amount.sgn.value())), + }; + w.exists_no_check(&amount.magnitude); + + let value = match (on_true.try_get_value(), on_false.try_get_value()) { + (Some(_), Some(_)) => Some(w.exists_no_check(amount.force_value())), + _ => None, + }; + Self { + value: Cell::new(value), + sgn, + ..amount.clone() } - amount } } @@ -288,9 +306,8 @@ impl CallForestInterface for SnarkCallForest { [x, ..] => x.stack_hash.get().unwrap(), // Never fail, it was already hashed }); let tree_hash = [account_update.hash, subforest.hash] - .checked_hash_with_param(Tree::::HASH_PARAM, w); - let _hash_cons = - [tree_hash, tl_hash].checked_hash_with_param(ACCOUNT_UPDATE_CONS_HASH_PARAM, w); + .checked_hash_with_param(&MINA_ACCOUNT_UPDATE_NODE, w); + let _hash_cons = [tree_hash, tl_hash].checked_hash_with_param(&MINA_ACCOUNT_UPDATE_CONS, w); let account = Self::AccountUpdate { body: account_update, authorization: auth.clone(), @@ -400,7 +417,7 @@ impl StackFrameInterface for StackFrameChecked { /// Call_stack_digest.Checked.cons fn call_stack_digest_checked_cons(h: Fp, t: Fp, w: &mut Witness) -> Fp { - checked_hash("MinaActUpStckFrmCons", &[h, t], w) + checked_hash(&MINA_ACCOUNT_UPDATE_STACK_FRAME_CONS, &[h, t], w) } impl StackInterface for WithHash>>> { @@ -862,7 +879,7 @@ impl AccountInterface for SnarkAccount { .as_ref() .unwrap(); let vk = w.exists(vk.vk()); - vk.checked_hash_with_param(VerificationKey::HASH_PARAM, w); + vk.checked_hash_with_param(&MINA_SIDELOADED_VK, w); } Signature | NoneGiven => {} } @@ -970,18 +987,16 @@ impl AccountInterface for SnarkAccount { } fn implied_root(account: &SnarkAccount, incl: &[(Boolean, Fp)], w: &mut Witness) -> Fp { - let mut param = String::with_capacity(16); incl.iter() .enumerate() - .fold(account.hash(w), |accum: Fp, (depth, (is_right, h))| { + .fold(account.hash(w), |accum: Fp, (height, (is_right, h))| { let hashes = match is_right { Boolean::False => [accum, *h], Boolean::True => [*h, accum], }; - param.clear(); - write!(&mut param, "MinaMklTree{:03}", depth).unwrap(); + let param = get_merkle_param_for_height(height); w.exists(hashes); - checked_hash(param.as_str(), &hashes, w) + checked_hash(param, &hashes, w) }) } @@ -1012,7 +1027,7 @@ impl LedgerInterface for LedgerWithHash { let account2 = account.0.clone(); let account = WithLazyHash::new(account.0, move |w: &mut Witness| { let zkapp = MyCow::borrow_or_default(&account2.zkapp); - zkapp.checked_hash_with_param(ZkAppAccount::HASH_PARAM, w); + zkapp.checked_hash_with_param(&MINA_ZKAPP_ACCOUNT, w); account2.checked_hash(w) }); let inclusion = w.exists( @@ -1095,7 +1110,7 @@ impl AccountIdInterface for SnarkAccountId { type W = Witness; fn derive_token_id(account_id: &AccountId, w: &mut Self::W) -> TokenId { - TokenId(account_id.checked_hash_with_param(AccountId::DERIVE_TOKEN_ID_HASH_PARAM, w)) + TokenId(account_id.checked_hash_with_param(&MINA_DERIVE_TOKEN_ID, w)) } } @@ -1204,7 +1219,7 @@ impl TransactionCommitmentInterface for SnarkTransactionCommitment { let fee_payer_hash = account_updates.body.hash; [memo_hash, fee_payer_hash, commitment] - .checked_hash_with_param(ACCOUNT_UPDATE_CONS_HASH_PARAM, w) + .checked_hash_with_param(&MINA_ACCOUNT_UPDATE_CONS, w) } } @@ -1355,10 +1370,8 @@ impl ActionsInterface for SnarkActions { } fn push_events(event: Fp, actions: &zkapp_command::Actions, w: &mut Self::W) -> Fp { - use zkapp_command::MakeEvents; - let hash = zkapp_command::events_to_field(actions); - checked_hash(zkapp_command::Actions::HASH_PREFIX, &[event, hash], w) + checked_hash(&MINA_ZKAPP_SEQ_EVENTS, &[event, hash], w) } } @@ -1378,11 +1391,7 @@ impl ReceiptChainHashInterface for SnarkReceiptChainHash { inputs.append_field(element); inputs.append(&other); - ReceiptChainHash(checked_hash( - ReceiptChainHash::HASH_PREFIX, - &inputs.to_fields(), - w, - )) + ReceiptChainHash(checked_hash(&CODA_RECEIPT_UC, &inputs.to_fields(), w)) } } diff --git a/ledger/src/zkapps/zkapp_logic.rs b/ledger/src/zkapps/zkapp_logic.rs index 74c7c6fe04..3c831b9c8e 100644 --- a/ledger/src/zkapps/zkapp_logic.rs +++ b/ledger/src/zkapps/zkapp_logic.rs @@ -696,7 +696,7 @@ where ); let first = Z::Bool::or( creation_overflow, - Z::SignedAmount::is_neg(balance_change), + Z::SignedAmount::is_neg(&balance_change), w, ); Z::LocalState::add_check( @@ -705,7 +705,7 @@ where Z::Bool::and(pay_creation_fee, first, w).neg(), w, ); - ((), balance_change.clone()) + ((), balance_change) }; // Apply balance change. diff --git a/macros/Cargo.toml b/macros/Cargo.toml index d6120d8ccc..e346c7882e 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-macros" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" authors = [ "Alexander Koptelov " ] diff --git a/mina-p2p-messages/Cargo.toml b/mina-p2p-messages/Cargo.toml index 760edce55e..a9aba883fc 100644 --- a/mina-p2p-messages/Cargo.toml +++ b/mina-p2p-messages/Cargo.toml @@ -30,10 +30,10 @@ openmina-macros = { path = "../macros" } mina-hasher = { workspace = true } mina-curves = { workspace = true } mina-signer = { workspace = true } -mina-poseidon = { workspace = true } +poseidon = { workspace = true } o1-utils = { workspace = true } -ark-ff = { version = "0.3.0", features = [ "parallel", "asm" ] } +ark-ff = { workspace = true } rsexp = "0.2.3" rsexp-derive = "0.2.3" diff --git a/mina-p2p-messages/examples/mina-types-converter.rs b/mina-p2p-messages/examples/mina-types-converter.rs index 1ac34d303e..3db40b4898 100644 --- a/mina-p2p-messages/examples/mina-types-converter.rs +++ b/mina-p2p-messages/examples/mina-types-converter.rs @@ -2,6 +2,7 @@ use std::{ fs::File, io::{self, Read, Write}, path::PathBuf, + sync::Arc, }; use anyhow::{bail, format_err, Result}; @@ -244,7 +245,7 @@ macro_rules! converter { fn converters() -> Vec { vec![ converter!("gossip", GossipNetMessageV2 => - ("new-state", MinaBlockBlockStableV2), + ("new-state", Arc), ("snark-pool-diff", NetworkPoolSnarkPoolDiffVersionedStableV2), ("tx-pool-diff", NetworkPoolTransactionPoolDiffVersionedStableV2), ), diff --git a/mina-p2p-messages/src/bigint.rs b/mina-p2p-messages/src/bigint.rs index 4f1d093d18..e1de3a4196 100644 --- a/mina-p2p-messages/src/bigint.rs +++ b/mina-p2p-messages/src/bigint.rs @@ -7,9 +7,9 @@ pub struct BigInt(BigInteger256); impl std::fmt::Debug for BigInt { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self(BigInteger256(array)) = self; + let Self(bigint) = self; // Avoid vertical alignment - f.write_fmt(format_args!("BigInt({:?})", array)) + f.write_fmt(format_args!("BigInt({:?})", bigint.to_native())) } } @@ -268,7 +268,7 @@ impl<'de> Deserialize<'de> for BigInt { .map_err(|_| serde::de::Error::custom("failed to convert vec to array".to_string())) } else { struct V; - impl<'de> serde::de::Visitor<'de> for V { + impl serde::de::Visitor<'_> for V { type Value = [u8; 32]; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -447,7 +447,7 @@ mod tests { let original_sexp = Sexp::Atom(hex_str.as_bytes().to_vec()); let result = BigInt::of_sexp(&original_sexp).expect("Failed to convert Sexp to BigInt"); - let expected_result = BigInt(BigInteger256::new(expected_array)); + let expected_result = BigInt(BigInteger256::from_64x4(expected_array)); assert_eq!(result, expected_result); diff --git a/mina-p2p-messages/src/char.rs b/mina-p2p-messages/src/char.rs index fe29762640..73eb742a7b 100644 --- a/mina-p2p-messages/src/char.rs +++ b/mina-p2p-messages/src/char.rs @@ -48,7 +48,7 @@ impl<'de> Deserialize<'de> for Char { } if deserializer.is_human_readable() { struct V; - impl<'de> Visitor<'de> for V { + impl Visitor<'_> for V { type Value = u8; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/mina-p2p-messages/src/gossip.rs b/mina-p2p-messages/src/gossip.rs index cc11af9705..10a7c23334 100644 --- a/mina-p2p-messages/src/gossip.rs +++ b/mina-p2p-messages/src/gossip.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use binprot_derive::{BinProtRead, BinProtWrite}; use derive_more::{From, TryInto}; use serde::{Deserialize, Serialize}; @@ -9,7 +11,7 @@ use crate::{number::Int32, v2}; )] #[serde(tag = "type", content = "message", rename_all = "snake_case")] pub enum GossipNetMessageV2 { - NewState(v2::MinaBlockBlockStableV2), + NewState(Arc), SnarkPoolDiff { message: v2::NetworkPoolSnarkPoolDiffVersionedStableV2, nonce: Int32, diff --git a/mina-p2p-messages/src/hash_input.rs b/mina-p2p-messages/src/hash_input.rs index 550c56f020..8d323c05b0 100644 --- a/mina-p2p-messages/src/hash_input.rs +++ b/mina-p2p-messages/src/hash_input.rs @@ -1,31 +1,30 @@ use std::ops::Deref; -use ark_ff::{fields::arithmetic::InvalidBigInt, BigInteger, BigInteger256, FromBytes}; -use mina_hasher::Fp; -use o1_utils::FieldHelpers; +use ark_ff::fields::arithmetic::InvalidBigInt; +use poseidon::hash::Inputs; use crate::{ - b58::Base58CheckOfBinProt, bigint::BigInt, list::List, number::{Int32, Int64, UInt32, UInt64}, - pseq::PaddedSeq, string::ByteString, string::ZkAppUri, }; -pub trait ToInput { +/// Difference with `ToInputs` in `ledger` is that it can fail here, due +/// to invalid bigints +pub trait FailableToInputs { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt>; } -impl ToInput for bool { +impl FailableToInputs for bool { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_bool(*self); Ok(()) } } -impl ToInput for BigInt { +impl FailableToInputs for BigInt { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let field = self.to_field()?; inputs.append_field(field); @@ -33,65 +32,52 @@ impl ToInput for BigInt { } } -impl ToInput for Int32 { +impl FailableToInputs for Int32 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_u32(self.as_u32()); Ok(()) } } -impl ToInput for Int64 { +impl FailableToInputs for Int64 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_u64(self.as_u64()); Ok(()) } } -impl ToInput for UInt32 { +impl FailableToInputs for UInt32 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_u32(self.as_u32()); Ok(()) } } -impl ToInput for UInt64 { +impl FailableToInputs for UInt64 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_u64(self.as_u64()); Ok(()) } } -impl ToInput for ByteString { +impl FailableToInputs for ByteString { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_bytes(self.as_ref()); Ok(()) } } -impl ToInput for ZkAppUri { +impl FailableToInputs for ZkAppUri { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_bytes(self.as_ref()); Ok(()) } } -impl ToInput for Vec +impl FailableToInputs for List where D: Deref, - T: ToInput, -{ - fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { - for v in self.iter() { - v.to_input(inputs)?; - } - Ok(()) - } -} - -impl ToInput for List -where - D: Deref, - T: ToInput, + T: FailableToInputs, { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { for v in self.deref().iter() { @@ -101,215 +87,6 @@ where } } -impl ToInput for (T, T) -where - T: ToInput, -{ - fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { - self.0.to_input(inputs)?; - self.1.to_input(inputs)?; - Ok(()) - } -} - -impl ToInput for Option -where - T: ToInput + Default, -{ - fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { - match self.as_ref() { - Some(v) => v.to_input(inputs), - None => T::default().to_input(inputs), - } - } -} - -impl ToInput for PaddedSeq -where - T: ToInput, -{ - fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { - for v in &self.0 { - v.to_input(inputs)?; - } - Ok(()) - } -} - -impl ToInput for Base58CheckOfBinProt -where - T: ToInput, -{ - fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { - self.inner().to_input(inputs) - } -} - -#[allow(unused)] -enum Packed { - Bool(bool), - U2(u8), - U8(u8), - U32(u32), - U48([u8; 6]), - U64(u64), -} - -impl std::fmt::Debug for Packed { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Bool(arg0) => f.write_fmt(format_args!("{}_bool", i32::from(*arg0))), - Self::U2(arg0) => f.write_fmt(format_args!("{}_u2", arg0)), - Self::U8(arg0) => f.write_fmt(format_args!("{}_u8", arg0)), - Self::U32(arg0) => f.write_fmt(format_args!("{}_u32", arg0)), - Self::U48(arg0) => f.write_fmt(format_args!("{:?}_u48", arg0)), - Self::U64(arg0) => f.write_fmt(format_args!("{}_u64", arg0)), - } - } -} - -impl Packed { - fn nbits(&self) -> u32 { - match self { - Packed::Bool(_) => 1, - Packed::U2(_) => 2, - Packed::U8(_) => 8, - Packed::U32(_) => 32, - Packed::U48(_) => 48, - Packed::U64(_) => 64, - } - } - - fn as_bigint(&self) -> BigInteger256 { - match self { - Packed::Bool(v) => { - if *v { - 1.into() - } else { - 0.into() - } - } - Packed::U2(v) => (*v as u64).into(), - Packed::U8(v) => (*v as u64).into(), - Packed::U32(v) => (*v as u64).into(), - Packed::U48(v) => { - let mut bytes = <[u8; 32]>::default(); - bytes[..6].copy_from_slice(&v[..]); - BigInteger256::read(&bytes[..]).unwrap() - } - Packed::U64(v) => (*v).into(), - } - } -} - -pub struct Inputs { - fields: Vec, - packeds: Vec, -} - -impl Default for Inputs { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Debug for Inputs { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Inputs") - .field( - &format!("fields[{:?}]", self.fields.len()), - &self.fields.iter().map(|f| f.to_hex()).collect::>(), - ) - .field(&format!("packeds[{:?}]", self.packeds.len()), &self.packeds) - .finish() - } -} - -impl Inputs { - pub fn new() -> Self { - Self { - fields: Vec::with_capacity(256), - packeds: Vec::with_capacity(256), - } - } - - pub fn append_bool(&mut self, value: bool) { - self.packeds.push(Packed::Bool(value)); - } - - #[allow(unused)] - pub fn append_u2(&mut self, value: u8) { - self.packeds.push(Packed::U2(value)); - } - - #[allow(unused)] - pub fn append_u8(&mut self, value: u8) { - self.packeds.push(Packed::U8(value)); - } - - pub fn append_u32(&mut self, value: u32) { - self.packeds.push(Packed::U32(value)); - } - - pub fn append_u64(&mut self, value: u64) { - self.packeds.push(Packed::U64(value)); - } - - #[allow(unused)] - pub fn append_u48(&mut self, value: [u8; 6]) { - self.packeds.push(Packed::U48(value)); - } - - pub fn append_field(&mut self, value: Fp) { - self.fields.push(value); - } - - pub fn append_bytes(&mut self, value: &[u8]) { - const BITS: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128]; - - self.packeds.reserve(value.len() * 8); - - for byte in value { - for bit in BITS { - self.append_bool(byte & bit != 0); - } - } - } - - #[allow(clippy::wrong_self_convention)] - pub fn to_fields(mut self) -> Vec { - let mut nbits = 0; - let mut current: BigInteger256 = 0.into(); - - for (item, item_nbits) in self.packeds.iter().map(|i| (i.as_bigint(), i.nbits())) { - nbits += item_nbits; - - if nbits < 255 { - current.muln(item_nbits); - - // Addition, but we use 'bitwise or' because we know bits of - // `current` are zero (we just shift-left them) - current = BigInteger256([ - current.0[0] | item.0[0], - current.0[1] | item.0[1], - current.0[2] | item.0[2], - current.0[3] | item.0[3], - ]); - } else { - self.fields.push(current.try_into().unwrap()); // Never fail - current = item; - nbits = item_nbits; - } - } - - if nbits > 0 { - self.fields.push(current.try_into().unwrap()); // Never fail - } - - self.fields - } -} - #[cfg(test)] mod tests { use o1_utils::FieldHelpers; diff --git a/mina-p2p-messages/src/string.rs b/mina-p2p-messages/src/string.rs index 71fdb6e519..06f97cca4d 100644 --- a/mina-p2p-messages/src/string.rs +++ b/mina-p2p-messages/src/string.rs @@ -241,7 +241,7 @@ impl<'de, const MAX_LENGTH: usize> Deserialize<'de> for BoundedCharString::deserialize(deserializer).map(|cs| Self(cs, PhantomData)); } struct V; - impl<'de> Visitor<'de> for V { + impl Visitor<'_> for V { type Value = Vec; fn expecting( diff --git a/mina-p2p-messages/src/v2/generated.rs b/mina-p2p-messages/src/v2/generated.rs index 866c3f22ce..b24dc00834 100644 --- a/mina-p2p-messages/src/v2/generated.rs +++ b/mina-p2p-messages/src/v2/generated.rs @@ -3,6 +3,7 @@ use derive_more::Deref; use openmina_macros::SerdeYojsonEnum; use rsexp_derive::{OfSexp, SexpOf}; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use crate::{array::ArrayN16, list::List, pseq::PaddedSeq}; @@ -243,7 +244,7 @@ pub struct TrustSystemPeerStatusStableV1 { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, BinProtRead, BinProtWrite)] pub struct BlockchainSnarkBlockchainStableV2 { pub state: MinaStateProtocolStateValueStableV2, - pub proof: MinaBaseProofStableV2, + pub proof: Arc, } /// **OCaml name**: `Transaction_witness.Stable.V2` @@ -270,7 +271,7 @@ pub struct ProverExtendBlockchainInputStableV2 { pub chain: BlockchainSnarkBlockchainStableV2, pub next_state: MinaStateProtocolStateValueStableV2, pub block: MinaStateSnarkTransitionValueStableV2, - pub ledger_proof: Option, + pub ledger_proof: Option>, pub prover_state: ConsensusStakeProofStableV2, pub pending_coinbase: MinaBasePendingCoinbaseWitnessStableV2, } @@ -3199,7 +3200,7 @@ pub struct TransactionSnarkScanStateLedgerProofWithSokMessageStableV2( #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, BinProtRead, BinProtWrite)] pub struct MinaBlockHeaderStableV2 { pub protocol_state: MinaStateProtocolStateValueStableV2, - pub protocol_state_proof: MinaBaseProofStableV2, + pub protocol_state_proof: Arc, pub delta_block_chain_proof: (StateHash, List), pub current_protocol_version: ProtocolVersionStableV2, pub proposed_protocol_version_opt: Option, diff --git a/mina-p2p-messages/src/v2/hashing.rs b/mina-p2p-messages/src/v2/hashing.rs index 71137d49b4..f138383ce6 100644 --- a/mina-p2p-messages/src/v2/hashing.rs +++ b/mina-p2p-messages/src/v2/hashing.rs @@ -1,14 +1,13 @@ -use std::{fmt, io}; +use std::{fmt, io, sync::Arc}; -use ark_ff::{fields::arithmetic::InvalidBigInt, FromBytes}; -use binprot::BinProtWrite; -use binprot_derive::{BinProtRead, BinProtWrite}; +use ark_ff::fields::arithmetic::InvalidBigInt; +use binprot::{BinProtRead, BinProtWrite}; use generated::MinaStateBlockchainStateValueStableV2; use mina_hasher::Fp; -use mina_poseidon::{ - constants::PlonkSpongeConstantsKimchi, - pasta::fp_kimchi::static_params, - poseidon::{ArithmeticSponge, Sponge}, +use poseidon::hash::{ + hash_with_kimchi, + params::{MINA_PROTO_STATE, MINA_PROTO_STATE_BODY}, + Inputs, }; use serde::{Deserialize, Serialize}; use sha2::{ @@ -16,11 +15,7 @@ use sha2::{ Digest, Sha256, }; -use crate::{ - bigint::BigInt, - hash::MinaHash, - hash_input::{Inputs, ToInput}, -}; +use crate::{bigint::BigInt, hash::MinaHash, hash_input::FailableToInputs}; use super::{ generated, ConsensusBodyReferenceStableV1, ConsensusGlobalSlotStableV1, @@ -72,21 +67,27 @@ impl generated::ConsensusVrfOutputTruncatedStableV1 { } } -#[derive(BinProtWrite, BinProtRead, Eq, PartialEq, Ord, PartialOrd, Clone)] -pub struct TransactionHash(Vec); +#[derive(Hash, Eq, PartialEq, Ord, PartialOrd, Clone)] +pub struct TransactionHash(Arc<[u8; 32]>); impl std::str::FromStr for TransactionHash { type Err = bs58::decode::Error; fn from_str(s: &str) -> Result { - let bytes = bs58::decode(s).with_check(Some(0x1D)).into_vec()?[1..].to_vec(); - Ok(Self(bytes)) + let bytes = bs58::decode(s).with_check(Some(0x1D)).into_vec()?; + dbg!(bytes.len()); + let bytes = (&bytes[2..]) + .try_into() + .map_err(|_| bs58::decode::Error::BufferTooSmall)?; + Ok(Self(Arc::new(bytes))) } } impl fmt::Display for TransactionHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - bs58::encode(&self.0) + let mut bytes = [32; 33]; + bytes[1..].copy_from_slice(&*self.0); + bs58::encode(bytes) .with_check_version(0x1D) .into_string() .fmt(f) @@ -102,7 +103,7 @@ impl fmt::Debug for TransactionHash { impl From<&[u8; 32]> for TransactionHash { fn from(value: &[u8; 32]) -> Self { - Self(value.to_vec()) + Self(Arc::new(*value)) } } @@ -128,11 +129,32 @@ impl<'de> serde::Deserialize<'de> for TransactionHash { let b58: String = Deserialize::deserialize(deserializer)?; Ok(b58.parse().map_err(|err| serde::de::Error::custom(err))?) } else { - Vec::deserialize(deserializer).map(|v| Self(v)) + let v = Vec::deserialize(deserializer)?; + v.try_into() + .map_err(|_| serde::de::Error::custom("transaction hash wrong size")) + .map(Arc::new) + .map(Self) } } } +impl BinProtWrite for TransactionHash { + fn binprot_write(&self, w: &mut W) -> std::io::Result<()> { + w.write_all(&*self.0) + } +} + +impl BinProtRead for TransactionHash { + fn binprot_read(r: &mut R) -> Result + where + Self: Sized, + { + let mut bytes = [0; 32]; + r.read_exact(&mut bytes)?; + Ok(Self(bytes.into())) + } +} + impl generated::MinaTransactionTransactionStableV2 { pub fn hash(&self) -> io::Result { match self { @@ -177,11 +199,12 @@ impl generated::MinaBaseSignedCommandStableV2 { let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size"); hasher.update(&self.binprot_write_with_default_sig()?); - let mut hash = vec![0; 33]; - hash[..1].copy_from_slice(&[32]); - hash[1..].copy_from_slice(&hasher.finalize_boxed()); + let mut hash = [0; 32]; + hasher + .finalize_variable(&mut hash) + .expect("Invalid buffer size"); // Never occur - Ok(TransactionHash(hash)) + Ok(TransactionHash(hash.into())) } } @@ -243,11 +266,12 @@ impl generated::MinaBaseZkappCommandTStableV1WireStableV1 { let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size"); hasher.update(&self.binprot_write_with_default()?); - let mut hash = vec![0; 33]; - hash[..1].copy_from_slice(&[32]); - hash[1..].copy_from_slice(&hasher.finalize_boxed()); + let mut hash = [0; 32]; + hasher + .finalize_variable(&mut hash) + .expect("Invalid buffer size"); // Never occur - Ok(TransactionHash(hash)) + Ok(TransactionHash(hash.into())) } } @@ -255,7 +279,6 @@ impl generated::MinaBaseZkappCommandTStableV1WireStableV1 { mod tests { use super::super::manual; use super::*; - use binprot::BinProtRead; use manual::MinaBaseSignedCommandMemoStableV1; fn pub_key(address: &str) -> manual::NonZeroCurvePoint { @@ -370,7 +393,7 @@ fn fp_state_hash_from_fp_hashes(previous_state_hash: Fp, body_hash: Fp) -> Fp { let mut inputs = Inputs::new(); inputs.append_field(previous_state_hash); inputs.append_field(body_hash); - hash_with_kimchi("MinaProtoState", &inputs.to_fields()) + hash_with_kimchi(&MINA_PROTO_STATE, &inputs.to_fields()) } impl StateHash { @@ -438,7 +461,10 @@ impl MinaHash for MinaStateProtocolStateBodyValueStableV2 { fn try_hash(&self) -> Result { let mut inputs = Inputs::new(); self.to_input(&mut inputs)?; - Ok(hash_with_kimchi("MinaProtoStateBody", &inputs.to_fields())) + Ok(hash_with_kimchi( + &MINA_PROTO_STATE_BODY, + &inputs.to_fields(), + )) } } @@ -451,45 +477,7 @@ impl MinaHash for MinaStateProtocolStateValueStableV2 { } } -fn param_to_field_impl(param: &str, default: &[u8; 32]) -> Fp { - let param_bytes = param.as_bytes(); - let len = param_bytes.len(); - - let mut fp = *default; - fp[..len].copy_from_slice(param_bytes); - - Fp::read(&fp[..]).expect("fp read failed") -} - -const INPUT_PARAMS: &[u8; 32] = b"********************\0\0\0\0\0\0\0\0\0\0\0\0"; -const NO_INPUT_PARAMS: &[u8; 32] = &[0; 32]; - -fn param_to_field(param: &str, pad: &[u8; 32]) -> Fp { - if param.len() > 20 { - panic!("must be 20 byte maximum"); - } - - param_to_field_impl(param, pad) -} - -pub fn hash_noinputs(param: &str) -> Fp { - let mut sponge = ArithmeticSponge::::new(static_params()); - - sponge.absorb(&[param_to_field(param, NO_INPUT_PARAMS)]); - sponge.squeeze() -} - -pub fn hash_with_kimchi(param: &str, fields: &[Fp]) -> Fp { - let mut sponge = ArithmeticSponge::::new(static_params()); - - sponge.absorb(&[param_to_field(param, INPUT_PARAMS)]); - sponge.squeeze(); - - sponge.absorb(fields); - sponge.squeeze() -} - -impl ToInput for MinaStateProtocolStateBodyValueStableV2 { +impl FailableToInputs for MinaStateProtocolStateBodyValueStableV2 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaStateProtocolStateBodyValueStableV2 { genesis_state_hash, @@ -506,7 +494,7 @@ impl ToInput for MinaStateProtocolStateBodyValueStableV2 { } } -impl ToInput for MinaBaseProtocolConstantsCheckedValueStableV1 { +impl FailableToInputs for MinaBaseProtocolConstantsCheckedValueStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaBaseProtocolConstantsCheckedValueStableV1 { k, @@ -527,7 +515,7 @@ impl ToInput for MinaBaseProtocolConstantsCheckedValueStableV1 { } } -impl ToInput for MinaStateBlockchainStateValueStableV2 { +impl FailableToInputs for MinaStateBlockchainStateValueStableV2 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaStateBlockchainStateValueStableV2 { staged_ledger_hash, @@ -547,7 +535,7 @@ impl ToInput for MinaStateBlockchainStateValueStableV2 { } } -impl ToInput for ConsensusProofOfStakeDataConsensusStateValueStableV2 { +impl FailableToInputs for ConsensusProofOfStakeDataConsensusStateValueStableV2 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let ConsensusProofOfStakeDataConsensusStateValueStableV2 { blockchain_length, @@ -585,7 +573,7 @@ impl ToInput for ConsensusProofOfStakeDataConsensusStateValueStableV2 { } } -impl ToInput for MinaBaseStagedLedgerHashStableV1 { +impl FailableToInputs for MinaBaseStagedLedgerHashStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaBaseStagedLedgerHashStableV1 { non_snark, @@ -597,14 +585,14 @@ impl ToInput for MinaBaseStagedLedgerHashStableV1 { } } -impl ToInput for MinaBaseStagedLedgerHashNonSnarkStableV1 { +impl FailableToInputs for MinaBaseStagedLedgerHashNonSnarkStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_bytes(self.sha256().as_ref()); Ok(()) } } -impl ToInput for MinaStateBlockchainStateValueStableV2LedgerProofStatement { +impl FailableToInputs for MinaStateBlockchainStateValueStableV2LedgerProofStatement { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaStateBlockchainStateValueStableV2LedgerProofStatement { source, @@ -625,14 +613,14 @@ impl ToInput for MinaStateBlockchainStateValueStableV2LedgerProofStatement { } } -impl ToInput for ConsensusBodyReferenceStableV1 { +impl FailableToInputs for ConsensusBodyReferenceStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_bytes(self.as_ref()); Ok(()) } } -impl ToInput for ConsensusVrfOutputTruncatedStableV1 { +impl FailableToInputs for ConsensusVrfOutputTruncatedStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let vrf: &[u8] = self.as_ref(); inputs.append_bytes(&vrf[..31]); @@ -645,7 +633,7 @@ impl ToInput for ConsensusVrfOutputTruncatedStableV1 { } } -impl ToInput for ConsensusGlobalSlotStableV1 { +impl FailableToInputs for ConsensusGlobalSlotStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let ConsensusGlobalSlotStableV1 { slot_number, @@ -657,7 +645,7 @@ impl ToInput for ConsensusGlobalSlotStableV1 { } } -impl ToInput for ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { +impl FailableToInputs for ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { ledger, @@ -675,7 +663,7 @@ impl ToInput for ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueSta } } -impl ToInput for ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { +impl FailableToInputs for ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { ledger, @@ -693,7 +681,7 @@ impl ToInput for ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStable } } -impl ToInput for NonZeroCurvePointUncompressedStableV1 { +impl FailableToInputs for NonZeroCurvePointUncompressedStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let NonZeroCurvePointUncompressedStableV1 { x, is_odd } = self; x.to_input(inputs)?; @@ -702,7 +690,7 @@ impl ToInput for NonZeroCurvePointUncompressedStableV1 { } } -impl ToInput for MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { +impl FailableToInputs for MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { first_pass_ledger, @@ -718,7 +706,7 @@ impl ToInput for MinaStateBlockchainStateValueStableV2LedgerProofStatementSource } } -impl ToInput for SignedAmount { +impl FailableToInputs for SignedAmount { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let SignedAmount { magnitude, sgn } = self; magnitude.to_input(inputs)?; @@ -727,7 +715,7 @@ impl ToInput for SignedAmount { } } -impl ToInput for MinaBaseFeeExcessStableV1 { +impl FailableToInputs for MinaBaseFeeExcessStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaBaseFeeExcessStableV1(left, right) = self; left.to_input(inputs)?; @@ -736,7 +724,7 @@ impl ToInput for MinaBaseFeeExcessStableV1 { } } -impl ToInput for TokenFeeExcess { +impl FailableToInputs for TokenFeeExcess { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let TokenFeeExcess { token, amount } = self; token.to_input(inputs)?; @@ -745,7 +733,7 @@ impl ToInput for TokenFeeExcess { } } -impl ToInput for MinaBaseEpochLedgerValueStableV1 { +impl FailableToInputs for MinaBaseEpochLedgerValueStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaBaseEpochLedgerValueStableV1 { hash, @@ -757,7 +745,7 @@ impl ToInput for MinaBaseEpochLedgerValueStableV1 { } } -impl ToInput for MinaBasePendingCoinbaseStackVersionedStableV1 { +impl FailableToInputs for MinaBasePendingCoinbaseStackVersionedStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaBasePendingCoinbaseStackVersionedStableV1 { data, state } = self; data.to_input(inputs)?; @@ -766,7 +754,7 @@ impl ToInput for MinaBasePendingCoinbaseStackVersionedStableV1 { } } -impl ToInput for MinaBasePendingCoinbaseStateStackStableV1 { +impl FailableToInputs for MinaBasePendingCoinbaseStateStackStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaBasePendingCoinbaseStateStackStableV1 { init, curr } = self; init.to_input(inputs)?; @@ -775,7 +763,7 @@ impl ToInput for MinaBasePendingCoinbaseStateStackStableV1 { } } -impl ToInput for MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { +impl FailableToInputs for MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { stack_frame, @@ -804,14 +792,14 @@ impl ToInput for MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { } } -impl ToInput for SgnStableV1 { +impl FailableToInputs for SgnStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { inputs.append_bool(self == &SgnStableV1::Pos); Ok(()) } } -impl ToInput for MinaNumbersGlobalSlotSinceGenesisMStableV1 { +impl FailableToInputs for MinaNumbersGlobalSlotSinceGenesisMStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { match self { MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(v) => v.to_input(inputs), @@ -819,7 +807,7 @@ impl ToInput for MinaNumbersGlobalSlotSinceGenesisMStableV1 { } } -impl ToInput for MinaStateBlockchainStateValueStableV2SignedAmount { +impl FailableToInputs for MinaStateBlockchainStateValueStableV2SignedAmount { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { let MinaStateBlockchainStateValueStableV2SignedAmount { magnitude, sgn } = self; magnitude.to_input(inputs)?; @@ -828,7 +816,7 @@ impl ToInput for MinaStateBlockchainStateValueStableV2SignedAmount { } } -impl ToInput for MinaNumbersGlobalSlotSinceHardForkMStableV1 { +impl FailableToInputs for MinaNumbersGlobalSlotSinceHardForkMStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { match self { MinaNumbersGlobalSlotSinceHardForkMStableV1::SinceHardFork(v) => v.to_input(inputs), @@ -836,7 +824,7 @@ impl ToInput for MinaNumbersGlobalSlotSinceHardForkMStableV1 { } } -impl ToInput for MinaNumbersGlobalSlotSpanStableV1 { +impl FailableToInputs for MinaNumbersGlobalSlotSpanStableV1 { fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> { match self { MinaNumbersGlobalSlotSpanStableV1::GlobalSlotSpan(v) => v.to_input(inputs), diff --git a/mina-p2p-messages/src/v2/manual.rs b/mina-p2p-messages/src/v2/manual.rs index de8e4c8c3a..e3ef9601a0 100644 --- a/mina-p2p-messages/src/v2/manual.rs +++ b/mina-p2p-messages/src/v2/manual.rs @@ -4,6 +4,7 @@ use ark_ff::BigInteger256; use binprot::{BinProtRead, BinProtWrite}; use binprot_derive::{BinProtRead, BinProtWrite}; use derive_more::Deref; +use poseidon::hash::params::NO_INPUT_COINBASE_STACK; use serde::{de::Visitor, ser::SerializeTuple, Deserialize, Serialize, Serializer}; use time::OffsetDateTime; @@ -544,7 +545,7 @@ impl CoinbaseStackData { pub fn empty() -> Self { // In OCaml: https://github.com/MinaProtocol/mina/blob/68b49fdaafabed0f2cd400c4c69f91e81db681e7/src/lib/mina_base/pending_coinbase.ml#L186 // let empty = Random_oracle.salt "CoinbaseStack" |> Random_oracle.digest - let empty = super::hashing::hash_noinputs("CoinbaseStack"); + let empty = poseidon::hash::hash_noinputs(&NO_INPUT_COINBASE_STACK); MinaBasePendingCoinbaseCoinbaseStackStableV1(empty.into()).into() } } @@ -1531,6 +1532,18 @@ impl From for BlockTimeTimeStableV1 { } } +impl AsRef for MinaBaseUserCommandStableV2 { + fn as_ref(&self) -> &MinaBaseUserCommandStableV2 { + self + } +} + +impl MinaBlockHeaderStableV2 { + pub fn genesis_state_hash(&self) -> &StateHash { + &self.protocol_state.body.genesis_state_hash + } +} + impl StagedLedgerDiffBodyStableV1 { pub fn diff(&self) -> &StagedLedgerDiffDiffDiffStableV2 { &self.staged_ledger_diff.diff diff --git a/node/Cargo.toml b/node/Cargo.toml index e969552580..63713c16b4 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "node" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" +[lints] +workspace = true + [dependencies] rand = "0.8.0" serde = "1.0.147" @@ -21,10 +24,11 @@ redux = { workspace = true } multihash = { version = "0.18.1", features = ["blake2b"] } mina-hasher = { workspace = true } mina-signer = { workspace = true } +poseidon = { workspace = true } ledger = { workspace = true } mina-p2p-messages = { workspace = true } vrf = { workspace = true } -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +ark-ff = { workspace = true } openmina-core = { path = "../core" } snark = { path = "../snark" } diff --git a/node/account/Cargo.toml b/node/account/Cargo.toml index f25eb4a1cf..b5ac1fec04 100644 --- a/node/account/Cargo.toml +++ b/node/account/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-node-account" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -13,9 +13,6 @@ bs58 = "0.4.0" hex = "0.4.3" rand = "0.8" lazy_static = "1.4.0" -argon2 = { version = "0.5.3", features = ["std"] } -crypto_secretbox = { version = "0.1.1", features = ["std"] } -base64 = "0.22" mina-p2p-messages = { workspace = true } mina-hasher = { workspace = true } mina-signer = { workspace = true } diff --git a/node/account/src/secret_key.rs b/node/account/src/secret_key.rs index 515c7550e4..bdc0ec941e 100644 --- a/node/account/src/secret_key.rs +++ b/node/account/src/secret_key.rs @@ -1,13 +1,10 @@ use std::{fmt, fs, path::Path, str::FromStr}; -use argon2::{password_hash::SaltString, Argon2, Params, PasswordHasher}; -use base64::Engine; -use crypto_secretbox::aead::{Aead, OsRng}; -use crypto_secretbox::{AeadCore, KeyInit, XSalsa20Poly1305}; use mina_p2p_messages::{bigint::BigInt, v2::SignatureLibPrivateKeyStableV1}; use mina_signer::seckey::SecKeyError; use mina_signer::{keypair::KeypairError, CompressedPubKey, Keypair}; use openmina_core::constants::GENESIS_PRODUCER_SK; +use openmina_core::{EncryptedSecretKey, EncryptedSecretKeyFile, EncryptionError}; use rand::{rngs::StdRng, CryptoRng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; @@ -26,7 +23,7 @@ lazy_static::lazy_static! { // TODO(binier): better way. static ref GENERATED_DETERMINISTIC: Vec = { let mut rng = StdRng::seed_from_u64(0); - (0..1000) + (0..10000) .map(|_| AccountSecretKey::rand_with(&mut rng)) .collect() }; @@ -43,6 +40,10 @@ impl AccountSecretKey { GENERATED_DETERMINISTIC[i as usize].clone() } + pub fn deterministic_iter() -> impl Iterator { + GENERATED_DETERMINISTIC.iter() + } + pub fn max_deterministic_count() -> usize { GENERATED_DETERMINISTIC.len() } @@ -87,8 +88,10 @@ impl AccountSecretKey { password: &str, ) -> Result { let key_file = fs::File::open(path)?; - let encrypted: EncryptedSecretKey = serde_json::from_reader(key_file)?; - encrypted.try_decrypt(password) + let encrypted: EncryptedSecretKeyFile = serde_json::from_reader(key_file)?; + let decrypted: Vec = Self::try_decrypt(&encrypted, password)?; + AccountSecretKey::from_bytes(&decrypted[1..]) + .map_err(|err| EncryptionError::Other(err.to_string())) } pub fn to_encrypted_file( @@ -101,13 +104,15 @@ impl AccountSecretKey { } let f = fs::File::create(path)?; - let encrypted = EncryptedSecretKey::encrypt(&self.to_bytes(), password)?; + let encrypted = Self::try_encrypt(&self.to_bytes(), password)?; serde_json::to_writer(f, &encrypted)?; Ok(()) } } +impl EncryptedSecretKey for AccountSecretKey {} + impl From for Keypair { fn from(value: AccountSecretKey) -> Self { value.0 @@ -170,134 +175,6 @@ impl<'de> serde::Deserialize<'de> for AccountSecretKey { } } -#[derive(Serialize, Deserialize, Debug)] -struct Base58String(String); - -impl Base58String { - pub fn new(raw: &[u8], version: u8) -> Self { - Base58String(bs58::encode(raw).with_check_version(version).into_string()) - } - - pub fn try_decode(&self, version: u8) -> Result, EncryptionError> { - let decoded = bs58::decode(&self.0).with_check(Some(version)).into_vec()?; - Ok(decoded[1..].to_vec()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum EncryptionError { - #[error(transparent)] - SecretBox(#[from] crypto_secretbox::aead::Error), - #[error(transparent)] - ArgonError(#[from] argon2::Error), - #[error(transparent)] - PasswordHash(#[from] argon2::password_hash::Error), - #[error(transparent)] - Base58DecodeError(#[from] bs58::decode::Error), - #[error(transparent)] - CipherKeyInvalidLength(#[from] crypto_secretbox::cipher::InvalidLength), - #[error("Password hash missing after hash_password")] - HashMissing, - #[error(transparent)] - Keypair(#[from] KeypairError), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - SerdeJson(#[from] serde_json::Error), -} - -#[derive(Serialize, Deserialize, Debug)] -struct EncryptedSecretKey { - box_primitive: String, - pw_primitive: String, - nonce: Base58String, - pwsalt: Base58String, - pwdiff: (u32, u32), - ciphertext: Base58String, -} - -impl EncryptedSecretKey { - const ENCRYPTION_DATA_VERSION_BYTE: u8 = 2; - const SECRET_KEY_PREFIX_BYTE: u8 = 1; - - // Based on the ocaml implementation - const BOX_PRIMITIVE: &'static str = "xsalsa20poly1305"; - const PW_PRIMITIVE: &'static str = "argon2i"; - // Note: Only used for enryption, for decryption use the pwdiff from the file - const PW_DIFF: (u32, u32) = (134217728, 6); - - fn setup_argon(pwdiff: (u32, u32)) -> Result, EncryptionError> { - let params = Params::new(pwdiff.0 / 1024, pwdiff.1, Params::DEFAULT_P_COST, None)?; - - Ok(Argon2::new( - argon2::Algorithm::Argon2i, - Default::default(), - params, - )) - } - - pub fn try_decrypt(&self, password: &str) -> Result { - // prepare inputs to cipher - let password = password.as_bytes(); - let pwsalt = self.pwsalt.try_decode(Self::ENCRYPTION_DATA_VERSION_BYTE)?; - let nonce = self.nonce.try_decode(Self::ENCRYPTION_DATA_VERSION_BYTE)?; - let ciphertext = self - .ciphertext - .try_decode(Self::ENCRYPTION_DATA_VERSION_BYTE)?; - - // The argon crate's SaltString can only be built from base64 string, ocaml node encodes the salt in base58 - // So we decoded it from base58 first, then convert to base64 and lastly to SaltString - let pwsalt_encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(pwsalt); - let salt = SaltString::from_b64(&pwsalt_encoded)?; - - let argon2 = Self::setup_argon(self.pwdiff)?; - let password_hash = argon2 - .hash_password(password, &salt)? - .hash - .ok_or(EncryptionError::HashMissing)?; - let password_bytes = password_hash.as_bytes(); - - // decrypt cipher - let cipher = XSalsa20Poly1305::new_from_slice(password_bytes)?; - let decrypted = cipher.decrypt(nonce.as_slice().into(), ciphertext.as_ref())?; - - // strip the prefix and create keypair - Ok(AccountSecretKey::from_bytes(&decrypted[1..])?) - } - - pub fn encrypt(key: &[u8], password: &str) -> Result { - let argon2 = Self::setup_argon(Self::PW_DIFF)?; - - // add the prefix byt to the key - let mut key_prefixed = vec![Self::SECRET_KEY_PREFIX_BYTE]; - key_prefixed.extend(key); - - let salt = SaltString::generate(&mut OsRng); - let password_hash = argon2 - .hash_password(password.as_bytes(), &salt)? - .hash - .ok_or(EncryptionError::HashMissing)?; - - let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); - let cipher = XSalsa20Poly1305::new_from_slice(password_hash.as_bytes())?; - - let ciphertext = cipher.encrypt(&nonce, key_prefixed.as_slice())?; - - // Same reason as in decrypt, we ned to decode the SaltString from base64 then encode it to base58 bellow - let mut salt_bytes = [0; 32]; - let salt_portion = salt.decode_b64(&mut salt_bytes)?; - - Ok(Self { - box_primitive: Self::BOX_PRIMITIVE.to_string(), - pw_primitive: Self::PW_PRIMITIVE.to_string(), - nonce: Base58String::new(&nonce, Self::ENCRYPTION_DATA_VERSION_BYTE), - pwsalt: Base58String::new(salt_portion, Self::ENCRYPTION_DATA_VERSION_BYTE), - pwdiff: (argon2.params().m_cost() * 1024, argon2.params().t_cost()), - ciphertext: Base58String::new(&ciphertext, Self::ENCRYPTION_DATA_VERSION_BYTE), - }) - } -} - #[cfg(test)] mod tests { use std::env; diff --git a/node/build.rs b/node/build.rs index f12d405c19..e56793cd02 100644 --- a/node/build.rs +++ b/node/build.rs @@ -126,7 +126,7 @@ fn main() -> Result<(), Box> { visit_dirs(&node_dir, &mut |file| { let mut path = file.path(); - let path_str = path.to_str().unwrap(); + let path_str = path.to_str().expect("path"); if !path_str.ends_with("_actions.rs") && !path_str.ends_with("action.rs") { return; } @@ -156,7 +156,8 @@ fn main() -> Result<(), Box> { if let Some(action_name) = matches.get(2) { let action_name = action_name.as_str().to_owned(); // Without 'Action' suffix - let action_name_base = action_name[..(action_name.len() - 6)].to_string(); + let action_name_base = + action_name[..(action_name.len().saturating_sub(6))].to_string(); let mut variant_lines = vec![]; loop { let Some(line) = lines.next() else { break }; @@ -252,10 +253,9 @@ fn main() -> Result<(), Box> { .map(|(k, v)| { let mut s = "use crate::".to_owned(); if !k.is_empty() { - s += &k.join("::"); - s += "::"; + s.push_str(&format!("{}::", k.join("::"))); } - s += &format!("{{{}}};", v.join(", ")); + s.push_str(&format!("{{{}}};", v.join(", "))); s }) .collect::>() @@ -268,8 +268,7 @@ fn main() -> Result<(), Box> { if meta.is_inlined() { meta.action_kinds() } else { - // Remove suffix `Action` from action name. - vec![name[..(name.len() - 6)].to_string()] + vec![name[..name.len().saturating_sub(6)].to_string()] } }); let action_kinds = std::iter::once("None".to_owned()) @@ -304,7 +303,7 @@ fn main() -> Result<(), Box> { while let Some(action_name) = queue.pop_front() { let fn_body = match actions.get(dbg!(&action_name)).unwrap() { ActionMeta::Struct => { - let action_kind = &action_name[..(action_name.len() - 6)]; + let action_kind = &action_name[..(action_name.len().saturating_sub(6))]; format!("ActionKind::{action_kind}") } ActionMeta::Enum(variants) => { diff --git a/node/common/Cargo.toml b/node/common/Cargo.toml index a88ff63eca..e26483f77a 100644 --- a/node/common/Cargo.toml +++ b/node/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-node-common" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -18,7 +18,7 @@ mina-signer = { workspace = true } vrf = { workspace = true } ledger = { workspace = true } sha3 = "0.10.8" -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +ark-ff = { workspace = true } node = { path = "../../node", features = ["replay"] } openmina-core = { path = "../../core" } diff --git a/node/common/src/service/block_producer/mod.rs b/node/common/src/service/block_producer/mod.rs index afb8288b79..748fe4983c 100644 --- a/node/common/src/service/block_producer/mod.rs +++ b/node/common/src/service/block_producer/mod.rs @@ -1,5 +1,7 @@ mod vrf_evaluator; +use std::sync::Arc; + use ledger::proofs::{ block::BlockParams, generate_block_proof, provers::BlockProver, transaction::ProofError, }; @@ -16,16 +18,16 @@ use node::{ use crate::EventSender; pub struct BlockProducerService { - provers: BlockProver, + provers: Option, keypair: AccountSecretKey, vrf_evaluation_sender: mpsc::UnboundedSender, } impl BlockProducerService { pub fn new( - provers: BlockProver, keypair: AccountSecretKey, vrf_evaluation_sender: mpsc::UnboundedSender, + provers: Option, ) -> Self { Self { provers, @@ -35,9 +37,9 @@ impl BlockProducerService { } pub fn start( - provers: BlockProver, event_sender: EventSender, keypair: AccountSecretKey, + provers: Option, ) -> Self { let (vrf_evaluation_sender, vrf_evaluation_receiver) = mpsc::unbounded_channel::(); @@ -54,7 +56,7 @@ impl BlockProducerService { }) .unwrap(); - BlockProducerService::new(provers, keypair, vrf_evaluation_sender) + BlockProducerService::new(keypair, vrf_evaluation_sender, provers) } pub fn keypair(&self) -> AccountSecretKey { @@ -67,7 +69,7 @@ pub fn prove( mut input: Box, keypair: AccountSecretKey, only_verify_constraints: bool, -) -> Result, ProofError> { +) -> Result, ProofError> { let height = input .next_state .body @@ -103,6 +105,7 @@ impl node::service::BlockProducerService for crate::NodeService { .expect("provers shouldn't be needed if block producer isn't initialized") .provers .clone() + .unwrap_or_else(BlockProver::get_once_made) } fn prove(&mut self, block_hash: StateHash, input: Box) { diff --git a/node/common/src/service/block_producer/vrf_evaluator.rs b/node/common/src/service/block_producer/vrf_evaluator.rs index 837a582a30..5f9768e538 100644 --- a/node/common/src/service/block_producer/vrf_evaluator.rs +++ b/node/common/src/service/block_producer/vrf_evaluator.rs @@ -51,7 +51,9 @@ pub fn vrf_evaluator( } } -impl node::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorService for NodeService { +impl node::block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorService + for NodeService +{ fn evaluate(&mut self, data: VrfEvaluatorInput) { if let Some(bp) = self.block_producer.as_mut() { let _ = bp.vrf_evaluation_sender.send(data); diff --git a/node/common/src/service/builder.rs b/node/common/src/service/builder.rs index 6ecbc9e3af..6b724ae3c3 100644 --- a/node/common/src/service/builder.rs +++ b/node/common/src/service/builder.rs @@ -80,13 +80,13 @@ impl NodeServiceCommonBuilder { pub fn block_producer_init( &mut self, - provers: BlockProver, keypair: AccountSecretKey, + provers: Option, ) -> &mut Self { self.block_producer = Some(BlockProducerService::start( - provers, self.event_sender.clone(), keypair, + provers, )); self } diff --git a/node/common/src/service/p2p.rs b/node/common/src/service/p2p.rs index 8a1c7cc197..50ab12e214 100644 --- a/node/common/src/service/p2p.rs +++ b/node/common/src/service/p2p.rs @@ -44,7 +44,7 @@ impl webrtc::P2pServiceWebrtc for NodeService { &mut self, other_pk: &PublicKey, message: &T, - ) -> Result { + ) -> Result> { let rng = &mut self.rng; self.p2p.sec_key.encrypt(other_pk, rng, message) } @@ -53,10 +53,21 @@ impl webrtc::P2pServiceWebrtc for NodeService { &mut self, other_pk: &PublicKey, encrypted: &T::Encrypted, - ) -> Result { + ) -> Result> { self.p2p.sec_key.decrypt(other_pk, encrypted) } + #[cfg(not(feature = "p2p-webrtc"))] + fn auth_encrypt_and_send( + &mut self, + peer_id: PeerId, + other_pub_key: &PublicKey, + auth: ConnectionAuth, + ) { + let _ = (peer_id, other_pub_key, auth); + } + + #[cfg(feature = "p2p-webrtc")] fn auth_encrypt_and_send( &mut self, peer_id: PeerId, diff --git a/node/common/src/service/service.rs b/node/common/src/service/service.rs index cb296af0a3..c13674fbcb 100644 --- a/node/common/src/service/service.rs +++ b/node/common/src/service/service.rs @@ -181,7 +181,9 @@ impl node::service::TransitionFrontierGenesisService for NodeService { } impl node::core::invariants::InvariantService for NodeService { - fn invariants_state(&mut self) -> &mut node::core::invariants::InvariantsState { + type ClusterInvariantsState<'a> = std::cell::RefMut<'a, InvariantsState>; + + fn invariants_state(&mut self) -> &mut InvariantsState { &mut self.invariants_state } } diff --git a/node/invariants/Cargo.toml b/node/invariants/Cargo.toml index 5f2dd853bd..306e58f1f7 100644 --- a/node/invariants/Cargo.toml +++ b/node/invariants/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-node-invariants" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/node/invariants/src/invariant_result.rs b/node/invariants/src/invariant_result.rs index 595307b5b3..6b6f3df40b 100644 --- a/node/invariants/src/invariant_result.rs +++ b/node/invariants/src/invariant_result.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum InvariantResult { + Ignored(InvariantIgnoreReason), /// Invariant check was triggered but as a result we didn't do any /// checks, instead internal state of invariant might have been updated. Updated, @@ -10,3 +11,8 @@ pub enum InvariantResult { /// Invariant check was done and it passed. Ok, } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum InvariantIgnoreReason { + GlobalInvariantNotInTestingCluster, +} diff --git a/node/invariants/src/lib.rs b/node/invariants/src/lib.rs index ce7d4a925f..c73ed9d45b 100644 --- a/node/invariants/src/lib.rs +++ b/node/invariants/src/lib.rs @@ -1,5 +1,5 @@ mod invariant_result; -pub use invariant_result::InvariantResult; +pub use invariant_result::{InvariantIgnoreReason, InvariantResult}; pub mod no_recursion; use no_recursion::*; @@ -11,7 +11,7 @@ pub use node::core::invariants::{InvariantService, InvariantsState}; use strum_macros::{EnumDiscriminants, EnumIter, EnumString, IntoStaticStr}; -use node::{ActionKind, ActionWithMeta, Store}; +use node::{ActionKind, ActionWithMeta, Service, Store}; pub trait Invariant { /// Internal state of the invariant. @@ -20,6 +20,11 @@ pub trait Invariant { /// this is the place. type InternalState: 'static + Send + Default; + /// Whether or not invariant is cluster-wide, or for just local node. + fn is_global(&self) -> bool { + false + } + /// Invariant triggers define a list actions, which should cause /// `Invariant::check` to be called. /// @@ -27,11 +32,11 @@ pub trait Invariant { fn triggers(&self) -> &[ActionKind]; /// Checks the state for invariant violation. - fn check( + fn check( self, internal_state: &mut Self::InternalState, store: &Store, - _action: &ActionWithMeta, + action: &ActionWithMeta, ) -> InvariantResult; } @@ -48,21 +53,49 @@ macro_rules! define_invariants_enum { InvariantsDiscriminants::from(self) as usize } + pub fn is_global(&self) -> bool { + match self { + $(Self::$invariant(invariant) => invariant.is_global(),)* + } + } + pub fn triggers(&self) -> &[ActionKind] { match self { $(Self::$invariant(invariant) => invariant.triggers(),)* } } - pub fn check(self, store: &mut Store, action: &ActionWithMeta) -> InvariantResult { - let mut invariants_state = store.service.invariants_state().take(); + pub fn check( + self, + store: &mut Store, + action: &ActionWithMeta, + ) -> InvariantResult { + let mut invariants_state = if self.is_global() { + match store.service.cluster_invariants_state() { + Some(mut v) => v.take(), + None => return InvariantResult::Ignored(InvariantIgnoreReason::GlobalInvariantNotInTestingCluster), + } + } else { + store.service.invariants_state().take() + }; + let res = match self { $(Self::$invariant(invariant) => { let invariant_state = invariants_state.get(self.index()); invariant.check(invariant_state, store, action) })* }; - *store.service.invariants_state() = invariants_state; + + if self.is_global() { + match store.service.cluster_invariants_state() { + Some(mut s) => + *s = invariants_state, + None => unreachable!("function should have returned above"), + } + } else { + *store.service.invariants_state() = invariants_state; + }; + res } } @@ -94,7 +127,7 @@ impl Invariants { ::iter() } - pub fn check_all<'a, S: InvariantService>( + pub fn check_all<'a, S: Service + InvariantService>( store: &'a mut Store, action: &'a ActionWithMeta, ) -> impl 'a + Iterator { diff --git a/node/native/Cargo.toml b/node/native/Cargo.toml index e43aaf442c..e7e6027881 100644 --- a/node/native/Cargo.toml +++ b/node/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-node-native" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/node/native/src/http_server.rs b/node/native/src/http_server.rs index e62ca24f79..05dc4b675c 100644 --- a/node/native/src/http_server.rs +++ b/node/native/src/http_server.rs @@ -27,6 +27,10 @@ macro_rules! compose_route { } pub async fn run(port: u16, rpc_sender: RpcSender) { + let build_env_get = warp::path!("build_env") + .and(warp::get()) + .then(move || async { with_json_reply(&node::BuildEnv::get(), StatusCode::OK) }); + #[cfg(feature = "p2p-webrtc")] let signaling = { use node::p2p::{ @@ -564,6 +568,7 @@ pub async fn run(port: u16, rpc_sender: RpcSender) { #[cfg(feature = "p2p-webrtc")] let routes = signaling.or(state_get).or(state_post); let routes = compose_route!( + build_env_get, routes, status, peers_get, diff --git a/node/native/src/node/builder.rs b/node/native/src/node/builder.rs index f8400cd91b..069021e4d1 100644 --- a/node/native/src/node/builder.rs +++ b/node/native/src/node/builder.rs @@ -4,7 +4,6 @@ use std::{ net::IpAddr, path::Path, sync::Arc, - time::Duration, }; use anyhow::Context; @@ -174,27 +173,31 @@ impl NodeBuilder { } /// Set up block producer. - pub fn block_producer(&mut self, provers: BlockProver, key: AccountSecretKey) -> &mut Self { + pub fn block_producer( + &mut self, + key: AccountSecretKey, + provers: Option, + ) -> &mut Self { let config = BlockProducerConfig { pub_key: key.public_key().into(), custom_coinbase_receiver: None, proposed_protocol_version: None, }; self.block_producer = Some(config); - self.service.block_producer_init(provers, key); + self.service.block_producer_init(key, provers); self } /// Set up block producer using keys from file. pub fn block_producer_from_file( &mut self, - provers: BlockProver, path: impl AsRef, password: &str, + provers: Option, ) -> anyhow::Result<&mut Self> { let key = AccountSecretKey::from_encrypted_file(path, password) .context("Failed to decrypt secret key file")?; - Ok(self.block_producer(provers, key)) + Ok(self.block_producer(key, provers)) } /// Receive block producer's coinbase reward to another account. @@ -322,7 +325,6 @@ impl NodeBuilder { identity_pub_key: p2p_sec_key.public_key(), initial_peers, external_addrs, - ask_initial_peers_interval: Duration::from_secs(3600), enabled_channels: ChannelId::iter_all().collect(), peer_discovery: !self.p2p_no_discovery, meshsub: P2pMeshsubConfig { diff --git a/node/native/src/service/builder.rs b/node/native/src/service/builder.rs index a258f6f6ab..abb4d156ec 100644 --- a/node/native/src/service/builder.rs +++ b/node/native/src/service/builder.rs @@ -46,10 +46,10 @@ impl NodeServiceBuilder { pub fn block_producer_init( &mut self, - provers: BlockProver, keypair: AccountSecretKey, + provers: Option, ) -> &mut Self { - self.common.block_producer_init(provers, keypair); + self.common.block_producer_init(keypair, provers); self } diff --git a/node/src/action.rs b/node/src/action.rs index 867890ea0b..3793d97058 100644 --- a/node/src/action.rs +++ b/node/src/action.rs @@ -5,10 +5,13 @@ pub type ActionWithMeta = redux::ActionWithMeta; pub type ActionWithMetaRef<'a> = redux::ActionWithMeta<&'a Action>; pub use crate::block_producer::BlockProducerAction; +pub use crate::block_producer_effectful::BlockProducerEffectfulAction; pub use crate::consensus::ConsensusAction; pub use crate::event_source::EventSourceAction; pub use crate::external_snark_worker::ExternalSnarkWorkerAction; +use crate::external_snark_worker_effectful::ExternalSnarkWorkerEffectfulAction; pub use crate::ledger::LedgerAction; +use crate::ledger_effectful::LedgerEffectfulAction; use crate::p2p::callbacks::P2pCallbacksAction; pub use crate::p2p::P2pAction; pub use crate::rpc::RpcAction; @@ -39,6 +42,7 @@ pub enum Action { P2pCallbacks(P2pCallbacksAction), Ledger(LedgerAction), + LedgerEffects(LedgerEffectfulAction), Snark(SnarkAction), Consensus(ConsensusAction), TransitionFrontier(TransitionFrontierAction), @@ -47,7 +51,9 @@ pub enum Action { TransactionPool(TransactionPoolAction), TransactionPoolEffect(TransactionPoolEffectfulAction), ExternalSnarkWorker(ExternalSnarkWorkerAction), + ExternalSnarkWorkerEffects(ExternalSnarkWorkerEffectfulAction), BlockProducer(BlockProducerAction), + BlockProducerEffectful(BlockProducerEffectfulAction), Rpc(RpcAction), RpcEffectful(RpcEffectfulAction), @@ -85,13 +91,16 @@ impl redux::EnablingCondition for Action { .ready() .map_or(false, |state| a.is_enabled(state, time)), Action::Ledger(a) => a.is_enabled(state, time), + Action::LedgerEffects(a) => a.is_enabled(state, time), Action::Snark(a) => a.is_enabled(&state.snark, time), Action::Consensus(a) => a.is_enabled(state, time), Action::TransitionFrontier(a) => a.is_enabled(state, time), Action::SnarkPool(a) => a.is_enabled(state, time), Action::SnarkPoolEffect(a) => a.is_enabled(state, time), Action::ExternalSnarkWorker(a) => a.is_enabled(state, time), + Action::ExternalSnarkWorkerEffects(a) => a.is_enabled(state, time), Action::BlockProducer(a) => a.is_enabled(state, time), + Action::BlockProducerEffectful(a) => a.is_enabled(state, time), Action::Rpc(a) => a.is_enabled(state, time), Action::WatchedAccounts(a) => a.is_enabled(state, time), Action::TransactionPool(a) => a.is_enabled(state, time), @@ -104,6 +113,9 @@ impl redux::EnablingCondition for Action { impl From for Action { fn from(action: redux::AnyAction) -> Self { - *action.0.downcast::().expect("Downcast failed") + match action.0.downcast() { + Ok(action) => *action, + Err(action) => Self::P2p(*action.downcast().expect("Downcast failed")), + } } } diff --git a/node/src/action_kind.rs b/node/src/action_kind.rs index 6009bf5971..1cea0afb15 100644 --- a/node/src/action_kind.rs +++ b/node/src/action_kind.rs @@ -17,29 +17,25 @@ use strum_macros::VariantArray; use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorAction; use crate::block_producer::BlockProducerAction; +use crate::block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorEffectfulAction; +use crate::block_producer_effectful::BlockProducerEffectfulAction; use crate::consensus::ConsensusAction; use crate::event_source::EventSourceAction; use crate::external_snark_worker::ExternalSnarkWorkerAction; +use crate::external_snark_worker_effectful::ExternalSnarkWorkerEffectfulAction; use crate::ledger::read::LedgerReadAction; use crate::ledger::write::LedgerWriteAction; use crate::ledger::LedgerAction; +use crate::ledger_effectful::LedgerEffectfulAction; use crate::p2p::callbacks::P2pCallbacksAction; use crate::p2p::channels::best_tip::P2pChannelsBestTipAction; -use crate::p2p::channels::best_tip_effectful::P2pChannelsBestTipEffectfulAction; use crate::p2p::channels::rpc::P2pChannelsRpcAction; -use crate::p2p::channels::rpc_effectful::P2pChannelsRpcEffectfulAction; use crate::p2p::channels::signaling::discovery::P2pChannelsSignalingDiscoveryAction; -use crate::p2p::channels::signaling::discovery_effectful::P2pChannelsSignalingDiscoveryEffectfulAction; use crate::p2p::channels::signaling::exchange::P2pChannelsSignalingExchangeAction; -use crate::p2p::channels::signaling::exchange_effectful::P2pChannelsSignalingExchangeEffectfulAction; use crate::p2p::channels::snark::P2pChannelsSnarkAction; -use crate::p2p::channels::snark_effectful::P2pChannelsSnarkEffectfulAction; use crate::p2p::channels::snark_job_commitment::P2pChannelsSnarkJobCommitmentAction; -use crate::p2p::channels::snark_job_commitment_effectful::P2pChannelsSnarkJobCommitmentEffectfulAction; use crate::p2p::channels::streaming_rpc::P2pChannelsStreamingRpcAction; -use crate::p2p::channels::streaming_rpc_effectful::P2pChannelsStreamingRpcEffectfulAction; use crate::p2p::channels::transaction::P2pChannelsTransactionAction; -use crate::p2p::channels::transaction_effectful::P2pChannelsTransactionEffectfulAction; use crate::p2p::channels::{ P2pChannelsAction, P2pChannelsEffectfulAction, P2pChannelsMessageReceivedAction, }; @@ -83,6 +79,7 @@ use crate::snark::work_verify_effectful::SnarkWorkVerifyEffectfulAction; use crate::snark::SnarkAction; use crate::snark_pool::candidate::SnarkPoolCandidateAction; use crate::snark_pool::{SnarkPoolAction, SnarkPoolEffectfulAction}; +use crate::transaction_pool::candidate::TransactionPoolCandidateAction; use crate::transaction_pool::{TransactionPoolAction, TransactionPoolEffectfulAction}; use crate::transition_frontier::genesis::TransitionFrontierGenesisAction; use crate::transition_frontier::genesis_effectful::TransitionFrontierGenesisEffectfulAction; @@ -129,6 +126,13 @@ pub enum ActionKind { BlockProducerWonSlotTransactionsGet, BlockProducerWonSlotTransactionsSuccess, BlockProducerWonSlotWait, + BlockProducerEffectfulBlockProveInit, + BlockProducerEffectfulBlockProveSuccess, + BlockProducerEffectfulBlockUnprovenBuild, + BlockProducerEffectfulStagedLedgerDiffCreateInit, + BlockProducerEffectfulStagedLedgerDiffCreateSuccess, + BlockProducerEffectfulWonSlot, + BlockProducerEffectfulWonSlotDiscard, BlockProducerVrfEvaluatorBeginDelegatorTableConstruction, BlockProducerVrfEvaluatorBeginEpochEvaluation, BlockProducerVrfEvaluatorCheckEpochBounds, @@ -145,9 +149,14 @@ pub enum ActionKind { BlockProducerVrfEvaluatorProcessSlotEvaluationSuccess, BlockProducerVrfEvaluatorSelectInitialSlot, BlockProducerVrfEvaluatorWaitForNextEvaluation, + BlockProducerVrfEvaluatorEffectfulEvaluateSlot, + BlockProducerVrfEvaluatorEffectfulInitializeStats, + BlockProducerVrfEvaluatorEffectfulSlotEvaluated, CheckTimeouts, ConsensusBestTipUpdate, ConsensusBlockChainProofUpdate, + ConsensusBlockPrevalidateError, + ConsensusBlockPrevalidateSuccess, ConsensusBlockReceived, ConsensusBlockSnarkVerifyError, ConsensusBlockSnarkVerifyPending, @@ -175,6 +184,12 @@ pub enum ActionKind { ExternalSnarkWorkerWorkError, ExternalSnarkWorkerWorkResult, ExternalSnarkWorkerWorkTimeout, + ExternalSnarkWorkerEffectfulCancelWork, + ExternalSnarkWorkerEffectfulKill, + ExternalSnarkWorkerEffectfulStart, + ExternalSnarkWorkerEffectfulSubmitWork, + LedgerEffectfulReadInit, + LedgerEffectfulWriteInit, LedgerReadFindTodos, LedgerReadInit, LedgerReadPending, @@ -199,9 +214,12 @@ pub enum ActionKind { P2pChannelsBestTipRequestReceived, P2pChannelsBestTipRequestSend, P2pChannelsBestTipResponseSend, - P2pChannelsBestTipEffectfulInit, - P2pChannelsBestTipEffectfulRequestSend, - P2pChannelsBestTipEffectfulResponseSend, + P2pChannelsEffectfulInitChannel, + P2pChannelsEffectfulMessageSend, + P2pChannelsEffectfulSignalingDiscoveryAnswerDecrypt, + P2pChannelsEffectfulSignalingDiscoveryOfferEncryptAndSend, + P2pChannelsEffectfulSignalingExchangeAnswerEncryptAndSend, + P2pChannelsEffectfulSignalingExchangeOfferDecrypt, P2pChannelsMessageReceived, P2pChannelsRpcInit, P2pChannelsRpcPending, @@ -212,9 +230,6 @@ pub enum ActionKind { P2pChannelsRpcResponseReceived, P2pChannelsRpcResponseSend, P2pChannelsRpcTimeout, - P2pChannelsRpcEffectfulInit, - P2pChannelsRpcEffectfulRequestSend, - P2pChannelsRpcEffectfulResponseSend, P2pChannelsSignalingDiscoveryAnswerDecrypted, P2pChannelsSignalingDiscoveryAnswerReceived, P2pChannelsSignalingDiscoveryAnswerSend, @@ -231,10 +246,6 @@ pub enum ActionKind { P2pChannelsSignalingDiscoveryReady, P2pChannelsSignalingDiscoveryRequestReceived, P2pChannelsSignalingDiscoveryRequestSend, - P2pChannelsSignalingDiscoveryEffectfulAnswerDecrypt, - P2pChannelsSignalingDiscoveryEffectfulInit, - P2pChannelsSignalingDiscoveryEffectfulMessageSend, - P2pChannelsSignalingDiscoveryEffectfulOfferEncryptAndSend, P2pChannelsSignalingExchangeAnswerReceived, P2pChannelsSignalingExchangeAnswerSend, P2pChannelsSignalingExchangeInit, @@ -246,10 +257,6 @@ pub enum ActionKind { P2pChannelsSignalingExchangeReady, P2pChannelsSignalingExchangeRequestReceived, P2pChannelsSignalingExchangeRequestSend, - P2pChannelsSignalingExchangeEffectfulAnswerEncryptAndSend, - P2pChannelsSignalingExchangeEffectfulInit, - P2pChannelsSignalingExchangeEffectfulMessageSend, - P2pChannelsSignalingExchangeEffectfulOfferDecrypt, P2pChannelsSnarkInit, P2pChannelsSnarkLibp2pBroadcast, P2pChannelsSnarkLibp2pReceived, @@ -260,9 +267,6 @@ pub enum ActionKind { P2pChannelsSnarkRequestReceived, P2pChannelsSnarkRequestSend, P2pChannelsSnarkResponseSend, - P2pChannelsSnarkEffectfulInit, - P2pChannelsSnarkEffectfulRequestSend, - P2pChannelsSnarkEffectfulResponseSend, P2pChannelsSnarkJobCommitmentInit, P2pChannelsSnarkJobCommitmentPending, P2pChannelsSnarkJobCommitmentPromiseReceived, @@ -271,9 +275,6 @@ pub enum ActionKind { P2pChannelsSnarkJobCommitmentRequestReceived, P2pChannelsSnarkJobCommitmentRequestSend, P2pChannelsSnarkJobCommitmentResponseSend, - P2pChannelsSnarkJobCommitmentEffectfulInit, - P2pChannelsSnarkJobCommitmentEffectfulRequestSend, - P2pChannelsSnarkJobCommitmentEffectfulResponseSend, P2pChannelsStreamingRpcInit, P2pChannelsStreamingRpcPending, P2pChannelsStreamingRpcReady, @@ -288,11 +289,6 @@ pub enum ActionKind { P2pChannelsStreamingRpcResponseSendInit, P2pChannelsStreamingRpcResponseSent, P2pChannelsStreamingRpcTimeout, - P2pChannelsStreamingRpcEffectfulInit, - P2pChannelsStreamingRpcEffectfulRequestSend, - P2pChannelsStreamingRpcEffectfulResponseNextPartGet, - P2pChannelsStreamingRpcEffectfulResponsePartSend, - P2pChannelsStreamingRpcEffectfulResponseSendInit, P2pChannelsTransactionInit, P2pChannelsTransactionLibp2pBroadcast, P2pChannelsTransactionLibp2pReceived, @@ -303,9 +299,6 @@ pub enum ActionKind { P2pChannelsTransactionRequestReceived, P2pChannelsTransactionRequestSend, P2pChannelsTransactionResponseSend, - P2pChannelsTransactionEffectfulInit, - P2pChannelsTransactionEffectfulRequestSend, - P2pChannelsTransactionEffectfulResponseSend, P2pConnectionIncomingAnswerReady, P2pConnectionIncomingAnswerSdpCreateError, P2pConnectionIncomingAnswerSdpCreatePending, @@ -346,8 +339,10 @@ pub enum ActionKind { P2pConnectionOutgoingEffectfulInit, P2pConnectionOutgoingEffectfulOfferSend, P2pConnectionOutgoingEffectfulRandomInit, + P2pDisconnectionFailedCleanup, P2pDisconnectionFinish, P2pDisconnectionInit, + P2pDisconnectionPeerClosed, P2pDisconnectionEffectfulInit, P2pEffectfulInitialize, P2pIdentifyNewRequest, @@ -358,7 +353,8 @@ pub enum ActionKind { P2pNetworkIdentifyStreamNew, P2pNetworkIdentifyStreamPrune, P2pNetworkIdentifyStreamRemoteClose, - P2pNetworkIdentifyStreamEffectfulSendIdentify, + P2pNetworkIdentifyStreamSendIdentify, + P2pNetworkIdentifyStreamEffectfulGetListenAddresses, P2pNetworkKadBootstrapAppendRequest, P2pNetworkKadBootstrapCreateRequests, P2pNetworkKadBootstrapFinalizeRequests, @@ -410,15 +406,18 @@ pub enum ActionKind { P2pNetworkPubsubGraft, P2pNetworkPubsubIncomingData, P2pNetworkPubsubIncomingMessage, + P2pNetworkPubsubIncomingMessageCleanup, P2pNetworkPubsubNewStream, P2pNetworkPubsubOutgoingData, P2pNetworkPubsubOutgoingMessage, + P2pNetworkPubsubOutgoingMessageClear, P2pNetworkPubsubOutgoingMessageError, P2pNetworkPubsubPrune, P2pNetworkPubsubSign, P2pNetworkPubsubSignError, - P2pNetworkPubsubEffectfulIncomingData, + P2pNetworkPubsubValidateIncomingMessages, P2pNetworkPubsubEffectfulSign, + P2pNetworkPubsubEffectfulValidateIncomingMessages, P2pNetworkRpcHeartbeatSend, P2pNetworkRpcIncomingData, P2pNetworkRpcIncomingMessage, @@ -442,12 +441,10 @@ pub enum ActionKind { P2pNetworkSchedulerOutgoingDidConnect, P2pNetworkSchedulerPrune, P2pNetworkSchedulerPruneStream, - P2pNetworkSchedulerPruneStreams, P2pNetworkSchedulerSelectDone, P2pNetworkSchedulerSelectError, P2pNetworkSchedulerYamuxDidInit, P2pNetworkSchedulerEffectfulDisconnect, - P2pNetworkSchedulerEffectfulError, P2pNetworkSchedulerEffectfulIncomingConnectionIsReady, P2pNetworkSchedulerEffectfulIncomingDataIsReady, P2pNetworkSchedulerEffectfulIncomingDidAccept, @@ -455,7 +452,6 @@ pub enum ActionKind { P2pNetworkSchedulerEffectfulNoiseSelectDone, P2pNetworkSchedulerEffectfulOutgoingConnect, P2pNetworkSchedulerEffectfulOutgoingDidConnect, - P2pNetworkSchedulerEffectfulSelectError, P2pNetworkSelectIncomingData, P2pNetworkSelectIncomingDataAuth, P2pNetworkSelectIncomingDataMux, @@ -572,9 +568,10 @@ pub enum ActionKind { SnarkPoolCandidateInfoReceived, SnarkPoolCandidatePeerPrune, SnarkPoolCandidateWorkFetchAll, + SnarkPoolCandidateWorkFetchError, SnarkPoolCandidateWorkFetchInit, SnarkPoolCandidateWorkFetchPending, - SnarkPoolCandidateWorkReceived, + SnarkPoolCandidateWorkFetchSuccess, SnarkPoolCandidateWorkVerifyError, SnarkPoolCandidateWorkVerifyNext, SnarkPoolCandidateWorkVerifyPending, @@ -599,12 +596,26 @@ pub enum ActionKind { TransactionPoolBestTipChanged, TransactionPoolBestTipChangedWithAccounts, TransactionPoolCollectTransactionsByFee, + TransactionPoolP2pSend, + TransactionPoolP2pSendAll, TransactionPoolRebroadcast, TransactionPoolStartVerify, TransactionPoolStartVerifyWithAccounts, TransactionPoolVerifyError, + TransactionPoolCandidateFetchAll, + TransactionPoolCandidateFetchError, + TransactionPoolCandidateFetchInit, + TransactionPoolCandidateFetchPending, + TransactionPoolCandidateFetchSuccess, + TransactionPoolCandidateInfoReceived, + TransactionPoolCandidatePeerPrune, + TransactionPoolCandidateVerifyError, + TransactionPoolCandidateVerifyNext, + TransactionPoolCandidateVerifyPending, + TransactionPoolCandidateVerifySuccess, TransactionPoolEffectfulFetchAccounts, TransitionFrontierGenesisInject, + TransitionFrontierGenesisProvenInject, TransitionFrontierSyncFailed, TransitionFrontierSynced, TransitionFrontierGenesisLedgerLoadInit, @@ -694,7 +705,7 @@ pub enum ActionKind { } impl ActionKind { - pub const COUNT: u16 = 581; + pub const COUNT: u16 = 595; } impl std::fmt::Display for ActionKind { @@ -712,6 +723,7 @@ impl ActionKindGet for Action { Self::P2pEffectful(a) => a.kind(), Self::P2pCallbacks(a) => a.kind(), Self::Ledger(a) => a.kind(), + Self::LedgerEffects(a) => a.kind(), Self::Snark(a) => a.kind(), Self::Consensus(a) => a.kind(), Self::TransitionFrontier(a) => a.kind(), @@ -720,7 +732,9 @@ impl ActionKindGet for Action { Self::TransactionPool(a) => a.kind(), Self::TransactionPoolEffect(a) => a.kind(), Self::ExternalSnarkWorker(a) => a.kind(), + Self::ExternalSnarkWorkerEffects(a) => a.kind(), Self::BlockProducer(a) => a.kind(), + Self::BlockProducerEffectful(a) => a.kind(), Self::Rpc(a) => a.kind(), Self::RpcEffectful(a) => a.kind(), Self::WatchedAccounts(a) => a.kind(), @@ -806,6 +820,15 @@ impl ActionKindGet for LedgerAction { } } +impl ActionKindGet for LedgerEffectfulAction { + fn kind(&self) -> ActionKind { + match self { + Self::WriteInit { .. } => ActionKind::LedgerEffectfulWriteInit, + Self::ReadInit { .. } => ActionKind::LedgerEffectfulReadInit, + } + } +} + impl ActionKindGet for SnarkAction { fn kind(&self) -> ActionKind { match self { @@ -823,6 +846,8 @@ impl ActionKindGet for ConsensusAction { fn kind(&self) -> ActionKind { match self { Self::BlockReceived { .. } => ActionKind::ConsensusBlockReceived, + Self::BlockPrevalidateSuccess { .. } => ActionKind::ConsensusBlockPrevalidateSuccess, + Self::BlockPrevalidateError { .. } => ActionKind::ConsensusBlockPrevalidateError, Self::BlockChainProofUpdate { .. } => ActionKind::ConsensusBlockChainProofUpdate, Self::BlockSnarkVerifyPending { .. } => ActionKind::ConsensusBlockSnarkVerifyPending, Self::BlockSnarkVerifySuccess { .. } => ActionKind::ConsensusBlockSnarkVerifySuccess, @@ -847,6 +872,7 @@ impl ActionKindGet for TransitionFrontierAction { Self::GenesisEffect(a) => a.kind(), Self::Sync(a) => a.kind(), Self::GenesisInject => ActionKind::TransitionFrontierGenesisInject, + Self::GenesisProvenInject => ActionKind::TransitionFrontierGenesisProvenInject, Self::Synced { .. } => ActionKind::TransitionFrontierSynced, Self::SyncFailed { .. } => ActionKind::TransitionFrontierSyncFailed, } @@ -884,6 +910,7 @@ impl ActionKindGet for SnarkPoolEffectfulAction { impl ActionKindGet for TransactionPoolAction { fn kind(&self) -> ActionKind { match self { + Self::Candidate(a) => a.kind(), Self::StartVerify { .. } => ActionKind::TransactionPoolStartVerify, Self::StartVerifyWithAccounts { .. } => { ActionKind::TransactionPoolStartVerifyWithAccounts @@ -905,6 +932,8 @@ impl ActionKindGet for TransactionPoolAction { } Self::Rebroadcast { .. } => ActionKind::TransactionPoolRebroadcast, Self::CollectTransactionsByFee => ActionKind::TransactionPoolCollectTransactionsByFee, + Self::P2pSendAll => ActionKind::TransactionPoolP2pSendAll, + Self::P2pSend { .. } => ActionKind::TransactionPoolP2pSend, } } } @@ -937,6 +966,17 @@ impl ActionKindGet for ExternalSnarkWorkerAction { } } +impl ActionKindGet for ExternalSnarkWorkerEffectfulAction { + fn kind(&self) -> ActionKind { + match self { + Self::Start { .. } => ActionKind::ExternalSnarkWorkerEffectfulStart, + Self::Kill => ActionKind::ExternalSnarkWorkerEffectfulKill, + Self::SubmitWork { .. } => ActionKind::ExternalSnarkWorkerEffectfulSubmitWork, + Self::CancelWork => ActionKind::ExternalSnarkWorkerEffectfulCancelWork, + } + } +} + impl ActionKindGet for BlockProducerAction { fn kind(&self) -> ActionKind { match self { @@ -969,6 +1009,25 @@ impl ActionKindGet for BlockProducerAction { } } +impl ActionKindGet for BlockProducerEffectfulAction { + fn kind(&self) -> ActionKind { + match self { + Self::VrfEvaluator(a) => a.kind(), + Self::WonSlot { .. } => ActionKind::BlockProducerEffectfulWonSlot, + Self::WonSlotDiscard { .. } => ActionKind::BlockProducerEffectfulWonSlotDiscard, + Self::StagedLedgerDiffCreateInit => { + ActionKind::BlockProducerEffectfulStagedLedgerDiffCreateInit + } + Self::StagedLedgerDiffCreateSuccess => { + ActionKind::BlockProducerEffectfulStagedLedgerDiffCreateSuccess + } + Self::BlockUnprovenBuild => ActionKind::BlockProducerEffectfulBlockUnprovenBuild, + Self::BlockProveInit => ActionKind::BlockProducerEffectfulBlockProveInit, + Self::BlockProveSuccess => ActionKind::BlockProducerEffectfulBlockProveSuccess, + } + } +} + impl ActionKindGet for RpcAction { fn kind(&self) -> ActionKind { match self { @@ -1154,6 +1213,8 @@ impl ActionKindGet for P2pDisconnectionAction { fn kind(&self) -> ActionKind { match self { Self::Init { .. } => ActionKind::P2pDisconnectionInit, + Self::PeerClosed { .. } => ActionKind::P2pDisconnectionPeerClosed, + Self::FailedCleanup { .. } => ActionKind::P2pDisconnectionFailedCleanup, Self::Finish { .. } => ActionKind::P2pDisconnectionFinish, } } @@ -1214,14 +1275,20 @@ impl ActionKindGet for P2pNetworkAction { impl ActionKindGet for P2pChannelsEffectfulAction { fn kind(&self) -> ActionKind { match self { - Self::SignalingDiscovery(a) => a.kind(), - Self::SignalingExchange(a) => a.kind(), - Self::BestTip(a) => a.kind(), - Self::Rpc(a) => a.kind(), - Self::Snark(a) => a.kind(), - Self::SnarkJobCommitment(a) => a.kind(), - Self::StreamingRpc(a) => a.kind(), - Self::Transaction(a) => a.kind(), + Self::InitChannel { .. } => ActionKind::P2pChannelsEffectfulInitChannel, + Self::MessageSend { .. } => ActionKind::P2pChannelsEffectfulMessageSend, + Self::SignalingDiscoveryAnswerDecrypt { .. } => { + ActionKind::P2pChannelsEffectfulSignalingDiscoveryAnswerDecrypt + } + Self::SignalingDiscoveryOfferEncryptAndSend { .. } => { + ActionKind::P2pChannelsEffectfulSignalingDiscoveryOfferEncryptAndSend + } + Self::SignalingExchangeOfferDecrypt { .. } => { + ActionKind::P2pChannelsEffectfulSignalingExchangeOfferDecrypt + } + Self::SignalingExchangeAnswerEncryptAndSend { .. } => { + ActionKind::P2pChannelsEffectfulSignalingExchangeAnswerEncryptAndSend + } } } } @@ -1423,7 +1490,8 @@ impl ActionKindGet for SnarkPoolCandidateAction { Self::WorkFetchAll => ActionKind::SnarkPoolCandidateWorkFetchAll, Self::WorkFetchInit { .. } => ActionKind::SnarkPoolCandidateWorkFetchInit, Self::WorkFetchPending { .. } => ActionKind::SnarkPoolCandidateWorkFetchPending, - Self::WorkReceived { .. } => ActionKind::SnarkPoolCandidateWorkReceived, + Self::WorkFetchError { .. } => ActionKind::SnarkPoolCandidateWorkFetchError, + Self::WorkFetchSuccess { .. } => ActionKind::SnarkPoolCandidateWorkFetchSuccess, Self::WorkVerifyNext => ActionKind::SnarkPoolCandidateWorkVerifyNext, Self::WorkVerifyPending { .. } => ActionKind::SnarkPoolCandidateWorkVerifyPending, Self::WorkVerifyError { .. } => ActionKind::SnarkPoolCandidateWorkVerifyError, @@ -1433,6 +1501,24 @@ impl ActionKindGet for SnarkPoolCandidateAction { } } +impl ActionKindGet for TransactionPoolCandidateAction { + fn kind(&self) -> ActionKind { + match self { + Self::InfoReceived { .. } => ActionKind::TransactionPoolCandidateInfoReceived, + Self::FetchAll => ActionKind::TransactionPoolCandidateFetchAll, + Self::FetchInit { .. } => ActionKind::TransactionPoolCandidateFetchInit, + Self::FetchPending { .. } => ActionKind::TransactionPoolCandidateFetchPending, + Self::FetchError { .. } => ActionKind::TransactionPoolCandidateFetchError, + Self::FetchSuccess { .. } => ActionKind::TransactionPoolCandidateFetchSuccess, + Self::VerifyNext => ActionKind::TransactionPoolCandidateVerifyNext, + Self::VerifyPending { .. } => ActionKind::TransactionPoolCandidateVerifyPending, + Self::VerifyError { .. } => ActionKind::TransactionPoolCandidateVerifyError, + Self::VerifySuccess { .. } => ActionKind::TransactionPoolCandidateVerifySuccess, + Self::PeerPrune { .. } => ActionKind::TransactionPoolCandidatePeerPrune, + } + } +} + impl ActionKindGet for BlockProducerVrfEvaluatorAction { fn kind(&self) -> ActionKind { match self { @@ -1482,6 +1568,20 @@ impl ActionKindGet for BlockProducerVrfEvaluatorAction { } } +impl ActionKindGet for BlockProducerVrfEvaluatorEffectfulAction { + fn kind(&self) -> ActionKind { + match self { + Self::EvaluateSlot { .. } => ActionKind::BlockProducerVrfEvaluatorEffectfulEvaluateSlot, + Self::SlotEvaluated { .. } => { + ActionKind::BlockProducerVrfEvaluatorEffectfulSlotEvaluated + } + Self::InitializeStats { .. } => { + ActionKind::BlockProducerVrfEvaluatorEffectfulInitializeStats + } + } + } +} + impl ActionKindGet for P2pConnectionOutgoingAction { fn kind(&self) -> ActionKind { match self { @@ -1744,7 +1844,6 @@ impl ActionKindGet for P2pNetworkSchedulerAction { Self::Error { .. } => ActionKind::P2pNetworkSchedulerError, Self::Disconnected { .. } => ActionKind::P2pNetworkSchedulerDisconnected, Self::Prune { .. } => ActionKind::P2pNetworkSchedulerPrune, - Self::PruneStreams { .. } => ActionKind::P2pNetworkSchedulerPruneStreams, Self::PruneStream { .. } => ActionKind::P2pNetworkSchedulerPruneStream, } } @@ -1833,7 +1932,13 @@ impl ActionKindGet for P2pNetworkPubsubAction { match self { Self::NewStream { .. } => ActionKind::P2pNetworkPubsubNewStream, Self::IncomingData { .. } => ActionKind::P2pNetworkPubsubIncomingData, + Self::ValidateIncomingMessages { .. } => { + ActionKind::P2pNetworkPubsubValidateIncomingMessages + } Self::IncomingMessage { .. } => ActionKind::P2pNetworkPubsubIncomingMessage, + Self::IncomingMessageCleanup { .. } => { + ActionKind::P2pNetworkPubsubIncomingMessageCleanup + } Self::Graft { .. } => ActionKind::P2pNetworkPubsubGraft, Self::Prune { .. } => ActionKind::P2pNetworkPubsubPrune, Self::Broadcast { .. } => ActionKind::P2pNetworkPubsubBroadcast, @@ -1841,6 +1946,7 @@ impl ActionKindGet for P2pNetworkPubsubAction { Self::SignError { .. } => ActionKind::P2pNetworkPubsubSignError, Self::BroadcastSigned { .. } => ActionKind::P2pNetworkPubsubBroadcastSigned, Self::OutgoingMessage { .. } => ActionKind::P2pNetworkPubsubOutgoingMessage, + Self::OutgoingMessageClear { .. } => ActionKind::P2pNetworkPubsubOutgoingMessageClear, Self::OutgoingMessageError { .. } => ActionKind::P2pNetworkPubsubOutgoingMessageError, Self::OutgoingData { .. } => ActionKind::P2pNetworkPubsubOutgoingData, } @@ -1862,116 +1968,10 @@ impl ActionKindGet for P2pNetworkRpcAction { } } -impl ActionKindGet for P2pChannelsSignalingDiscoveryEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsSignalingDiscoveryEffectfulInit, - Self::MessageSend { .. } => { - ActionKind::P2pChannelsSignalingDiscoveryEffectfulMessageSend - } - Self::OfferEncryptAndSend { .. } => { - ActionKind::P2pChannelsSignalingDiscoveryEffectfulOfferEncryptAndSend - } - Self::AnswerDecrypt { .. } => { - ActionKind::P2pChannelsSignalingDiscoveryEffectfulAnswerDecrypt - } - } - } -} - -impl ActionKindGet for P2pChannelsSignalingExchangeEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsSignalingExchangeEffectfulInit, - Self::MessageSend { .. } => { - ActionKind::P2pChannelsSignalingExchangeEffectfulMessageSend - } - Self::OfferDecrypt { .. } => { - ActionKind::P2pChannelsSignalingExchangeEffectfulOfferDecrypt - } - Self::AnswerEncryptAndSend { .. } => { - ActionKind::P2pChannelsSignalingExchangeEffectfulAnswerEncryptAndSend - } - } - } -} - -impl ActionKindGet for P2pChannelsBestTipEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsBestTipEffectfulInit, - Self::RequestSend { .. } => ActionKind::P2pChannelsBestTipEffectfulRequestSend, - Self::ResponseSend { .. } => ActionKind::P2pChannelsBestTipEffectfulResponseSend, - } - } -} - -impl ActionKindGet for P2pChannelsRpcEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsRpcEffectfulInit, - Self::RequestSend { .. } => ActionKind::P2pChannelsRpcEffectfulRequestSend, - Self::ResponseSend { .. } => ActionKind::P2pChannelsRpcEffectfulResponseSend, - } - } -} - -impl ActionKindGet for P2pChannelsSnarkEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsSnarkEffectfulInit, - Self::RequestSend { .. } => ActionKind::P2pChannelsSnarkEffectfulRequestSend, - Self::ResponseSend { .. } => ActionKind::P2pChannelsSnarkEffectfulResponseSend, - } - } -} - -impl ActionKindGet for P2pChannelsSnarkJobCommitmentEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsSnarkJobCommitmentEffectfulInit, - Self::RequestSend { .. } => { - ActionKind::P2pChannelsSnarkJobCommitmentEffectfulRequestSend - } - Self::ResponseSend { .. } => { - ActionKind::P2pChannelsSnarkJobCommitmentEffectfulResponseSend - } - } - } -} - -impl ActionKindGet for P2pChannelsStreamingRpcEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsStreamingRpcEffectfulInit, - Self::RequestSend { .. } => ActionKind::P2pChannelsStreamingRpcEffectfulRequestSend, - Self::ResponseNextPartGet { .. } => { - ActionKind::P2pChannelsStreamingRpcEffectfulResponseNextPartGet - } - Self::ResponseSendInit { .. } => { - ActionKind::P2pChannelsStreamingRpcEffectfulResponseSendInit - } - Self::ResponsePartSend { .. } => { - ActionKind::P2pChannelsStreamingRpcEffectfulResponsePartSend - } - } - } -} - -impl ActionKindGet for P2pChannelsTransactionEffectfulAction { - fn kind(&self) -> ActionKind { - match self { - Self::Init { .. } => ActionKind::P2pChannelsTransactionEffectfulInit, - Self::RequestSend { .. } => ActionKind::P2pChannelsTransactionEffectfulRequestSend, - Self::ResponseSend { .. } => ActionKind::P2pChannelsTransactionEffectfulResponseSend, - } - } -} - impl ActionKindGet for P2pConnectionOutgoingEffectfulAction { fn kind(&self) -> ActionKind { match self { - Self::RandomInit => ActionKind::P2pConnectionOutgoingEffectfulRandomInit, + Self::RandomInit { .. } => ActionKind::P2pConnectionOutgoingEffectfulRandomInit, Self::Init { .. } => ActionKind::P2pConnectionOutgoingEffectfulInit, Self::OfferSend { .. } => ActionKind::P2pConnectionOutgoingEffectfulOfferSend, Self::AnswerSet { .. } => ActionKind::P2pConnectionOutgoingEffectfulAnswerSet, @@ -2019,9 +2019,7 @@ impl ActionKindGet for P2pNetworkSchedulerEffectfulAction { ActionKind::P2pNetworkSchedulerEffectfulIncomingDataIsReady } Self::NoiseSelectDone { .. } => ActionKind::P2pNetworkSchedulerEffectfulNoiseSelectDone, - Self::SelectError { .. } => ActionKind::P2pNetworkSchedulerEffectfulSelectError, Self::Disconnect { .. } => ActionKind::P2pNetworkSchedulerEffectfulDisconnect, - Self::Error { .. } => ActionKind::P2pNetworkSchedulerEffectfulError, } } } @@ -2039,7 +2037,9 @@ impl ActionKindGet for P2pNetworkPubsubEffectfulAction { fn kind(&self) -> ActionKind { match self { Self::Sign { .. } => ActionKind::P2pNetworkPubsubEffectfulSign, - Self::IncomingData { .. } => ActionKind::P2pNetworkPubsubEffectfulIncomingData, + Self::ValidateIncomingMessages { .. } => { + ActionKind::P2pNetworkPubsubEffectfulValidateIncomingMessages + } } } } @@ -2080,6 +2080,7 @@ impl ActionKindGet for P2pNetworkIdentifyStreamAction { Self::Close { .. } => ActionKind::P2pNetworkIdentifyStreamClose, Self::RemoteClose { .. } => ActionKind::P2pNetworkIdentifyStreamRemoteClose, Self::Prune { .. } => ActionKind::P2pNetworkIdentifyStreamPrune, + Self::SendIdentify { .. } => ActionKind::P2pNetworkIdentifyStreamSendIdentify, } } } @@ -2148,7 +2149,9 @@ impl ActionKindGet for P2pNetworkKademliaStreamAction { impl ActionKindGet for P2pNetworkIdentifyStreamEffectfulAction { fn kind(&self) -> ActionKind { match self { - Self::SendIdentify { .. } => ActionKind::P2pNetworkIdentifyStreamEffectfulSendIdentify, + Self::GetListenAddresses { .. } => { + ActionKind::P2pNetworkIdentifyStreamEffectfulGetListenAddresses + } } } } diff --git a/node/src/block_producer/block_producer_actions.rs b/node/src/block_producer/block_producer_actions.rs index b4baca2dfc..f4745f249c 100644 --- a/node/src/block_producer/block_producer_actions.rs +++ b/node/src/block_producer/block_producer_actions.rs @@ -1,14 +1,15 @@ +use std::sync::Arc; + use ledger::scan_state::transaction_logic::valid; use mina_p2p_messages::v2::MinaBaseProofStableV2; use openmina_core::block::ArcBlockWithHash; use openmina_core::ActionEvent; use serde::{Deserialize, Serialize}; +use crate::block_producer_effectful::StagedLedgerDiffCreateOutput; + use super::vrf_evaluator::BlockProducerVrfEvaluatorAction; -use super::{ - BlockProducerCurrentState, BlockProducerWonSlot, BlockProducerWonSlotDiscardReason, - StagedLedgerDiffCreateOutput, -}; +use super::{BlockProducerCurrentState, BlockProducerWonSlot, BlockProducerWonSlotDiscardReason}; pub type BlockProducerActionWithMeta = redux::ActionWithMeta; pub type BlockProducerActionWithMetaRef<'a> = redux::ActionWithMeta<&'a BlockProducerAction>; @@ -52,15 +53,16 @@ pub enum BlockProducerAction { StagedLedgerDiffCreateInit, StagedLedgerDiffCreatePending, StagedLedgerDiffCreateSuccess { - output: Box, + output: Arc, }, BlockUnprovenBuild, BlockProveInit, BlockProvePending, BlockProveSuccess { - proof: Box, + proof: Arc, }, BlockProduced, + #[action_event(level = trace)] BlockInject, BlockInjected, } @@ -100,9 +102,18 @@ impl redux::EnablingCondition for BlockProducerAction { BlockProducerAction::WonSlotWait => state .block_producer .with(false, |this| this.current.won_slot_should_wait(time)), - BlockProducerAction::WonSlotProduceInit { .. } => state - .block_producer - .with(false, |this| this.current.won_slot_should_produce(time)), + BlockProducerAction::WonSlotProduceInit { .. } => { + state.block_producer.with(false, |this| { + let has_genesis_proven_if_needed = || { + state.transition_frontier.best_tip().map_or(false, |tip| { + let proven_block = state.transition_frontier.genesis.proven_block(); + !tip.is_genesis() + || proven_block.map_or(false, |b| Arc::ptr_eq(&b.block, &tip.block)) + }) + }; + this.current.won_slot_should_produce(time) && has_genesis_proven_if_needed() + }) + } BlockProducerAction::WonSlotTransactionsGet => { state.block_producer.with(false, |this| { matches!( diff --git a/node/src/block_producer/block_producer_effects.rs b/node/src/block_producer/block_producer_effects.rs deleted file mode 100644 index 66bca390a4..0000000000 --- a/node/src/block_producer/block_producer_effects.rs +++ /dev/null @@ -1,342 +0,0 @@ -use mina_p2p_messages::v2::{ - BlockchainSnarkBlockchainStableV2, ConsensusStakeProofStableV2, - MinaStateSnarkTransitionValueStableV2, ProverExtendBlockchainInputStableV2, -}; -use openmina_core::bug_condition; -use openmina_core::consensus::in_seed_update_range; - -use crate::account::AccountSecretKey; -use crate::ledger::write::{LedgerWriteAction, LedgerWriteRequest}; -use crate::transition_frontier::sync::TransitionFrontierSyncAction; -use crate::{Store, TransactionPoolAction}; - -use super::vrf_evaluator::{BlockProducerVrfEvaluatorAction, InterruptReason}; -use super::{ - next_epoch_first_slot, to_epoch_and_slot, BlockProducerAction, BlockProducerActionWithMeta, - BlockProducerCurrentState, -}; - -pub fn block_producer_effects( - store: &mut Store, - action: BlockProducerActionWithMeta, -) { - let (action, meta) = action.split(); - - match action { - BlockProducerAction::VrfEvaluator(a) => { - // TODO: does the order matter? can this clone be avoided? - let has_won_slot = match &a { - BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { - vrf_output, - .. - } => { - matches!(vrf_output, vrf::VrfEvaluationOutput::SlotWon(_)) - } - _ => false, - }; - a.effects(&meta, store); - if has_won_slot { - store.dispatch(BlockProducerAction::WonSlotSearch); - } - } - BlockProducerAction::BestTipUpdate { best_tip } => { - let global_slot = best_tip - .consensus_state() - .curr_global_slot_since_hard_fork - .clone(); - - let (best_tip_epoch, best_tip_slot) = to_epoch_and_slot(&global_slot); - let root_block_epoch = if let Some(root_block) = - store.state().transition_frontier.root() - { - let root_block_global_slot = root_block.curr_global_slot_since_hard_fork(); - to_epoch_and_slot(root_block_global_slot).0 - } else { - bug_condition!("Expected to find a block at the root of the transition frontier but there was none"); - best_tip_epoch.saturating_sub(1) - }; - let next_epoch_first_slot = next_epoch_first_slot(&global_slot); - let current_epoch = store.state().current_epoch(); - let current_slot = store.state().current_slot(); - - store.dispatch(BlockProducerVrfEvaluatorAction::InitializeEvaluator { - best_tip: best_tip.clone(), - }); - - // None if the evaluator is not evaluating - let currenty_evaluated_epoch = store - .state() - .block_producer - .vrf_evaluator() - .and_then(|vrf_evaluator| vrf_evaluator.currently_evaluated_epoch()); - - if let Some(currently_evaluated_epoch) = currenty_evaluated_epoch { - // if we receive a block with higher epoch than the current one, interrupt the evaluation - if currently_evaluated_epoch < best_tip_epoch { - store.dispatch(BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { - reason: InterruptReason::BestTipWithHigherEpoch, - }); - } - } - - let is_next_epoch_seed_finalized = if let Some(current_slot) = current_slot { - !in_seed_update_range(current_slot, best_tip.constants()) - } else { - false - }; - - store.dispatch(BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { - current_epoch, - is_next_epoch_seed_finalized, - root_block_epoch, - best_tip_epoch, - best_tip_slot, - best_tip_global_slot: best_tip.global_slot(), - next_epoch_first_slot, - staking_epoch_data: Box::new(best_tip.consensus_state().staking_epoch_data.clone()), - next_epoch_data: Box::new(best_tip.consensus_state().next_epoch_data.clone()), - }); - - if let Some(reason) = store - .state() - .block_producer - .with(None, |bp| bp.current.won_slot_should_discard(&best_tip)) - { - store.dispatch(BlockProducerAction::WonSlotDiscard { reason }); - } else { - store.dispatch(BlockProducerAction::WonSlotSearch); - } - } - BlockProducerAction::WonSlotSearch => { - if let Some(won_slot) = store.state().block_producer.with(None, |bp| { - let best_tip = store.state().transition_frontier.best_tip()?; - let cur_global_slot = store.state().cur_global_slot()?; - bp.vrf_evaluator.next_won_slot(cur_global_slot, best_tip) - }) { - store.dispatch(BlockProducerAction::WonSlot { won_slot }); - } - } - BlockProducerAction::WonSlot { won_slot } => { - if let Some(stats) = store.service.stats() { - stats.block_producer().scheduled(meta.time(), &won_slot); - } - if !store.dispatch(BlockProducerAction::WonSlotWait) { - store.dispatch(BlockProducerAction::WonSlotProduceInit); - } - } - BlockProducerAction::WonSlotWait => {} - BlockProducerAction::WonSlotProduceInit => { - store.dispatch(BlockProducerAction::WonSlotTransactionsGet); - } - BlockProducerAction::WonSlotTransactionsGet => { - store.dispatch(TransactionPoolAction::CollectTransactionsByFee); - } - BlockProducerAction::WonSlotTransactionsSuccess { .. } => { - store.dispatch(BlockProducerAction::StagedLedgerDiffCreateInit); - } - BlockProducerAction::StagedLedgerDiffCreateInit => { - if let Some(stats) = store.service.stats() { - stats - .block_producer() - .staged_ledger_diff_create_start(meta.time()); - } - let state = store.state.get(); - let Some((won_slot, pred_block, producer, coinbase_receiver)) = None.or_else(|| { - let pred_block = state.block_producer.current_parent_chain()?.last()?; - let won_slot = state.block_producer.current_won_slot()?; - let config = state.block_producer.config()?; - Some(( - won_slot, - pred_block, - &config.pub_key, - config.coinbase_receiver(), - )) - }) else { - return; - }; - - let completed_snarks = state - .snark_pool - .completed_snarks_iter() - .map(|snark| (snark.job_id(), snark.clone())) - .collect(); - // TODO(binier) - let supercharge_coinbase = true; - // We want to know if this is a new epoch to decide which staking ledger to use - // (staking epoch ledger or next epoch ledger). - let is_new_epoch = won_slot.epoch() - > pred_block - .header() - .protocol_state - .body - .consensus_state - .epoch_count - .as_u32(); - - let transactions_by_fee = state.block_producer.pending_transactions(); - - store.dispatch(LedgerWriteAction::Init { - request: LedgerWriteRequest::StagedLedgerDiffCreate { - pred_block: pred_block.clone(), - global_slot_since_genesis: won_slot - .global_slot_since_genesis(pred_block.global_slot_diff()), - is_new_epoch, - producer: producer.clone(), - delegator: won_slot.delegator.0.clone(), - coinbase_receiver: coinbase_receiver.clone(), - completed_snarks, - supercharge_coinbase, - transactions_by_fee, - }, - on_init: redux::callback!( - on_staged_ledger_diff_create_init(_request: LedgerWriteRequest) -> crate::Action { - BlockProducerAction::StagedLedgerDiffCreatePending - } - ), - }); - } - BlockProducerAction::StagedLedgerDiffCreatePending => {} - BlockProducerAction::StagedLedgerDiffCreateSuccess { .. } => { - if let Some(stats) = store.service.stats() { - stats - .block_producer() - .staged_ledger_diff_create_end(meta.time()); - } - store.dispatch(BlockProducerAction::BlockUnprovenBuild); - } - BlockProducerAction::BlockUnprovenBuild => { - if let Some(stats) = store.service.stats() { - let bp = &store.state.get().block_producer; - if let Some((block_hash, block)) = bp.with(None, |bp| match &bp.current { - BlockProducerCurrentState::BlockUnprovenBuilt { - block, block_hash, .. - } => Some((block_hash, block)), - _ => None, - }) { - stats - .block_producer() - .produced(meta.time(), block_hash, block); - } - } - - store.dispatch(BlockProducerAction::BlockProveInit); - } - BlockProducerAction::BlockProveInit => { - let service = &mut store.service; - - if let Some(stats) = service.stats() { - stats.block_producer().proof_create_start(meta.time()); - } - let Some((block_hash, input)) = store.state.get().block_producer.with(None, |bp| { - let BlockProducerCurrentState::BlockUnprovenBuilt { - won_slot, - chain, - emitted_ledger_proof, - pending_coinbase_update, - pending_coinbase_witness, - stake_proof_sparse_ledger, - block, - block_hash, - .. - } = &bp.current - else { - return None; - }; - - let pred_block = chain.last()?; - - let producer_public_key = block - .protocol_state - .body - .consensus_state - .block_creator - .clone(); - - let input = Box::new(ProverExtendBlockchainInputStableV2 { - chain: BlockchainSnarkBlockchainStableV2 { - state: pred_block.header().protocol_state.clone(), - proof: pred_block.header().protocol_state_proof.clone(), - }, - next_state: block.protocol_state.clone(), - block: MinaStateSnarkTransitionValueStableV2 { - blockchain_state: block.protocol_state.body.blockchain_state.clone(), - consensus_transition: block - .protocol_state - .body - .consensus_state - .curr_global_slot_since_hard_fork - .slot_number - .clone(), - pending_coinbase_update: pending_coinbase_update.clone(), - }, - ledger_proof: emitted_ledger_proof.as_ref().map(|proof| (**proof).clone()), - prover_state: ConsensusStakeProofStableV2 { - delegator: won_slot.delegator.1.into(), - delegator_pk: won_slot.delegator.0.clone(), - coinbase_receiver_pk: block - .protocol_state - .body - .consensus_state - .coinbase_receiver - .clone(), - ledger: stake_proof_sparse_ledger.clone(), - // it is replaced with correct keys in the service. - producer_private_key: AccountSecretKey::genesis_producer().into(), - producer_public_key, - }, - pending_coinbase: pending_coinbase_witness.clone(), - }); - Some((block_hash.clone(), input)) - }) else { - return; - }; - service.prove(block_hash, input); - store.dispatch(BlockProducerAction::BlockProvePending); - } - BlockProducerAction::BlockProvePending => {} - BlockProducerAction::BlockProveSuccess { .. } => { - if let Some(stats) = store.service.stats() { - stats.block_producer().proof_create_end(meta.time()); - } - store.dispatch(BlockProducerAction::BlockProduced); - } - BlockProducerAction::BlockProduced => { - store.dispatch(BlockProducerAction::BlockInject); - } - BlockProducerAction::BlockInject => { - let Some((best_tip, root_block, blocks_inbetween)) = None.or_else(|| { - let (best_tip, chain) = store.state().block_producer.produced_block_with_chain()?; - let mut iter = chain.iter(); - let root_block = iter.next()?.block_with_hash(); - let blocks_inbetween = iter.map(|b| b.hash().clone()).collect(); - Some((best_tip.clone(), root_block.clone(), blocks_inbetween)) - }) else { - return; - }; - - let previous_root_snarked_ledger_hash = store - .state() - .transition_frontier - .root() - .map(|b| b.snarked_ledger_hash().clone()); - - if store.dispatch(TransitionFrontierSyncAction::BestTipUpdate { - previous_root_snarked_ledger_hash, - best_tip: best_tip.clone(), - root_block, - blocks_inbetween, - }) { - store.dispatch(BlockProducerAction::BlockInjected); - } - } - BlockProducerAction::BlockInjected => { - store.dispatch(BlockProducerAction::WonSlotSearch); - } - BlockProducerAction::WonSlotDiscard { reason } => { - if let Some(stats) = store.service.stats() { - stats.block_producer().discarded(meta.time(), reason); - } - store.dispatch(BlockProducerAction::WonSlotSearch); - } - } -} diff --git a/node/src/block_producer/block_producer_event.rs b/node/src/block_producer/block_producer_event.rs index c31598f27c..e1d6f4424f 100644 --- a/node/src/block_producer/block_producer_event.rs +++ b/node/src/block_producer/block_producer_event.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use mina_p2p_messages::v2::{MinaBaseProofStableV2, StateHash}; use serde::{Deserialize, Serialize}; @@ -6,7 +8,7 @@ pub use super::vrf_evaluator::BlockProducerVrfEvaluatorEvent; #[derive(derive_more::From, Serialize, Deserialize, Debug, Clone)] pub enum BlockProducerEvent { VrfEvaluator(BlockProducerVrfEvaluatorEvent), - BlockProve(StateHash, Result, String>), + BlockProve(StateHash, Result, String>), } impl std::fmt::Display for BlockProducerEvent { diff --git a/node/src/block_producer/block_producer_reducer.rs b/node/src/block_producer/block_producer_reducer.rs index d51596312f..fb47032ba5 100644 --- a/node/src/block_producer/block_producer_reducer.rs +++ b/node/src/block_producer/block_producer_reducer.rs @@ -1,148 +1,190 @@ use ledger::scan_state::currency::{Amount, Signed}; -use mina_p2p_messages::{ - list::List, - v2::{ - ConsensusProofOfStakeDataConsensusStateValueStableV2, - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, - ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1, - ConsensusVrfOutputTruncatedStableV1, LedgerProofProdStableV2, - MinaBaseEpochLedgerValueStableV1, MinaStateBlockchainStateValueStableV2, - MinaStateBlockchainStateValueStableV2LedgerProofStatement, - MinaStateProtocolStateBodyValueStableV2, MinaStateProtocolStateValueStableV2, - StagedLedgerDiffBodyStableV1, StateBodyHash, StateHash, UnsignedExtendedUInt32StableV1, - }, +use mina_p2p_messages::{list::List, v2}; +use openmina_core::{ + block::ArcBlockWithHash, consensus::ConsensusConstants, constants::constraint_constants, }; -use openmina_core::constants::constraint_constants; use openmina_core::{ - block::AppliedBlock, + bug_condition, consensus::{ - global_sub_window, grace_period_end, in_same_checkpoint_window, in_seed_update_range, - relative_sub_window, + global_sub_window, in_same_checkpoint_window, in_seed_update_range, relative_sub_window, }, }; +use p2p::P2pNetworkPubsubAction; +use redux::{callback, Dispatcher, Timestamp}; + +use crate::{ + transition_frontier::sync::TransitionFrontierSyncAction, Action, BlockProducerEffectfulAction, + State, Substate, TransactionPoolAction, +}; use super::{ - calc_epoch_seed, to_epoch_and_slot, BlockProducerAction, BlockProducerActionWithMetaRef, - BlockProducerCurrentState, BlockProducerEnabled, BlockProducerState, BlockWithoutProof, + calc_epoch_seed, next_epoch_first_slot, to_epoch_and_slot, + vrf_evaluator::{ + BlockProducerVrfEvaluatorAction, BlockProducerVrfEvaluatorState, InterruptReason, + }, + BlockProducerAction, BlockProducerActionWithMetaRef, BlockProducerCurrentState, + BlockProducerEnabled, BlockProducerState, BlockWithoutProof, }; impl BlockProducerState { - pub fn reducer( - &mut self, - action: BlockProducerActionWithMetaRef<'_>, - best_chain: &[AppliedBlock], - ) { - self.with_mut((), move |state| state.reducer(action, best_chain)) + pub fn reducer(state_context: Substate, action: BlockProducerActionWithMetaRef<'_>) { + BlockProducerEnabled::reducer(state_context, action); } } impl BlockProducerEnabled { - pub fn reducer( - &mut self, - action: BlockProducerActionWithMetaRef<'_>, - best_chain: &[AppliedBlock], - ) { + /// Substate is accesses from global state, because applied blocks from transition frontier are required + pub fn reducer(mut state_context: Substate, action: BlockProducerActionWithMetaRef<'_>) { let (action, meta) = action.split(); + let Ok(global_state) = state_context.get_substate_mut() else { + return; + }; + let consensus_constants = &global_state.config.consensus_constants; + + let best_chain = &global_state.transition_frontier.best_chain; + let Some(state) = global_state.block_producer.as_mut() else { + return; + }; + match action { BlockProducerAction::VrfEvaluator(action) => { - self.vrf_evaluator.reducer(meta.with_action(action)) + BlockProducerVrfEvaluatorState::reducer( + Substate::from_compatible_substate(state_context), + meta.with_action(action), + ); } BlockProducerAction::BestTipUpdate { best_tip } => { - self.injected_blocks.remove(best_tip.hash()); + state.injected_blocks.remove(best_tip.hash()); // set the genesis timestamp on the first best tip update // TODO: move/remove once we can generate the genesis block - if self.vrf_evaluator.genesis_timestamp == redux::Timestamp::ZERO { - self.vrf_evaluator.genesis_timestamp = best_tip.genesis_timestamp(); + if state.vrf_evaluator.genesis_timestamp == redux::Timestamp::ZERO { + state.vrf_evaluator.genesis_timestamp = best_tip.genesis_timestamp(); + } + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + Self::dispatch_best_tip_update(dispatcher, state, best_tip); + } + BlockProducerAction::WonSlotSearch => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if let Some(won_slot) = state.block_producer.with(None, |bp| { + let best_tip = state.transition_frontier.best_tip()?; + let cur_global_slot = state.cur_global_slot()?; + bp.vrf_evaluator.next_won_slot(cur_global_slot, best_tip) + }) { + dispatcher.push(BlockProducerAction::WonSlot { won_slot }); } } - BlockProducerAction::WonSlotSearch => {} BlockProducerAction::WonSlot { won_slot } => { - self.current = BlockProducerCurrentState::WonSlot { + state.current = BlockProducerCurrentState::WonSlot { time: meta.time(), won_slot: won_slot.clone(), }; + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::WonSlot { + won_slot: won_slot.clone(), + }); } BlockProducerAction::WonSlotDiscard { reason } => { - if let Some(won_slot) = self.current.won_slot() { - self.current = BlockProducerCurrentState::WonSlotDiscarded { + if let Some(won_slot) = state.current.won_slot() { + state.current = BlockProducerCurrentState::WonSlotDiscarded { time: meta.time(), won_slot: won_slot.clone(), - reason: reason.clone(), + reason: *reason, }; } + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::WonSlotDiscard { reason: *reason }); } BlockProducerAction::WonSlotWait => { - if let Some(won_slot) = self.current.won_slot() { - self.current = BlockProducerCurrentState::WonSlotWait { + if let Some(won_slot) = state.current.won_slot() { + state.current = BlockProducerCurrentState::WonSlotWait { time: meta.time(), won_slot: won_slot.clone(), }; } } + BlockProducerAction::WonSlotProduceInit => { + if let Some(won_slot) = state.current.won_slot() { + if let Some(chain) = best_chain.last().map(|best_tip| { + if best_tip.global_slot() == won_slot.global_slot() { + // We are producing block which replaces current best tip + // instead of extending it. + best_chain + .get(..best_chain.len().saturating_sub(1)) + .unwrap_or(&[]) + .to_vec() + } else { + best_chain.to_vec() + } + }) { + state.current = BlockProducerCurrentState::WonSlotProduceInit { + time: meta.time(), + won_slot: won_slot.clone(), + chain, + }; + }; + } + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::WonSlotTransactionsGet); + } BlockProducerAction::WonSlotTransactionsGet => { let BlockProducerCurrentState::WonSlotProduceInit { won_slot, chain, .. - } = &mut self.current + } = &mut state.current else { + bug_condition!("Invalid state for `BlockProducerAction::WonSlotTransactionsGet` expected: `BlockProducerCurrentState::WonSlotProduceInit`, found: {:?}", state.current); return; }; - self.current = BlockProducerCurrentState::WonSlotTransactionsGet { + state.current = BlockProducerCurrentState::WonSlotTransactionsGet { time: meta.time(), won_slot: won_slot.clone(), chain: chain.clone(), - } + }; + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(TransactionPoolAction::CollectTransactionsByFee); } BlockProducerAction::WonSlotTransactionsSuccess { transactions_by_fee, } => { let BlockProducerCurrentState::WonSlotTransactionsGet { won_slot, chain, .. - } = &mut self.current + } = &mut state.current else { + bug_condition!("Invalid state for `BlockProducerAction::WonSlotTransactionsSuccess` expected: `BlockProducerCurrentState::WonSlotTransactionsGet`, found: {:?}", state.current); return; }; - self.current = BlockProducerCurrentState::WonSlotTransactionsSuccess { + state.current = BlockProducerCurrentState::WonSlotTransactionsSuccess { time: meta.time(), won_slot: won_slot.clone(), chain: chain.clone(), transactions_by_fee: transactions_by_fee.clone(), - } - } - BlockProducerAction::WonSlotProduceInit => { - if let Some(won_slot) = self.current.won_slot() { - let Some(chain) = best_chain.last().map(|best_tip| { - if best_tip.global_slot() == won_slot.global_slot() { - // We are producing block which replaces current best tip - // instead of extending it. - best_chain[..(best_chain.len() - 1)].to_vec() - } else { - best_chain.to_vec() - } - }) else { - return; - }; + }; - self.current = BlockProducerCurrentState::WonSlotProduceInit { - time: meta.time(), - won_slot: won_slot.clone(), - chain, - }; - } + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::StagedLedgerDiffCreateInit); + } + BlockProducerAction::StagedLedgerDiffCreateInit => { + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateInit); } - BlockProducerAction::StagedLedgerDiffCreateInit => {} BlockProducerAction::StagedLedgerDiffCreatePending => { let BlockProducerCurrentState::WonSlotTransactionsSuccess { won_slot, chain, transactions_by_fee, .. - } = &mut self.current + } = &mut state.current else { + bug_condition!("Invalid state for `BlockProducerAction::StagedLedgerDiffCreatePending` expected: `BlockProducerCurrentState::WonSlotTransactionsSuccess`, found: {:?}", state.current); return; }; - self.current = BlockProducerCurrentState::StagedLedgerDiffCreatePending { + state.current = BlockProducerCurrentState::StagedLedgerDiffCreatePending { time: meta.time(), won_slot: won_slot.clone(), chain: std::mem::take(chain), @@ -154,11 +196,12 @@ impl BlockProducerEnabled { won_slot, chain, .. - } = &mut self.current + } = &mut state.current else { + bug_condition!("Invalid state for `BlockProducerAction::StagedLedgerDiffCreateSuccess` expected: `BlockProducerCurrentState::StagedLedgerDiffCreatePending`, found: {:?}", state.current); return; }; - self.current = BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { + state.current = BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { time: meta.time(), won_slot: won_slot.clone(), chain: std::mem::take(chain), @@ -170,303 +213,23 @@ impl BlockProducerEnabled { pending_coinbase_witness: output.pending_coinbase_witness.clone(), stake_proof_sparse_ledger: output.stake_proof_sparse_ledger.clone(), }; + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess); } BlockProducerAction::BlockUnprovenBuild => { - let BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { - won_slot, - chain, - diff, - diff_hash, - staged_ledger_hash, - emitted_ledger_proof, - pending_coinbase_update, - pending_coinbase_witness, - stake_proof_sparse_ledger, - .. - } = std::mem::take(&mut self.current) - else { - return; - }; - let Some(pred_block) = chain.last() else { - return; - }; - - let pred_consensus_state = &pred_block.header().protocol_state.body.consensus_state; - let pred_blockchain_state = - &pred_block.header().protocol_state.body.blockchain_state; - - let genesis_ledger_hash = &pred_blockchain_state.genesis_ledger_hash; - - let block_timestamp = won_slot.timestamp(); - let pred_global_slot = pred_consensus_state - .curr_global_slot_since_hard_fork - .clone(); - let curr_global_slot_since_hard_fork = won_slot.global_slot.clone(); - let global_slot_since_genesis = - won_slot.global_slot_since_genesis(pred_block.global_slot_diff()); - let (pred_epoch, _) = to_epoch_and_slot(&pred_global_slot); - let (next_epoch, next_slot) = to_epoch_and_slot(&curr_global_slot_since_hard_fork); - let has_ancestor_in_same_checkpoint_window = - in_same_checkpoint_window(&pred_global_slot, &curr_global_slot_since_hard_fork); - - let block_stake_winner = won_slot.delegator.0.clone(); - let vrf_truncated_output: ConsensusVrfOutputTruncatedStableV1 = - (*won_slot.vrf_output).clone().into(); - let vrf_hash = won_slot.vrf_output.hash(); - let block_creator = self.config.pub_key.clone(); - let coinbase_receiver = self.config.coinbase_receiver().clone(); - let proposed_protocol_version_opt = self.config.proposed_protocol_version.clone(); - - let ledger_proof_statement = ledger_proof_statement_from_emitted_proof( - emitted_ledger_proof.as_deref(), - &pred_blockchain_state.ledger_proof_statement, - ); - - let supply_increase = emitted_ledger_proof.as_ref().map_or(Signed::zero(), |v| { - Signed::from(&v.statement.supply_increase) - }); - - let total_currency = { - let (amount, overflowed) = - Amount::from(pred_consensus_state.total_currency.clone()) - .add_signed_flagged(supply_increase); - if overflowed { - todo!("total_currency overflowed"); - } - amount - }; - - let (staking_epoch_data, next_epoch_data, epoch_count) = { - let next_staking_ledger = - if pred_block.snarked_ledger_hash() == genesis_ledger_hash { - pred_consensus_state.next_epoch_data.ledger.clone() - } else { - MinaBaseEpochLedgerValueStableV1 { - hash: pred_block.snarked_ledger_hash().clone(), - total_currency: (&total_currency).into(), - } - }; - let (staking_data, next_data, epoch_count) = if next_epoch > pred_epoch { - let staking_data = - next_to_staking_epoch_data(&pred_consensus_state.next_epoch_data); - let next_data = - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { - seed: pred_consensus_state.next_epoch_data.seed.clone(), - ledger: next_staking_ledger, - start_checkpoint: pred_block.hash().clone(), - // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *) - lock_checkpoint: StateHash::zero(), - epoch_length: UnsignedExtendedUInt32StableV1(1.into()), - }; - let epoch_count = UnsignedExtendedUInt32StableV1( - (pred_consensus_state.epoch_count.as_u32() + 1).into(), - ); - (staking_data, next_data, epoch_count) - } else { - assert_eq!(pred_epoch, next_epoch); - let mut next_data = pred_consensus_state.next_epoch_data.clone(); - next_data.epoch_length = UnsignedExtendedUInt32StableV1( - (next_data.epoch_length.as_u32() + 1).into(), - ); - ( - pred_consensus_state.staking_epoch_data.clone(), - next_data, - pred_consensus_state.epoch_count, - ) - }; + state.reduce_block_unproved_build(consensus_constants, meta.time()); - let next_data = if in_seed_update_range(next_slot, pred_block.constants()) { - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { - seed: calc_epoch_seed(&next_data.seed, vrf_hash), - lock_checkpoint: pred_block.hash().clone(), - ..next_data - } - } else { - next_data - }; - - (staking_data, next_data, epoch_count) - }; - - let (min_window_density, sub_window_densities) = { - // TODO(binier): when should this be false? - let incr_window = true; - let pred_sub_window_densities = &pred_consensus_state.sub_window_densities; - - let pred_global_sub_window = - global_sub_window(&pred_global_slot, pred_block.constants()); - let next_global_sub_window = global_sub_window( - &curr_global_slot_since_hard_fork, - pred_block.constants(), - ); - - let pred_relative_sub_window = relative_sub_window(pred_global_sub_window); - let next_relative_sub_window = relative_sub_window(next_global_sub_window); - - let is_same_global_sub_window = - pred_global_sub_window == next_global_sub_window; - let are_windows_overlapping = pred_global_sub_window - + constraint_constants().sub_windows_per_window as u32 - >= next_global_sub_window; - - let current_sub_window_densities = pred_sub_window_densities - .iter() - .enumerate() - .map(|(i, density)| (i as u32, density.as_u32())) - .map(|(i, density)| { - let gt_pred_sub_window = i > pred_relative_sub_window; - let lt_next_sub_window = i < next_relative_sub_window; - let within_range = - if pred_relative_sub_window < next_relative_sub_window { - gt_pred_sub_window && lt_next_sub_window - } else { - gt_pred_sub_window || lt_next_sub_window - }; - if is_same_global_sub_window || are_windows_overlapping && !within_range - { - density - } else { - 0 - } - }) - .collect::>(); - - let grace_period_end = grace_period_end(pred_block.constants()); - let min_window_density = if is_same_global_sub_window - || curr_global_slot_since_hard_fork.slot_number.as_u32() < grace_period_end - { - pred_consensus_state.min_window_density - } else { - let cur_density = current_sub_window_densities.iter().sum(); - let min_density = pred_consensus_state - .min_window_density - .as_u32() - .min(cur_density); - UnsignedExtendedUInt32StableV1(min_density.into()) - }; - - let next_sub_window_densities = current_sub_window_densities - .into_iter() - .enumerate() - .map(|(i, density)| (i as u32, density)) - .map(|(i, density)| { - let is_next_sub_window = i == next_relative_sub_window; - if is_next_sub_window { - let density = if is_same_global_sub_window { - density - } else { - 0 - }; - if incr_window { - density + 1 - } else { - density - } - } else { - density - } - }) - .map(|v| UnsignedExtendedUInt32StableV1(v.into())) - .collect(); - - (min_window_density, next_sub_window_densities) - }; - - let consensus_state = ConsensusProofOfStakeDataConsensusStateValueStableV2 { - blockchain_length: UnsignedExtendedUInt32StableV1( - (pred_block.height() + 1).into(), - ), - epoch_count, - min_window_density, - sub_window_densities, - last_vrf_output: vrf_truncated_output, - total_currency: (&total_currency).into(), - curr_global_slot_since_hard_fork, - global_slot_since_genesis, - staking_epoch_data, - next_epoch_data, - has_ancestor_in_same_checkpoint_window, - block_stake_winner, - block_creator, - coinbase_receiver, - // TODO(binier): Staged_ledger.can_apply_supercharged_coinbase_exn - supercharge_coinbase: constraint_constants().supercharged_coinbase_factor != 0, - }; - - let protocol_state = MinaStateProtocolStateValueStableV2 { - previous_state_hash: pred_block.hash().clone(), - body: MinaStateProtocolStateBodyValueStableV2 { - genesis_state_hash: if pred_block.is_genesis() { - pred_block.hash().clone() - } else { - pred_block - .header() - .protocol_state - .body - .genesis_state_hash - .clone() - }, - constants: pred_block.header().protocol_state.body.constants.clone(), - blockchain_state: MinaStateBlockchainStateValueStableV2 { - staged_ledger_hash: staged_ledger_hash.clone(), - genesis_ledger_hash: genesis_ledger_hash.clone(), - ledger_proof_statement, - timestamp: block_timestamp, - body_reference: diff_hash.clone(), - }, - consensus_state, - }, - }; - - let chain_proof_len = pred_block.constants().delta.as_u32() as usize; - let delta_block_chain_proof = match chain_proof_len { - 0 => (pred_block.hash().clone(), List::new()), - chain_proof_len => { - // TODO(binier): test - let mut iter = chain.iter().rev().take(chain_proof_len + 1).rev(); - if let Some(first_block) = iter.next() { - let first_hash = first_block.hash().clone(); - let body_hashes = iter - .filter_map(|b| b.header().protocol_state.body.try_hash().ok()) // TODO: Handle error ? - .map(StateBodyHash::from) - .collect(); - (first_hash, body_hashes) - } else { - // TODO: test this as well - // If the chain is empty, return the same as when chain_proof_len is 0 - (pred_block.hash().clone(), List::new()) - } - } - }; - - let block = BlockWithoutProof { - protocol_state, - delta_block_chain_proof, - current_protocol_version: pred_block.header().current_protocol_version.clone(), - proposed_protocol_version_opt, - body: StagedLedgerDiffBodyStableV1 { - staged_ledger_diff: diff.clone(), - }, - }; - let Ok(block_hash) = block.protocol_state.try_hash() else { - openmina_core::log::inner::error!("Invalid protocol state"); - return; - }; - - self.current = BlockProducerCurrentState::BlockUnprovenBuilt { - time: meta.time(), - won_slot, - chain, - emitted_ledger_proof, - pending_coinbase_update, - pending_coinbase_witness, - stake_proof_sparse_ledger, - block, - block_hash, - } + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::BlockUnprovenBuild); + } + BlockProducerAction::BlockProveInit => { + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::BlockProveInit); } - BlockProducerAction::BlockProveInit => {} BlockProducerAction::BlockProvePending => { + let current_state = std::mem::take(&mut state.current); + if let BlockProducerCurrentState::BlockUnprovenBuilt { won_slot, chain, @@ -477,9 +240,9 @@ impl BlockProducerEnabled { block, block_hash, .. - } = std::mem::take(&mut self.current) + } = current_state { - self.current = BlockProducerCurrentState::BlockProvePending { + state.current = BlockProducerCurrentState::BlockProvePending { time: meta.time(), won_slot, chain, @@ -490,18 +253,22 @@ impl BlockProducerEnabled { block, block_hash, }; + } else { + bug_condition!("Invalid state for `BlockProducerAction::BlockProvePending` expected: `BlockProducerCurrentState::BlockUnprovenBuilt`, found: {:?}", current_state); } } BlockProducerAction::BlockProveSuccess { proof } => { + let current_state = std::mem::take(&mut state.current); + if let BlockProducerCurrentState::BlockProvePending { won_slot, chain, block, block_hash, .. - } = std::mem::take(&mut self.current) + } = current_state { - self.current = BlockProducerCurrentState::BlockProveSuccess { + state.current = BlockProducerCurrentState::BlockProveSuccess { time: meta.time(), won_slot, chain, @@ -509,9 +276,16 @@ impl BlockProducerEnabled { block_hash, proof: proof.clone(), }; + } else { + bug_condition!("Invalid state for `BlockProducerAction::BlockProveSuccess` expected: `BlockProducerCurrentState::BlockProvePending`, found: {:?}", current_state); } + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::BlockProveSuccess); } BlockProducerAction::BlockProduced => { + let current_state = std::mem::take(&mut state.current); + if let BlockProducerCurrentState::BlockProveSuccess { won_slot, chain, @@ -519,42 +293,506 @@ impl BlockProducerEnabled { block_hash, proof, .. - } = std::mem::take(&mut self.current) + } = current_state { - self.current = BlockProducerCurrentState::Produced { + state.current = BlockProducerCurrentState::Produced { time: meta.time(), won_slot, chain, - block: block.with_hash_and_proof(block_hash, *proof), + block: block.with_hash_and_proof(block_hash, proof), }; + } else { + bug_condition!("Invalid state for `BlockProducerAction::BlockProduced` expected: `BlockProducerCurrentState::BlockProveSuccess`, found: {:?}", current_state); } + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::BlockInject); + } + BlockProducerAction::BlockInject => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + let Some((best_tip, root_block, blocks_inbetween)) = None.or_else(|| { + let (best_tip, chain) = state.block_producer.produced_block_with_chain()?; + let mut iter = chain.iter(); + let root_block = iter.next()?.block_with_hash(); + let blocks_inbetween = iter.map(|b| b.hash().clone()).collect(); + Some((best_tip.clone(), root_block.clone(), blocks_inbetween)) + }) else { + bug_condition!("Invalid state for `BlockProducerAction::BlockInject`: did not find best_tip/root_block in block producer"); + return; + }; + + let previous_root_snarked_ledger_hash = state + .transition_frontier + .root() + .map(|b| b.snarked_ledger_hash().clone()); + + dispatcher.push(TransitionFrontierSyncAction::BestTipUpdate { + previous_root_snarked_ledger_hash, + best_tip: best_tip.clone(), + root_block, + blocks_inbetween, + on_success: Some(callback!( + on_transition_frontier_sync_best_tip_update(_p: ()) -> crate::Action{ + BlockProducerAction::BlockInjected + } + )), + }); } - BlockProducerAction::BlockInject => {} BlockProducerAction::BlockInjected => { if let BlockProducerCurrentState::Produced { won_slot, chain, block, .. - } = &mut self.current + } = &mut state.current { - self.injected_blocks.insert(block.hash().clone()); - self.current = BlockProducerCurrentState::Injected { + state.injected_blocks.insert(block.hash().clone()); + state.current = BlockProducerCurrentState::Injected { time: meta.time(), won_slot: won_slot.clone(), chain: std::mem::take(chain), block: block.clone(), }; + } else { + bug_condition!("Invalid state for `BlockProducerAction::BlockInjected` expected: `BlockProducerCurrentState::Produced`, found: {:?}", state.current); + } + + let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); + + #[cfg(feature = "p2p-libp2p")] + broadcast_injected_block(global_state, dispatcher); + + dispatcher.push(BlockProducerAction::WonSlotSearch); + } + } + } + + fn reduce_block_unproved_build( + &mut self, + consensus_constants: &ConsensusConstants, + time: Timestamp, + ) { + let current_state = std::mem::take(&mut self.current); + + let BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { + won_slot, + chain, + diff, + diff_hash, + staged_ledger_hash, + emitted_ledger_proof, + pending_coinbase_update, + pending_coinbase_witness, + stake_proof_sparse_ledger, + .. + } = current_state + else { + bug_condition!("Invalid state for `BlockProducerAction::BlockUnprovenBuild` expected: `BlockProducerCurrentState::StagedLedgerDiffCreateSuccess`, found: {:?}", current_state); + return; + }; + let Some(pred_block) = chain.last() else { + bug_condition!("Invalid state for `BlockProducerAction::BlockUnprovenBuild`: did not find predecessor block"); + return; + }; + + let pred_consensus_state = &pred_block.header().protocol_state.body.consensus_state; + let pred_blockchain_state = &pred_block.header().protocol_state.body.blockchain_state; + + let genesis_ledger_hash = &pred_blockchain_state.genesis_ledger_hash; + + let block_timestamp = won_slot.timestamp(); + let pred_global_slot = pred_consensus_state + .curr_global_slot_since_hard_fork + .clone(); + let curr_global_slot_since_hard_fork = won_slot.global_slot.clone(); + let global_slot_since_genesis = + won_slot.global_slot_since_genesis(pred_block.global_slot_diff()); + let (pred_epoch, _) = to_epoch_and_slot(&pred_global_slot); + let (next_epoch, next_slot) = to_epoch_and_slot(&curr_global_slot_since_hard_fork); + let has_ancestor_in_same_checkpoint_window = + in_same_checkpoint_window(&pred_global_slot, &curr_global_slot_since_hard_fork); + + let block_stake_winner = won_slot.delegator.0.clone(); + let vrf_truncated_output: v2::ConsensusVrfOutputTruncatedStableV1 = + (*won_slot.vrf_output).clone().into(); + let vrf_hash = won_slot.vrf_output.hash(); + let block_creator = self.config.pub_key.clone(); + let coinbase_receiver = self.config.coinbase_receiver().clone(); + let proposed_protocol_version_opt = self.config.proposed_protocol_version.clone(); + + let ledger_proof_statement = ledger_proof_statement_from_emitted_proof( + emitted_ledger_proof.as_deref(), + &pred_blockchain_state.ledger_proof_statement, + ); + + let supply_increase = emitted_ledger_proof.as_ref().map_or(Signed::zero(), |v| { + Signed::from(&v.statement.supply_increase) + }); + + let total_currency = { + let (amount, overflowed) = Amount::from(pred_consensus_state.total_currency.clone()) + .add_signed_flagged(supply_increase); + if overflowed { + todo!("total_currency overflowed"); + } + amount + }; + + let (staking_epoch_data, next_epoch_data, epoch_count) = { + let next_staking_ledger = if pred_block.snarked_ledger_hash() == genesis_ledger_hash { + pred_consensus_state.next_epoch_data.ledger.clone() + } else { + v2::MinaBaseEpochLedgerValueStableV1 { + hash: pred_block.snarked_ledger_hash().clone(), + total_currency: (&total_currency).into(), + } + }; + let (staking_data, next_data, epoch_count) = if next_epoch > pred_epoch { + let staking_data = + next_to_staking_epoch_data(&pred_consensus_state.next_epoch_data); + let next_data = + v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { + seed: pred_consensus_state.next_epoch_data.seed.clone(), + ledger: next_staking_ledger, + start_checkpoint: pred_block.hash().clone(), + // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *) + lock_checkpoint: v2::StateHash::zero(), + epoch_length: v2::UnsignedExtendedUInt32StableV1(1.into()), + }; + let epoch_count = v2::UnsignedExtendedUInt32StableV1( + (pred_consensus_state + .epoch_count + .as_u32() + .checked_add(1) + .expect("overflow")) + .into(), + ); + (staking_data, next_data, epoch_count) + } else { + assert_eq!(pred_epoch, next_epoch); + let mut next_data = pred_consensus_state.next_epoch_data.clone(); + next_data.epoch_length = v2::UnsignedExtendedUInt32StableV1( + (next_data + .epoch_length + .as_u32() + .checked_add(1) + .expect("overflow")) + .into(), + ); + ( + pred_consensus_state.staking_epoch_data.clone(), + next_data, + pred_consensus_state.epoch_count, + ) + }; + + let next_data = if in_seed_update_range(next_slot, pred_block.constants()) { + v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { + seed: calc_epoch_seed(&next_data.seed, vrf_hash), + lock_checkpoint: pred_block.hash().clone(), + ..next_data + } + } else { + next_data + }; + + (staking_data, next_data, epoch_count) + }; + + let (min_window_density, sub_window_densities) = { + // TODO(binier): when should this be false? + // https://github.com/MinaProtocol/mina/blob/4aac38814556b9641ffbdfaef19b38ab7980011b/src/lib/consensus/proof_of_stake.ml#L2864 + let incr_window = true; + let pred_sub_window_densities = &pred_consensus_state.sub_window_densities; + + let pred_global_sub_window = + global_sub_window(&pred_global_slot, pred_block.constants()); + let next_global_sub_window = + global_sub_window(&curr_global_slot_since_hard_fork, pred_block.constants()); + + let pred_relative_sub_window = relative_sub_window(pred_global_sub_window); + let next_relative_sub_window = relative_sub_window(next_global_sub_window); + + let is_same_global_sub_window = pred_global_sub_window == next_global_sub_window; + let are_windows_overlapping = pred_global_sub_window + .checked_add(constraint_constants().sub_windows_per_window as u32) + .expect("overflow") + >= next_global_sub_window; + + let current_sub_window_densities = pred_sub_window_densities + .iter() + .enumerate() + .map(|(i, density)| (i as u32, density.as_u32())) + .map(|(i, density)| { + let gt_pred_sub_window = i > pred_relative_sub_window; + let lt_next_sub_window = i < next_relative_sub_window; + let within_range = if pred_relative_sub_window < next_relative_sub_window { + gt_pred_sub_window && lt_next_sub_window + } else { + gt_pred_sub_window || lt_next_sub_window + }; + if is_same_global_sub_window || (are_windows_overlapping && !within_range) { + density + } else { + 0 + } + }) + .collect::>(); + + let grace_period_end = consensus_constants.grace_period_end; + let min_window_density = if is_same_global_sub_window + || curr_global_slot_since_hard_fork.slot_number.as_u32() < grace_period_end + { + pred_consensus_state.min_window_density + } else { + let cur_density = current_sub_window_densities.iter().sum(); + let min_density = pred_consensus_state + .min_window_density + .as_u32() + .min(cur_density); + v2::UnsignedExtendedUInt32StableV1(min_density.into()) + }; + + let next_sub_window_densities = current_sub_window_densities + .into_iter() + .enumerate() + .map(|(i, density)| (i as u32, density)) + .map(|(i, density)| { + let is_next_sub_window = i == next_relative_sub_window; + if is_next_sub_window { + let density = if is_same_global_sub_window { + density + } else { + 0 + }; + if incr_window { + density.saturating_add(1) + } else { + density + } + } else { + density + } + }) + .map(|v| v2::UnsignedExtendedUInt32StableV1(v.into())) + .collect(); + + (min_window_density, next_sub_window_densities) + }; + + let supercharge_coinbase = can_apply_supercharged_coinbase( + &block_stake_winner, + &stake_proof_sparse_ledger, + &global_slot_since_genesis, + ); + let consensus_state = v2::ConsensusProofOfStakeDataConsensusStateValueStableV2 { + blockchain_length: v2::UnsignedExtendedUInt32StableV1( + (pred_block.height().checked_add(1).expect("overflow")).into(), + ), + epoch_count, + min_window_density, + sub_window_densities, + last_vrf_output: vrf_truncated_output, + total_currency: (&total_currency).into(), + curr_global_slot_since_hard_fork, + global_slot_since_genesis, + staking_epoch_data, + next_epoch_data, + has_ancestor_in_same_checkpoint_window, + block_stake_winner, + block_creator, + coinbase_receiver, + supercharge_coinbase, + }; + + let protocol_state = v2::MinaStateProtocolStateValueStableV2 { + previous_state_hash: pred_block.hash().clone(), + body: v2::MinaStateProtocolStateBodyValueStableV2 { + genesis_state_hash: if pred_block.is_genesis() { + pred_block.hash().clone() + } else { + pred_block + .header() + .protocol_state + .body + .genesis_state_hash + .clone() + }, + constants: pred_block.header().protocol_state.body.constants.clone(), + blockchain_state: v2::MinaStateBlockchainStateValueStableV2 { + staged_ledger_hash: staged_ledger_hash.clone(), + genesis_ledger_hash: genesis_ledger_hash.clone(), + ledger_proof_statement, + timestamp: block_timestamp, + body_reference: diff_hash.clone(), + }, + consensus_state, + }, + }; + + let chain_proof_len = pred_block.constants().delta.as_u32() as usize; + let delta_block_chain_proof = match chain_proof_len { + 0 => (pred_block.hash().clone(), List::new()), + chain_proof_len => { + // TODO(binier): test + let mut iter = chain + .iter() + .rev() + .take(chain_proof_len.saturating_add(1)) + .rev(); + if let Some(first_block) = iter.next() { + let first_hash = first_block.hash().clone(); + let body_hashes = iter + .filter_map(|b| b.header().protocol_state.body.try_hash().ok()) // TODO: Handle error ? + .map(v2::StateBodyHash::from) + .collect(); + (first_hash, body_hashes) + } else { + // TODO: test this as well + // If the chain is empty, return the same as when chain_proof_len is 0 + (pred_block.hash().clone(), List::new()) } } + }; + + let block = BlockWithoutProof { + protocol_state, + delta_block_chain_proof, + current_protocol_version: pred_block.header().current_protocol_version.clone(), + proposed_protocol_version_opt, + body: v2::StagedLedgerDiffBodyStableV1 { + staged_ledger_diff: diff.clone(), + }, + }; + let Ok(block_hash) = block.protocol_state.try_hash() else { + openmina_core::log::inner::error!("Invalid protocol state"); + return; + }; + + self.current = BlockProducerCurrentState::BlockUnprovenBuilt { + time, + won_slot, + chain, + emitted_ledger_proof, + pending_coinbase_update, + pending_coinbase_witness, + stake_proof_sparse_ledger, + block, + block_hash, + }; + } + + fn dispatch_best_tip_update( + dispatcher: &mut Dispatcher, + state: &State, + best_tip: &ArcBlockWithHash, + ) { + let global_slot = best_tip + .consensus_state() + .curr_global_slot_since_hard_fork + .clone(); + + let (best_tip_epoch, best_tip_slot) = to_epoch_and_slot(&global_slot); + let root_block_epoch = if let Some(root_block) = state.transition_frontier.root() { + let root_block_global_slot = root_block.curr_global_slot_since_hard_fork(); + to_epoch_and_slot(root_block_global_slot).0 + } else { + bug_condition!("Expected to find a block at the root of the transition frontier but there was none"); + best_tip_epoch.saturating_sub(1) + }; + let next_epoch_first_slot = next_epoch_first_slot(&global_slot); + let current_epoch = state.current_epoch(); + let current_slot = state.current_slot(); + + dispatcher.push(BlockProducerVrfEvaluatorAction::InitializeEvaluator { + best_tip: best_tip.clone(), + }); + + // None if the evaluator is not evaluating + let currenty_evaluated_epoch = state + .block_producer + .vrf_evaluator() + .and_then(|vrf_evaluator| vrf_evaluator.currently_evaluated_epoch()); + + if let Some(currently_evaluated_epoch) = currenty_evaluated_epoch { + // if we receive a block with higher epoch than the current one, interrupt the evaluation + if currently_evaluated_epoch < best_tip_epoch { + dispatcher.push(BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { + reason: InterruptReason::BestTipWithHigherEpoch, + }); + } + } + + let is_next_epoch_seed_finalized = if let Some(current_slot) = current_slot { + !in_seed_update_range(current_slot, best_tip.constants()) + } else { + false + }; + + dispatcher.push(BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { + current_epoch, + is_next_epoch_seed_finalized, + root_block_epoch, + best_tip_epoch, + best_tip_slot, + best_tip_global_slot: best_tip.global_slot(), + next_epoch_first_slot, + staking_epoch_data: Box::new(best_tip.consensus_state().staking_epoch_data.clone()), + next_epoch_data: Box::new(best_tip.consensus_state().next_epoch_data.clone()), + }); + + if let Some(reason) = state + .block_producer + .with(None, |bp| bp.current.won_slot_should_discard(best_tip)) + { + dispatcher.push(BlockProducerAction::WonSlotDiscard { reason }); + } else { + dispatcher.push(BlockProducerAction::WonSlotSearch); } } } +#[cfg(feature = "p2p-libp2p")] +fn broadcast_injected_block(global_state: &State, dispatcher: &mut Dispatcher) { + use mina_p2p_messages::gossip::GossipNetMessageV2; + + let Some(block) = global_state + .block_producer + .as_ref() + .and_then(|bp| bp.current.injected_block()) + .map(|pb| pb.block.clone()) + else { + // Should be impossible, we call this immediately after having injected the block. + return; + }; + + let message = GossipNetMessageV2::NewState(block); + dispatcher.push(P2pNetworkPubsubAction::Broadcast { message }); +} + +fn can_apply_supercharged_coinbase( + block_stake_winner: &v2::NonZeroCurvePoint, + stake_proof_sparse_ledger: &v2::MinaBaseSparseLedgerBaseStableV2, + global_slot_since_genesis: &v2::MinaNumbersGlobalSlotSinceGenesisMStableV1, +) -> bool { + use ledger::staged_ledger::staged_ledger::StagedLedger; + + let winner = (block_stake_winner) + .try_into() + .expect("Public key being used cannot be invalid here"); + let epoch_ledger = (stake_proof_sparse_ledger) + .try_into() + .expect("Sparse ledger being used cannot be invalid here"); + let global_slot = (global_slot_since_genesis).into(); + + StagedLedger::can_apply_supercharged_coinbase_exn(winner, &epoch_ledger, global_slot) +} + fn next_to_staking_epoch_data( - data: &ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, -) -> ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { - ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { + data: &v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, +) -> v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { + v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { seed: data.seed.clone(), ledger: data.ledger.clone(), start_checkpoint: data.start_checkpoint.clone(), @@ -564,12 +802,12 @@ fn next_to_staking_epoch_data( } fn ledger_proof_statement_from_emitted_proof( - emitted_ledger_proof: Option<&LedgerProofProdStableV2>, - pred_proof_statement: &MinaStateBlockchainStateValueStableV2LedgerProofStatement, -) -> MinaStateBlockchainStateValueStableV2LedgerProofStatement { + emitted_ledger_proof: Option<&v2::LedgerProofProdStableV2>, + pred_proof_statement: &v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement, +) -> v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement { match emitted_ledger_proof.map(|proof| &proof.statement) { None => pred_proof_statement.clone(), - Some(stmt) => MinaStateBlockchainStateValueStableV2LedgerProofStatement { + Some(stmt) => v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement { source: stmt.source.clone(), target: stmt.target.clone(), connecting_ledger_left: stmt.connecting_ledger_left.clone(), diff --git a/node/src/block_producer/block_producer_state.rs b/node/src/block_producer/block_producer_state.rs index 015b4aa2e0..bf4ea9c2f8 100644 --- a/node/src/block_producer/block_producer_state.rs +++ b/node/src/block_producer/block_producer_state.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeSet, time::Duration}; +use std::{collections::BTreeSet, sync::Arc, time::Duration}; use ledger::scan_state::transaction_logic::valid; use mina_p2p_messages::v2; @@ -81,7 +81,7 @@ pub enum BlockProducerCurrentState { /// `protocol_state.blockchain_state.body_reference` diff_hash: v2::ConsensusBodyReferenceStableV1, staged_ledger_hash: v2::MinaBaseStagedLedgerHashStableV1, - emitted_ledger_proof: Option>, + emitted_ledger_proof: Option>, pending_coinbase_update: v2::MinaBasePendingCoinbaseUpdateStableV1, pending_coinbase_witness: v2::MinaBasePendingCoinbaseWitnessStableV2, stake_proof_sparse_ledger: v2::MinaBaseSparseLedgerBaseStableV2, @@ -91,7 +91,7 @@ pub enum BlockProducerCurrentState { won_slot: BlockProducerWonSlot, /// Chain that we are extending. chain: Vec, - emitted_ledger_proof: Option>, + emitted_ledger_proof: Option>, pending_coinbase_update: v2::MinaBasePendingCoinbaseUpdateStableV1, pending_coinbase_witness: v2::MinaBasePendingCoinbaseWitnessStableV2, stake_proof_sparse_ledger: v2::MinaBaseSparseLedgerBaseStableV2, @@ -103,7 +103,7 @@ pub enum BlockProducerCurrentState { won_slot: BlockProducerWonSlot, /// Chain that we are extending. chain: Vec, - emitted_ledger_proof: Option>, + emitted_ledger_proof: Option>, pending_coinbase_update: v2::MinaBasePendingCoinbaseUpdateStableV1, pending_coinbase_witness: v2::MinaBasePendingCoinbaseWitnessStableV2, stake_proof_sparse_ledger: v2::MinaBaseSparseLedgerBaseStableV2, @@ -117,7 +117,7 @@ pub enum BlockProducerCurrentState { chain: Vec, block: BlockWithoutProof, block_hash: v2::StateHash, - proof: Box, + proof: Arc, }, Produced { time: redux::Timestamp, @@ -135,7 +135,7 @@ pub enum BlockProducerCurrentState { }, } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Copy)] pub enum BlockProducerWonSlotDiscardReason { BestTipStakingLedgerDifferent, BestTipGlobalSlotHigher, @@ -152,20 +152,19 @@ impl BlockProducerState { })) } - #[inline(always)] - pub(super) fn with<'a, F, R: 'a>(&'a self, default: R, fun: F) -> R + pub fn with<'a, F, R: 'a>(&'a self, default: R, fun: F) -> R where F: FnOnce(&'a BlockProducerEnabled) -> R, { self.0.as_ref().map_or(default, fun) } - #[inline(always)] - pub(super) fn with_mut(&mut self, default: R, fun: F) -> R - where - F: FnOnce(&mut BlockProducerEnabled) -> R, - { - self.0.as_mut().map_or(default, fun) + pub fn as_mut(&mut self) -> Option<&mut BlockProducerEnabled> { + self.0.as_mut() + } + + pub fn as_ref(&self) -> Option<&BlockProducerEnabled> { + self.0.as_ref() } pub fn is_enabled(&self) -> bool { @@ -297,14 +296,19 @@ impl BlockProducerCurrentState { #[cfg(not(target_arch = "wasm32"))] const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(5).as_nanos() as u64; #[cfg(target_arch = "wasm32")] - const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(20).as_nanos() as u64; + const BLOCK_PRODUCTION_ESTIMATE: u64 = Duration::from_secs(18).as_nanos() as u64; let slot_interval = Duration::from_secs(3 * 60).as_nanos() as u64; match self { Self::WonSlot { won_slot, .. } | Self::WonSlotWait { won_slot, .. } => { // Make sure to only producer blocks when in the slot interval - let slot_upper_bound = won_slot.slot_time + slot_interval; - let estimated_produced_time = now + BLOCK_PRODUCTION_ESTIMATE; + let slot_upper_bound = won_slot + .slot_time + .checked_add(slot_interval) + .expect("overflow"); + let estimated_produced_time = now + .checked_add(BLOCK_PRODUCTION_ESTIMATE) + .expect("overflow"); estimated_produced_time >= won_slot.slot_time && now < slot_upper_bound } _ => false, @@ -368,6 +372,13 @@ impl BlockProducerCurrentState { } } + pub fn injected_block(&self) -> Option<&ArcBlockWithHash> { + match self { + Self::Injected { block, .. } => Some(block), + _ => None, + } + } + pub fn produced_block_with_chain(&self) -> Option<(&ArcBlockWithHash, &[AppliedBlock])> { match self { Self::Produced { chain, block, .. } => Some((block, chain)), diff --git a/node/src/block_producer/mod.rs b/node/src/block_producer/mod.rs index ba0008c70a..12467edf76 100644 --- a/node/src/block_producer/mod.rs +++ b/node/src/block_producer/mod.rs @@ -1,6 +1,8 @@ pub mod vrf_evaluator; mod block_producer_config; +use std::sync::Arc; + pub use block_producer_config::*; mod block_producer_state; @@ -14,15 +16,10 @@ pub use block_producer_actions::*; mod block_producer_reducer; -mod block_producer_effects; -pub use block_producer_effects::*; - -mod block_producer_service; -pub use block_producer_service::*; - use ledger::AccountIndex; use mina_p2p_messages::{list::List, v2}; use openmina_core::block::ArcBlockWithHash; +use poseidon::hash::params::MINA_EPOCH_SEED; use serde::{Deserialize, Serialize}; use vrf::output::VrfOutput; @@ -84,7 +81,13 @@ impl BlockProducerWonSlot { fn calculate_slot_time(genesis_timestamp: redux::Timestamp, slot: u32) -> redux::Timestamp { // FIXME: this calculation must use values from the protocol constants, // now it assumes 3 minutes blocks. - genesis_timestamp + (slot as u64) * 3 * 60 * 1_000_000_000_u64 + genesis_timestamp + .checked_add( + (slot as u64) + .checked_mul(3u64.saturating_mul(60).saturating_mul(1_000_000_000_u64)) + .expect("overflow"), + ) + .expect("overflow") } pub fn global_slot(&self) -> u32 { @@ -92,14 +95,16 @@ impl BlockProducerWonSlot { } pub fn epoch(&self) -> u32 { - self.global_slot() / self.global_slot.slots_per_epoch.as_u32() + self.global_slot() + .checked_div(self.global_slot.slots_per_epoch.as_u32()) + .expect("division by 0") } pub fn global_slot_since_genesis( &self, slot_diff: u32, ) -> v2::MinaNumbersGlobalSlotSinceGenesisMStableV1 { - let slot = self.global_slot() + slot_diff; + let slot = self.global_slot().checked_add(slot_diff).expect("overflow"); v2::MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(slot.into()) } @@ -111,7 +116,9 @@ impl BlockProducerWonSlot { } pub fn next_slot_time(&self) -> redux::Timestamp { - self.slot_time + 3 * 60 * 1_000_000_000_u64 + self.slot_time + .checked_add(3u64.saturating_mul(60).saturating_mul(1_000_000_000_u64)) + .expect("overflow") } } @@ -151,14 +158,24 @@ impl PartialOrd for BlockProducerWonSlot { } pub fn to_epoch_and_slot(global_slot: &v2::ConsensusGlobalSlotStableV1) -> (u32, u32) { - let epoch = global_slot.slot_number.as_u32() / global_slot.slots_per_epoch.as_u32(); - let slot = global_slot.slot_number.as_u32() % global_slot.slots_per_epoch.as_u32(); + let epoch = global_slot + .slot_number + .as_u32() + .checked_div(global_slot.slots_per_epoch.as_u32()) + .expect("division by 0"); + let slot = global_slot + .slot_number + .as_u32() + .checked_rem(global_slot.slots_per_epoch.as_u32()) + .expect("division by 0"); (epoch, slot) } pub fn next_epoch_first_slot(global_slot: &v2::ConsensusGlobalSlotStableV1) -> u32 { let (epoch, _) = to_epoch_and_slot(global_slot); - (epoch + 1) * global_slot.slots_per_epoch.as_u32() + (epoch.saturating_add(1)) + .checked_mul(global_slot.slots_per_epoch.as_u32()) + .expect("overflow") } // Returns the epoch number and whether it is the last slot of the epoch @@ -173,7 +190,7 @@ impl BlockWithoutProof { pub fn with_hash_and_proof( self, hash: v2::StateHash, - proof: v2::MinaBaseProofStableV2, + proof: Arc, ) -> ArcBlockWithHash { let block = v2::MinaBlockBlockStableV2 { header: v2::MinaBlockHeaderStableV2 { @@ -199,6 +216,6 @@ pub fn calc_epoch_seed( ) -> v2::EpochSeed { // TODO(adonagy): fix this unwrap let old_seed = prev_epoch_seed.to_field().unwrap(); - let new_seed = ledger::hash_with_kimchi("MinaEpochSeed", &[old_seed, vrf_hash]); + let new_seed = poseidon::hash::hash_with_kimchi(&MINA_EPOCH_SEED, &[old_seed, vrf_hash]); v2::MinaBaseEpochSeedStableV1(new_seed.into()).into() } diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_actions.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_actions.rs index 9a1afb2c6d..6a3def263b 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_actions.rs +++ b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_actions.rs @@ -78,6 +78,7 @@ pub enum BlockProducerVrfEvaluatorAction { #[action_event(level = info, fields(current_global_slot, best_tip_height))] SelectInitialSlot { current_global_slot: u32, + current_slot: u32, best_tip_slot: u32, best_tip_global_slot: u32, best_tip_epoch: u32, @@ -85,11 +86,12 @@ pub enum BlockProducerVrfEvaluatorAction { next_epoch_first_slot: u32, }, /// Starting epoch evaluation. - #[action_event(level = info, fields(best_tip_epoch, best_tip_slot, best_tip_global_slot))] + #[action_event(level = info, fields(evaluation_epoch, best_tip_epoch, best_tip_slot, best_tip_global_slot))] BeginEpochEvaluation { best_tip_slot: u32, best_tip_global_slot: u32, best_tip_epoch: u32, + evaluation_epoch: u32, staking_epoch_data: EpochData, latest_evaluated_global_slot: u32, }, @@ -133,7 +135,11 @@ impl redux::EnablingCondition for BlockProducerVrfEvaluatorAction } => state.block_producer.with(false, |this| { if this.vrf_evaluator.is_slot_requested() { if let Some(current_evaluation) = this.vrf_evaluator.current_evaluation() { - current_evaluation.latest_evaluated_slot + 1 == vrf_output.global_slot() + current_evaluation + .latest_evaluated_slot + .checked_add(1) + .expect("overflow") + == vrf_output.global_slot() && current_evaluation.epoch_data.ledger == *staking_ledger_hash } else { false diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs deleted file mode 100644 index 0127e3eb71..0000000000 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs +++ /dev/null @@ -1,238 +0,0 @@ -use redux::ActionMeta; - -use crate::block_producer::to_epoch_and_slot; -use crate::ledger::read::LedgerReadAction; -use crate::ledger::read::LedgerReadInitCallback; -use crate::ledger::read::LedgerReadRequest; -use crate::Service; -use crate::Store; - -use super::BlockProducerVrfEvaluatorAction; -use super::BlockProducerVrfEvaluatorStatus; -use super::SlotPositionInEpoch; - -impl BlockProducerVrfEvaluatorAction { - pub fn effects(self, _: &ActionMeta, store: &mut Store) { - match self { - BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input } => { - store.service.evaluate(vrf_input); - } - BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { - vrf_output, .. - } => { - if let Some(vrf_evaluator_state) = store.state().block_producer.vrf_evaluator() { - if let Some(pending_evaluation) = vrf_evaluator_state.current_evaluation() { - store.dispatch(BlockProducerVrfEvaluatorAction::CheckEpochBounds { - epoch_number: pending_evaluation.epoch_number, - latest_evaluated_global_slot: vrf_output.global_slot(), - }); - } - } - } - BlockProducerVrfEvaluatorAction::CheckEpochBounds { - latest_evaluated_global_slot, - epoch_number, - } => { - if let Some(epoch_bound) = store - .state() - .block_producer - .vrf_evaluator() - .and_then(|s| s.get_epoch_bound_from_check()) - { - match epoch_bound { - SlotPositionInEpoch::Beginning | SlotPositionInEpoch::Within => { - store.dispatch( - BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { - latest_evaluated_global_slot, - epoch_number, - }, - ); - } - SlotPositionInEpoch::End => { - store.dispatch( - BlockProducerVrfEvaluatorAction::FinishEpochEvaluation { - latest_evaluated_global_slot, - epoch_number, - }, - ); - } - } - } - } - BlockProducerVrfEvaluatorAction::InitializeEvaluator { best_tip } => { - // Note: pure function, but needs access to other parts of the state - if store.state().block_producer.vrf_evaluator().is_some() { - if best_tip.consensus_state().epoch_count.as_u32() == 0 { - store.dispatch( - BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { - previous_epoch_and_height: None, - }, - ); - } else { - let k = best_tip.constants().k.as_u32(); - let (epoch, slot) = to_epoch_and_slot( - &best_tip.consensus_state().curr_global_slot_since_hard_fork, - ); - let previous_epoch = epoch.saturating_sub(1); - let last_height = if slot < k { - let found = store - .state() - .transition_frontier - .best_chain - .iter() - .rev() - .find(|b| { - b.consensus_state().epoch_count.as_u32() == previous_epoch - }); - - if let Some(block) = found { - block.height() - } else { - Default::default() - } - } else if let Some(root_block) = store.state().transition_frontier.root() { - root_block.height() - } else { - Default::default() - }; - store.dispatch( - BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { - previous_epoch_and_height: Some((previous_epoch, last_height)), - }, - ); - } - } - } - BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { .. } => {} - BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { - root_block_epoch: _, - best_tip_global_slot, - best_tip_epoch, - best_tip_slot, - next_epoch_first_slot, - current_epoch: _, - is_next_epoch_seed_finalized: _, - staking_epoch_data: _, - next_epoch_data: _, - } => { - let vrf_evaluator_state = store.state().block_producer.vrf_evaluator_with_config(); - - if let Some((vrf_evaluator_state, config)) = vrf_evaluator_state { - if let Some(epoch_data) = vrf_evaluator_state.epoch_context().get_epoch_data() { - store.dispatch( - BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { - staking_epoch_data: epoch_data, - producer: config.pub_key.clone().into(), - best_tip_global_slot, - best_tip_epoch, - best_tip_slot, - next_epoch_first_slot, - }, - ); - } else { - // If None is returned, than we are waiting for evaluation - store.dispatch(BlockProducerVrfEvaluatorAction::WaitForNextEvaluation); - } - - store.dispatch(BlockProducerVrfEvaluatorAction::CleanupOldSlots { - best_tip_epoch, - }); - } - } - BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { .. } => { - store.dispatch(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); - } - BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction => { - let (staking_ledger_hash, producer) = - match store.state().block_producer.vrf_delegator_table_inputs() { - Some((v1, v2)) => (v1.clone(), v2.clone()), - None => return, - }; - if store.dispatch(LedgerReadAction::Init { - request: LedgerReadRequest::DelegatorTable(staking_ledger_hash, producer), - callback: LedgerReadInitCallback::None, - }) { - // TODO(binier): have pending action. - } else { - unreachable!() - } - } - BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { .. } => { - let Some(( - current_global_slot, - BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { - best_tip_epoch, - best_tip_slot, - best_tip_global_slot, - next_epoch_first_slot, - staking_epoch_data, - .. - }, - )) = None.or_else(|| { - let cur_global_slot = store.state().cur_global_slot()?; - let status = &store.state().block_producer.vrf_evaluator()?.status; - - Some((cur_global_slot, status)) - }) - else { - // error here! - return; - }; - - store.dispatch(BlockProducerVrfEvaluatorAction::SelectInitialSlot { - current_global_slot, - best_tip_epoch: *best_tip_epoch, - best_tip_slot: *best_tip_slot, - best_tip_global_slot: *best_tip_global_slot, - next_epoch_first_slot: *next_epoch_first_slot, - staking_epoch_data: staking_epoch_data.clone(), - }); - } - BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { - latest_evaluated_global_slot, - best_tip_epoch: current_epoch_number, - .. - } => { - if store.state().block_producer.vrf_evaluator().is_some() { - store.dispatch(BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { - latest_evaluated_global_slot, - epoch_number: current_epoch_number, - }); - } - } - BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { .. } => { - if let Some(vrf_evaluator_state) = store.state().block_producer.vrf_evaluator() { - if let Some(vrf_input) = vrf_evaluator_state.construct_vrf_input() { - store.dispatch(BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input }); - } - } - } - BlockProducerVrfEvaluatorAction::FinishEpochEvaluation { .. } => {} - BlockProducerVrfEvaluatorAction::WaitForNextEvaluation { .. } => {} - BlockProducerVrfEvaluatorAction::SelectInitialSlot { - best_tip_epoch: current_epoch_number, - best_tip_global_slot: current_best_tip_global_slot, - best_tip_slot: current_best_tip_slot, - staking_epoch_data, - .. - } => { - if let Some(initial_slot) = store - .state() - .block_producer - .vrf_evaluator() - .and_then(|v| v.initial_slot()) - { - store.dispatch(BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { - best_tip_epoch: current_epoch_number, - best_tip_global_slot: current_best_tip_global_slot, - best_tip_slot: current_best_tip_slot, - staking_epoch_data, - latest_evaluated_global_slot: initial_slot, - }); - } - } - BlockProducerVrfEvaluatorAction::CleanupOldSlots { .. } => {} - BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { .. } => {} - } - } -} diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs index 37b53fb660..669c00659a 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs +++ b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs @@ -1,18 +1,40 @@ +use openmina_core::bug_condition; +use vrf::VrfEvaluationOutput; + +use crate::{ + block_producer::to_epoch_and_slot, + block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorEffectfulAction, + ledger::read::{LedgerReadAction, LedgerReadInitCallback, LedgerReadRequest}, + BlockProducerAction, Substate, +}; + use super::{ BlockProducerVrfEvaluatorAction, BlockProducerVrfEvaluatorActionWithMetaRef, BlockProducerVrfEvaluatorState, BlockProducerVrfEvaluatorStatus, PendingEvaluation, - VrfWonSlotWithHash, + SlotPositionInEpoch, VrfWonSlotWithHash, }; impl BlockProducerVrfEvaluatorState { - pub fn reducer(&mut self, action: BlockProducerVrfEvaluatorActionWithMetaRef<'_>) { + pub fn reducer( + mut state_context: Substate, + action: BlockProducerVrfEvaluatorActionWithMetaRef<'_>, + ) { + let Ok(state) = state_context.get_substate_mut() else { + return; + }; + let (action, meta) = action.split(); match action { BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input } => { - self.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationPending { + state.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationPending { time: meta.time(), global_slot: vrf_input.global_slot, }; + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerVrfEvaluatorEffectfulAction::EvaluateSlot { + vrf_input: vrf_input.clone(), + }); } BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { vrf_output, @@ -20,7 +42,7 @@ impl BlockProducerVrfEvaluatorState { } => { let global_slot_evaluated = match &vrf_output { vrf::VrfEvaluationOutput::SlotWon(won_slot_data) => { - self.won_slots.insert( + state.won_slots.insert( won_slot_data.global_slot, VrfWonSlotWithHash::new( won_slot_data.clone(), @@ -31,36 +53,125 @@ impl BlockProducerVrfEvaluatorState { } vrf::VrfEvaluationOutput::SlotLost(global_slot) => *global_slot, }; - self.set_latest_evaluated_global_slot(&global_slot_evaluated); + state.set_latest_evaluated_global_slot(&global_slot_evaluated); - self.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationReceived { + state.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationReceived { time: meta.time(), global_slot: global_slot_evaluated, + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + if let Some(vrf_evaluator_state) = state.block_producer.vrf_evaluator() { + if let Some(pending_evaluation) = vrf_evaluator_state.current_evaluation() { + dispatcher.push(BlockProducerVrfEvaluatorEffectfulAction::SlotEvaluated { + epoch: pending_evaluation.epoch_number, + }); + dispatcher.push(BlockProducerVrfEvaluatorAction::CheckEpochBounds { + epoch_number: pending_evaluation.epoch_number, + latest_evaluated_global_slot: vrf_output.global_slot(), + }); + } + } + + if matches!(vrf_output, VrfEvaluationOutput::SlotWon(_)) { + dispatcher.push(BlockProducerAction::WonSlotSearch); } } BlockProducerVrfEvaluatorAction::CheckEpochBounds { epoch_number, latest_evaluated_global_slot, } => { - let epoch_current_bound = Self::evaluate_epoch_bounds(latest_evaluated_global_slot); - self.status = BlockProducerVrfEvaluatorStatus::EpochBoundsCheck { + let latest_evaluated_global_slot = *latest_evaluated_global_slot; + let epoch_number = *epoch_number; + + let epoch_current_bound = + Self::evaluate_epoch_bounds(&latest_evaluated_global_slot); + state.status = BlockProducerVrfEvaluatorStatus::EpochBoundsCheck { time: meta.time(), - epoch_number: *epoch_number, - latest_evaluated_global_slot: *latest_evaluated_global_slot, + epoch_number, + latest_evaluated_global_slot, epoch_current_bound, }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + if let Some(epoch_bound) = state + .block_producer + .vrf_evaluator() + .and_then(|s| s.get_epoch_bound_from_check()) + { + match epoch_bound { + SlotPositionInEpoch::Beginning | SlotPositionInEpoch::Within => { + dispatcher.push( + BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { + latest_evaluated_global_slot, + epoch_number, + }, + ); + } + SlotPositionInEpoch::End => { + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinishEpochEvaluation { + latest_evaluated_global_slot, + epoch_number, + }, + ); + } + } + } } - BlockProducerVrfEvaluatorAction::InitializeEvaluator { .. } => { - self.status = - BlockProducerVrfEvaluatorStatus::InitialisationPending { time: meta.time() } + BlockProducerVrfEvaluatorAction::InitializeEvaluator { best_tip } => { + state.status = + BlockProducerVrfEvaluatorStatus::InitialisationPending { time: meta.time() }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + // Note: pure function, but needs access to other parts of the state + if state.block_producer.vrf_evaluator().is_some() { + if best_tip.consensus_state().epoch_count.as_u32() == 0 { + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { + previous_epoch_and_height: None, + }, + ); + } else { + let k = best_tip.constants().k.as_u32(); + let (epoch, slot) = to_epoch_and_slot( + &best_tip.consensus_state().curr_global_slot_since_hard_fork, + ); + let previous_epoch = epoch.saturating_sub(1); + let last_height = if slot < k { + let found = + state.transition_frontier.best_chain.iter().rev().find(|b| { + b.consensus_state().epoch_count.as_u32() == previous_epoch + }); + + if let Some(block) = found { + block.height() + } else { + Default::default() + } + } else if let Some(root_block) = state.transition_frontier.root() { + root_block.height() + } else { + Default::default() + }; + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { + previous_epoch_and_height: Some((previous_epoch, last_height)), + }, + ); + } + } } BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { previous_epoch_and_height, } => { if let Some((epoch, last_height)) = previous_epoch_and_height { - self.initialize_evaluator(*epoch, *last_height); + state.initialize_evaluator(*epoch, *last_height); } - self.status = + state.status = BlockProducerVrfEvaluatorStatus::InitialisationComplete { time: meta.time() } } BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { @@ -70,24 +181,51 @@ impl BlockProducerVrfEvaluatorState { root_block_epoch, staking_epoch_data, next_epoch_data, - best_tip_slot: _, - best_tip_global_slot: _, - next_epoch_first_slot: _, + best_tip_slot, + best_tip_global_slot, + next_epoch_first_slot, } => { - self.status = BlockProducerVrfEvaluatorStatus::ReadinessCheck { + let best_tip_epoch = *best_tip_epoch; + + state.status = BlockProducerVrfEvaluatorStatus::ReadinessCheck { time: meta.time(), current_epoch: *current_epoch, is_next_epoch_seed_finalized: *is_next_epoch_seed_finalized, - best_tip_epoch: *best_tip_epoch, + best_tip_epoch, root_block_epoch: *root_block_epoch, - is_current_epoch_evaluated: self.is_epoch_evaluated(*best_tip_epoch), - is_next_epoch_evaluated: self.is_epoch_evaluated(best_tip_epoch + 1), - last_evaluated_epoch: self.last_evaluated_epoch(), + is_current_epoch_evaluated: state.is_epoch_evaluated(best_tip_epoch), + is_next_epoch_evaluated: state + .is_epoch_evaluated(best_tip_epoch.checked_add(1).expect("overflow")), + last_evaluated_epoch: state.last_evaluated_epoch(), staking_epoch_data: staking_epoch_data.clone(), next_epoch_data: next_epoch_data.clone(), }; - self.set_epoch_context(); + state.set_epoch_context(); + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let vrf_evaluator_state = state.block_producer.vrf_evaluator_with_config(); + + if let Some((vrf_evaluator_state, config)) = vrf_evaluator_state { + if let Some(epoch_data) = vrf_evaluator_state.epoch_context().get_epoch_data() { + dispatcher.push( + BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { + staking_epoch_data: epoch_data, + producer: config.pub_key.clone().into(), + best_tip_global_slot: *best_tip_global_slot, + best_tip_epoch, + best_tip_slot: *best_tip_slot, + next_epoch_first_slot: *next_epoch_first_slot, + }, + ); + } else { + // If None is returned, than we are waiting for evaluation + dispatcher.push(BlockProducerVrfEvaluatorAction::WaitForNextEvaluation); + } + + dispatcher + .push(BlockProducerVrfEvaluatorAction::CleanupOldSlots { best_tip_epoch }); + } } BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { best_tip_epoch, @@ -97,17 +235,21 @@ impl BlockProducerVrfEvaluatorState { staking_epoch_data, producer, } => { - self.status = BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { + state.status = BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { time: meta.time(), best_tip_epoch: *best_tip_epoch, - is_current_epoch_evaluated: self.is_epoch_evaluated(*best_tip_epoch), - is_next_epoch_evaluated: self.is_epoch_evaluated(best_tip_epoch + 1), + is_current_epoch_evaluated: state.is_epoch_evaluated(*best_tip_epoch), + is_next_epoch_evaluated: state + .is_epoch_evaluated(best_tip_epoch.checked_add(1).expect("overflow")), best_tip_slot: *best_tip_slot, best_tip_global_slot: *best_tip_global_slot, next_epoch_first_slot: *next_epoch_first_slot, staking_epoch_data: staking_epoch_data.clone(), producer: producer.clone(), - } + }; + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); } BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction => { let BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { @@ -120,11 +262,11 @@ impl BlockProducerVrfEvaluatorState { time: _, is_current_epoch_evaluated: _, is_next_epoch_evaluated: _, - } = &self.status + } = &state.status else { return; }; - self.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTablePending { + state.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTablePending { time: meta.time(), best_tip_epoch: *best_tip_epoch, staking_epoch_ledger_hash: staking_epoch_data.ledger.clone(), @@ -133,7 +275,19 @@ impl BlockProducerVrfEvaluatorState { next_epoch_first_slot: *next_epoch_first_slot, staking_epoch_data: staking_epoch_data.clone(), producer: producer.clone(), - } + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let (staking_ledger_hash, producer) = + match state.block_producer.vrf_delegator_table_inputs() { + Some((v1, v2)) => (v1.clone(), v2.clone()), + None => return, + }; + + dispatcher.push(LedgerReadAction::Init { + request: LedgerReadRequest::DelegatorTable(staking_ledger_hash, producer), + callback: LedgerReadInitCallback::None, + }) } BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { delegator_table, @@ -147,15 +301,22 @@ impl BlockProducerVrfEvaluatorState { producer, time: _, staking_epoch_ledger_hash: _, - } = &self.status + } = &state.status else { + bug_condition!("Invalid state for `BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction` expected: `BlockProducerVrfEvaluatorStatus::EpochDelegatorTablePending`, found: {:?}", state.status); return; }; + openmina_core::log::warn!( + meta.time(); + kind = "BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction", + message = "Empty delegator table, account may not exist yet in the staking ledger" + ); + let mut staking_epoch_data = staking_epoch_data.clone(); staking_epoch_data.delegator_table = delegator_table.clone(); - self.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { + state.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { time: meta.time(), best_tip_epoch: *best_tip_epoch, staking_epoch_ledger_hash: staking_epoch_data.ledger.clone(), @@ -164,37 +325,93 @@ impl BlockProducerVrfEvaluatorState { next_epoch_first_slot: *next_epoch_first_slot, staking_epoch_data: staking_epoch_data.clone(), producer: producer.clone(), - } + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let get_slot_and_status = || { + let cur_global_slot = state.cur_global_slot()?; + let current_slot = state.current_slot()?; + let status = &state.block_producer.vrf_evaluator()?.status; + + Some((cur_global_slot, current_slot, status)) + }; + + let Some(( + current_global_slot, + current_slot, + BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { + best_tip_epoch, + best_tip_slot, + best_tip_global_slot, + next_epoch_first_slot, + staking_epoch_data, + .. + }, + )) = get_slot_and_status() + else { + bug_condition!("Invalid state for `BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction`"); + return; + }; + + dispatcher.push(BlockProducerVrfEvaluatorAction::SelectInitialSlot { + current_global_slot, + current_slot, + best_tip_epoch: *best_tip_epoch, + best_tip_slot: *best_tip_slot, + best_tip_global_slot: *best_tip_global_slot, + next_epoch_first_slot: *next_epoch_first_slot, + staking_epoch_data: staking_epoch_data.clone(), + }); } BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { - best_tip_epoch, + best_tip_epoch: _, + evaluation_epoch, latest_evaluated_global_slot, staking_epoch_data, best_tip_slot: _, best_tip_global_slot: _, } => { - self.set_pending_evaluation(PendingEvaluation { - epoch_number: *best_tip_epoch, + let latest_evaluated_global_slot = *latest_evaluated_global_slot; + let epoch_number = *evaluation_epoch; + + state.set_pending_evaluation(PendingEvaluation { + epoch_number, epoch_data: staking_epoch_data.clone(), - latest_evaluated_slot: *latest_evaluated_global_slot, + latest_evaluated_slot: latest_evaluated_global_slot, }); - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { time: meta.time(), - epoch_number: *best_tip_epoch, + epoch_number, epoch_data: staking_epoch_data.clone(), - latest_evaluated_global_slot: *latest_evaluated_global_slot, + latest_evaluated_global_slot, + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if state.block_producer.vrf_evaluator().is_some() { + dispatcher.push(BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { + latest_evaluated_global_slot, + epoch_number, + }); } } BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { epoch_number, latest_evaluated_global_slot, } => { - if let Some(pending_evaluation) = self.current_evaluation() { - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { + if let Some(pending_evaluation) = state.current_evaluation() { + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { time: meta.time(), epoch_number: *epoch_number, epoch_data: pending_evaluation.epoch_data, latest_evaluated_global_slot: *latest_evaluated_global_slot, + }; + } + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if let Some(vrf_evaluator_state) = state.block_producer.vrf_evaluator() { + if let Some(vrf_input) = vrf_evaluator_state.construct_vrf_input() { + dispatcher + .push(BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input }); } } } @@ -202,41 +419,69 @@ impl BlockProducerVrfEvaluatorState { epoch_number, latest_evaluated_global_slot: _, } => { - self.unset_pending_evaluation(); - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationSuccess { + state.unset_pending_evaluation(); + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationSuccess { time: meta.time(), epoch_number: *epoch_number, }; - self.set_last_evaluated_epoch(); + state.set_last_evaluated_epoch(); } BlockProducerVrfEvaluatorAction::WaitForNextEvaluation => { - self.status = + state.status = BlockProducerVrfEvaluatorStatus::WaitingForNextEvaluation { time: meta.time() }; } BlockProducerVrfEvaluatorAction::SelectInitialSlot { best_tip_epoch, current_global_slot, + current_slot, next_epoch_first_slot, - best_tip_slot: _, - best_tip_global_slot: _, - staking_epoch_data: _, + best_tip_slot: current_best_tip_slot, + best_tip_global_slot: current_best_tip_global_slot, + staking_epoch_data, } => { - let (epoch_number, initial_slot) = match self.epoch_context() { - super::EpochContext::Current(_) => (*best_tip_epoch, *current_global_slot), - super::EpochContext::Next(_) => (best_tip_epoch + 1, next_epoch_first_slot - 1), + let (epoch_number, initial_global_slot, initial_slot) = match state.epoch_context() + { + super::EpochContext::Current(_) => { + (*best_tip_epoch, *current_global_slot, *current_slot) + } + super::EpochContext::Next(_) => ( + best_tip_epoch.checked_add(1).expect("overflow"), + next_epoch_first_slot.checked_sub(1).expect("underflow"), + 0, + ), super::EpochContext::Waiting => todo!(), }; - self.status = BlockProducerVrfEvaluatorStatus::InitialSlotSelection { + state.status = BlockProducerVrfEvaluatorStatus::InitialSlotSelection { time: meta.time(), epoch_number, + initial_slot: initial_global_slot, + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + dispatcher.push(BlockProducerVrfEvaluatorEffectfulAction::InitializeStats { + epoch: epoch_number, initial_slot, + }); + if let Some(initial_slot) = state + .block_producer + .vrf_evaluator() + .and_then(|v| v.initial_slot()) + { + dispatcher.push(BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { + best_tip_epoch: *best_tip_epoch, + best_tip_global_slot: *current_best_tip_global_slot, + best_tip_slot: *current_best_tip_slot, + evaluation_epoch: epoch_number, + staking_epoch_data: staking_epoch_data.clone(), + latest_evaluated_global_slot: initial_slot, + }); } } BlockProducerVrfEvaluatorAction::CleanupOldSlots { best_tip_epoch } => { - self.cleanup_old_won_slots(best_tip_epoch); + state.cleanup_old_won_slots(best_tip_epoch); } BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { reason } => { - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationInterrupted { + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationInterrupted { time: meta.time(), reason: reason.clone(), }; diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_state.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_state.rs index 78c0b34b32..94032af56a 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_state.rs +++ b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_state.rs @@ -46,7 +46,7 @@ impl BlockProducerVrfEvaluatorState { pub fn evaluate_epoch_bounds(global_slot: &u32) -> SlotPositionInEpoch { if global_slot % SLOTS_PER_EPOCH == 0 { SlotPositionInEpoch::Beginning - } else if (global_slot + 1) % SLOTS_PER_EPOCH == 0 { + } else if (global_slot.checked_add(1).expect("overflow")) % SLOTS_PER_EPOCH == 0 { SlotPositionInEpoch::End } else { SlotPositionInEpoch::Within @@ -111,7 +111,7 @@ impl BlockProducerVrfEvaluatorState { if !self.is_epoch_evaluated(best_tip_epoch) { self.epoch_context = EpochContext::Current((*staking_epoch_data).into()) - } else if !self.is_epoch_evaluated(best_tip_epoch + 1) { + } else if !self.is_epoch_evaluated(best_tip_epoch.checked_add(1).expect("overflow")) { if root_block_epoch == best_tip_epoch && is_next_epoch_seed_finalized { self.epoch_context = EpochContext::Next((*next_epoch_data).into()) } else { @@ -154,7 +154,6 @@ impl BlockProducerVrfEvaluatorState { /// /// Returns: /// - `Option`: The epoch number of the last evaluated epoch, or `None` if no epoch has been evaluated yet. - pub fn last_evaluated_epoch(&self) -> Option { self.last_evaluated_epoch } @@ -284,7 +283,9 @@ impl BlockProducerVrfEvaluatorState { { match self.epoch_context { EpochContext::Current(_) => self.last_evaluated_epoch = Some(*epoch_number), - EpochContext::Next(_) => self.last_evaluated_epoch = Some(epoch_number + 1), + EpochContext::Next(_) => { + self.last_evaluated_epoch = Some(epoch_number.checked_add(1).expect("overflow")) + } EpochContext::Waiting => {} } } @@ -333,7 +334,10 @@ impl BlockProducerVrfEvaluatorState { Some(VrfEvaluatorInput::new( pending_evaluation.epoch_data.seed, pending_evaluation.epoch_data.delegator_table, - pending_evaluation.latest_evaluated_slot + 1, + pending_evaluation + .latest_evaluated_slot + .checked_add(1) + .expect("overflow"), pending_evaluation.epoch_data.total_currency, pending_evaluation.epoch_data.ledger, )) @@ -345,7 +349,7 @@ impl BlockProducerVrfEvaluatorState { pub fn retention_slot(&self, current_epoch_number: &u32) -> u32 { const PAST_EPOCHS_TO_KEEP: u32 = 2; let cutoff_epoch = current_epoch_number.saturating_sub(PAST_EPOCHS_TO_KEEP); - (cutoff_epoch * SLOTS_PER_EPOCH).saturating_sub(1) + (cutoff_epoch.saturating_mul(SLOTS_PER_EPOCH)).saturating_sub(1) } pub fn cleanup_old_won_slots(&mut self, current_epoch_number: &u32) { diff --git a/node/src/block_producer/vrf_evaluator/mod.rs b/node/src/block_producer/vrf_evaluator/mod.rs index 1c8f624618..0cfe2e0014 100644 --- a/node/src/block_producer/vrf_evaluator/mod.rs +++ b/node/src/block_producer/vrf_evaluator/mod.rs @@ -9,11 +9,6 @@ pub use block_producer_vrf_evaluator_actions::*; mod block_producer_vrf_evaluator_reducer; -mod block_producer_vrf_evaluator_effects; - -mod block_producer_vrf_evaluator_service; -pub use block_producer_vrf_evaluator_service::*; - use std::collections::BTreeMap; use std::sync::Arc; diff --git a/node/src/block_producer_effectful/block_producer_effectful_actions.rs b/node/src/block_producer_effectful/block_producer_effectful_actions.rs new file mode 100644 index 0000000000..a81b4702b9 --- /dev/null +++ b/node/src/block_producer_effectful/block_producer_effectful_actions.rs @@ -0,0 +1,26 @@ +use super::vrf_evaluator_effectful::BlockProducerVrfEvaluatorEffectfulAction; +use crate::block_producer::{BlockProducerWonSlot, BlockProducerWonSlotDiscardReason}; +use openmina_core::ActionEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] +pub enum BlockProducerEffectfulAction { + VrfEvaluator(BlockProducerVrfEvaluatorEffectfulAction), + WonSlot { + won_slot: BlockProducerWonSlot, + }, + WonSlotDiscard { + reason: BlockProducerWonSlotDiscardReason, + }, + StagedLedgerDiffCreateInit, + StagedLedgerDiffCreateSuccess, + BlockUnprovenBuild, + BlockProveInit, + BlockProveSuccess, +} + +impl redux::EnablingCondition for BlockProducerEffectfulAction { + fn is_enabled(&self, _state: &crate::State, _time: redux::Timestamp) -> bool { + true + } +} diff --git a/node/src/block_producer_effectful/block_producer_effectful_effects.rs b/node/src/block_producer_effectful/block_producer_effectful_effects.rs new file mode 100644 index 0000000000..ab7a5cdd3f --- /dev/null +++ b/node/src/block_producer_effectful/block_producer_effectful_effects.rs @@ -0,0 +1,225 @@ +use crate::{ + block_producer::BlockProducerCurrentState, + ledger::write::{LedgerWriteAction, LedgerWriteRequest}, + BlockProducerAction, Store, +}; +use mina_p2p_messages::v2::{ + BlockchainSnarkBlockchainStableV2, ConsensusStakeProofStableV2, + MinaStateSnarkTransitionValueStableV2, ProverExtendBlockchainInputStableV2, +}; +use openmina_node_account::AccountSecretKey; +use redux::ActionWithMeta; + +use super::BlockProducerEffectfulAction; + +pub fn block_producer_effects( + store: &mut Store, + action: ActionWithMeta, +) { + let (action, meta) = action.split(); + + match action { + BlockProducerEffectfulAction::VrfEvaluator(a) => { + a.effects(&meta, store); + } + BlockProducerEffectfulAction::WonSlot { won_slot } => { + if let Some(stats) = store.service.stats() { + stats.block_producer().scheduled(meta.time(), &won_slot); + } + if !store.dispatch(BlockProducerAction::WonSlotWait) { + store.dispatch(BlockProducerAction::WonSlotProduceInit); + } + } + BlockProducerEffectfulAction::StagedLedgerDiffCreateInit => { + if let Some(stats) = store.service.stats() { + stats + .block_producer() + .staged_ledger_diff_create_start(meta.time()); + } + let state = store.state.get(); + let Some((won_slot, pred_block, producer, coinbase_receiver)) = None.or_else(|| { + let pred_block = state.block_producer.current_parent_chain()?.last()?; + let won_slot = state.block_producer.current_won_slot()?; + let config = state.block_producer.config()?; + Some(( + won_slot, + pred_block, + &config.pub_key, + config.coinbase_receiver(), + )) + }) else { + return; + }; + + let completed_snarks = state + .snark_pool + .completed_snarks_iter() + .map(|snark| (snark.job_id(), snark.clone())) + .collect(); + // TODO(binier) + let supercharge_coinbase = true; + // We want to know if this is a new epoch to decide which staking ledger to use + // (staking epoch ledger or next epoch ledger). + let is_new_epoch = won_slot.epoch() + > pred_block + .header() + .protocol_state + .body + .consensus_state + .epoch_count + .as_u32(); + + let transactions_by_fee = state.block_producer.pending_transactions(); + + store.dispatch(LedgerWriteAction::Init { + request: LedgerWriteRequest::StagedLedgerDiffCreate { + pred_block: pred_block.clone(), + global_slot_since_genesis: won_slot + .global_slot_since_genesis(pred_block.global_slot_diff()), + is_new_epoch, + producer: producer.clone(), + delegator: won_slot.delegator.0.clone(), + coinbase_receiver: coinbase_receiver.clone(), + completed_snarks, + supercharge_coinbase, + transactions_by_fee, + }, + on_init: redux::callback!( + on_staged_ledger_diff_create_init(_request: LedgerWriteRequest) -> crate::Action { + BlockProducerAction::StagedLedgerDiffCreatePending + } + ), + }); + } + BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess => { + if let Some(stats) = store.service.stats() { + stats + .block_producer() + .staged_ledger_diff_create_end(meta.time()); + } + store.dispatch(BlockProducerAction::BlockUnprovenBuild); + } + BlockProducerEffectfulAction::BlockUnprovenBuild => { + if let Some(stats) = store.service.stats() { + let bp = &store.state.get().block_producer; + if let Some((block_hash, block, just_emitted_ledger_proof)) = + bp.with(None, |bp| match &bp.current { + BlockProducerCurrentState::BlockUnprovenBuilt { + block, + block_hash, + emitted_ledger_proof, + .. + } => Some((block_hash, block, emitted_ledger_proof.is_some())), + _ => None, + }) + { + let ps = &block.protocol_state; + let cs = &ps.body.consensus_state; + let previous_state_hash = ps.previous_state_hash.to_string(); + let state_hash = block_hash.to_string(); + let global_slot_since_genesis = cs.global_slot_since_genesis.as_u32(); + let height = cs.blockchain_length.as_u32(); + openmina_core::info!( + meta.time(); + message = "Unproven block built", + state_hash = state_hash, + previous_state_hash = previous_state_hash, + global_slot_since_genesis = global_slot_since_genesis, + height = height, + just_emitted_ledger_proof = just_emitted_ledger_proof, + ); + + stats + .block_producer() + .produced(meta.time(), block_hash, block); + } + } + + store.dispatch(BlockProducerAction::BlockProveInit); + } + BlockProducerEffectfulAction::BlockProveInit => { + let service = &mut store.service; + + if let Some(stats) = service.stats() { + stats.block_producer().proof_create_start(meta.time()); + } + let Some((block_hash, input)) = store.state.get().block_producer.with(None, |bp| { + let BlockProducerCurrentState::BlockUnprovenBuilt { + won_slot, + chain, + emitted_ledger_proof, + pending_coinbase_update, + pending_coinbase_witness, + stake_proof_sparse_ledger, + block, + block_hash, + .. + } = &bp.current + else { + return None; + }; + + let pred_block = chain.last()?; + + let producer_public_key = block + .protocol_state + .body + .consensus_state + .block_creator + .clone(); + + let input = Box::new(ProverExtendBlockchainInputStableV2 { + chain: BlockchainSnarkBlockchainStableV2 { + state: pred_block.header().protocol_state.clone(), + proof: pred_block.header().protocol_state_proof.clone(), + }, + next_state: block.protocol_state.clone(), + block: MinaStateSnarkTransitionValueStableV2 { + blockchain_state: block.protocol_state.body.blockchain_state.clone(), + consensus_transition: block + .protocol_state + .body + .consensus_state + .curr_global_slot_since_hard_fork + .slot_number + .clone(), + pending_coinbase_update: pending_coinbase_update.clone(), + }, + ledger_proof: emitted_ledger_proof.clone(), + prover_state: ConsensusStakeProofStableV2 { + delegator: won_slot.delegator.1.into(), + delegator_pk: won_slot.delegator.0.clone(), + coinbase_receiver_pk: block + .protocol_state + .body + .consensus_state + .coinbase_receiver + .clone(), + ledger: stake_proof_sparse_ledger.clone(), + // it is replaced with correct keys in the service. + producer_private_key: AccountSecretKey::genesis_producer().into(), + producer_public_key, + }, + pending_coinbase: pending_coinbase_witness.clone(), + }); + Some((block_hash.clone(), input)) + }) else { + return; + }; + service.prove(block_hash, input); + store.dispatch(BlockProducerAction::BlockProvePending); + } + BlockProducerEffectfulAction::BlockProveSuccess => { + if let Some(stats) = store.service.stats() { + stats.block_producer().proof_create_end(meta.time()); + } + store.dispatch(BlockProducerAction::BlockProduced); + } + BlockProducerEffectfulAction::WonSlotDiscard { reason } => { + if let Some(stats) = store.service.stats() { + stats.block_producer().discarded(meta.time(), reason); + } + store.dispatch(BlockProducerAction::WonSlotSearch); + } + } +} diff --git a/node/src/block_producer/block_producer_service.rs b/node/src/block_producer_effectful/block_producer_effectful_service.rs similarity index 92% rename from node/src/block_producer/block_producer_service.rs rename to node/src/block_producer_effectful/block_producer_effectful_service.rs index 9725416f2f..145f1e249c 100644 --- a/node/src/block_producer/block_producer_service.rs +++ b/node/src/block_producer_effectful/block_producer_effectful_service.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use ledger::proofs::provers::BlockProver; use mina_p2p_messages::v2::{ ConsensusBodyReferenceStableV1, LedgerProofProdStableV2, MinaBasePendingCoinbaseUpdateStableV1, @@ -13,7 +15,7 @@ pub struct StagedLedgerDiffCreateOutput { /// `protocol_state.blockchain_state.body_reference` pub diff_hash: ConsensusBodyReferenceStableV1, pub staged_ledger_hash: MinaBaseStagedLedgerHashStableV1, - pub emitted_ledger_proof: Option>, + pub emitted_ledger_proof: Option>, pub pending_coinbase_update: MinaBasePendingCoinbaseUpdateStableV1, pub pending_coinbase_witness: MinaBasePendingCoinbaseWitnessStableV2, pub stake_proof_sparse_ledger: MinaBaseSparseLedgerBaseStableV2, diff --git a/node/src/block_producer_effectful/mod.rs b/node/src/block_producer_effectful/mod.rs new file mode 100644 index 0000000000..40ce48fda6 --- /dev/null +++ b/node/src/block_producer_effectful/mod.rs @@ -0,0 +1,10 @@ +pub mod vrf_evaluator_effectful; + +mod block_producer_effectful_actions; +pub use block_producer_effectful_actions::*; + +mod block_producer_effectful_effects; +pub use block_producer_effectful_effects::*; + +mod block_producer_effectful_service; +pub use block_producer_effectful_service::*; diff --git a/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs new file mode 100644 index 0000000000..199bc34f18 --- /dev/null +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs @@ -0,0 +1,22 @@ +use crate::block_producer::vrf_evaluator::VrfEvaluatorInput; +use openmina_core::ActionEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] +pub enum BlockProducerVrfEvaluatorEffectfulAction { + EvaluateSlot { vrf_input: VrfEvaluatorInput }, + SlotEvaluated { epoch: u32 }, + InitializeStats { epoch: u32, initial_slot: u32 }, +} + +impl redux::EnablingCondition for BlockProducerVrfEvaluatorEffectfulAction { + fn is_enabled(&self, _state: &crate::State, _time: redux::Timestamp) -> bool { + true + } +} + +impl From for crate::Action { + fn from(value: BlockProducerVrfEvaluatorEffectfulAction) -> Self { + Self::BlockProducerEffectful(crate::BlockProducerEffectfulAction::VrfEvaluator(value)) + } +} diff --git a/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs new file mode 100644 index 0000000000..4f11f33b2f --- /dev/null +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs @@ -0,0 +1,39 @@ +use crate::Service; +use crate::Store; +use redux::ActionMeta; + +use super::BlockProducerVrfEvaluatorEffectfulAction; + +impl BlockProducerVrfEvaluatorEffectfulAction { + pub fn effects(self, _: &ActionMeta, store: &mut Store) { + match self { + BlockProducerVrfEvaluatorEffectfulAction::EvaluateSlot { vrf_input } => { + store.service.evaluate(vrf_input); + } + BlockProducerVrfEvaluatorEffectfulAction::SlotEvaluated { epoch } => { + if let Some(stats) = store.service.stats() { + stats.block_producer().increment_slot_evaluated(epoch); + } + } + BlockProducerVrfEvaluatorEffectfulAction::InitializeStats { + epoch, + initial_slot, + } => { + if let Some(stats) = store.service.stats() { + let slots_per_epoch = + store.state.get().config.consensus_constants.slots_per_epoch; + // We add 1 because the evaluation starts from the next slot, except for the first slot 0 + let initial_slot = if initial_slot == 0 { + 0 + } else { + initial_slot.saturating_add(1) + }; + let remaining_slots = slots_per_epoch.saturating_sub(initial_slot); + stats + .block_producer() + .new_epoch_evaluation(epoch, remaining_slots); + } + } + } + } +} diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs similarity index 65% rename from node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs rename to node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs index 84b3a0564d..f66525d360 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs @@ -1,4 +1,4 @@ -use super::VrfEvaluatorInput; +use crate::block_producer::vrf_evaluator::VrfEvaluatorInput; pub trait BlockProducerVrfEvaluatorService: redux::Service { fn evaluate(&mut self, data: VrfEvaluatorInput); diff --git a/node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs new file mode 100644 index 0000000000..c55fbe7da8 --- /dev/null +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs @@ -0,0 +1,7 @@ +mod block_producer_vrf_evaluator_effectful_actions; +pub use block_producer_vrf_evaluator_effectful_actions::*; + +mod block_producer_vrf_evaluator_effectful_effects; + +mod block_producer_vrf_evaluator_effectful_service; +pub use block_producer_vrf_evaluator_effectful_service::*; diff --git a/node/src/consensus/consensus_actions.rs b/node/src/consensus/consensus_actions.rs index a35ab7d6ff..0aeb10923a 100644 --- a/node/src/consensus/consensus_actions.rs +++ b/node/src/consensus/consensus_actions.rs @@ -9,6 +9,7 @@ use snark::block_verify::SnarkBlockVerifyError; use crate::consensus::ConsensusBlockStatus; use crate::snark::block_verify::SnarkBlockVerifyId; +use crate::state::BlockPrevalidationError; pub type ConsensusActionWithMeta = redux::ActionWithMeta; pub type ConsensusActionWithMetaRef<'a> = redux::ActionWithMeta<&'a ConsensusAction>; @@ -24,6 +25,13 @@ pub enum ConsensusAction { block: Arc, chain_proof: Option<(Vec, ArcBlockWithHash)>, }, + BlockPrevalidateSuccess { + hash: StateHash, + }, + BlockPrevalidateError { + hash: StateHash, + error: BlockPrevalidationError, + }, BlockChainProofUpdate { hash: StateHash, chain_proof: (Vec, ArcBlockWithHash), @@ -71,6 +79,12 @@ impl redux::EnablingCondition for ConsensusAction { }; !block.is_genesis() && !state.consensus.blocks.contains_key(hash) }, + ConsensusAction::BlockPrevalidateSuccess { hash } + | ConsensusAction::BlockPrevalidateError { hash, .. } => state + .consensus + .blocks + .get(hash) + .map_or(false, |block| block.status.is_received()), ConsensusAction::BlockChainProofUpdate { hash, .. } => { (state.consensus.best_tip.as_ref() == Some(hash) && state.consensus.best_tip_chain_proof.is_none()) @@ -85,7 +99,7 @@ impl redux::EnablingCondition for ConsensusAction { .consensus .blocks .get(hash) - .map_or(false, |block| block.status.is_received()) + .map_or(false, |block| block.status.is_prevalidated()) && state.snark.block_verify.jobs.contains(*req_id) }, ConsensusAction::BlockSnarkVerifySuccess { hash } => { diff --git a/node/src/consensus/consensus_reducer.rs b/node/src/consensus/consensus_reducer.rs index 19fd383106..0f7c977c1e 100644 --- a/node/src/consensus/consensus_reducer.rs +++ b/node/src/consensus/consensus_reducer.rs @@ -1,5 +1,5 @@ use openmina_core::{ - block::BlockHash, + block::{ArcBlockWithHash, BlockHash}, bug_condition, consensus::{is_short_range_fork, long_range_fork_take, short_range_fork_take}, }; @@ -48,9 +48,35 @@ impl ConsensusState { ); // Dispatch + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + let hash = hash.clone(); + let block = ArcBlockWithHash { + hash: hash.clone(), + block: block.clone(), + }; + let allow_block_too_late = allow_block_too_late(state, &block); + + match state.prevalidate_block(&block, allow_block_too_late) { + Ok(()) => { + dispatcher.push(ConsensusAction::BlockPrevalidateSuccess { hash }); + } + Err(error) => { + dispatcher.push(ConsensusAction::BlockPrevalidateError { hash, error }); + } + } + } + ConsensusAction::BlockPrevalidateSuccess { hash } => { + let Some(block) = state.blocks.get_mut(hash) else { + return; + }; + block.status = ConsensusBlockStatus::Prevalidated; + + // Dispatch + let block = (hash.clone(), block.block.clone()).into(); let dispatcher = state_context.into_dispatcher(); dispatcher.push(SnarkBlockVerifyAction::Init { - block: (hash.clone(), block.clone()).into(), + block, on_init: redux::callback!( on_received_block_snark_verify_init((hash: BlockHash, req_id: SnarkBlockVerifyId)) -> crate::Action { ConsensusAction::BlockSnarkVerifyPending { hash, req_id } @@ -65,6 +91,9 @@ impl ConsensusState { }), }); } + ConsensusAction::BlockPrevalidateError { hash, .. } => { + state.blocks.remove(hash); + } ConsensusAction::BlockChainProofUpdate { hash, chain_proof } => { if state.best_tip.as_ref() == Some(hash) { state.best_tip_chain_proof = Some(chain_proof.clone()); @@ -268,6 +297,7 @@ impl ConsensusState { best_tip, root_block, blocks_inbetween, + on_success: None, }); } ConsensusAction::P2pBestTipUpdate { best_tip } => { @@ -306,3 +336,26 @@ impl ConsensusState { } } } + +/// Decide if the time-reception check should be done for this block or not. +/// +/// The check is skipped if the block's global_slot is greater than the +/// current best tip and the difference greater than 2. +/// +/// Ideally we would differentiate between requested blocks and blocks +/// received from gossip, but this difference doesn't really exist +/// in the WebRTC transport, hence this heuristic. +fn allow_block_too_late(state: &crate::State, block: &ArcBlockWithHash) -> bool { + let (has_greater_blobal_slot, diff_with_best_tip) = state + .transition_frontier + .best_tip() + .map(|b| { + ( + block.global_slot() > b.global_slot(), + b.global_slot().abs_diff(block.global_slot()), + ) + }) + .unwrap_or((false, 0)); + + has_greater_blobal_slot && diff_with_best_tip > 2 +} diff --git a/node/src/consensus/consensus_state.rs b/node/src/consensus/consensus_state.rs index bfa5abc1f4..330e89421b 100644 --- a/node/src/consensus/consensus_state.rs +++ b/node/src/consensus/consensus_state.rs @@ -44,6 +44,7 @@ pub enum ConsensusBlockStatus { Received { time: redux::Timestamp, }, + Prevalidated, SnarkVerifyPending { time: redux::Timestamp, req_id: SnarkBlockVerifyId, @@ -73,6 +74,10 @@ impl ConsensusBlockStatus { matches!(self, Self::Received { .. }) } + pub fn is_prevalidated(&self) -> bool { + matches!(self, Self::Prevalidated) + } + pub fn is_snark_verify_pending(&self) -> bool { matches!(self, Self::SnarkVerifyPending { .. }) } @@ -167,6 +172,7 @@ impl ConsensusState { }; match &candidate.status { ConsensusBlockStatus::Received { .. } => false, + ConsensusBlockStatus::Prevalidated => false, ConsensusBlockStatus::SnarkVerifyPending { .. } => false, ConsensusBlockStatus::SnarkVerifySuccess { .. } => false, ConsensusBlockStatus::ForkRangeDetected { .. } => false, @@ -218,7 +224,7 @@ pub struct BlockRef<'a> { pub status: &'a ConsensusBlockStatus, } -impl<'a> BlockRef<'a> { +impl BlockRef<'_> { pub fn height(&self) -> u32 { self.header .protocol_state diff --git a/node/src/effects.rs b/node/src/effects.rs index b94fca3399..f3755e2feb 100644 --- a/node/src/effects.rs +++ b/node/src/effects.rs @@ -1,20 +1,25 @@ use openmina_core::log::system_time; use rand::prelude::*; -use crate::block_producer::{block_producer_effects, BlockProducerAction}; +use crate::block_producer::BlockProducerAction; +use crate::block_producer_effectful::block_producer_effects; use crate::event_source::event_source_effects; -use crate::external_snark_worker::external_snark_worker_effects; -use crate::ledger::ledger_effects; +use crate::external_snark_worker_effectful::external_snark_worker_effectful_effects; use crate::ledger::read::LedgerReadAction; +use crate::ledger_effectful::ledger_effectful_effects; use crate::logger::logger_effects; use crate::p2p::node_p2p_effects; use crate::rpc_effectful::rpc_effects; use crate::snark::snark_effects; use crate::snark_pool::candidate::SnarkPoolCandidateAction; use crate::snark_pool::{snark_pool_effects, SnarkPoolAction}; +use crate::transaction_pool::candidate::TransactionPoolCandidateAction; use crate::transition_frontier::genesis::TransitionFrontierGenesisAction; use crate::transition_frontier::transition_frontier_effects; -use crate::{p2p_ready, Action, ActionWithMeta, ExternalSnarkWorkerAction, Service, Store}; +use crate::{ + p2p_ready, Action, ActionWithMeta, ExternalSnarkWorkerAction, Service, Store, + TransactionPoolAction, +}; use crate::p2p::channels::rpc::{P2pChannelsRpcAction, P2pRpcRequest}; @@ -36,12 +41,18 @@ pub fn effects(store: &mut Store, action: ActionWithMeta) { store.dispatch(TransitionFrontierGenesisAction::LedgerLoadInit); store.dispatch(ExternalSnarkWorkerAction::Start); + store.dispatch(TransitionFrontierGenesisAction::ProveInit); + if store.state().p2p.ready().is_some() { p2p_request_best_tip_if_needed(store); p2p_request_transactions_if_needed(store); p2p_request_snarks_if_needed(store); } + store.dispatch(TransactionPoolAction::P2pSendAll); + store.dispatch(TransactionPoolCandidateAction::FetchAll); + store.dispatch(TransactionPoolCandidateAction::VerifyNext); + store.dispatch(SnarkPoolAction::CheckTimeouts); store.dispatch(SnarkPoolAction::P2pSendAll); @@ -61,10 +72,6 @@ pub fn effects(store: &mut Store, action: ActionWithMeta) { Action::Snark(action) => { snark_effects(store, meta.with_action(action)); } - Action::Consensus(_) => { - // Handled by reducer - } - Action::TransactionPool(_action) => {} Action::TransactionPoolEffect(action) => { action.effects(store); } @@ -74,32 +81,31 @@ pub fn effects(store: &mut Store, action: ActionWithMeta) { Action::P2pEffectful(action) => { node_p2p_effects(store, meta.with_action(action)); } - Action::Ledger(action) => { - ledger_effects(store, meta.with_action(action)); + Action::LedgerEffects(action) => { + ledger_effectful_effects(store, meta.with_action(action)); } - Action::SnarkPool(_) => {} Action::SnarkPoolEffect(action) => { snark_pool_effects(store, meta.with_action(action)); } - Action::BlockProducer(action) => { + Action::BlockProducerEffectful(action) => { block_producer_effects(store, meta.with_action(action)); } - Action::ExternalSnarkWorker(action) => { - external_snark_worker_effects(store, meta.with_action(action)); - } - Action::Rpc(_) => { - // Handled by reducer + Action::ExternalSnarkWorkerEffects(action) => { + external_snark_worker_effectful_effects(store, meta.with_action(action)); } Action::RpcEffectful(action) => { rpc_effects(store, meta.with_action(action)); } - Action::WatchedAccounts(_) => { - // Handled by reducer - } - Action::P2pCallbacks(_) => { - // Handled by reducer - } - Action::P2p(_) => { + Action::BlockProducer(_) + | Action::SnarkPool(_) + | Action::ExternalSnarkWorker(_) + | Action::TransactionPool(_) + | Action::Consensus(_) + | Action::Ledger(_) + | Action::Rpc(_) + | Action::WatchedAccounts(_) + | Action::P2pCallbacks(_) + | Action::P2p(_) => { // Handled by reducer } } @@ -122,10 +128,28 @@ fn p2p_request_best_tip_if_needed(store: &mut Store) { } use mina_p2p_messages::v2::StateHash; -fn request_best_tip(store: &mut Store, _consensus_best_tip_hash: Option) { +fn request_best_tip(store: &mut Store, consensus_best_tip_hash: Option) { let p2p = p2p_ready!(store.state().p2p, "request_best_tip", system_time()); - let peers = p2p.ready_rpc_peers_iter().collect::>(); + let (ready_peers, ready_peers_matching_best_tip) = p2p.ready_rpc_peers_iter().fold( + (Vec::new(), Vec::new()), + |(mut all, mut matching), (peer_id, peer)| { + let rpc_id = peer.channels.next_local_rpc_id(); + if peer.best_tip.as_ref().map(|b| b.hash()) == consensus_best_tip_hash.as_ref() { + matching.push((*peer_id, rpc_id)); + } else if matching.is_empty() { + all.push((*peer_id, rpc_id)); + } + (all, matching) + }, + ); + + let peers = if !ready_peers_matching_best_tip.is_empty() { + ready_peers_matching_best_tip + } else { + ready_peers + }; + if let Some((peer_id, id)) = peers.choose(&mut store.state().pseudo_rng()) { store.dispatch(P2pChannelsRpcAction::RequestSend { peer_id: *peer_id, @@ -136,8 +160,31 @@ fn request_best_tip(store: &mut Store, _consensus_best_tip_hash: } } -fn p2p_request_transactions_if_needed(_store: &mut Store) { - // TODO(binier): request tx from peers for which we have tx_info. +fn p2p_request_transactions_if_needed(store: &mut Store) { + use p2p::channels::transaction::P2pChannelsTransactionAction; + + const MAX_PEER_PENDING_TXS: usize = 32; + + let state = store.state(); + let p2p = p2p_ready!( + state.p2p, + "p2p_request_transactions_if_needed", + system_time() + ); + let transaction_reqs = p2p + .ready_peers_iter() + .filter(|(_, p)| p.channels.transaction.can_send_request()) + .map(|(peer_id, _)| { + let pending_txs = state.snark_pool.candidates.peer_work_count(peer_id); + (peer_id, MAX_PEER_PENDING_TXS.saturating_sub(pending_txs)) + }) + .filter(|(_, limit)| *limit > 0) + .map(|(peer_id, limit)| (*peer_id, limit.min(u8::MAX as usize) as u8)) + .collect::>(); + + for (peer_id, limit) in transaction_reqs { + store.dispatch(P2pChannelsTransactionAction::RequestSend { peer_id, limit }); + } } fn p2p_request_snarks_if_needed(store: &mut Store) { diff --git a/node/src/event_source/event.rs b/node/src/event_source/event.rs index c37a913b6e..66bdfa78d9 100644 --- a/node/src/event_source/event.rs +++ b/node/src/event_source/event.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; pub use crate::block_producer::BlockProducerEvent; -pub use crate::external_snark_worker::ExternalSnarkWorkerEvent; +pub use crate::external_snark_worker_effectful::ExternalSnarkWorkerEvent; pub use crate::ledger::LedgerEvent; pub use crate::p2p::{P2pConnectionEvent, P2pEvent}; pub use crate::rpc::{RpcId, RpcRequest}; diff --git a/node/src/event_source/event_source_effects.rs b/node/src/event_source/event_source_effects.rs index de299f9c0e..480b2560d0 100644 --- a/node/src/event_source/event_source_effects.rs +++ b/node/src/event_source/event_source_effects.rs @@ -8,7 +8,7 @@ use snark::user_command_verify::{SnarkUserCommandVerifyAction, SnarkUserCommandV use crate::action::CheckTimeoutsAction; use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorAction; use crate::block_producer::{BlockProducerEvent, BlockProducerVrfEvaluatorEvent}; -use crate::external_snark_worker::ExternalSnarkWorkerEvent; +use crate::external_snark_worker_effectful::ExternalSnarkWorkerEvent; use crate::ledger::read::LedgerReadAction; use crate::ledger::write::LedgerWriteAction; use crate::p2p::channels::best_tip::P2pChannelsBestTipAction; @@ -197,7 +197,7 @@ pub fn event_source_effects(store: &mut Store, action: EventSourc } }, P2pConnectionEvent::Closed(peer_id) => { - store.dispatch(P2pDisconnectionAction::Finish { peer_id }); + store.dispatch(P2pDisconnectionAction::PeerClosed { peer_id }); } }, P2pEvent::Channel(e) => match e { diff --git a/node/src/external_snark_worker/external_snark_worker_actions.rs b/node/src/external_snark_worker/external_snark_worker_actions.rs index 776b231e41..2599906985 100644 --- a/node/src/external_snark_worker/external_snark_worker_actions.rs +++ b/node/src/external_snark_worker/external_snark_worker_actions.rs @@ -47,7 +47,6 @@ pub enum ExternalSnarkWorkerAction { }, } -pub type ExternalSnarkWorkerActionWithMeta = redux::ActionWithMeta; pub type ExternalSnarkWorkerActionWithMetaRef<'a> = redux::ActionWithMeta<&'a ExternalSnarkWorkerAction>; diff --git a/node/src/external_snark_worker/external_snark_worker_effects.rs b/node/src/external_snark_worker/external_snark_worker_effects.rs deleted file mode 100644 index 63bbcdb6b4..0000000000 --- a/node/src/external_snark_worker/external_snark_worker_effects.rs +++ /dev/null @@ -1,106 +0,0 @@ -use openmina_core::snark::Snark; - -use crate::{p2p_ready, snark_pool::SnarkPoolAction}; - -use super::{ - available_job_to_snark_worker_spec, ExternalSnarkWorkerAction, - ExternalSnarkWorkerActionWithMeta, -}; - -pub fn external_snark_worker_effects( - store: &mut crate::Store, - action: ExternalSnarkWorkerActionWithMeta, -) { - let (action, meta) = action.split(); - match action { - ExternalSnarkWorkerAction::Start => { - let Some(config) = &store.state.get().config.snarker else { - return; - }; - let public_key = config.public_key.clone().into(); - let fee = config.fee.clone(); - if let Err(err) = store.service.start(public_key, fee) { - store.dispatch(ExternalSnarkWorkerAction::Error { - error: err, - permanent: true, - }); - } - } - ExternalSnarkWorkerAction::Started => { - store.dispatch(SnarkPoolAction::AutoCreateCommitment); - } - ExternalSnarkWorkerAction::StartTimeout { .. } => { - store.dispatch(ExternalSnarkWorkerAction::Error { - error: super::ExternalSnarkWorkerError::StartTimeout, - permanent: true, - }); - } - ExternalSnarkWorkerAction::Kill => { - if let Err(err) = store.service().kill() { - store.dispatch(ExternalSnarkWorkerAction::Error { - error: err, - permanent: true, - }); - } - } - ExternalSnarkWorkerAction::Killed => {} - ExternalSnarkWorkerAction::Error { .. } => { - store.dispatch(ExternalSnarkWorkerAction::Kill); - } - ExternalSnarkWorkerAction::SubmitWork { job_id, .. } => { - let Some(job) = store.state().snark_pool.get(&job_id) else { - return; - }; - let input = match available_job_to_snark_worker_spec( - job.job.clone(), - &store.state().transition_frontier, - ) { - Ok(v) => v, - Err(err) => { - store.dispatch(ExternalSnarkWorkerAction::WorkError { error: err.into() }); - return; - } - }; - if let Err(err) = store.service().submit(input) { - store.dispatch(ExternalSnarkWorkerAction::WorkError { error: err.into() }); - } - } - ExternalSnarkWorkerAction::WorkResult { result } => { - let Some(config) = &store.state().config.snarker else { - return; - }; - let p2p = p2p_ready!(store.state().p2p, meta.time()); - let snarker = config.public_key.clone().into(); - let fee = config.fee.clone(); - let snark = Snark { - snarker, - fee, - proofs: result.clone(), - }; - let sender = p2p.my_id(); - // Directly add snark to the snark pool as it's produced by us. - store.dispatch(SnarkPoolAction::WorkAdd { snark, sender }); - store.dispatch(ExternalSnarkWorkerAction::PruneWork); - } - ExternalSnarkWorkerAction::WorkError { .. } => { - store.dispatch(ExternalSnarkWorkerAction::PruneWork); - } - ExternalSnarkWorkerAction::WorkTimeout { .. } => { - store.dispatch(ExternalSnarkWorkerAction::CancelWork); - } - ExternalSnarkWorkerAction::CancelWork => { - if let Err(error) = store.service().cancel() { - store.dispatch(ExternalSnarkWorkerAction::Error { - error, - permanent: true, - }); - } - } - ExternalSnarkWorkerAction::WorkCancelled => { - store.dispatch(ExternalSnarkWorkerAction::PruneWork); - } - ExternalSnarkWorkerAction::PruneWork => { - store.dispatch(SnarkPoolAction::AutoCreateCommitment); - } - } -} diff --git a/node/src/external_snark_worker/external_snark_worker_reducer.rs b/node/src/external_snark_worker/external_snark_worker_reducer.rs index 6a0e7934a2..6ad5c9677c 100644 --- a/node/src/external_snark_worker/external_snark_worker_reducer.rs +++ b/node/src/external_snark_worker/external_snark_worker_reducer.rs @@ -1,70 +1,179 @@ +use openmina_core::snark::Snark; +use redux::Timestamp; + use super::{ + available_job_to_snark_worker_spec, external_snark_worker_state::{ExternalSnarkWorker, ExternalSnarkWorkerState}, - ExternalSnarkWorkerAction, ExternalSnarkWorkerActionWithMetaRef, ExternalSnarkWorkers, + ExternalSnarkWorkerAction, ExternalSnarkWorkerActionWithMetaRef, ExternalSnarkWorkerWorkError, + ExternalSnarkWorkers, +}; +use crate::{ + external_snark_worker_effectful::ExternalSnarkWorkerEffectfulAction, p2p_ready, + SnarkPoolAction, Substate, }; impl ExternalSnarkWorkers { - pub fn reducer(&mut self, action: ExternalSnarkWorkerActionWithMetaRef<'_>) { - self.0.reducer(action) + pub fn reducer( + state_context: Substate, + action: ExternalSnarkWorkerActionWithMetaRef<'_>, + ) { + ExternalSnarkWorker::reducer(Substate::from_compatible_substate(state_context), action); } } impl ExternalSnarkWorker { - pub fn reducer(&mut self, action: ExternalSnarkWorkerActionWithMetaRef<'_>) { + pub fn reducer( + mut state_context: Substate, + action: ExternalSnarkWorkerActionWithMetaRef<'_>, + ) { + let Ok(worker_state) = state_context.get_substate_mut() else { + return; + }; let (action, meta) = action.split(); match action { ExternalSnarkWorkerAction::Start => { - self.state = ExternalSnarkWorkerState::Starting; + worker_state.state = ExternalSnarkWorkerState::Starting; + worker_state.update_timestamp(meta.time()); + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let Some(config) = &state.config.snarker else { + return; + }; + + let public_key = config.public_key.clone().into(); + let fee = config.fee.clone(); + + dispatcher.push(ExternalSnarkWorkerEffectfulAction::Start { public_key, fee }); } ExternalSnarkWorkerAction::Started => { - self.state = ExternalSnarkWorkerState::Idle; + worker_state.state = ExternalSnarkWorkerState::Idle; + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(SnarkPoolAction::AutoCreateCommitment); } ExternalSnarkWorkerAction::StartTimeout { .. } => { - return; + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerAction::Error { + error: super::ExternalSnarkWorkerError::StartTimeout, + permanent: true, + }); } ExternalSnarkWorkerAction::Kill => { - self.state = ExternalSnarkWorkerState::Killing; + worker_state.state = ExternalSnarkWorkerState::Killing; + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerEffectfulAction::Kill); } ExternalSnarkWorkerAction::Killed => { - self.state = ExternalSnarkWorkerState::None; + worker_state.state = ExternalSnarkWorkerState::None; + worker_state.update_timestamp(meta.time()); } ExternalSnarkWorkerAction::Error { error, permanent } => { - self.state = ExternalSnarkWorkerState::Error(error.clone(), *permanent); + worker_state.state = ExternalSnarkWorkerState::Error(error.clone(), *permanent); + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerAction::Kill); } ExternalSnarkWorkerAction::SubmitWork { job_id, summary } => { - self.state = ExternalSnarkWorkerState::Working(job_id.clone(), summary.clone()); + worker_state.state = + ExternalSnarkWorkerState::Working(job_id.clone(), summary.clone()); + worker_state.update_timestamp(meta.time()); + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let Some(job) = state.snark_pool.get(job_id) else { + return; + }; + + match available_job_to_snark_worker_spec( + job.job.clone(), + &state.transition_frontier, + ) { + Ok(spec) => { + dispatcher.push(ExternalSnarkWorkerEffectfulAction::SubmitWork { + spec: Box::new(spec), + }); + } + Err(err) => { + dispatcher.push(ExternalSnarkWorkerAction::WorkError { + error: ExternalSnarkWorkerWorkError::WorkSpecError(err), + }); + } + } } ExternalSnarkWorkerAction::WorkResult { result } => { - let ExternalSnarkWorkerState::Working(job_id, _) = &self.state else { + let ExternalSnarkWorkerState::Working(job_id, _) = &worker_state.state else { + return; + }; + worker_state.state = + ExternalSnarkWorkerState::WorkReady(job_id.clone(), result.clone()); + worker_state.update_timestamp(meta.time()); + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let Some(config) = &state.config.snarker else { return; }; - self.state = ExternalSnarkWorkerState::WorkReady(job_id.clone(), result.clone()); + let p2p = p2p_ready!(state.p2p, meta.time()); + let snarker = config.public_key.clone().into(); + let fee = config.fee.clone(); + let snark = Snark { + snarker, + fee, + proofs: result.clone(), + }; + let sender = p2p.my_id(); + // Directly add snark to the snark pool as it's produced by us. + dispatcher.push(SnarkPoolAction::WorkAdd { snark, sender }); + dispatcher.push(ExternalSnarkWorkerAction::PruneWork); } ExternalSnarkWorkerAction::WorkError { error } => { - let ExternalSnarkWorkerState::Working(job_id, _) = &self.state else { + let ExternalSnarkWorkerState::Working(job_id, _) = &worker_state.state else { return; }; - self.state = ExternalSnarkWorkerState::WorkError(job_id.clone(), error.clone()); + worker_state.state = + ExternalSnarkWorkerState::WorkError(job_id.clone(), error.clone()); + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerAction::PruneWork); } ExternalSnarkWorkerAction::WorkTimeout { .. } => { - return; + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerAction::CancelWork); } ExternalSnarkWorkerAction::CancelWork => { - let ExternalSnarkWorkerState::Working(job_id, _) = &self.state else { + let ExternalSnarkWorkerState::Working(job_id, _) = &worker_state.state else { return; }; - self.state = ExternalSnarkWorkerState::Cancelling(job_id.clone()); + worker_state.state = ExternalSnarkWorkerState::Cancelling(job_id.clone()); + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerEffectfulAction::CancelWork); } ExternalSnarkWorkerAction::WorkCancelled => { - let ExternalSnarkWorkerState::Cancelling(job_id) = &self.state else { + let ExternalSnarkWorkerState::Cancelling(job_id) = &worker_state.state else { return; }; - self.state = ExternalSnarkWorkerState::Cancelled(job_id.clone()); + worker_state.state = ExternalSnarkWorkerState::Cancelled(job_id.clone()); + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(ExternalSnarkWorkerAction::PruneWork); } ExternalSnarkWorkerAction::PruneWork => { - self.state = ExternalSnarkWorkerState::Idle; + worker_state.state = ExternalSnarkWorkerState::Idle; + worker_state.update_timestamp(meta.time()); + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(SnarkPoolAction::AutoCreateCommitment); } } - self.timestamp = meta.time(); + } + + fn update_timestamp(&mut self, time: Timestamp) { + self.timestamp = time; } } diff --git a/node/src/external_snark_worker/mod.rs b/node/src/external_snark_worker/mod.rs index dcb8dea9f0..2580a960ea 100644 --- a/node/src/external_snark_worker/mod.rs +++ b/node/src/external_snark_worker/mod.rs @@ -9,12 +9,6 @@ pub use external_snark_worker_actions::*; mod external_snark_worker_reducer; -mod external_snark_worker_effects; -pub use external_snark_worker_effects::external_snark_worker_effects; - -mod external_snark_worker_service; -pub use external_snark_worker_service::*; - mod external_snark_worker_errors; pub use external_snark_worker_errors::*; diff --git a/node/src/external_snark_worker_effectful/external_snark_worker_effectful_actions.rs b/node/src/external_snark_worker_effectful/external_snark_worker_effectful_actions.rs new file mode 100644 index 0000000000..e493e3d906 --- /dev/null +++ b/node/src/external_snark_worker_effectful/external_snark_worker_effectful_actions.rs @@ -0,0 +1,25 @@ +use mina_p2p_messages::v2::{CurrencyFeeStableV1, NonZeroCurvePoint}; +use openmina_core::ActionEvent; +use redux::EnablingCondition; +use serde::{Deserialize, Serialize}; + +use crate::{external_snark_worker::SnarkWorkSpec, State}; + +#[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] +pub enum ExternalSnarkWorkerEffectfulAction { + Start { + public_key: NonZeroCurvePoint, + fee: CurrencyFeeStableV1, + }, + Kill, + SubmitWork { + spec: Box, + }, + CancelWork, +} + +impl EnablingCondition for ExternalSnarkWorkerEffectfulAction { + fn is_enabled(&self, _state: &State, _time: redux::Timestamp) -> bool { + true + } +} diff --git a/node/src/external_snark_worker_effectful/external_snark_worker_effectful_effects.rs b/node/src/external_snark_worker_effectful/external_snark_worker_effectful_effects.rs new file mode 100644 index 0000000000..e2487f0e67 --- /dev/null +++ b/node/src/external_snark_worker_effectful/external_snark_worker_effectful_effects.rs @@ -0,0 +1,41 @@ +use super::ExternalSnarkWorkerEffectfulAction; +use crate::ExternalSnarkWorkerAction; +use redux::ActionWithMeta; + +pub fn external_snark_worker_effectful_effects( + store: &mut crate::Store, + action: ActionWithMeta, +) { + let (action, _) = action.split(); + match action { + ExternalSnarkWorkerEffectfulAction::Start { public_key, fee } => { + if let Err(err) = store.service.start(public_key, fee) { + store.dispatch(ExternalSnarkWorkerAction::Error { + error: err, + permanent: true, + }); + } + } + ExternalSnarkWorkerEffectfulAction::Kill => { + if let Err(err) = store.service().kill() { + store.dispatch(ExternalSnarkWorkerAction::Error { + error: err, + permanent: true, + }); + } + } + ExternalSnarkWorkerEffectfulAction::SubmitWork { spec } => { + if let Err(err) = store.service().submit(*spec) { + store.dispatch(ExternalSnarkWorkerAction::WorkError { error: err.into() }); + } + } + ExternalSnarkWorkerEffectfulAction::CancelWork => { + if let Err(error) = store.service().cancel() { + store.dispatch(ExternalSnarkWorkerAction::Error { + error, + permanent: true, + }); + } + } + } +} diff --git a/node/src/external_snark_worker/external_snark_worker_service.rs b/node/src/external_snark_worker_effectful/external_snark_worker_service.rs similarity index 96% rename from node/src/external_snark_worker/external_snark_worker_service.rs rename to node/src/external_snark_worker_effectful/external_snark_worker_service.rs index 4a7b5269b1..730b12b195 100644 --- a/node/src/external_snark_worker/external_snark_worker_service.rs +++ b/node/src/external_snark_worker_effectful/external_snark_worker_service.rs @@ -1,7 +1,7 @@ use mina_p2p_messages::v2::{CurrencyFeeStableV1, NonZeroCurvePoint}; use serde::{Deserialize, Serialize}; -use super::{ +use crate::external_snark_worker::{ ExternalSnarkWorkerError, ExternalSnarkWorkerWorkError, SnarkWorkResult, SnarkWorkSpec, }; diff --git a/node/src/external_snark_worker_effectful/mod.rs b/node/src/external_snark_worker_effectful/mod.rs new file mode 100644 index 0000000000..371ec46f8d --- /dev/null +++ b/node/src/external_snark_worker_effectful/mod.rs @@ -0,0 +1,8 @@ +mod external_snark_worker_effectful_actions; +pub use external_snark_worker_effectful_actions::ExternalSnarkWorkerEffectfulAction; + +mod external_snark_worker_effectful_effects; +pub use external_snark_worker_effectful_effects::external_snark_worker_effectful_effects; + +mod external_snark_worker_service; +pub use external_snark_worker_service::*; diff --git a/node/src/ledger/ledger_actions.rs b/node/src/ledger/ledger_actions.rs index a64022c368..32367f5762 100644 --- a/node/src/ledger/ledger_actions.rs +++ b/node/src/ledger/ledger_actions.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; use super::{read::LedgerReadAction, write::LedgerWriteAction}; -pub type LedgerActionWithMeta = redux::ActionWithMeta; pub type LedgerActionWithMetaRef<'a> = redux::ActionWithMeta<&'a LedgerAction>; #[derive(Serialize, Deserialize, derive_more::From, Debug, Clone)] diff --git a/node/src/ledger/ledger_effects.rs b/node/src/ledger/ledger_effects.rs deleted file mode 100644 index d581d69c30..0000000000 --- a/node/src/ledger/ledger_effects.rs +++ /dev/null @@ -1,527 +0,0 @@ -use mina_p2p_messages::v2; -use p2p::channels::rpc::P2pRpcRequest; -use p2p::channels::streaming_rpc::{P2pChannelsStreamingRpcAction, P2pStreamingRpcRequest}; -use p2p::P2pAction; - -use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorAction; -use crate::p2p::channels::rpc::{P2pChannelsRpcAction, P2pRpcId, P2pRpcResponse}; -use crate::p2p::PeerId; -use crate::transition_frontier::sync::ledger::staged::TransitionFrontierSyncLedgerStagedAction; -use crate::transition_frontier::sync::TransitionFrontierSyncAction; -use crate::{BlockProducerAction, RpcAction, Store}; - -use super::read::{ - LedgerReadAction, LedgerReadId, LedgerReadInitCallback, LedgerReadRequest, LedgerReadResponse, - LedgerReadStagedLedgerAuxAndPendingCoinbases, -}; -use super::write::{LedgerWriteAction, LedgerWriteResponse}; -use super::{LedgerAction, LedgerActionWithMeta, LedgerAddress, LedgerService}; - -pub fn ledger_effects(store: &mut Store, action: LedgerActionWithMeta) { - let (action, _) = action.split(); - - match action { - LedgerAction::Write(a) => match a { - LedgerWriteAction::Init { request, on_init } => { - store.service.write_init(request.clone()); - store.dispatch(LedgerWriteAction::Pending); - store.dispatch_callback(on_init, request); - } - LedgerWriteAction::Pending => {} - LedgerWriteAction::Success { response } => { - propagate_write_response(store, response); - next_write_request_init(store); - } - }, - LedgerAction::Read(a) => match a { - LedgerReadAction::FindTodos => { - next_read_requests_init(store); - } - LedgerReadAction::Init { request, callback } => { - if store.state().ledger.read.has_same_request(&request) { - return; - } - let id = store.state().ledger.read.next_req_id(); - store.service.read_init(id, request.clone()); - store.dispatch(LedgerReadAction::Pending { id, request }); - - match callback { - LedgerReadInitCallback::RpcLedgerAccountsGetPending { callback, args } => { - store.dispatch_callback(callback, args); - } - LedgerReadInitCallback::RpcScanStateSummaryGetPending { callback, args } => { - store.dispatch_callback(callback, args); - } - LedgerReadInitCallback::P2pChannelsResponsePending { callback, args } => { - store.dispatch_callback(callback, args); - } - LedgerReadInitCallback::None => {} - }; - } - LedgerReadAction::Pending { .. } => {} - LedgerReadAction::Success { id, response } => { - propagate_read_response(store, id, response); - store.dispatch(LedgerReadAction::Prune { id }); - } - LedgerReadAction::Prune { .. } => {} - }, - } -} - -fn next_write_request_init(store: &mut Store) { - if store.dispatch(BlockProducerAction::StagedLedgerDiffCreateInit) { - } else if store.dispatch(TransitionFrontierSyncAction::BlocksNextApplyInit) { - } else if store.dispatch(TransitionFrontierSyncAction::CommitInit) { - } else if store.dispatch(TransitionFrontierSyncLedgerStagedAction::ReconstructInit) { - } -} - -fn propagate_write_response( - store: &mut Store, - response: LedgerWriteResponse, -) { - let Some(request) = &store.state().ledger.write.request() else { - return; - }; - match (request, response) { - ( - _, - LedgerWriteResponse::StagedLedgerReconstruct { - staged_ledger_hash, - result, - }, - ) => { - let sync = &store.state().transition_frontier.sync; - let expected_ledger = sync - .ledger_target() - .and_then(|target| target.staged) - .map(|v| v.hashes.non_snark.ledger_hash); - if expected_ledger.as_ref() == Some(&staged_ledger_hash) { - match result { - Err(error) => { - store.dispatch( - TransitionFrontierSyncLedgerStagedAction::ReconstructError { error }, - ); - } - Ok(()) => { - store.dispatch( - TransitionFrontierSyncLedgerStagedAction::ReconstructSuccess { - ledger_hash: staged_ledger_hash, - }, - ); - } - } - } - } - ( - _, - LedgerWriteResponse::StagedLedgerDiffCreate { - pred_block_hash, - global_slot_since_genesis, - result, - }, - ) => { - let state = store.state.get(); - let Some((expected_pred_block_hash, expected_global_slot)) = None.or_else(|| { - let pred_block = state.block_producer.current_parent_chain()?.last()?; - let won_slot = state.block_producer.current_won_slot()?; - let slot = won_slot.global_slot_since_genesis(pred_block.global_slot_diff()); - Some((pred_block.hash(), slot)) - }) else { - return; - }; - - if &pred_block_hash == expected_pred_block_hash - && global_slot_since_genesis == expected_global_slot - { - match result { - Err(err) => todo!("handle staged ledger diff creation err: {err}"), - Ok(output) => { - store.dispatch(BlockProducerAction::StagedLedgerDiffCreateSuccess { - output, - }); - } - } - } - } - ( - _, - LedgerWriteResponse::BlockApply { - block_hash: hash, - result, - }, - ) => match result { - Err(error) => { - store.dispatch(TransitionFrontierSyncAction::BlocksNextApplyError { hash, error }); - } - Ok(result) => { - store.dispatch(TransitionFrontierSyncAction::BlocksNextApplySuccess { - hash, - just_emitted_a_proof: result.just_emitted_a_proof, - }); - } - }, - ( - _, - LedgerWriteResponse::Commit { - best_tip_hash, - result, - }, - ) => { - let best_tip = store.state().transition_frontier.sync.best_tip(); - if best_tip.map_or(false, |tip| tip.hash() == &best_tip_hash) { - store.dispatch(TransitionFrontierSyncAction::CommitSuccess { result }); - } - } - } -} - -fn next_read_requests_init(store: &mut Store) { - // fetching delegator table - store.dispatch(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); - - // p2p rpcs - let mut peers = store - .state() - .p2p - .ready_peers_iter() - .filter(|(_, peer)| { - peer.channels - .rpc - .remote_todo_requests_iter() - .next() - .is_some() - || peer.channels.streaming_rpc.remote_todo_request().is_some() - }) - .map(|(peer_id, peer)| (*peer_id, peer.channels.rpc_remote_last_responded())) - .collect::>(); - peers.sort_by_key(|(_, last_responded)| *last_responded); - for (peer_id, _) in peers { - let Some((id, request, is_streaming)) = None.or_else(|| { - let peer = store.state().p2p.ready()?.get_ready_peer(&peer_id)?; - let mut reqs = peer.channels.rpc.remote_todo_requests_iter(); - reqs.find_map(|req| { - let ledger_request = match &req.request { - P2pRpcRequest::LedgerQuery(hash, query) => match query { - v2::MinaLedgerSyncLedgerQueryStableV1::NumAccounts => { - LedgerReadRequest::GetNumAccounts(hash.clone()) - } - v2::MinaLedgerSyncLedgerQueryStableV1::WhatChildHashes(addr) => { - LedgerReadRequest::GetChildHashesAtAddr(hash.clone(), addr.into()) - } - v2::MinaLedgerSyncLedgerQueryStableV1::WhatContents(addr) => { - LedgerReadRequest::GetChildAccountsAtAddr(hash.clone(), addr.into()) - } - }, - P2pRpcRequest::StagedLedgerAuxAndPendingCoinbasesAtBlock(block_hash) => { - build_staged_ledger_parts_request(store.state(), block_hash)? - } - _ => return None, - }; - - Some((req.id, ledger_request, false)) - }) - .or_else(|| { - let (id, req) = peer.channels.streaming_rpc.remote_todo_request()?; - let ledger_request = match req { - P2pStreamingRpcRequest::StagedLedgerParts(block_hash) => { - build_staged_ledger_parts_request(store.state(), block_hash)? - } - }; - Some((id, ledger_request, true)) - }) - }) else { - continue; - }; - - store.dispatch(LedgerReadAction::Init { - request, - callback: LedgerReadInitCallback::P2pChannelsResponsePending - { callback: redux::callback!(on_ledger_read_init_p2p_channels_response_pending((is_streaming: bool, id: P2pRpcId, peer_id: PeerId)) -> crate::Action{ - if is_streaming { - P2pAction::from(P2pChannelsStreamingRpcAction::ResponsePending { - peer_id, - id, - }) - } else { - P2pAction::from(P2pChannelsRpcAction::ResponsePending { - peer_id, - id, - }) - } - }), - args:(is_streaming, id, peer_id) - } - }); - - if !store.state().ledger.read.is_total_cost_under_limit() { - return; - } - } - - // rpcs - let rpcs = store - .state() - .rpc - .scan_state_summary_rpc_ids() - .filter(|(.., status)| status.is_init()) - .map(|(id, ..)| id) - .collect::>(); - - for rpc_id in rpcs { - store.dispatch(RpcAction::ScanStateSummaryLedgerGetInit { rpc_id }); - if !store.state().ledger.read.is_total_cost_under_limit() { - return; - } - } - - let ledger_account_rpc = store - .state() - .rpc - .accounts_request_rpc_ids() - .filter(|(.., status)| status.is_init()) - .map(|(id, req, _)| (id, req)) - .collect::>(); - - for (rpc_id, req) in ledger_account_rpc { - store.dispatch(RpcAction::LedgerAccountsGetInit { - rpc_id, - account_query: req, - }); - if !store.state().ledger.read.is_total_cost_under_limit() { - return; - } - } -} - -fn build_staged_ledger_parts_request( - state: &crate::State, - block_hash: &v2::StateHash, -) -> Option { - let tf = &state.transition_frontier; - let ledger_hash = tf - .best_chain - .iter() - .find(|b| b.hash() == block_hash) - .map(|b| b.staged_ledger_hashes().clone())?; - let protocol_states = tf - .needed_protocol_states - .iter() - .map(|(hash, b)| (hash.clone(), b.clone())) - .chain( - tf.best_chain - .iter() - .take_while(|b| b.hash() != block_hash) - .map(|b| (b.hash().clone(), b.header().protocol_state.clone())), - ) - .collect(); - - Some(LedgerReadRequest::GetStagedLedgerAuxAndPendingCoinbases( - LedgerReadStagedLedgerAuxAndPendingCoinbases { - ledger_hash, - protocol_states, - }, - )) -} - -fn find_peers_with_ledger_rpc( - state: &crate::State, - req: &LedgerReadRequest, -) -> Vec<(PeerId, P2pRpcId, bool)> { - let Some(p2p) = state.p2p.ready() else { - return Vec::new(); - }; - p2p.ready_peers_iter() - .flat_map(|(peer_id, peer)| { - let rpcs = peer - .channels - .rpc - .remote_pending_requests_iter() - .map(move |req| (peer_id, req.id, &req.request)) - .filter(|(_, _, peer_req)| match (req, peer_req) { - ( - LedgerReadRequest::GetNumAccounts(h1), - P2pRpcRequest::LedgerQuery( - h2, - v2::MinaLedgerSyncLedgerQueryStableV1::NumAccounts, - ), - ) => h1 == h2, - ( - LedgerReadRequest::GetChildHashesAtAddr(h1, addr1), - P2pRpcRequest::LedgerQuery( - h2, - v2::MinaLedgerSyncLedgerQueryStableV1::WhatChildHashes(addr2), - ), - ) => h1 == h2 && addr1 == &LedgerAddress::from(addr2), - ( - LedgerReadRequest::GetChildAccountsAtAddr(h1, addr1), - P2pRpcRequest::LedgerQuery( - h2, - v2::MinaLedgerSyncLedgerQueryStableV1::WhatContents(addr2), - ), - ) => h1 == h2 && addr1 == &LedgerAddress::from(addr2), - ( - LedgerReadRequest::GetStagedLedgerAuxAndPendingCoinbases(data), - P2pRpcRequest::StagedLedgerAuxAndPendingCoinbasesAtBlock(block_hash), - ) => state - .transition_frontier - .get_state_body(block_hash) - .map_or(false, |b| { - b.blockchain_state.staged_ledger_hash == data.ledger_hash - }), - _ => false, - }) - .map(|(peer_id, rpc_id, _)| (*peer_id, rpc_id, false)); - let streaming_rpcs = peer - .channels - .streaming_rpc - .remote_pending_request() - .into_iter() - .filter(|(_, peer_req)| match (req, peer_req) { - ( - LedgerReadRequest::GetStagedLedgerAuxAndPendingCoinbases(data), - P2pStreamingRpcRequest::StagedLedgerParts(block_hash), - ) => state - .transition_frontier - .get_state_body(block_hash) - .map_or(false, |b| { - b.blockchain_state.staged_ledger_hash == data.ledger_hash - }), - _ => false, - }) - .map(|(rpc_id, _)| (*peer_id, rpc_id, true)); - rpcs.chain(streaming_rpcs) - }) - .collect() -} - -fn propagate_read_response( - store: &mut Store, - id: LedgerReadId, - response: LedgerReadResponse, -) { - let request = match store.state().ledger.read.get(id) { - None => return, - Some(v) => v.request(), - }; - match (request, response) { - ( - LedgerReadRequest::DelegatorTable(ledger_hash, pub_key), - LedgerReadResponse::DelegatorTable(table), - ) => { - let expected = store.state().block_producer.vrf_delegator_table_inputs(); - if !expected.map_or(false, |(expected_hash, producer)| { - ledger_hash == expected_hash && pub_key == producer - }) { - eprintln!("delegator table unexpected"); - return; - } - match table { - None => todo!("delegator table construction error handling"), - Some(table) => { - store.dispatch( - BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { - delegator_table: table.into(), - }, - ); - } - } - } - (_, LedgerReadResponse::DelegatorTable(..)) => unreachable!(), - (req, LedgerReadResponse::GetNumAccounts(resp)) => { - for (peer_id, id, _) in find_peers_with_ledger_rpc(store.state(), req) { - store.dispatch(P2pChannelsRpcAction::ResponseSend { - peer_id, - id, - response: resp.as_ref().map(|(num_accounts, hash)| { - Box::new(P2pRpcResponse::LedgerQuery( - v2::MinaLedgerSyncLedgerAnswerStableV2::NumAccounts( - (*num_accounts).into(), - hash.clone(), - ), - )) - }), - }); - } - } - (req, LedgerReadResponse::GetChildHashesAtAddr(resp)) => { - for (peer_id, id, _) in find_peers_with_ledger_rpc(store.state(), req) { - store.dispatch(P2pChannelsRpcAction::ResponseSend { - peer_id, - id, - response: resp.as_ref().map(|(left, right)| { - Box::new(P2pRpcResponse::LedgerQuery( - v2::MinaLedgerSyncLedgerAnswerStableV2::ChildHashesAre( - left.clone(), - right.clone(), - ), - )) - }), - }); - } - } - (req, LedgerReadResponse::GetChildAccountsAtAddr(resp)) => { - for (peer_id, id, _) in find_peers_with_ledger_rpc(store.state(), req) { - store.dispatch(P2pChannelsRpcAction::ResponseSend { - peer_id, - id, - response: resp.as_ref().map(|accounts| { - Box::new(P2pRpcResponse::LedgerQuery( - v2::MinaLedgerSyncLedgerAnswerStableV2::ContentsAre( - accounts.iter().cloned().collect(), - ), - )) - }), - }); - } - } - (req, LedgerReadResponse::GetStagedLedgerAuxAndPendingCoinbases(resp)) => { - for (peer_id, id, is_streaming) in find_peers_with_ledger_rpc(store.state(), req) { - if is_streaming { - store.dispatch(P2pChannelsStreamingRpcAction::ResponseSendInit { - peer_id, - id, - response: resp.clone().map(Into::into), - }); - } else { - store.dispatch(P2pChannelsRpcAction::ResponseSend { - peer_id, - id, - response: resp.clone().map(|data| { - Box::new(P2pRpcResponse::StagedLedgerAuxAndPendingCoinbasesAtBlock( - data, - )) - }), - }); - } - } - } - ( - LedgerReadRequest::ScanStateSummary(ledger_hash), - LedgerReadResponse::ScanStateSummary(scan_state), - ) => { - for rpc_id in store - .state() - .rpc - .scan_state_summary_rpc_ids() - .filter(|(_, hash, _)| *hash == ledger_hash) - .map(|(id, ..)| id) - .collect::>() - { - store.dispatch(RpcAction::ScanStateSummaryGetSuccess { - rpc_id, - scan_state: scan_state.clone(), - }); - } - } - (_, LedgerReadResponse::ScanStateSummary(..)) => unreachable!(), - (_req, LedgerReadResponse::GetAccounts(..)) => todo!(), - (_, LedgerReadResponse::AccountsForRpc(rpc_id, accounts, account_query)) => { - store.dispatch(RpcAction::LedgerAccountsGetSuccess { - rpc_id, - accounts, - account_query, - }); - } - } -} diff --git a/node/src/ledger/ledger_event.rs b/node/src/ledger/ledger_event.rs index c5531a691c..b91e5a5f82 100644 --- a/node/src/ledger/ledger_event.rs +++ b/node/src/ledger/ledger_event.rs @@ -1,7 +1,9 @@ use serde::{Deserialize, Serialize}; -use super::read::{LedgerReadId, LedgerReadResponse}; -use super::write::LedgerWriteResponse; +use super::{ + read::{LedgerReadId, LedgerReadResponse}, + write::LedgerWriteResponse, +}; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum LedgerEvent { diff --git a/node/src/ledger/ledger_manager.rs b/node/src/ledger/ledger_manager.rs index 064cf6d5e3..99318272f9 100644 --- a/node/src/ledger/ledger_manager.rs +++ b/node/src/ledger/ledger_manager.rs @@ -1,20 +1,20 @@ -use std::collections::BTreeMap; - -use ledger::staged_ledger::staged_ledger::{SkipVerification, StagedLedger}; +use super::{ + read::{LedgerReadId, LedgerReadRequest, LedgerReadResponse}, + write::{LedgerWriteRequest, LedgerWriteResponse}, + {LedgerCtx, LedgerService}, +}; +use crate::{ + account::AccountPublicKey, ledger::LedgerAddress, rpc::AccountQuery, + transition_frontier::sync::ledger::snarked::TransitionFrontierSyncLedgerSnarkedService, +}; +use ledger::{ + staged_ledger::staged_ledger::{SkipVerification, StagedLedger}, + Account, AccountId, Mask, +}; use mina_p2p_messages::v2::{self, LedgerHash, MinaBaseAccountBinableArgStableV2}; -use openmina_core::channels::mpsc; -use openmina_core::thread; - -use super::ledger_service::LedgerCtx; -use super::read::{LedgerReadId, LedgerReadRequest, LedgerReadResponse}; -use super::write::{LedgerWriteRequest, LedgerWriteResponse}; -use super::LedgerService; -use crate::account::AccountPublicKey; -use crate::ledger::LedgerAddress; -use crate::rpc::AccountQuery; -use crate::transition_frontier::sync::ledger::snarked::TransitionFrontierSyncLedgerSnarkedService; -use ledger::{Account, AccountId, Mask}; use mina_signer::CompressedPubKey; +use openmina_core::{channels::mpsc, thread}; +use std::collections::BTreeMap; /// The type enumerating different requests that can be made to the /// service. Each specific constructor has a specific response diff --git a/node/src/ledger/ledger_reducer.rs b/node/src/ledger/ledger_reducer.rs index 2dddab45e2..cd5e3a2eff 100644 --- a/node/src/ledger/ledger_reducer.rs +++ b/node/src/ledger/ledger_reducer.rs @@ -1,11 +1,22 @@ -use super::{LedgerAction, LedgerActionWithMetaRef, LedgerState}; +use crate::Substate; + +use super::{ + read::LedgerReadState, write::LedgerWriteState, LedgerAction, LedgerActionWithMetaRef, + LedgerState, +}; impl LedgerState { - pub fn reducer(&mut self, action: LedgerActionWithMetaRef<'_>) { + pub fn reducer(state_context: Substate, action: LedgerActionWithMetaRef<'_>) { let (action, meta) = action.split(); match action { - LedgerAction::Write(a) => self.write.reducer(meta.with_action(a)), - LedgerAction::Read(a) => self.read.reducer(meta.with_action(a)), + LedgerAction::Write(action) => LedgerWriteState::reducer( + Substate::from_compatible_substate(state_context), + meta.with_action(action), + ), + LedgerAction::Read(action) => LedgerReadState::reducer( + Substate::from_compatible_substate(state_context), + meta.with_action(action), + ), } } } diff --git a/node/src/ledger/ledger_service.rs b/node/src/ledger/ledger_service.rs index 301ec0788c..257970a8b6 100644 --- a/node/src/ledger/ledger_service.rs +++ b/node/src/ledger/ledger_service.rs @@ -1,12 +1,29 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - path::Path, - sync::Arc, -}; - use super::{ - ledger_manager::{LedgerManager, LedgerRequest}, - write::BlockApplyResult, + ledger_empty_hash_at_depth, + read::LedgerReadResponse, + read::{LedgerReadId, LedgerReadRequest}, + write::CommitResult, + write::LedgerWriteRequest, + write::LedgerWriteResponse, + LedgerAddress, LedgerEvent, LEDGER_DEPTH, +}; +use crate::{ + account::AccountPublicKey, + block_producer_effectful::StagedLedgerDiffCreateOutput, + ledger::{ + ledger_manager::{LedgerManager, LedgerRequest}, + write::BlockApplyResult, + }, + p2p::channels::rpc::StagedLedgerAuxAndPendingCoinbases, + rpc::{ + RpcScanStateSummaryBlockTransaction, RpcScanStateSummaryScanStateJob, + RpcScanStateSummaryScanStateJobKind, RpcSnarkPoolJobSnarkWorkDone, + }, + transition_frontier::genesis::empty_pending_coinbase_hash, + transition_frontier::sync::{ + ledger::staged::StagedLedgerAuxAndPendingCoinbasesValid, + TransitionFrontierRootSnarkedLedgerUpdates, + }, }; use ark_ff::fields::arithmetic::InvalidBigInt; use ledger::{ @@ -42,34 +59,18 @@ use mina_p2p_messages::{ StateHash, }, }; -use openmina_core::snark::{Snark, SnarkJobId}; -use openmina_core::thread; -use openmina_core::{block::AppliedBlock, constants::constraint_constants}; - use mina_signer::CompressedPubKey; -use openmina_core::block::ArcBlockWithHash; - -use crate::block_producer::StagedLedgerDiffCreateOutput; -use crate::p2p::channels::rpc::StagedLedgerAuxAndPendingCoinbases; -use crate::rpc::{ - RpcScanStateSummaryBlockTransaction, RpcScanStateSummaryScanStateJob, - RpcScanStateSummaryScanStateJobKind, RpcSnarkPoolJobSnarkWorkDone, -}; -use crate::transition_frontier::sync::{ - ledger::staged::StagedLedgerAuxAndPendingCoinbasesValid, - TransitionFrontierRootSnarkedLedgerUpdates, +use openmina_core::{ + block::AppliedBlock, + block::ArcBlockWithHash, + constants::constraint_constants, + snark::{Snark, SnarkJobId}, + thread, }; -use crate::{account::AccountPublicKey, transition_frontier::genesis::empty_pending_coinbase_hash}; - -use super::write::CommitResult; - -use super::{ - ledger_empty_hash_at_depth, read::LedgerReadResponse, write::LedgerWriteResponse, - LedgerAddress, LedgerEvent, LEDGER_DEPTH, -}; -use super::{ - read::{LedgerReadId, LedgerReadRequest}, - write::LedgerWriteRequest, +use std::{ + collections::{BTreeMap, BTreeSet}, + path::Path, + sync::Arc, }; fn merkle_root(mask: &mut Mask) -> LedgerHash { @@ -829,15 +830,16 @@ impl LedgerCtx { }) .unwrap_or_default(); - let available_jobs = self - .staged_ledger_mut(new_best_tip.staged_ledger_hashes()) - .map(|l| { - l.scan_state() - .all_job_pairs_iter() - .map(|job| job.map(|single| AvailableJobMessage::from(single))) - .collect() - }) - .unwrap_or_default(); + let available_jobs = Arc::new( + self.staged_ledger_mut(new_best_tip.staged_ledger_hashes()) + .map(|l| { + l.scan_state() + .all_job_pairs_iter() + .map(|job| job.map(|single| AvailableJobMessage::from(single))) + .collect() + }) + .unwrap_or_default(), + ); CommitResult { available_jobs, @@ -856,7 +858,7 @@ impl LedgerCtx { let num_accounts = mask.num_accounts() as u64; let first_node_addr = ledger::Address::first( - LEDGER_DEPTH - super::tree_height_for_num_accounts(num_accounts), + LEDGER_DEPTH.saturating_sub(super::tree_height_for_num_accounts(num_accounts)), ); let hash = LedgerHash::from_fp(mask.get_hash(first_node_addr)?); Some((num_accounts, hash)) @@ -1047,7 +1049,7 @@ impl LedgerCtx { emitted_ledger_proof: res .ledger_proof .map(|(proof, ..)| (&proof).into()) - .map(Box::new), + .map(Arc::new), pending_coinbase_update: (&res.pending_coinbase_update.1).into(), pending_coinbase_witness: MinaBasePendingCoinbaseWitnessStableV2 { pending_coinbases: pending_coinbase_witness, diff --git a/node/src/ledger/ledger_state.rs b/node/src/ledger/ledger_state.rs index 2daaf6982d..8f92156bd2 100644 --- a/node/src/ledger/ledger_state.rs +++ b/node/src/ledger/ledger_state.rs @@ -1,10 +1,7 @@ +use super::{read::LedgerReadState, write::LedgerWriteState, LedgerConfig}; use serde::{Deserialize, Serialize}; -use super::read::LedgerReadState; -use super::write::LedgerWriteState; -use super::LedgerConfig; - -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct LedgerState { pub write: LedgerWriteState, pub read: LedgerReadState, @@ -12,9 +9,6 @@ pub struct LedgerState { impl LedgerState { pub fn new(_config: LedgerConfig) -> Self { - Self { - write: Default::default(), - read: Default::default(), - } + Self::default() } } diff --git a/node/src/ledger/mod.rs b/node/src/ledger/mod.rs index c6ccf614eb..d30ba68896 100644 --- a/node/src/ledger/mod.rs +++ b/node/src/ledger/mod.rs @@ -16,11 +16,9 @@ pub use ledger_state::*; mod ledger_reducer; -mod ledger_effects; -pub use ledger_effects::*; - mod ledger_service; pub use ledger_service::*; + pub mod ledger_manager; pub use ledger::AccountIndex as LedgerAccountIndex; @@ -40,7 +38,7 @@ lazy_static::lazy_static! { use ledger::TreeVersion; std::array::from_fn(|i| { - let hash = ledger::V2::empty_hash_at_height(LEDGER_DEPTH - i); + let hash = ledger::V2::empty_hash_at_height(LEDGER_DEPTH.saturating_sub(i)); v2::MinaBaseLedgerHash0StableV1(hash.into()).into() }) }; @@ -61,7 +59,7 @@ pub fn complete_height_tree_with_empties( let content_hash = content_hash.0.to_field()?; let computed_hash = (subtree_height..LEDGER_DEPTH).fold(content_hash, |prev_hash, height| { - let depth = LEDGER_DEPTH - height; + let depth = LEDGER_DEPTH.saturating_sub(height); let empty_right = ledger_empty_hash_at_depth(depth).0.to_field().unwrap(); // We know empties are valid ledger::V2::hash_node(height, prev_hash, empty_right) }); @@ -109,7 +107,7 @@ pub fn hash_node_at_depth( left: mina_hasher::Fp, right: mina_hasher::Fp, ) -> mina_hasher::Fp { - let height = LEDGER_DEPTH - depth - 1; + let height = LEDGER_DEPTH.saturating_sub(depth).saturating_sub(1); ledger::V2::hash_node(height, left, right) } diff --git a/node/src/ledger/read/ledger_read_actions.rs b/node/src/ledger/read/ledger_read_actions.rs index 7d9689952a..b286585c83 100644 --- a/node/src/ledger/read/ledger_read_actions.rs +++ b/node/src/ledger/read/ledger_read_actions.rs @@ -5,7 +5,6 @@ use super::{ LedgerReadResponse, }; -pub type LedgerReadActionWithMeta = redux::ActionWithMeta; pub type LedgerReadActionWithMetaRef<'a> = redux::ActionWithMeta<&'a LedgerReadAction>; #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/node/src/ledger/read/ledger_read_reducer.rs b/node/src/ledger/read/ledger_read_reducer.rs index 1b51861a52..52b4c9720b 100644 --- a/node/src/ledger/read/ledger_read_reducer.rs +++ b/node/src/ledger/read/ledger_read_reducer.rs @@ -1,20 +1,421 @@ -use super::{LedgerReadAction, LedgerReadActionWithMetaRef, LedgerReadState}; +use mina_p2p_messages::v2; +use openmina_core::{bug_condition, requests::RequestId}; +use p2p::{ + channels::{ + rpc::{P2pChannelsRpcAction, P2pRpcId, P2pRpcRequest, P2pRpcResponse}, + streaming_rpc::{P2pChannelsStreamingRpcAction, P2pStreamingRpcRequest}, + }, + P2pAction, PeerId, +}; +use redux::Dispatcher; + +use crate::{ + block_producer::vrf_evaluator::BlockProducerVrfEvaluatorAction, + ledger_effectful::LedgerEffectfulAction, Action, RpcAction, State, Substate, +}; + +use super::{ + LedgerAddress, LedgerReadAction, LedgerReadActionWithMetaRef, LedgerReadIdType, + LedgerReadInitCallback, LedgerReadRequest, LedgerReadResponse, + LedgerReadStagedLedgerAuxAndPendingCoinbases, LedgerReadState, +}; impl LedgerReadState { - pub fn reducer(&mut self, action: LedgerReadActionWithMetaRef<'_>) { + pub fn reducer(mut state_context: Substate, action: LedgerReadActionWithMetaRef<'_>) { let (action, meta) = action.split(); + let Ok(state) = state_context.get_substate_mut() else { + return; + }; + match action { - LedgerReadAction::FindTodos => {} - LedgerReadAction::Init { .. } => {} + LedgerReadAction::FindTodos => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + Self::next_read_requests_init(dispatcher, state); + } + LedgerReadAction::Init { request, callback } => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if state.ledger.read.has_same_request(request) { + return; + } + + let id = state.ledger.read.next_req_id(); + dispatcher.push(LedgerEffectfulAction::ReadInit { + request: request.clone(), + callback: callback.clone(), + id, + }); + } LedgerReadAction::Pending { request, .. } => { - self.add(meta.time(), request.clone()); + state.add(meta.time(), request.clone()); } LedgerReadAction::Success { id, response } => { - self.add_response(*id, meta.time(), response.clone()); + state.add_response(*id, meta.time(), response.clone()); + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + Self::propagate_read_response(dispatcher, state, *id, response.clone()); + dispatcher.push(LedgerReadAction::Prune { id: *id }); } LedgerReadAction::Prune { id } => { - self.remove(*id); + state.remove(*id); + } + } + } + + fn propagate_read_response( + dispatcher: &mut Dispatcher, + state: &State, + id: RequestId, + response: LedgerReadResponse, + ) { + let Some(request) = state.ledger.read.get(id) else { + bug_condition!("Request with id: {} not found", id); + return; + }; + + match (request.request(), response) { + ( + LedgerReadRequest::DelegatorTable(ledger_hash, pub_key), + LedgerReadResponse::DelegatorTable(table), + ) => { + let expected = state.block_producer.vrf_delegator_table_inputs(); + if !expected.map_or(false, |(expected_hash, producer)| { + ledger_hash == expected_hash && pub_key == producer + }) { + bug_condition!("delegator table unexpected"); + return; + } + match table { + None => { + // TODO(tizoc): Revise this, may be better to dispatch a different action here + // and avoid running the VRF evaluator altogether when we know that the + // table is empty. + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { + delegator_table: Default::default(), + }, + ); + } + Some(table) => { + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { + delegator_table: table.into(), + }, + ); + } + } + } + (_, LedgerReadResponse::DelegatorTable(..)) => unreachable!(), + (req, LedgerReadResponse::GetNumAccounts(resp)) => { + for (peer_id, id, _) in find_peers_with_ledger_rpc(state, req) { + dispatcher.push(P2pChannelsRpcAction::ResponseSend { + peer_id, + id, + response: resp.as_ref().map(|(num_accounts, hash)| { + Box::new(P2pRpcResponse::LedgerQuery( + v2::MinaLedgerSyncLedgerAnswerStableV2::NumAccounts( + (*num_accounts).into(), + hash.clone(), + ), + )) + }), + }); + } + } + (req, LedgerReadResponse::GetChildHashesAtAddr(resp)) => { + for (peer_id, id, _) in find_peers_with_ledger_rpc(state, req) { + dispatcher.push(P2pChannelsRpcAction::ResponseSend { + peer_id, + id, + response: resp.as_ref().map(|(left, right)| { + Box::new(P2pRpcResponse::LedgerQuery( + v2::MinaLedgerSyncLedgerAnswerStableV2::ChildHashesAre( + left.clone(), + right.clone(), + ), + )) + }), + }); + } + } + (req, LedgerReadResponse::GetChildAccountsAtAddr(resp)) => { + for (peer_id, id, _) in find_peers_with_ledger_rpc(state, req) { + dispatcher.push(P2pChannelsRpcAction::ResponseSend { + peer_id, + id, + response: resp.as_ref().map(|accounts| { + Box::new(P2pRpcResponse::LedgerQuery( + v2::MinaLedgerSyncLedgerAnswerStableV2::ContentsAre( + accounts.iter().cloned().collect(), + ), + )) + }), + }); + } + } + (req, LedgerReadResponse::GetStagedLedgerAuxAndPendingCoinbases(resp)) => { + for (peer_id, id, is_streaming) in find_peers_with_ledger_rpc(state, req) { + if is_streaming { + dispatcher.push(P2pChannelsStreamingRpcAction::ResponseSendInit { + peer_id, + id, + response: resp.clone().map(Into::into), + }); + } else { + dispatcher.push(P2pChannelsRpcAction::ResponseSend { + peer_id, + id, + response: resp.clone().map(|data| { + Box::new(P2pRpcResponse::StagedLedgerAuxAndPendingCoinbasesAtBlock( + data, + )) + }), + }); + } + } + } + ( + LedgerReadRequest::ScanStateSummary(ledger_hash), + LedgerReadResponse::ScanStateSummary(scan_state), + ) => { + for rpc_id in state + .rpc + .scan_state_summary_rpc_ids() + .filter(|(_, hash, _)| *hash == ledger_hash) + .map(|(id, ..)| id) + .collect::>() + { + dispatcher.push(RpcAction::ScanStateSummaryGetSuccess { + rpc_id, + scan_state: scan_state.clone(), + }); + } + } + (_, LedgerReadResponse::ScanStateSummary(..)) => unreachable!(), + (_req, LedgerReadResponse::GetAccounts(..)) => todo!(), + (_, LedgerReadResponse::AccountsForRpc(rpc_id, accounts, account_query)) => { + dispatcher.push(RpcAction::LedgerAccountsGetSuccess { + rpc_id, + accounts, + account_query, + }); + } + } + } + + fn next_read_requests_init(dispatcher: &mut Dispatcher, state: &State) { + // fetching delegator table, this is required because delegator table construction requires reading from ledger. + // It could be that ledger read quota was reached when vrf tried to initiate that read, so we need to "retry" it if that's the case + dispatcher.push(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); + + // p2p rpcs + let mut peers = state + .p2p + .ready_peers_iter() + .filter(|(_, peer)| { + peer.channels + .rpc + .remote_todo_requests_iter() + .next() + .is_some() + || peer.channels.streaming_rpc.remote_todo_request().is_some() + }) + .map(|(peer_id, peer)| (*peer_id, peer.channels.rpc_remote_last_responded())) + .collect::>(); + peers.sort_by_key(|(_, last_responded)| *last_responded); + for (peer_id, _) in peers { + let Some((id, request, is_streaming)) = None.or_else(|| { + let peer = state.p2p.ready()?.get_ready_peer(&peer_id)?; + let mut reqs = peer.channels.rpc.remote_todo_requests_iter(); + reqs.find_map(|req| { + let ledger_request = match &req.request { + P2pRpcRequest::LedgerQuery(hash, query) => match query { + v2::MinaLedgerSyncLedgerQueryStableV1::NumAccounts => { + LedgerReadRequest::GetNumAccounts(hash.clone()) + } + v2::MinaLedgerSyncLedgerQueryStableV1::WhatChildHashes(addr) => { + LedgerReadRequest::GetChildHashesAtAddr(hash.clone(), addr.into()) + } + v2::MinaLedgerSyncLedgerQueryStableV1::WhatContents(addr) => { + LedgerReadRequest::GetChildAccountsAtAddr(hash.clone(), addr.into()) + } + }, + P2pRpcRequest::StagedLedgerAuxAndPendingCoinbasesAtBlock(block_hash) => { + build_staged_ledger_parts_request(state, block_hash)? + } + _ => return None, + }; + + Some((req.id, ledger_request, false)) + }) + .or_else(|| { + let (id, req) = peer.channels.streaming_rpc.remote_todo_request()?; + let ledger_request = match req { + P2pStreamingRpcRequest::StagedLedgerParts(block_hash) => { + build_staged_ledger_parts_request(state, block_hash)? + } + }; + Some((id, ledger_request, true)) + }) + }) else { + continue; + }; + + dispatcher.push(LedgerReadAction::Init { + request, + callback: LedgerReadInitCallback::P2pChannelsResponsePending + { callback: redux::callback!(on_ledger_read_init_p2p_channels_response_pending((is_streaming: bool, id: P2pRpcId, peer_id: PeerId)) -> crate::Action{ + if is_streaming { + P2pAction::from(P2pChannelsStreamingRpcAction::ResponsePending { + peer_id, + id, + }) + } else { + P2pAction::from(P2pChannelsRpcAction::ResponsePending { + peer_id, + id, + }) + } + }), + args:(is_streaming, id, peer_id) + } + }); + + if !state.ledger.read.is_total_cost_under_limit() { + return; + } + } + + // rpcs + let rpcs = state + .rpc + .scan_state_summary_rpc_ids() + .filter(|(.., status)| status.is_init()) + .map(|(id, ..)| id) + .collect::>(); + + for rpc_id in rpcs { + dispatcher.push(RpcAction::ScanStateSummaryLedgerGetInit { rpc_id }); + if !state.ledger.read.is_total_cost_under_limit() { + return; + } + } + + let ledger_account_rpc = state + .rpc + .accounts_request_rpc_ids() + .filter(|(.., status)| status.is_init()) + .map(|(id, req, _)| (id, req)) + .collect::>(); + + for (rpc_id, req) in ledger_account_rpc { + dispatcher.push(RpcAction::LedgerAccountsGetInit { + rpc_id, + account_query: req, + }); + if !state.ledger.read.is_total_cost_under_limit() { + return; } } } } + +fn find_peers_with_ledger_rpc( + state: &crate::State, + req: &LedgerReadRequest, +) -> Vec<(PeerId, P2pRpcId, bool)> { + let Some(p2p) = state.p2p.ready() else { + return Vec::new(); + }; + p2p.ready_peers_iter() + .flat_map(|(peer_id, peer)| { + let rpcs = peer + .channels + .rpc + .remote_pending_requests_iter() + .map(move |req| (peer_id, req.id, &req.request)) + .filter(|(_, _, peer_req)| match (req, peer_req) { + ( + LedgerReadRequest::GetNumAccounts(h1), + P2pRpcRequest::LedgerQuery( + h2, + v2::MinaLedgerSyncLedgerQueryStableV1::NumAccounts, + ), + ) => h1 == h2, + ( + LedgerReadRequest::GetChildHashesAtAddr(h1, addr1), + P2pRpcRequest::LedgerQuery( + h2, + v2::MinaLedgerSyncLedgerQueryStableV1::WhatChildHashes(addr2), + ), + ) => h1 == h2 && addr1 == &LedgerAddress::from(addr2), + ( + LedgerReadRequest::GetChildAccountsAtAddr(h1, addr1), + P2pRpcRequest::LedgerQuery( + h2, + v2::MinaLedgerSyncLedgerQueryStableV1::WhatContents(addr2), + ), + ) => h1 == h2 && addr1 == &LedgerAddress::from(addr2), + ( + LedgerReadRequest::GetStagedLedgerAuxAndPendingCoinbases(data), + P2pRpcRequest::StagedLedgerAuxAndPendingCoinbasesAtBlock(block_hash), + ) => state + .transition_frontier + .get_state_body(block_hash) + .map_or(false, |b| { + b.blockchain_state.staged_ledger_hash == data.ledger_hash + }), + _ => false, + }) + .map(|(peer_id, rpc_id, _)| (*peer_id, rpc_id, false)); + let streaming_rpcs = peer + .channels + .streaming_rpc + .remote_pending_request() + .into_iter() + .filter(|(_, peer_req)| match (req, peer_req) { + ( + LedgerReadRequest::GetStagedLedgerAuxAndPendingCoinbases(data), + P2pStreamingRpcRequest::StagedLedgerParts(block_hash), + ) => state + .transition_frontier + .get_state_body(block_hash) + .map_or(false, |b| { + b.blockchain_state.staged_ledger_hash == data.ledger_hash + }), + _ => false, + }) + .map(|(rpc_id, _)| (*peer_id, rpc_id, true)); + rpcs.chain(streaming_rpcs) + }) + .collect() +} + +fn build_staged_ledger_parts_request( + state: &crate::State, + block_hash: &v2::StateHash, +) -> Option { + let tf = &state.transition_frontier; + let ledger_hash = tf + .best_chain + .iter() + .find(|b| b.hash() == block_hash) + .map(|b| b.staged_ledger_hashes().clone())?; + let protocol_states = tf + .needed_protocol_states + .iter() + .map(|(hash, b)| (hash.clone(), b.clone())) + .chain( + tf.best_chain + .iter() + .take_while(|b| b.hash() != block_hash) + .map(|b| (b.hash().clone(), b.header().protocol_state.clone())), + ) + .collect(); + + Some(LedgerReadRequest::GetStagedLedgerAuxAndPendingCoinbases( + LedgerReadStagedLedgerAuxAndPendingCoinbases { + ledger_hash, + protocol_states, + }, + )) +} diff --git a/node/src/ledger/write/ledger_write_actions.rs b/node/src/ledger/write/ledger_write_actions.rs index 38e594f5d3..dee32e747f 100644 --- a/node/src/ledger/write/ledger_write_actions.rs +++ b/node/src/ledger/write/ledger_write_actions.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; use super::{LedgerWriteRequest, LedgerWriteResponse, LedgerWriteState}; -pub type LedgerWriteActionWithMeta = redux::ActionWithMeta; pub type LedgerWriteActionWithMetaRef<'a> = redux::ActionWithMeta<&'a LedgerWriteAction>; #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/node/src/ledger/write/ledger_write_reducer.rs b/node/src/ledger/write/ledger_write_reducer.rs index fb5c9b0ddf..7333bdb041 100644 --- a/node/src/ledger/write/ledger_write_reducer.rs +++ b/node/src/ledger/write/ledger_write_reducer.rs @@ -1,34 +1,163 @@ -use super::{LedgerWriteAction, LedgerWriteActionWithMetaRef, LedgerWriteState}; +use redux::Dispatcher; + +use crate::{ + ledger_effectful::LedgerEffectfulAction, + transition_frontier::sync::{ + ledger::staged::TransitionFrontierSyncLedgerStagedAction, TransitionFrontierSyncAction, + }, + Action, BlockProducerAction, State, Substate, +}; + +use super::{ + LedgerWriteAction, LedgerWriteActionWithMetaRef, LedgerWriteResponse, LedgerWriteState, +}; impl LedgerWriteState { - pub fn reducer(&mut self, action: LedgerWriteActionWithMetaRef<'_>) { + pub fn reducer(mut state_context: Substate, action: LedgerWriteActionWithMetaRef<'_>) { let (action, meta) = action.split(); + let Ok(state) = state_context.get_substate_mut() else { + return; + }; + match action { - LedgerWriteAction::Init { - request, - on_init: _, - } => { - *self = Self::Init { + LedgerWriteAction::Init { request, on_init } => { + *state = Self::Init { time: meta.time(), request: request.clone(), }; + + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(LedgerEffectfulAction::WriteInit { + request: request.clone(), + on_init: on_init.clone(), + }); } LedgerWriteAction::Pending => { - if let Self::Init { request, .. } = self { - *self = Self::Pending { + if let Self::Init { request, .. } = state { + *state = Self::Pending { time: meta.time(), request: request.clone(), }; } } LedgerWriteAction::Success { response } => { - if let Self::Pending { request, .. } = self { - *self = Self::Success { + if let Self::Pending { request, .. } = state { + *state = Self::Success { time: meta.time(), request: request.clone(), response: response.clone(), }; } + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + Self::propagate_write_response(dispatcher, state, response.clone()); + dispatcher.push(BlockProducerAction::StagedLedgerDiffCreateInit); + dispatcher.push(TransitionFrontierSyncAction::BlocksNextApplyInit); + dispatcher.push(TransitionFrontierSyncAction::CommitInit); + dispatcher.push(TransitionFrontierSyncLedgerStagedAction::ReconstructInit); + } + } + } + + fn propagate_write_response( + dispatcher: &mut Dispatcher, + state: &State, + response: LedgerWriteResponse, + ) { + let Some(request) = &state.ledger.write.request() else { + return; + }; + match (request, response) { + ( + _, + LedgerWriteResponse::StagedLedgerReconstruct { + staged_ledger_hash, + result, + }, + ) => { + let sync = &state.transition_frontier.sync; + let expected_ledger = sync + .ledger_target() + .and_then(|target| target.staged) + .map(|v| v.hashes.non_snark.ledger_hash); + if expected_ledger.as_ref() == Some(&staged_ledger_hash) { + match result { + Err(error) => { + dispatcher.push( + TransitionFrontierSyncLedgerStagedAction::ReconstructError { + error, + }, + ); + } + Ok(()) => { + dispatcher.push( + TransitionFrontierSyncLedgerStagedAction::ReconstructSuccess { + ledger_hash: staged_ledger_hash, + }, + ); + } + } + } + } + ( + _, + LedgerWriteResponse::StagedLedgerDiffCreate { + pred_block_hash, + global_slot_since_genesis, + result, + }, + ) => { + let Some((expected_pred_block_hash, expected_global_slot)) = None.or_else(|| { + let pred_block = state.block_producer.current_parent_chain()?.last()?; + let won_slot = state.block_producer.current_won_slot()?; + let slot = won_slot.global_slot_since_genesis(pred_block.global_slot_diff()); + Some((pred_block.hash(), slot)) + }) else { + return; + }; + + if &pred_block_hash == expected_pred_block_hash + && global_slot_since_genesis == expected_global_slot + { + match result { + Err(err) => todo!("handle staged ledger diff creation err: {err}"), + Ok(output) => { + dispatcher.push(BlockProducerAction::StagedLedgerDiffCreateSuccess { + output, + }); + } + } + } + } + ( + _, + LedgerWriteResponse::BlockApply { + block_hash: hash, + result, + }, + ) => match result { + Err(error) => { + dispatcher + .push(TransitionFrontierSyncAction::BlocksNextApplyError { hash, error }); + } + Ok(result) => { + dispatcher.push(TransitionFrontierSyncAction::BlocksNextApplySuccess { + hash, + just_emitted_a_proof: result.just_emitted_a_proof, + }); + } + }, + ( + _, + LedgerWriteResponse::Commit { + best_tip_hash, + result, + }, + ) => { + let best_tip = state.transition_frontier.sync.best_tip(); + if best_tip.map_or(false, |tip| tip.hash() == &best_tip_hash) { + dispatcher.push(TransitionFrontierSyncAction::CommitSuccess { result }); + } } } } diff --git a/node/src/ledger/write/mod.rs b/node/src/ledger/write/mod.rs index 88a8646a65..1ce5d91fa4 100644 --- a/node/src/ledger/write/mod.rs +++ b/node/src/ledger/write/mod.rs @@ -16,7 +16,7 @@ use ledger::scan_state::scan_state::AvailableJobMessage; use mina_p2p_messages::v2; use serde::{Deserialize, Serialize}; -use crate::block_producer::StagedLedgerDiffCreateOutput; +use crate::block_producer_effectful::StagedLedgerDiffCreateOutput; use crate::core::block::ArcBlockWithHash; use crate::core::snark::{Snark, SnarkJobId}; use crate::transition_frontier::sync::ledger::staged::StagedLedgerAuxAndPendingCoinbasesValid; @@ -70,7 +70,7 @@ pub enum LedgerWriteResponse { StagedLedgerDiffCreate { pred_block_hash: v2::StateHash, global_slot_since_genesis: v2::MinaNumbersGlobalSlotSinceGenesisMStableV1, - result: Result, String>, + result: Result, String>, }, BlockApply { block_hash: v2::StateHash, @@ -89,7 +89,7 @@ pub struct BlockApplyResult { #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct CommitResult { - pub available_jobs: Vec>, + pub available_jobs: Arc>>, pub needed_protocol_states: BTreeSet, } diff --git a/node/src/ledger_effectful/ledger_effectful_actions.rs b/node/src/ledger_effectful/ledger_effectful_actions.rs new file mode 100644 index 0000000000..548b518829 --- /dev/null +++ b/node/src/ledger_effectful/ledger_effectful_actions.rs @@ -0,0 +1,26 @@ +use crate::ledger::{ + read::{LedgerReadIdType, LedgerReadInitCallback, LedgerReadRequest}, + write::LedgerWriteRequest, +}; +use openmina_core::requests::RequestId; +use redux::Callback; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum LedgerEffectfulAction { + WriteInit { + request: LedgerWriteRequest, + on_init: Callback, + }, + ReadInit { + request: LedgerReadRequest, + callback: LedgerReadInitCallback, + id: RequestId, + }, +} + +impl redux::EnablingCondition for LedgerEffectfulAction { + fn is_enabled(&self, _state: &crate::State, _time: redux::Timestamp) -> bool { + true + } +} diff --git a/node/src/ledger_effectful/ledger_effectful_effects.rs b/node/src/ledger_effectful/ledger_effectful_effects.rs new file mode 100644 index 0000000000..ff3c20e831 --- /dev/null +++ b/node/src/ledger_effectful/ledger_effectful_effects.rs @@ -0,0 +1,50 @@ +use redux::ActionWithMeta; + +use crate::{ + ledger::{ + read::{LedgerReadAction, LedgerReadInitCallback}, + write::LedgerWriteAction, + LedgerService, + }, + Store, +}; + +use super::LedgerEffectfulAction; + +pub fn ledger_effectful_effects( + store: &mut Store, + action: ActionWithMeta, +) where + S: LedgerService, +{ + let (action, _meta) = action.split(); + + match action { + LedgerEffectfulAction::WriteInit { request, on_init } => { + store.service.write_init(request.clone()); + store.dispatch(LedgerWriteAction::Pending); + store.dispatch_callback(on_init, request); + } + LedgerEffectfulAction::ReadInit { + request, + callback, + id, + } => { + store.service.read_init(id, request.clone()); + store.dispatch(LedgerReadAction::Pending { id, request }); + + match callback { + LedgerReadInitCallback::RpcLedgerAccountsGetPending { callback, args } => { + store.dispatch_callback(callback, args); + } + LedgerReadInitCallback::RpcScanStateSummaryGetPending { callback, args } => { + store.dispatch_callback(callback, args); + } + LedgerReadInitCallback::P2pChannelsResponsePending { callback, args } => { + store.dispatch_callback(callback, args); + } + LedgerReadInitCallback::None => {} + } + } + } +} diff --git a/node/src/ledger_effectful/mod.rs b/node/src/ledger_effectful/mod.rs new file mode 100644 index 0000000000..c5b1f2ee46 --- /dev/null +++ b/node/src/ledger_effectful/mod.rs @@ -0,0 +1,5 @@ +mod ledger_effectful_actions; +pub use ledger_effectful_actions::LedgerEffectfulAction; + +mod ledger_effectful_effects; +pub use ledger_effectful_effects::ledger_effectful_effects; diff --git a/node/src/lib.rs b/node/src/lib.rs index 7e18daf5e8..38b7f2a7c4 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -30,11 +30,14 @@ pub mod recorder; pub mod stats; pub mod block_producer; +pub mod block_producer_effectful; pub mod consensus; pub mod daemon_json; pub mod event_source; pub mod external_snark_worker; +pub mod external_snark_worker_effectful; pub mod ledger; +pub mod ledger_effectful; pub mod logger; pub mod p2p; pub mod rpc; diff --git a/node/src/logger/logger_effects.rs b/node/src/logger/logger_effects.rs index 7f7a7d939a..8e60710c26 100644 --- a/node/src/logger/logger_effects.rs +++ b/node/src/logger/logger_effects.rs @@ -1,7 +1,6 @@ use openmina_core::log::inner::field::{display, DisplayValue}; use openmina_core::log::inner::Value; use openmina_core::log::{time_to_str, ActionEvent, EventContext}; -use p2p::channels::P2pChannelsEffectfulAction; use p2p::connection::P2pConnectionEffectfulAction; use p2p::{P2pNetworkConnectionError, P2pNetworkSchedulerAction, PeerId}; @@ -106,22 +105,7 @@ pub fn logger_effects(store: &Store, action: ActionWithMetaRef<'_ }, }, Action::P2pEffectful(action) => match action { - p2p::P2pEffectfulAction::Channels(action) => match action { - P2pChannelsEffectfulAction::SignalingDiscovery(action) => { - action.action_event(&context) - } - P2pChannelsEffectfulAction::SignalingExchange(action) => { - action.action_event(&context) - } - P2pChannelsEffectfulAction::BestTip(action) => action.action_event(&context), - P2pChannelsEffectfulAction::Rpc(action) => action.action_event(&context), - P2pChannelsEffectfulAction::StreamingRpc(action) => action.action_event(&context), - P2pChannelsEffectfulAction::SnarkJobCommitment(action) => { - action.action_event(&context) - } - P2pChannelsEffectfulAction::Snark(action) => action.action_event(&context), - P2pChannelsEffectfulAction::Transaction(action) => action.action_event(&context), - }, + p2p::P2pEffectfulAction::Channels(action) => action.action_event(&context), p2p::P2pEffectfulAction::Connection(action) => match action { P2pConnectionEffectfulAction::Outgoing(action) => action.action_event(&context), P2pConnectionEffectfulAction::Incoming(action) => action.action_event(&context), diff --git a/node/src/p2p/callbacks/p2p_callbacks_reducer.rs b/node/src/p2p/callbacks/p2p_callbacks_reducer.rs index 2b24054fba..0eb4826b90 100644 --- a/node/src/p2p/callbacks/p2p_callbacks_reducer.rs +++ b/node/src/p2p/callbacks/p2p_callbacks_reducer.rs @@ -1,6 +1,6 @@ use ark_ff::fields::arithmetic::InvalidBigInt; use mina_p2p_messages::v2::{MinaLedgerSyncLedgerAnswerStableV2, StateHash}; -use openmina_core::{block::BlockWithHash, bug_condition}; +use openmina_core::{block::BlockWithHash, bug_condition, transaction::TransactionWithHash}; use p2p::{ channels::{ best_tip::P2pChannelsBestTipAction, @@ -15,6 +15,7 @@ use redux::{ActionMeta, ActionWithMeta, Dispatcher}; use crate::{ p2p_ready, snark_pool::candidate::SnarkPoolCandidateAction, + transaction_pool::candidate::TransactionPoolCandidateAction, transition_frontier::sync::{ ledger::{ snarked::{ @@ -33,6 +34,14 @@ use crate::{ use super::P2pCallbacksAction; +fn get_rpc_request<'a>(state: &'a State, peer_id: &PeerId) -> Option<&'a P2pRpcRequest> { + state + .p2p + .get_ready_peer(peer_id) + .and_then(|s| s.channels.rpc.local_responded_request()) + .map(|(_, req)| req) +} + impl crate::State { pub fn p2p_callback_reducer( state_context: crate::Substate, @@ -73,6 +82,13 @@ impl crate::State { return; }; + dispatcher.push( + TransitionFrontierSyncLedgerSnarkedAction::PeerQueryNumAccountsError { + peer_id, + rpc_id, + error: PeerLedgerQueryError::Timeout, + }, + ); dispatcher.push( TransitionFrontierSyncLedgerSnarkedAction::PeerQueryAddressError { peer_id, @@ -102,7 +118,10 @@ impl crate::State { id, response, } => { - State::handle_rpc_channels_response(dispatcher, meta, *id, *peer_id, response); + let request = || get_rpc_request(state, peer_id); + State::handle_rpc_channels_response( + dispatcher, meta, *id, *peer_id, request, response, + ); dispatcher.push(TransitionFrontierSyncLedgerSnarkedAction::PeersQuery); dispatcher.push(TransitionFrontierSyncLedgerStagedAction::PartsPeerFetchInit); dispatcher.push(TransitionFrontierSyncAction::BlocksPeersQuery); @@ -257,6 +276,7 @@ impl crate::State { }) .for_each(|action| dispatcher.push(action)); + dispatcher.push(TransactionPoolCandidateAction::PeerPrune { peer_id }); dispatcher.push(SnarkPoolCandidateAction::PeerPrune { peer_id }); } P2pCallbacksAction::RpcRespondBestTip { peer_id } => { @@ -287,9 +307,7 @@ impl crate::State { let response = None.or_else(|| { let best_tip = best_chain.last()?; let mut chain_iter = best_chain.iter(); - let root_block = chain_iter.next(); - // when our best tip is genesis block. - let root_block = root_block.unwrap_or(best_tip); + let root_block = chain_iter.next()?; // TODO(binier): cache body hashes let Ok(body_hashes) = chain_iter .map(|b| b.header().protocol_state.body.try_hash()) @@ -334,6 +352,20 @@ impl crate::State { // async ledger request will be triggered // by `LedgerReadAction::FindTodos`. } + P2pRpcRequest::Transaction(hash) => { + let tx = state.transaction_pool.get(&hash); + let response = tx + .map(|v| v.forget_check()) + .map(|tx| (&tx).into()) + .map(P2pRpcResponse::Transaction) + .map(Box::new); + + dispatcher.push(P2pChannelsRpcAction::ResponseSend { + peer_id, + id, + response, + }); + } P2pRpcRequest::Snark(job_id) => { let job = state.snark_pool.get(&job_id); let response = job @@ -366,15 +398,39 @@ impl crate::State { } } - fn handle_rpc_channels_response( + fn handle_rpc_channels_response<'a>( dispatcher: &mut Dispatcher, meta: ActionMeta, id: u64, peer_id: PeerId, + request: impl FnOnce() -> Option<&'a P2pRpcRequest>, response: &Option>, ) { match response.as_deref() { None => { + match request() { + Some(P2pRpcRequest::Transaction(hash)) => { + let hash = hash.clone(); + dispatcher + .push(TransactionPoolCandidateAction::FetchError { peer_id, hash }); + return; + } + Some(P2pRpcRequest::Snark(job_id)) => { + let job_id = job_id.clone(); + dispatcher + .push(SnarkPoolCandidateAction::WorkFetchError { peer_id, job_id }); + return; + } + _ => {} + } + + dispatcher.push( + TransitionFrontierSyncLedgerSnarkedAction::PeerQueryNumAccountsError { + peer_id, + rpc_id: id, + error: PeerLedgerQueryError::DataUnavailable, + }, + ); dispatcher.push( TransitionFrontierSyncLedgerSnarkedAction::PeerQueryAddressError { peer_id, @@ -497,8 +553,19 @@ impl crate::State { response: block, }); } + Some(P2pRpcResponse::Transaction(transaction)) => { + match TransactionWithHash::try_new(transaction.clone()) { + Err(err) => bug_condition!("tx hashing failed: {err}"), + Ok(transaction) => { + dispatcher.push(TransactionPoolCandidateAction::FetchSuccess { + peer_id, + transaction, + }) + } + } + } Some(P2pRpcResponse::Snark(snark)) => { - dispatcher.push(SnarkPoolCandidateAction::WorkReceived { + dispatcher.push(SnarkPoolCandidateAction::WorkFetchSuccess { peer_id, work: snark.clone(), }); diff --git a/node/src/p2p/mod.rs b/node/src/p2p/mod.rs index 06d72936dc..991f4242c5 100644 --- a/node/src/p2p/mod.rs +++ b/node/src/p2p/mod.rs @@ -1,17 +1,6 @@ pub use ::p2p::*; use p2p::{ - channels::{ - best_tip_effectful::P2pChannelsBestTipEffectfulAction, - rpc_effectful::P2pChannelsRpcEffectfulAction, - signaling::{ - discovery_effectful::P2pChannelsSignalingDiscoveryEffectfulAction, - exchange_effectful::P2pChannelsSignalingExchangeEffectfulAction, - }, - snark_effectful::P2pChannelsSnarkEffectfulAction, - snark_job_commitment_effectful::P2pChannelsSnarkJobCommitmentEffectfulAction, - streaming_rpc_effectful::P2pChannelsStreamingRpcEffectfulAction, - transaction_effectful::P2pChannelsTransactionEffectfulAction, - }, + channels::P2pChannelsEffectfulAction, network::identify::stream_effectful::P2pNetworkIdentifyStreamEffectfulAction, }; @@ -138,17 +127,8 @@ impl_into_global_action!(effectful p2p::P2pNetworkPnetEffectfulAction); impl_into_global_action!(effectful connection::incoming_effectful::P2pConnectionIncomingEffectfulAction); impl_into_global_action!(effectful connection::outgoing_effectful::P2pConnectionOutgoingEffectfulAction); impl_into_global_action!(effectful p2p::disconnection_effectful::P2pDisconnectionEffectfulAction); -impl_into_global_action!( - effectful P2pChannelsSignalingDiscoveryEffectfulAction -); -impl_into_global_action!(effectful P2pChannelsSignalingExchangeEffectfulAction); -impl_into_global_action!(effectful P2pChannelsBestTipEffectfulAction); -impl_into_global_action!(effectful P2pChannelsStreamingRpcEffectfulAction); -impl_into_global_action!(effectful P2pChannelsTransactionEffectfulAction); -impl_into_global_action!(effectful P2pChannelsSnarkJobCommitmentEffectfulAction); -impl_into_global_action!(effectful P2pChannelsRpcEffectfulAction); -impl_into_global_action!(effectful P2pChannelsSnarkEffectfulAction); impl_into_global_action!(effectful network::pubsub::P2pNetworkPubsubEffectfulAction); impl_into_global_action!(effectful P2pNetworkIdentifyStreamEffectfulAction); +impl_into_global_action!(effectful P2pChannelsEffectfulAction); impl p2p::P2pActionTrait for crate::Action {} diff --git a/node/src/recorder/mod.rs b/node/src/recorder/mod.rs index 60ad237dda..b586927fcc 100644 --- a/node/src/recorder/mod.rs +++ b/node/src/recorder/mod.rs @@ -32,7 +32,7 @@ pub struct RecordedInitialState<'a> { pub state: Cow<'a, State>, } -impl<'a> RecordedInitialState<'a> { +impl RecordedInitialState<'_> { pub fn write_to(&self, writer: &mut W) -> postcard::Result<()> { postcard::to_io(self, writer).and(Ok(())) } @@ -49,7 +49,7 @@ pub struct RecordedActionWithMeta<'a> { pub action: Option>, } -impl<'a> RecordedActionWithMeta<'a> { +impl RecordedActionWithMeta<'_> { pub fn encode(&self) -> postcard::Result> { postcard::to_stdvec(self) } diff --git a/node/src/recorder/recorder.rs b/node/src/recorder/recorder.rs index eacd8560c3..081c88b011 100644 --- a/node/src/recorder/recorder.rs +++ b/node/src/recorder/recorder.rs @@ -38,7 +38,7 @@ impl Recorder { actions_files.push(Some(file)); Self::OnlyInputActions { - recorder_i: actions_files.len() - 1, + recorder_i: actions_files.len().saturating_sub(1), recorder_path: path, actions_f_bytes_written: 0, actions_f_index, @@ -90,12 +90,14 @@ impl Recorder { }; let mut files = ACTIONS_F.try_lock().unwrap(); - let cur_f = &mut files[*recorder_i]; + let cur_f = files.get_mut(*recorder_i).unwrap(); // TODO: error propagation let file = if *actions_f_bytes_written > 64 * 1024 * 1024 { cur_f.take().unwrap().sync_all().unwrap(); *actions_f_bytes_written = 0; - *actions_f_index += 1; + *actions_f_index = actions_f_index + .checked_add(1) + .expect("overflow in actions_f_index"); cur_f.insert( fs::File::create(super::actions_path(recorder_path, *actions_f_index)) .unwrap(), @@ -113,7 +115,12 @@ impl Recorder { writer.write_all(&encoded).unwrap(); writer.flush().unwrap(); - *actions_f_bytes_written += 8 + encoded.len() as u64; + *actions_f_bytes_written = actions_f_bytes_written + .checked_add( + 8u64.checked_add(encoded.len() as u64) + .expect("overflow in encoded len"), + ) + .expect("overflow in actions_f_bytes_written"); } } } diff --git a/node/src/reducer.rs b/node/src/reducer.rs index 4809b93858..060e710723 100644 --- a/node/src/reducer.rs +++ b/node/src/reducer.rs @@ -2,7 +2,10 @@ use openmina_core::{bug_condition, error, Substate}; use p2p::{P2pAction, P2pEffectfulAction, P2pInitializeAction, P2pState}; use crate::{ - rpc::RpcState, Action, ActionWithMeta, ConsensusAction, EventSourceAction, P2p, State, + external_snark_worker::ExternalSnarkWorkers, + rpc::RpcState, + state::{BlockProducerState, LedgerState}, + Action, ActionWithMeta, ConsensusAction, EventSourceAction, P2p, State, }; pub fn reducer( @@ -49,9 +52,10 @@ pub fn reducer( }, }, Action::P2pEffectful(_) => {} - Action::Ledger(a) => { - state.ledger.reducer(meta.with_action(a)); + Action::Ledger(action) => { + LedgerState::reducer(Substate::new(state, dispatcher), meta.with_action(action)); } + Action::LedgerEffects(_) => {} Action::Snark(a) => { snark::SnarkState::reducer(Substate::new(state, dispatcher), meta.with_action(a)); } @@ -81,14 +85,17 @@ pub fn reducer( ); } Action::TransactionPoolEffect(_) => {} - Action::BlockProducer(a) => { - state - .block_producer - .reducer(meta.with_action(a), &state.transition_frontier.best_chain); + Action::BlockProducer(action) => { + BlockProducerState::reducer(Substate::new(state, dispatcher), meta.with_action(action)); } - Action::ExternalSnarkWorker(a) => { - state.external_snark_worker.reducer(meta.with_action(a)); + Action::BlockProducerEffectful(_) => {} + Action::ExternalSnarkWorker(action) => { + ExternalSnarkWorkers::reducer( + Substate::new(state, dispatcher), + meta.with_action(action), + ); } + Action::ExternalSnarkWorkerEffects(_) => {} Action::Rpc(action) => { RpcState::reducer(Substate::new(state, dispatcher), meta.with_action(action)); } diff --git a/node/src/rpc/mod.rs b/node/src/rpc/mod.rs index 28def8f2e0..0433895af3 100644 --- a/node/src/rpc/mod.rs +++ b/node/src/rpc/mod.rs @@ -45,7 +45,9 @@ use crate::p2p::connection::outgoing::P2pConnectionOutgoingInitOpts; use crate::p2p::PeerId; use crate::snark_pool::{JobCommitment, JobSummary}; use crate::stats::actions::{ActionStatsForBlock, ActionStatsSnapshot}; -use crate::stats::block_producer::{BlockProductionAttempt, BlockProductionAttemptWonSlot}; +use crate::stats::block_producer::{ + BlockProductionAttempt, BlockProductionAttemptWonSlot, VrfEvaluatorStats, +}; use crate::stats::sync::SyncStatsSnapshot; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -294,7 +296,7 @@ pub struct RpcMessageProgressResponse { pub messages_stats: BTreeMap, pub staking_ledger_sync: Option, pub next_epoch_ledger_sync: Option, - pub root_ledger_sync: Option, + pub root_ledger_sync: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -309,6 +311,19 @@ pub struct LedgerSyncProgress { pub estimation: u64, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RootLedgerSyncProgress { + pub fetched: u64, + pub estimation: u64, + pub staged: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RootStagedLedgerSyncProgress { + pub fetched: u64, + pub total: u64, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CurrentMessageProgress { pub name: String, @@ -397,7 +412,7 @@ impl From for RpcTransactionInjectedCommand { // fee_token: signedcmd.fee_token(), from: signedcmd.fee_payer_pk().clone().into(), to: payment.receiver_pk.clone().into(), - hash: TransactionHash::from(value.hash.as_ref()).to_string(), + hash: value.hash.to_string(), is_delegation: false, // memo: signedcmd.payload.common.memo.clone(), memo: signedcmd.payload.common.memo.to_string(), @@ -440,6 +455,7 @@ pub struct RpcNodeStatus { pub peers: Vec, pub snark_pool: RpcNodeStatusSnarkPool, pub transaction_pool: RpcNodeStatusTransactionPool, + pub current_block_production_attempt: Option, } #[derive(Serialize, Debug, Clone)] @@ -466,6 +482,8 @@ pub struct RpcNodeStatusTransitionFrontierBlockSummary { #[derive(Serialize, Debug, Default, Clone)] pub struct RpcNodeStatusTransactionPool { pub transactions: usize, + pub transactions_for_propagation: usize, + pub transaction_candidates: usize, } #[derive(Serialize, Debug, Default, Clone)] @@ -478,10 +496,13 @@ pub struct RpcNodeStatusSnarkPool { pub struct RpcBlockProducerStats { pub current_time: redux::Timestamp, pub current_global_slot: Option, + pub current_epoch: Option, pub epoch_start: Option, pub epoch_end: Option, pub attempts: Vec, pub future_won_slots: Vec, + pub current_epoch_vrf_stats: Option, + pub vrf_stats: BTreeMap, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -640,7 +661,7 @@ pub mod discovery { peer_id: value.peer_id, libp2p: value.peer_id.try_into()?, key: value.key, - dist: this_key - value.key, + dist: this_key.distance(&value.key), addrs: value.addresses().clone(), connection: value.connection, }) diff --git a/node/src/rpc/rpc_reducer.rs b/node/src/rpc/rpc_reducer.rs index aae8af2b4a..89c85a9a8a 100644 --- a/node/src/rpc/rpc_reducer.rs +++ b/node/src/rpc/rpc_reducer.rs @@ -1,11 +1,12 @@ use openmina_core::{ block::AppliedBlock, bug_condition, - requests::{RequestId, RpcIdType}, + requests::{RequestId, RpcId, RpcIdType}, }; use p2p::{ connection::{incoming::P2pConnectionIncomingAction, outgoing::P2pConnectionOutgoingAction}, webrtc::P2pConnectionResponse, + PeerId, }; use redux::ActionWithMeta; @@ -79,11 +80,20 @@ impl RpcState { state.requests.insert(*rpc_id, rpc_state); let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pConnectionOutgoingAction::Init { opts: opts.clone(), rpc_id: Some(*rpc_id), + on_success: Some(redux::callback!( + on_p2p_connection_outgoing_rpc_connection_success((peer_id: PeerId, rpc_id: Option)) -> crate::Action { + let Some(rpc_id) = rpc_id else { + unreachable!("RPC ID not provided"); + }; + + RpcAction::P2pConnectionOutgoingPending{ rpc_id } + } + )), }); - dispatcher.push(RpcAction::P2pConnectionOutgoingPending { rpc_id: *rpc_id }); } RpcAction::P2pConnectionOutgoingPending { rpc_id } => { let Some(rpc) = state.requests.get_mut(rpc_id) else { diff --git a/node/src/rpc_effectful/rpc_effectful_action.rs b/node/src/rpc_effectful/rpc_effectful_action.rs index 5eb0423242..d1c7d41714 100644 --- a/node/src/rpc_effectful/rpc_effectful_action.rs +++ b/node/src/rpc_effectful/rpc_effectful_action.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use crate::{ external_snark_worker::{ExternalSnarkWorker, SnarkWorkId}, p2p::connection::P2pConnectionResponse, @@ -14,7 +12,7 @@ use ledger::{ scan_state::transaction_logic::{valid::UserCommand, zkapp_command::WithHash}, Account, }; -use mina_p2p_messages::v2::MinaBaseUserCommandStableV2; +use mina_p2p_messages::v2::{self, MinaBaseUserCommandStableV2}; use openmina_core::{ consensus::ConsensusConstants, requests::RpcId, snark::SnarkJobId, ActionEvent, }; @@ -111,7 +109,7 @@ pub enum RpcEffectfulAction { }, TransactionPool { rpc_id: RpcId, - response: Vec>>, + response: Vec>, }, LedgerAccountsGetSuccess { rpc_id: RpcId, diff --git a/node/src/rpc_effectful/rpc_effectful_effects.rs b/node/src/rpc_effectful/rpc_effectful_effects.rs index fc992fde8e..b2602d68f2 100644 --- a/node/src/rpc_effectful/rpc_effectful_effects.rs +++ b/node/src/rpc_effectful/rpc_effectful_effects.rs @@ -6,15 +6,15 @@ use crate::{ p2p_ready, rpc::{ AccountQuery, AccountSlim, ActionStatsQuery, ActionStatsResponse, CurrentMessageProgress, - LedgerSyncProgress, MessagesStats, RpcAction, RpcBlockProducerStats, - RpcMessageProgressResponse, RpcNodeStatus, RpcNodeStatusTransactionPool, - RpcNodeStatusTransitionFrontier, RpcNodeStatusTransitionFrontierBlockSummary, - RpcNodeStatusTransitionFrontierSync, RpcRequestExtraData, RpcScanStateSummary, - RpcScanStateSummaryBlock, RpcScanStateSummaryBlockTransaction, - RpcScanStateSummaryBlockTransactionKind, RpcScanStateSummaryScanStateJob, - RpcSnarkPoolJobFull, RpcSnarkPoolJobSnarkWork, RpcSnarkPoolJobSummary, - RpcSnarkerJobCommitResponse, RpcSnarkerJobSpecResponse, RpcTransactionInjectResponse, - TransactionStatus, + MessagesStats, RootLedgerSyncProgress, RootStagedLedgerSyncProgress, RpcAction, + RpcBlockProducerStats, RpcMessageProgressResponse, RpcNodeStatus, + RpcNodeStatusTransactionPool, RpcNodeStatusTransitionFrontier, + RpcNodeStatusTransitionFrontierBlockSummary, RpcNodeStatusTransitionFrontierSync, + RpcRequestExtraData, RpcScanStateSummary, RpcScanStateSummaryBlock, + RpcScanStateSummaryBlockTransaction, RpcScanStateSummaryBlockTransactionKind, + RpcScanStateSummaryScanStateJob, RpcSnarkPoolJobFull, RpcSnarkPoolJobSnarkWork, + RpcSnarkPoolJobSummary, RpcSnarkerJobCommitResponse, RpcSnarkerJobSpecResponse, + RpcTransactionInjectResponse, TransactionStatus, }, snark_pool::SnarkPoolAction, transition_frontier::sync::{ @@ -26,12 +26,12 @@ use ledger::{ scan_state::currency::{Balance, Magnitude}, Account, }; -use mina_p2p_messages::{ - rpc_kernel::QueryHeader, - v2::{MinaBaseTransactionStatusStableV2, TransactionHash}, -}; +use mina_p2p_messages::{rpc_kernel::QueryHeader, v2::MinaBaseTransactionStatusStableV2}; use mina_signer::CompressedPubKey; use openmina_core::block::ArcBlockWithHash; +use p2p::channels::streaming_rpc::{ + staged_ledger_parts::calc_total_pieces_to_transfer, P2pStreamingRpcReceiveProgress, +}; use redux::ActionWithMeta; use std::{collections::BTreeMap, time::Duration}; @@ -61,6 +61,10 @@ pub fn rpc_effects(store: &mut Store, action: ActionWithMeta(store: &mut Store, action: ActionWithMeta(store: &mut Store, action: ActionWithMeta(store: &mut Store, action: ActionWithMeta(store: &mut Store, action: ActionWithMeta match &state.ledger { TransitionFrontierSyncLedgerState::Snarked(state) => { - response.root_ledger_sync = state.estimation() + response.root_ledger_sync = + state.estimation().map(|data| RootLedgerSyncProgress { + fetched: data.fetched, + estimation: data.estimation, + staged: None, + }); } - TransitionFrontierSyncLedgerState::Staged(_) => { + TransitionFrontierSyncLedgerState::Staged(state) => { + let unknown_staged_progress = || RootStagedLedgerSyncProgress { + fetched: 0, + total: 1, + }; + let staged = match state.fetch_attempts() { + None => state.target_with_parts().map(|(_, parts)| { + let v = parts + .map(|parts| calc_total_pieces_to_transfer(parts)) + .unwrap_or(0); + RootStagedLedgerSyncProgress { + fetched: v, + total: v, + } + }), + Some(attempts) => attempts + .iter() + .find(|(_, s)| s.fetch_pending_rpc_id().is_some()) + .map(|(id, _)| id) + .and_then(|peer_id| store.state().p2p.get_ready_peer(peer_id)) + .map(|peer| { + match peer.channels.streaming_rpc.pending_local_rpc_progress() { + None => unknown_staged_progress(), + Some( + P2pStreamingRpcReceiveProgress::StagedLedgerParts( + progress, + ), + ) => { + let (fetched, total) = progress.progress(); + RootStagedLedgerSyncProgress { fetched, total } + } + } + }), + }; + // We want to answer with a result that will serve as a 100% complete process for the // frontend while it is still waiting for the staged ledger to complete. Could be cleaner. - response.root_ledger_sync = Some(LedgerSyncProgress { + response.root_ledger_sync = Some(RootLedgerSyncProgress { fetched: 1, estimation: 1, + staged, }); } _ => {} @@ -666,9 +733,7 @@ pub fn rpc_effects(store: &mut Store, action: ActionWithMeta(store: &mut Store, action: SnarkActionWithMe SnarkAction::BlockVerifyEffect(a) => { a.effects(&meta, store); } - SnarkAction::WorkVerify(a) => match a { - // TODO(tizoc): handle this logic with the on_error callback passed on the Init action - SnarkWorkVerifyAction::Error { req_id, .. } => { - let req = store.state().snark.work_verify.jobs.get(req_id); - let Some(req) = req else { return }; - let sender = req.sender().parse().unwrap(); - - store.dispatch(SnarkPoolCandidateAction::WorkVerifyError { - peer_id: sender, - verify_id: req_id, - }); - } - // TODO(tizoc): handle this logic with the on_success callback passed on the Init action - SnarkWorkVerifyAction::Success { req_id } => { - let req = store.state().snark.work_verify.jobs.get(req_id); - let Some(req) = req else { return }; - let sender = req.sender().parse().unwrap(); - let batch = req.batch().to_vec(); - - store.dispatch(SnarkPoolCandidateAction::WorkVerifySuccess { - peer_id: sender, - verify_id: req_id, - batch, - }); - } - SnarkWorkVerifyAction::Init { .. } => {} - SnarkWorkVerifyAction::Pending { .. } => {} - SnarkWorkVerifyAction::Finish { .. } => {} - }, + SnarkAction::WorkVerify(_) => {} SnarkAction::WorkVerifyEffect(a) => { a.effects(&meta, store); } diff --git a/node/src/snark_pool/candidate/snark_pool_candidate_actions.rs b/node/src/snark_pool/candidate/snark_pool_candidate_actions.rs index 457a51f08f..7ddbb16731 100644 --- a/node/src/snark_pool/candidate/snark_pool_candidate_actions.rs +++ b/node/src/snark_pool/candidate/snark_pool_candidate_actions.rs @@ -31,7 +31,11 @@ pub enum SnarkPoolCandidateAction { job_id: SnarkJobId, rpc_id: P2pRpcId, }, - WorkReceived { + WorkFetchError { + peer_id: PeerId, + job_id: SnarkJobId, + }, + WorkFetchSuccess { peer_id: PeerId, work: Snark, }, @@ -91,7 +95,14 @@ impl redux::EnablingCondition for SnarkPoolCandidateAction { .map_or(false, |s| { matches!(s, SnarkPoolCandidateState::InfoReceived { .. }) }), - SnarkPoolCandidateAction::WorkReceived { peer_id, work } => { + SnarkPoolCandidateAction::WorkFetchError { peer_id, job_id } => state + .snark_pool + .candidates + .get(*peer_id, job_id) + .map_or(false, |s| { + matches!(s, SnarkPoolCandidateState::WorkFetchPending { .. }) + }), + SnarkPoolCandidateAction::WorkFetchSuccess { peer_id, work } => { let job_id = work.job_id(); state.snark_pool.contains(&job_id) && state diff --git a/node/src/snark_pool/candidate/snark_pool_candidate_reducer.rs b/node/src/snark_pool/candidate/snark_pool_candidate_reducer.rs index 845ad0adab..97968688a8 100644 --- a/node/src/snark_pool/candidate/snark_pool_candidate_reducer.rs +++ b/node/src/snark_pool/candidate/snark_pool_candidate_reducer.rs @@ -85,7 +85,10 @@ impl SnarkPoolCandidatesState { } => { state.work_fetch_pending(meta.time(), peer_id, job_id, *rpc_id); } - SnarkPoolCandidateAction::WorkReceived { peer_id, work } => { + SnarkPoolCandidateAction::WorkFetchError { peer_id, job_id } => { + state.peer_work_remove(*peer_id, job_id); + } + SnarkPoolCandidateAction::WorkFetchSuccess { peer_id, work } => { state.work_received(meta.time(), *peer_id, work.clone()); } SnarkPoolCandidateAction::WorkVerifyNext => { diff --git a/node/src/snark_pool/candidate/snark_pool_candidate_state.rs b/node/src/snark_pool/candidate/snark_pool_candidate_state.rs index 0c8a9887f1..6a1bb5df62 100644 --- a/node/src/snark_pool/candidate/snark_pool_candidate_state.rs +++ b/node/src/snark_pool/candidate/snark_pool_candidate_state.rs @@ -271,6 +271,18 @@ impl SnarkPoolCandidatesState { } } + pub fn peer_work_remove(&mut self, peer_id: PeerId, job_id: &SnarkJobId) { + if let Some(works) = self.by_peer.get_mut(&peer_id) { + works.remove(job_id); + if let Some(peers) = self.by_job_id.get_mut(job_id) { + peers.remove(&peer_id); + if peers.is_empty() { + self.by_job_id.remove(job_id); + } + } + } + } + pub fn remove_inferior_snarks(&mut self, snark: &Snark) { let job_id = snark.job_id(); let by_peer = &mut self.by_peer; diff --git a/node/src/snark_pool/snark_pool_actions.rs b/node/src/snark_pool/snark_pool_actions.rs index 305f8f737e..b06e9796d0 100644 --- a/node/src/snark_pool/snark_pool_actions.rs +++ b/node/src/snark_pool/snark_pool_actions.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use ledger::scan_state::scan_state::transaction_snark::OneOrTwo; use ledger::scan_state::scan_state::AvailableJobMessage; use openmina_core::snark::{Snark, SnarkJobCommitment, SnarkJobId}; @@ -17,7 +19,7 @@ pub enum SnarkPoolAction { Candidate(SnarkPoolCandidateAction), JobsUpdate { - jobs: Vec>, + jobs: Arc>>, orphaned_snarks: Vec, }, AutoCreateCommitment, diff --git a/node/src/snark_pool/snark_pool_reducer.rs b/node/src/snark_pool/snark_pool_reducer.rs index ff3dbd1a1c..22312cc523 100644 --- a/node/src/snark_pool/snark_pool_reducer.rs +++ b/node/src/snark_pool/snark_pool_reducer.rs @@ -62,10 +62,7 @@ impl SnarkPoolState { .and_then(|job| job.snark.as_ref()) .map_or(true, |old_snark| snark.work > old_snark.work); if take { - if let Some(mut job) = state.remove(&id) { - job.snark = Some(snark.clone()); - state.insert(job); - } + state.set_snark_work(snark.clone()); } } @@ -154,15 +151,11 @@ impl SnarkPoolState { } } SnarkPoolAction::CommitmentAdd { commitment, sender } => { - let Some(mut job) = state.remove(&commitment.job_id) else { - return; - }; - job.commitment = Some(JobCommitment { + state.set_commitment(JobCommitment { commitment: commitment.clone(), received_t: meta.time(), sender: *sender, }); - state.insert(job); // Dispatch let commitment = commitment.clone(); @@ -179,16 +172,11 @@ impl SnarkPoolState { } } SnarkPoolAction::WorkAdd { snark, sender } => { - let job_id = snark.job_id(); - let Some(mut job) = state.remove(&job_id) else { - return; - }; - job.snark = Some(SnarkWork { + state.set_snark_work(SnarkWork { work: snark.clone(), received_t: meta.time(), sender: *sender, }); - state.insert(job); state.candidates.remove_inferior_snarks(snark); // Dispatch @@ -233,10 +221,9 @@ impl SnarkPoolState { .channels .snark_job_commitment .next_send_index_and_limit(); - let (commitments, first_index, last_index) = - data_to_send(global_state, index_and_limit, |job| { - job.commitment_msg().cloned() - }); + let (commitments, first_index, last_index) = global_state + .snark_pool + .next_commitments_to_send(index_and_limit); let send_commitments = P2pChannelsSnarkJobCommitmentAction::ResponseSend { peer_id, @@ -248,7 +235,7 @@ impl SnarkPoolState { // Send snarks. let index_and_limit = peer.channels.snark.next_send_index_and_limit(); let (snarks, first_index, last_index) = - data_to_send(global_state, index_and_limit, |job| job.snark_msg()); + global_state.snark_pool.next_snarks_to_send(index_and_limit); dispatcher.push(send_commitments); dispatcher.push(P2pChannelsSnarkAction::ResponseSend { @@ -282,42 +269,3 @@ impl SnarkPoolState { } } } - -pub fn data_to_send( - state: &crate::State, - (index, limit): (u64, u8), - get_data: F, -) -> (Vec, u64, u64) -where - F: Fn(&JobState) -> Option, -{ - if limit == 0 { - let index = index.saturating_sub(1); - return (vec![], index, index); - } - - state - .snark_pool - .range(index..) - .try_fold( - (vec![], None), - |(mut list, mut first_index), (index, job)| { - if let Some(data) = get_data(job) { - let first_index = *first_index.get_or_insert(index); - list.push(data); - if list.len() >= limit as usize { - return Err((list, first_index, index)); - } - } - - Ok((list, first_index)) - }, - ) - // Loop iterated on whole snark pool. - .map(|(list, first_index)| { - let snark_pool_last_index = state.snark_pool.last_index(); - (list, first_index.unwrap_or(index), snark_pool_last_index) - }) - // Loop preemptively ended. - .unwrap_or_else(|v| v) -} diff --git a/node/src/snark_pool/snark_pool_state.rs b/node/src/snark_pool/snark_pool_state.rs index 74c142fe30..93ce18faa1 100644 --- a/node/src/snark_pool/snark_pool_state.rs +++ b/node/src/snark_pool/snark_pool_state.rs @@ -1,22 +1,21 @@ use std::time::Duration; -use std::{collections::BTreeMap, fmt, ops::RangeBounds}; +use std::{fmt, ops::RangeBounds}; use ledger::scan_state::scan_state::{transaction_snark::OneOrTwo, AvailableJobMessage}; use openmina_core::snark::{Snark, SnarkInfo, SnarkJobCommitment, SnarkJobId}; use redux::Timestamp; use serde::{Deserialize, Serialize}; +use crate::core::distributed_pool::DistributedPool; use crate::p2p::PeerId; use super::candidate::SnarkPoolCandidatesState; use super::SnarkPoolConfig; -#[derive(Clone)] +#[derive(Serialize, Deserialize, Clone)] pub struct SnarkPoolState { config: SnarkPoolConfig, - counter: u64, - list: BTreeMap, - by_ledger_hash_index: BTreeMap, + pool: DistributedPool, pub candidates: SnarkPoolCandidatesState, pub(super) last_check_timeouts: Timestamp, } @@ -63,76 +62,62 @@ impl SnarkPoolState { pub fn new() -> Self { Self { config: SnarkPoolConfig {}, - counter: 0, - list: Default::default(), - by_ledger_hash_index: Default::default(), + pool: Default::default(), candidates: SnarkPoolCandidatesState::new(), last_check_timeouts: Timestamp::ZERO, } } pub fn is_empty(&self) -> bool { - self.list.is_empty() + self.pool.is_empty() } pub fn last_index(&self) -> u64 { - self.list.last_key_value().map_or(0, |(k, _)| *k) + self.pool.last_index() } pub fn contains(&self, id: &SnarkJobId) -> bool { - self.by_ledger_hash_index - .get(id) - .map_or(false, |i| self.list.contains_key(i)) - } - - #[inline] - fn get_by_job_id<'a>( - by_job_id: &BTreeMap, - list: &'a BTreeMap, - id: &SnarkJobId, - ) -> Option<&'a JobState> { - by_job_id.get(id).and_then(|i| list.get(i)) + self.pool.contains(id) } pub fn get(&self, id: &SnarkJobId) -> Option<&JobState> { - Self::get_by_job_id(&self.by_ledger_hash_index, &self.list, id) + self.pool.get(id) } pub fn insert(&mut self, job: JobState) { - let id = job.id.clone(); - self.list.insert(self.counter, job); - self.by_ledger_hash_index.insert(id, self.counter); - self.counter += 1; + self.pool.insert(job) } pub fn remove(&mut self, id: &SnarkJobId) -> Option { - let index = self.by_ledger_hash_index.remove(id)?; - self.list.remove(&index) + self.pool.remove(id) + } + + pub fn set_snark_work(&mut self, snark: SnarkWork) -> Option { + self.pool + .update(&snark.work.job_id(), move |job| job.snark.replace(snark))? + } + + pub fn set_commitment(&mut self, commitment: JobCommitment) -> Option { + let job_id = commitment.commitment.job_id.clone(); + self.pool + .update(&job_id, move |job| job.commitment.replace(commitment))? } pub fn remove_commitment(&mut self, id: &SnarkJobId) -> Option { - let index = self.by_ledger_hash_index.get(id)?; - self.list.get_mut(index)?.commitment.take() + self.pool + .silent_update(id, |job_state| job_state.commitment.take())? } pub fn retain(&mut self, mut get_new_job_order: F) where F: FnMut(&SnarkJobId) -> Option, { - let list = &mut self.list; - self.by_ledger_hash_index - .retain(|id, index| match get_new_job_order(id) { - None => { - list.remove(index); - false - } + self.pool + .retain_and_update(|id, job| match get_new_job_order(id) { + None => false, Some(order) => { - if let Some(job) = list.get_mut(index) { - job.order = order; - true - } else { - false - } + job.order = order; + true } }); } @@ -141,7 +126,7 @@ impl SnarkPoolState { where R: RangeBounds, { - self.list.range(range).map(|(k, v)| (*k, v)) + self.pool.range(range) } pub fn should_create_commitment(&self, job_id: &SnarkJobId) -> bool { @@ -149,42 +134,21 @@ impl SnarkPoolState { } pub fn is_commitment_timed_out(&self, id: &SnarkJobId, time_now: Timestamp) -> bool { - self.by_ledger_hash_index.get(id).map_or(false, |i| { - self.is_commitment_timed_out_by_index(i, time_now) - }) - } - - pub fn is_commitment_timed_out_by_index(&self, index: &u64, time_now: Timestamp) -> bool { - let Some(job) = self.list.get(index) else { - return false; - }; - let Some(commitment) = job.commitment.as_ref() else { - return false; - }; - - let timeout = job.estimated_duration(); - let passed_time = time_now.checked_sub(commitment.commitment.timestamp()); - let is_timed_out = passed_time.map_or(false, |dur| dur >= timeout); - let didnt_deliver = job - .snark - .as_ref() - .map_or(true, |snark| snark.work < commitment.commitment); - - is_timed_out && didnt_deliver + self.get(id) + .map_or(false, |job| is_job_commitment_timed_out(job, time_now)) } pub fn timed_out_commitments_iter( &self, time_now: Timestamp, ) -> impl Iterator { - self.by_ledger_hash_index - .iter() - .filter(move |(_, index)| self.is_commitment_timed_out_by_index(index, time_now)) - .map(|(id, _)| id) + self.jobs_iter() + .filter(move |job| is_job_commitment_timed_out(job, time_now)) + .map(|job| &job.id) } pub fn jobs_iter(&self) -> impl Iterator { - self.list.values() + self.pool.states() } pub fn available_jobs_iter(&self) -> impl Iterator { @@ -194,7 +158,7 @@ impl SnarkPoolState { pub fn available_jobs_with_highest_priority(&self, n: usize) -> Vec<&JobState> { // find `n` jobs with lowest order (highest priority). self.available_jobs_iter() - .fold(Vec::with_capacity(n + 1), |mut jobs, job| { + .fold(Vec::with_capacity(n.saturating_add(1)), |mut jobs, job| { jobs.push(job); if jobs.len() > n { jobs.sort_by_key(|job| job.order); @@ -205,9 +169,8 @@ impl SnarkPoolState { } pub fn completed_snarks_iter(&self) -> impl '_ + Iterator { - self.list - .iter() - .filter_map(|(_, job)| job.snark.as_ref()) + self.jobs_iter() + .filter_map(|job| job.snark.as_ref()) .map(|snark| &snark.work) } @@ -217,7 +180,7 @@ impl SnarkPoolState { pub fn candidates_prune(&mut self) { self.candidates.retain(|id| { - let job = Self::get_by_job_id(&self.by_ledger_hash_index, &self.list, id); + let job = self.pool.get(id); move |candidate| match job { None => false, Some(job) => match job.snark.as_ref() { @@ -227,13 +190,41 @@ impl SnarkPoolState { } }); } + + pub fn next_commitments_to_send( + &self, + index_and_limit: (u64, u8), + ) -> (Vec, u64, u64) { + self.pool + .next_messages_to_send(index_and_limit, |job| job.commitment_msg().cloned()) + } + + pub fn next_snarks_to_send(&self, index_and_limit: (u64, u8)) -> (Vec, u64, u64) { + self.pool + .next_messages_to_send(index_and_limit, |job| job.snark_msg()) + } +} + +fn is_job_commitment_timed_out(job: &JobState, time_now: Timestamp) -> bool { + let Some(commitment) = job.commitment.as_ref() else { + return false; + }; + + let timeout = job.estimated_duration(); + let passed_time = time_now.checked_sub(commitment.commitment.timestamp()); + let is_timed_out = passed_time.map_or(false, |dur| dur >= timeout); + let didnt_deliver = job + .snark + .as_ref() + .map_or(true, |snark| snark.work < commitment.commitment); + + is_timed_out && didnt_deliver } impl fmt::Debug for SnarkPoolState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("JobCommitments") - .field("counter", &self.counter) - .field("len", &self.list.len()) + f.debug_struct("SnarkPoolState") + .field("pool", &self.pool) .finish() } } @@ -267,7 +258,9 @@ impl JobState { }; let account_updates = match &self.job { OneOrTwo::One(job) => account_updates(job), - OneOrTwo::Two((job1, job2)) => account_updates(job1) + account_updates(job2), + OneOrTwo::Two((job1, job2)) => account_updates(job1) + .checked_add(account_updates(job2)) + .expect("overflow"), }; if matches!( @@ -286,58 +279,18 @@ impl JobState { } } +impl AsRef for JobState { + fn as_ref(&self) -> &SnarkJobId { + &self.id + } +} + impl JobSummary { pub fn estimated_duration(&self) -> Duration { const BASE: Duration = Duration::from_secs(10); const MAX_LATENCY: Duration = Duration::from_secs(10); let (JobSummary::Tx(n) | JobSummary::Merge(n)) = self; - BASE * (*n as u32) + MAX_LATENCY - } -} - -mod ser { - use super::*; - use serde::ser::SerializeStruct; - - #[derive(Serialize, Deserialize)] - struct SnarkPool { - config: SnarkPoolConfig, - counter: u64, - list: BTreeMap, - candidates: SnarkPoolCandidatesState, - last_check_timeouts: Timestamp, - } - - impl Serialize for super::SnarkPoolState { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("SnarkPool", 5)?; - s.serialize_field("config", &self.config)?; - s.serialize_field("counter", &self.counter)?; - s.serialize_field("list", &self.list)?; - s.serialize_field("candidates", &self.candidates)?; - s.serialize_field("last_check_timeouts", &self.last_check_timeouts)?; - s.end() - } - } - impl<'de> Deserialize<'de> for super::SnarkPoolState { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let v = SnarkPool::deserialize(deserializer)?; - let by_ledger_hash_index = v.list.iter().map(|(k, v)| (v.id.clone(), *k)).collect(); - Ok(Self { - config: v.config, - counter: v.counter, - list: v.list, - by_ledger_hash_index, - candidates: v.candidates, - last_check_timeouts: v.last_check_timeouts, - }) - } + BASE.saturating_mul(*n as u32).saturating_add(MAX_LATENCY) } } diff --git a/node/src/state.rs b/node/src/state.rs index 76de2ce522..4fa33e5726 100644 --- a/node/src/state.rs +++ b/node/src/state.rs @@ -1,6 +1,9 @@ use std::sync::Arc; +use std::time::Duration; use mina_p2p_messages::v2::{MinaBaseUserCommandStableV2, MinaBlockBlockStableV2}; +use openmina_core::constants::PROTOCOL_VERSION; +use openmina_core::transaction::TransactionInfo; use rand::prelude::*; use openmina_core::block::BlockWithHash; @@ -24,9 +27,12 @@ use snark::block_verify::SnarkBlockVerifyState; use snark::user_command_verify::SnarkUserCommandVerifyState; use snark::work_verify::SnarkWorkVerifyState; +use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorState; pub use crate::block_producer::BlockProducerState; pub use crate::consensus::ConsensusState; -use crate::external_snark_worker::ExternalSnarkWorkers; +use crate::external_snark_worker::{ExternalSnarkWorker, ExternalSnarkWorkers}; +use crate::ledger::read::LedgerReadState; +use crate::ledger::write::LedgerWriteState; pub use crate::ledger::LedgerState; use crate::p2p::callbacks::P2pCallbacksAction; pub use crate::p2p::P2pState; @@ -35,6 +41,9 @@ pub use crate::snark::SnarkState; use crate::snark_pool::candidate::SnarkPoolCandidateAction; pub use crate::snark_pool::candidate::SnarkPoolCandidatesState; pub use crate::snark_pool::SnarkPoolState; +use crate::transaction_pool::candidate::{ + TransactionPoolCandidateAction, TransactionPoolCandidatesState, +}; use crate::transaction_pool::TransactionPoolState; use crate::transition_frontier::genesis::TransitionFrontierGenesisState; use crate::transition_frontier::sync::ledger::snarked::TransitionFrontierSyncLedgerSnarkedState; @@ -69,8 +78,27 @@ pub struct State { applied_actions_count: u64, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum BlockPrevalidationError { + GenesisNotReady, + ReceivedTooEarly { + current_global_slot: u32, + block_global_slot: u32, + }, + ReceivedTooLate { + current_global_slot: u32, + block_global_slot: u32, + delta: u32, + }, + InvalidGenesisProtocolState, + InvalidProtocolVersion, + MismatchedProtocolVersion, + ConsantsMismatch, + InvalidDeltaBlockChainProof, +} + // Substate accessors that will be used in reducers -use openmina_core::{impl_substate_access, SubstateAccess}; +use openmina_core::{bug_condition, impl_substate_access, SubstateAccess}; impl_substate_access!(State, SnarkState, snark); impl_substate_access!(State, SnarkBlockVerifyState, snark.block_verify); @@ -83,6 +111,11 @@ impl_substate_access!( impl_substate_access!(State, ConsensusState, consensus); impl_substate_access!(State, TransitionFrontierState, transition_frontier); impl_substate_access!(State, TransactionPoolState, transaction_pool); +impl_substate_access!( + State, + TransactionPoolCandidatesState, + transaction_pool.candidates +); impl_substate_access!( State, TransitionFrontierGenesisState, @@ -95,6 +128,10 @@ impl_substate_access!(State, ExternalSnarkWorkers, external_snark_worker); impl_substate_access!(State, BlockProducerState, block_producer); impl_substate_access!(State, RpcState, rpc); impl_substate_access!(State, WatchedAccountsState, watched_accounts); +impl_substate_access!(State, ExternalSnarkWorker, external_snark_worker.0); +impl_substate_access!(State, LedgerState, ledger); +impl_substate_access!(State, LedgerReadState, ledger.read); +impl_substate_access!(State, LedgerWriteState, ledger.write); impl openmina_core::SubstateAccess for State { fn substate(&self) -> openmina_core::SubstateResult<&P2pState> { @@ -128,6 +165,24 @@ impl openmina_core::SubstateAccess for State } } +impl SubstateAccess for State { + fn substate(&self) -> openmina_core::SubstateResult<&BlockProducerVrfEvaluatorState> { + self.block_producer + .as_ref() + .map(|state| &state.vrf_evaluator) + .ok_or_else(|| "Block producer VRF evaluator state unavailable".to_owned()) + } + + fn substate_mut( + &mut self, + ) -> openmina_core::SubstateResult<&mut BlockProducerVrfEvaluatorState> { + self.block_producer + .as_mut() + .map(|state| &mut state.vrf_evaluator) + .ok_or_else(|| "Block producer VRF evaluator state unavailable".to_owned()) + } +} + impl openmina_core::SubstateAccess for State { fn substate(&self) -> openmina_core::SubstateResult<&TransitionFrontierSyncLedgerSnarkedState> { self.transition_frontier @@ -261,7 +316,7 @@ impl State { /// and only there! pub fn action_applied(&mut self, action: &ActionWithMeta) { self.last_action = action.meta().clone(); - self.applied_actions_count += 1; + self.applied_actions_count = self.applied_actions_count.checked_add(1).expect("overflow"); } pub fn genesis_block(&self) -> Option { @@ -271,12 +326,18 @@ impl State { } fn cur_slot(&self, initial_slot: impl FnOnce(&ArcBlockWithHash) -> u32) -> Option { - let best_tip = self.transition_frontier.best_tip()?; - let best_tip_ms = u64::from(best_tip.timestamp()) / 1_000_000; + let genesis = self.genesis_block()?; + let initial_ms = u64::from(genesis.timestamp()) / 1_000_000; let now_ms = u64::from(self.time()) / 1_000_000; - let ms = now_ms.saturating_sub(best_tip_ms); - let slots = ms / constraint_constants().block_window_duration_ms; - Some(initial_slot(best_tip) + slots as u32) + let ms = now_ms.saturating_sub(initial_ms); + let slots = ms + .checked_div(constraint_constants().block_window_duration_ms) + .expect("division by 0"); + Some( + initial_slot(&genesis) + .checked_add(slots as u32) + .expect("overflow"), + ) } /// Current global slot based on constants and current time. @@ -287,7 +348,12 @@ impl State { } pub fn current_slot(&self) -> Option { - self.cur_slot(|b| b.global_slot() % b.constants().slots_per_epoch.as_u32()) + let slots_per_epoch = self.genesis_block()?.constants().slots_per_epoch.as_u32(); + Some( + self.cur_global_slot()? + .checked_rem(slots_per_epoch) + .expect("division by 0"), + ) } pub fn cur_global_slot_since_genesis(&self) -> Option { @@ -296,16 +362,109 @@ impl State { pub fn current_epoch(&self) -> Option { let slots_per_epoch = self.genesis_block()?.constants().slots_per_epoch.as_u32(); - Some(self.cur_global_slot()? / slots_per_epoch) + Some( + self.cur_global_slot()? + .checked_div(slots_per_epoch) + .expect("division by 0"), + ) + } + + pub fn producing_block_after_genesis(&self) -> bool { + #[allow(clippy::arithmetic_side_effects)] + let two_mins_in_future = self.time() + Duration::from_secs(2 * 60); + self.block_producer.with(false, |bp| { + bp.current.won_slot_should_produce(two_mins_in_future) + }) && self.genesis_block().map_or(false, |b| { + let slot = &b.consensus_state().curr_global_slot_since_hard_fork; + let epoch = slot + .slot_number + .as_u32() + .checked_div(slot.slots_per_epoch.as_u32()) + .expect("division by 0"); + self.current_epoch() <= Some(epoch) + }) } - pub fn should_produce_blocks_after_genesis(&self) -> bool { - self.block_producer.is_enabled() - && self.genesis_block().map_or(false, |b| { - let slot = &b.consensus_state().curr_global_slot_since_hard_fork; - let epoch = slot.slot_number.as_u32() / slot.slots_per_epoch.as_u32(); - self.current_epoch() <= Some(epoch) - }) + pub fn prevalidate_block( + &self, + block: &ArcBlockWithHash, + allow_block_too_late: bool, + ) -> Result<(), BlockPrevalidationError> { + let Some((genesis, cur_global_slot)) = + None.or_else(|| Some((self.genesis_block()?, self.cur_global_slot()?))) + else { + // we don't have genesis block. This should be impossible + // because we don't even know chain_id before we have genesis + // block, so we can't be connected to any peers from which + // we would receive a block. + bug_condition!("Tried to prevalidate a block before the genesis block was ready"); + return Err(BlockPrevalidationError::GenesisNotReady); + }; + + // received_at_valid_time + // https://github.com/minaprotocol/mina/blob/6af211ad58e9356f00ea4a636cea70aa8267c072/src/lib/consensus/proof_of_stake.ml#L2746 + { + let block_global_slot = block.global_slot(); + + let delta = genesis.constants().delta.as_u32(); + if cur_global_slot < block_global_slot { + // Too_early + return Err(BlockPrevalidationError::ReceivedTooEarly { + current_global_slot: cur_global_slot, + block_global_slot, + }); + } else if !allow_block_too_late + && cur_global_slot.saturating_sub(block_global_slot) > delta + { + // Too_late + return Err(BlockPrevalidationError::ReceivedTooLate { + current_global_slot: cur_global_slot, + block_global_slot, + delta, + }); + } + } + + if block.header().genesis_state_hash() != genesis.hash() { + return Err(BlockPrevalidationError::InvalidGenesisProtocolState); + } + + let (protocol_versions_are_valid, protocol_version_matches_daemon) = { + let min_transaction_version = 1.into(); + let v = &block.header().current_protocol_version; + let nv = block + .header() + .proposed_protocol_version_opt + .as_ref() + .unwrap_or(v); + + // Our version values are unsigned, so there is no need to check that the + // other parts are not negative. + let valid = v.transaction >= min_transaction_version + && nv.transaction >= min_transaction_version; + let compatible = v.transaction == PROTOCOL_VERSION.transaction + && v.network == PROTOCOL_VERSION.network; + + (valid, compatible) + }; + + if !protocol_versions_are_valid { + return Err(BlockPrevalidationError::InvalidProtocolVersion); + } else if !protocol_version_matches_daemon { + return Err(BlockPrevalidationError::MismatchedProtocolVersion); + } + + // NOTE: currently these cannot change between blocks, but that + // may not always be true? + if block.constants() != genesis.constants() { + return Err(BlockPrevalidationError::ConsantsMismatch); + } + + // TODO(tizoc): check for InvalidDeltaBlockChainProof + // https://github.com/MinaProtocol/mina/blob/d800da86a764d8d37ffb8964dd8d54d9f522b358/src/lib/mina_block/validation.ml#L369 + // https://github.com/MinaProtocol/mina/blob/d800da86a764d8d37ffb8964dd8d54d9f522b358/src/lib/transition_chain_verifier/transition_chain_verifier.ml + + Ok(()) } pub fn should_log_node_id(&self) -> bool { @@ -364,9 +523,20 @@ impl P2p { fn p2p_callbacks() -> P2pCallbacks { P2pCallbacks { + on_p2p_channels_transaction_received: Some(redux::callback!( + on_p2p_channels_transaction_received((peer_id: PeerId, info: Box)) -> crate::Action{ + TransactionPoolCandidateAction::InfoReceived { + peer_id, + info: *info, + } + } + )), on_p2p_channels_transaction_libp2p_received: Some(redux::callback!( on_p2p_channels_transaction_libp2p_received(transaction: Box) -> crate::Action{ - TransactionPoolAction::StartVerify { commands: std::iter::once(*transaction).collect(), from_rpc: None } + TransactionPoolAction::StartVerify { + commands: std::iter::once(*transaction).collect(), + from_rpc: None + } } )), on_p2p_channels_snark_job_commitment_received: Some(redux::callback!( @@ -380,8 +550,8 @@ impl P2p { } )), on_p2p_channels_snark_libp2p_received: Some(redux::callback!( - on_p2p_channels_snark_received((peer_id: PeerId, snark: Box)) -> crate::Action{ - SnarkPoolCandidateAction::WorkReceived { peer_id, work: *snark } + on_p2p_channels_snark_libp2p_received((peer_id: PeerId, snark: Box)) -> crate::Action{ + SnarkPoolCandidateAction::WorkFetchSuccess { peer_id, work: *snark } } )), on_p2p_channels_streaming_rpc_ready: Some(redux::callback!( @@ -426,12 +596,12 @@ impl P2p { )), on_p2p_peer_best_tip_update: Some(redux::callback!( on_p2p_peer_best_tip_update(best_tip: BlockWithHash>) -> crate::Action{ - ConsensusAction::P2pBestTipUpdate{best_tip} + ConsensusAction::P2pBestTipUpdate { best_tip } } )), on_p2p_channels_rpc_ready: Some(redux::callback!( on_p2p_channels_rpc_ready(peer_id: PeerId) -> crate::Action{ - P2pCallbacksAction::P2pChannelsRpcReady {peer_id} + P2pCallbacksAction::P2pChannelsRpcReady { peer_id } } )), on_p2p_channels_rpc_timeout: Some(redux::callback!( @@ -441,17 +611,17 @@ impl P2p { )), on_p2p_channels_rpc_response_received: Some(redux::callback!( on_p2p_channels_rpc_response_received((peer_id: PeerId, id: P2pRpcId, response: Option>)) -> crate::Action{ - P2pCallbacksAction::P2pChannelsRpcResponseReceived {peer_id, id, response} + P2pCallbacksAction::P2pChannelsRpcResponseReceived { peer_id, id, response } } )), on_p2p_channels_rpc_request_received: Some(redux::callback!( on_p2p_channels_rpc_request_received((peer_id: PeerId, id: P2pRpcId, request: Box)) -> crate::Action{ - P2pCallbacksAction::P2pChannelsRpcRequestReceived {peer_id, id, request} + P2pCallbacksAction::P2pChannelsRpcRequestReceived { peer_id, id, request } } )), on_p2p_channels_streaming_rpc_response_received: Some(redux::callback!( on_p2p_channels_streaming_rpc_response_received((peer_id: PeerId, id: P2pRpcId, response: Option)) -> crate::Action{ - P2pCallbacksAction::P2pChannelsStreamingRpcResponseReceived {peer_id, id, response} + P2pCallbacksAction::P2pChannelsStreamingRpcResponseReceived { peer_id, id, response } } )), on_p2p_channels_streaming_rpc_timeout: Some(redux::callback!( diff --git a/node/src/stats/stats_actions.rs b/node/src/stats/stats_actions.rs index 3c4d3b82a4..6a81a70088 100644 --- a/node/src/stats/stats_actions.rs +++ b/node/src/stats/stats_actions.rs @@ -21,7 +21,10 @@ impl ActionStats { while self.per_block.len() >= 20000 { self.per_block.pop_back(); } - let id = self.per_block.back().map_or(0, |v| v.id + 1); + let id = self + .per_block + .back() + .map_or(0, |v| v.id.checked_add(1).expect("overflow")); self.per_block.push_back(ActionStatsForBlock { id, time, @@ -54,7 +57,7 @@ impl ActionStats { }; let i = id .checked_add(blocks.len() as u64)? - .checked_sub(last.id + 1)?; + .checked_sub(last.id.checked_add(1)?)?; blocks.get(i as usize).cloned() } } @@ -69,16 +72,22 @@ impl ActionStatsSnapshot { } let kind_i = *prev_action.action() as usize; - let duration = action.meta().time_as_nanos() - prev_action.meta().time_as_nanos(); + let duration = action + .meta() + .time_as_nanos() + .saturating_sub(prev_action.meta().time_as_nanos()); // TODO(binier): add constant len in ActionKind instead and use // that for constant vec length. let len = self.0.len(); - let need_len = kind_i + 1; + let need_len = kind_i.checked_add(1).expect("overflow"); if len < need_len { self.0.resize(need_len, Default::default()); } - self.0[kind_i].add(duration); + self.0 + .get_mut(kind_i) + .expect("kind_i out of bounds") + .add(duration); } } @@ -128,11 +137,16 @@ pub struct ActionStatsForBlock { impl ActionStatsForBlock { fn new_action(&mut self, action: &ActionKindWithMeta, prev_action: &ActionKindWithMeta) { - let duration = action.meta().time_as_nanos() - prev_action.meta().time_as_nanos(); + let duration = action + .meta() + .time_as_nanos() + .saturating_sub(prev_action.meta().time_as_nanos()); match prev_action.action() { ActionKind::None => {} - ActionKind::EventSourceWaitForEvents => self.cpu_idle += duration, - _ => self.cpu_busy += duration, + ActionKind::EventSourceWaitForEvents => { + self.cpu_idle = self.cpu_idle.saturating_add(duration) + } + _ => self.cpu_busy = self.cpu_busy.saturating_add(duration), } self.stats.add(action, prev_action); } @@ -182,8 +196,8 @@ impl ActionStatsForRanges { } else { &mut self.above_50_ms }; - stats.total_calls += 1; - stats.total_duration += duration; + stats.total_calls = stats.total_calls.saturating_add(1); + stats.total_duration = stats.total_duration.saturating_add(duration); stats.max_duration = std::cmp::max(stats.max_duration, duration); } } diff --git a/node/src/stats/stats_block_producer.rs b/node/src/stats/stats_block_producer.rs index 2a413ea22e..afdc28dc8a 100644 --- a/node/src/stats/stats_block_producer.rs +++ b/node/src/stats/stats_block_producer.rs @@ -1,4 +1,4 @@ -use std::collections::VecDeque; +use std::collections::{BTreeMap, VecDeque}; use ledger::AccountIndex; use mina_p2p_messages::v2; @@ -15,6 +15,7 @@ const MAX_HISTORY: usize = 2048; #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct BlockProducerStats { pub(super) attempts: VecDeque, + pub vrf_evaluator: BTreeMap, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -90,6 +91,21 @@ pub struct ProducedBlockTransactions { pub zkapps: u16, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VrfEvaluatorStats { + pub total_slots: u32, + pub evaluated_slots: u32, +} + +impl Default for VrfEvaluatorStats { + fn default() -> Self { + Self { + total_slots: 7140, + evaluated_slots: 0, + } + } +} + impl BlockProducerStats { fn latest_attempt_block_hash_matches(&self, hash: &BlockHash) -> bool { self.attempts @@ -261,12 +277,7 @@ impl BlockProducerStats { } pub fn block_apply_start(&mut self, time: redux::Timestamp, hash: &BlockHash) { - let is_our_block = self - .attempts - .back() - .and_then(|v| v.block.as_ref()) - .map_or(false, |b| &b.hash == hash); - if !is_our_block { + if !self.is_our_just_produced_block(hash) { return; } @@ -319,6 +330,43 @@ impl BlockProducerStats { true }); } + + /// Returns `true` if this is a block we just produced + pub fn is_our_just_produced_block(&self, hash: &BlockHash) -> bool { + // For the block to be ours: + // - we must have an attempt to produce a block + // - we must have just produced the proof for that block + // - the hash must match + if let Some(attempt) = self.attempts.back() { + match (&attempt.status, attempt.block.as_ref()) { + (BlockProductionStatus::ProofCreateSuccess, Some(block)) => &block.hash == hash, + _ => false, + } + } else { + false + } + } + + /// In case a new run, when the current epoch has less than `slots_per_epoch` slots to evaluate. + pub fn new_epoch_evaluation(&mut self, epoch: u32, remaining_slots: u32) { + self.vrf_evaluator.insert( + epoch, + VrfEvaluatorStats { + total_slots: remaining_slots, + evaluated_slots: 0, + }, + ); + } + + pub fn increment_slot_evaluated(&mut self, epoch: u32) { + self.vrf_evaluator + .entry(epoch) + .and_modify(|v| v.evaluated_slots = v.evaluated_slots.checked_add(1).expect("overflow")) + .or_insert_with(|| VrfEvaluatorStats { + evaluated_slots: 1, + ..Default::default() + }); + } } impl From<&BlockProducerWonSlot> for BlockProductionAttemptWonSlot { @@ -361,13 +409,15 @@ impl From<&BlockWithoutProof> for ProducedBlockTransactions { match &cmd.data { v2::MinaBaseUserCommandStableV2::SignedCommand(v) => match &v.payload.body { v2::MinaBaseSignedCommandPayloadBodyStableV2::Payment(_) => { - res.payments += 1 + res.payments = res.payments.checked_add(1).expect("overflow") } v2::MinaBaseSignedCommandPayloadBodyStableV2::StakeDelegation(_) => { - res.delegations += 1 + res.delegations = res.delegations.checked_add(1).expect("overflow") } }, - v2::MinaBaseUserCommandStableV2::ZkappCommand(_) => res.zkapps += 1, + v2::MinaBaseUserCommandStableV2::ZkappCommand(_) => { + res.zkapps = res.zkapps.checked_add(1).expect("overflow") + } } res }) diff --git a/node/src/stats/stats_sync.rs b/node/src/stats/stats_sync.rs index 596a1fdd57..04a323288c 100644 --- a/node/src/stats/stats_sync.rs +++ b/node/src/stats/stats_sync.rs @@ -309,12 +309,23 @@ impl SyncStats { // }) .enumerate() .map(|(i, s)| { - let height = best_tip_height - i as u32; + let height = best_tip_height.checked_sub(i as u32).expect("underflow"); let hash = s.block_hash().clone(); let pred_hash = s .block() .map(|b| b.pred_hash()) - .unwrap_or_else(|| states[states.len() - i - 2].block_hash()) + .unwrap_or_else(|| { + states + .get( + states + .len() + .saturating_sub(i) + .checked_sub(2) + .expect("underflow"), + ) + .unwrap() // can't fail because index is less than states.len() + .block_hash() + }) .clone(); let mut stats = SyncBlock::new(height, hash, pred_hash); stats.update_with_block_state(s); diff --git a/node/src/transaction_pool/candidate/mod.rs b/node/src/transaction_pool/candidate/mod.rs new file mode 100644 index 0000000000..7e4c5c9ee1 --- /dev/null +++ b/node/src/transaction_pool/candidate/mod.rs @@ -0,0 +1,7 @@ +mod transaction_pool_candidate_state; +pub use transaction_pool_candidate_state::*; + +mod transaction_pool_candidate_actions; +pub use transaction_pool_candidate_actions::*; + +mod transaction_pool_candidate_reducer; diff --git a/node/src/transaction_pool/candidate/transaction_pool_candidate_actions.rs b/node/src/transaction_pool/candidate/transaction_pool_candidate_actions.rs new file mode 100644 index 0000000000..b2f1875c52 --- /dev/null +++ b/node/src/transaction_pool/candidate/transaction_pool_candidate_actions.rs @@ -0,0 +1,145 @@ +use openmina_core::transaction::{TransactionHash, TransactionInfo, TransactionWithHash}; +use openmina_core::ActionEvent; +use serde::{Deserialize, Serialize}; + +use crate::p2p::channels::rpc::P2pRpcId; +use crate::p2p::PeerId; + +use super::TransactionPoolCandidateState; + +pub type TransactionPoolCandidateActionWithMeta = + redux::ActionWithMeta; +pub type TransactionPoolCandidateActionWithMetaRef<'a> = + redux::ActionWithMeta<&'a TransactionPoolCandidateAction>; + +#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] +pub enum TransactionPoolCandidateAction { + InfoReceived { + peer_id: PeerId, + info: TransactionInfo, + }, + #[action_event(level = trace)] + FetchAll, + FetchInit { + peer_id: PeerId, + hash: TransactionHash, + }, + FetchPending { + peer_id: PeerId, + hash: TransactionHash, + rpc_id: P2pRpcId, + }, + FetchError { + peer_id: PeerId, + hash: TransactionHash, + }, + FetchSuccess { + peer_id: PeerId, + transaction: TransactionWithHash, + }, + #[action_event(level = trace)] + VerifyNext, + VerifyPending { + peer_id: PeerId, + transaction_hashes: Vec, + verify_id: (), + }, + VerifyError { + peer_id: PeerId, + verify_id: (), + }, + VerifySuccess { + peer_id: PeerId, + verify_id: (), + }, + PeerPrune { + peer_id: PeerId, + }, +} + +impl redux::EnablingCondition for TransactionPoolCandidateAction { + fn is_enabled(&self, state: &crate::State, _time: redux::Timestamp) -> bool { + match self { + TransactionPoolCandidateAction::InfoReceived { peer_id, info } => { + !state.transaction_pool.contains(&info.hash) + && !state + .transaction_pool + .candidates + .peer_contains(*peer_id, &info.hash) + } + TransactionPoolCandidateAction::FetchAll => state.p2p.ready().is_some(), + TransactionPoolCandidateAction::FetchInit { peer_id, hash } => { + let is_peer_available = state + .p2p + .get_ready_peer(peer_id) + .map_or(false, |peer| peer.channels.rpc.can_send_request()); + is_peer_available + && state + .transaction_pool + .candidates + .get(*peer_id, hash) + .map_or(false, |s| { + matches!(s, TransactionPoolCandidateState::InfoReceived { .. }) + }) + } + TransactionPoolCandidateAction::FetchPending { peer_id, hash, .. } => state + .transaction_pool + .candidates + .get(*peer_id, hash) + .map_or(false, |s| { + matches!(s, TransactionPoolCandidateState::InfoReceived { .. }) + }), + TransactionPoolCandidateAction::FetchError { peer_id, hash } => state + .transaction_pool + .candidates + .get(*peer_id, hash) + .is_some(), + TransactionPoolCandidateAction::FetchSuccess { + peer_id, + transaction, + } => state + .transaction_pool + .candidates + .get(*peer_id, transaction.hash()) + .is_some(), + TransactionPoolCandidateAction::VerifyNext => true, + TransactionPoolCandidateAction::VerifyPending { + peer_id, + transaction_hashes, + .. + } => { + !transaction_hashes.is_empty() + && state + .transaction_pool + .candidates + .candidates_from_peer_with_hashes(*peer_id, transaction_hashes) + .all(|(_, state)| { + matches!(state, Some(TransactionPoolCandidateState::Received { .. })) + }) + } + TransactionPoolCandidateAction::VerifyError { .. } => { + // TODO(binier) + true + } + TransactionPoolCandidateAction::VerifySuccess { .. } => { + // TODO(binier) + true + } + TransactionPoolCandidateAction::PeerPrune { peer_id } => { + state + .transaction_pool + .candidates + .peer_transaction_count(peer_id) + > 0 + } + } + } +} + +use crate::transaction_pool::TransactionPoolAction; + +impl From for crate::Action { + fn from(value: TransactionPoolCandidateAction) -> Self { + Self::TransactionPool(TransactionPoolAction::Candidate(value)) + } +} diff --git a/node/src/transaction_pool/candidate/transaction_pool_candidate_reducer.rs b/node/src/transaction_pool/candidate/transaction_pool_candidate_reducer.rs new file mode 100644 index 0000000000..dbd58954b8 --- /dev/null +++ b/node/src/transaction_pool/candidate/transaction_pool_candidate_reducer.rs @@ -0,0 +1,149 @@ +#![allow(clippy::unit_arg)] + +use crate::{p2p_ready, TransactionPoolAction}; +use p2p::{ + channels::rpc::{P2pChannelsRpcAction, P2pRpcId, P2pRpcRequest}, + PeerId, +}; + +use super::{ + TransactionPoolCandidateAction, TransactionPoolCandidateActionWithMetaRef, + TransactionPoolCandidatesState, +}; + +impl TransactionPoolCandidatesState { + pub fn reducer( + mut state_context: crate::Substate, + action: TransactionPoolCandidateActionWithMetaRef<'_>, + ) { + let Ok(state) = state_context.get_substate_mut() else { + // TODO: log or propagate + return; + }; + let (action, meta) = action.split(); + + match action { + TransactionPoolCandidateAction::InfoReceived { peer_id, info } => { + state.info_received(meta.time(), *peer_id, info.clone()); + } + TransactionPoolCandidateAction::FetchAll => { + let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); + let p2p = p2p_ready!(global_state.p2p, meta.time()); + let peers = p2p.ready_peers_iter().map(|(id, _)| *id); + let get_order = |_hash: &_| { + // TODO(binier) + 0 + }; + let list = global_state + .transaction_pool + .candidates + .peers_next_transactions_to_fetch(peers, get_order); + + for (peer_id, hash) in list { + dispatcher.push(TransactionPoolCandidateAction::FetchInit { peer_id, hash }); + } + } + TransactionPoolCandidateAction::FetchInit { peer_id, hash } => { + let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); + let peer_id = *peer_id; + let hash = hash.clone(); + let p2p = p2p_ready!(global_state.p2p, meta.time()); + let Some(peer) = p2p.get_ready_peer(&peer_id) else { + return; + }; + let rpc_id = peer.channels.next_local_rpc_id(); + + dispatcher.push(P2pChannelsRpcAction::RequestSend { + peer_id, + id: rpc_id, + request: Box::new(P2pRpcRequest::Transaction(hash.clone())), + on_init: Some(redux::callback!( + on_send_p2p_snark_rpc_request( + (peer_id: PeerId, rpc_id: P2pRpcId, request: P2pRpcRequest) + ) -> crate::Action { + let P2pRpcRequest::Transaction(hash) = request else { + unreachable!() + }; + TransactionPoolCandidateAction::FetchPending { + hash, + peer_id, + rpc_id, + } + } + )), + }); + } + TransactionPoolCandidateAction::FetchPending { + peer_id, + hash, + rpc_id, + } => { + state.fetch_pending(meta.time(), peer_id, hash, *rpc_id); + } + TransactionPoolCandidateAction::FetchError { peer_id, hash } => { + state.peer_transaction_remove(*peer_id, hash); + } + TransactionPoolCandidateAction::FetchSuccess { + peer_id, + transaction, + } => { + state.transaction_received(meta.time(), *peer_id, transaction.clone()); + } + TransactionPoolCandidateAction::VerifyNext => { + let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); + + let batch = global_state + .transaction_pool + .candidates + .get_batch_to_verify(); + let Some((peer_id, batch)) = batch else { + return; + }; + + let transaction_hashes = batch.iter().map(|tx| tx.hash().clone()).collect(); + dispatcher.push(TransactionPoolAction::StartVerify { + commands: batch.into_iter().map(|tx| tx.into_body()).collect(), + from_rpc: None, + }); + dispatcher.push(TransactionPoolCandidateAction::VerifyPending { + peer_id, + transaction_hashes, + verify_id: (), + }); + } + TransactionPoolCandidateAction::VerifyPending { + peer_id, + transaction_hashes, + verify_id, + } => { + state.verify_pending(meta.time(), peer_id, *verify_id, transaction_hashes); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(TransactionPoolCandidateAction::VerifySuccess { + peer_id: *peer_id, + verify_id: *verify_id, + }); + } + TransactionPoolCandidateAction::VerifyError { + peer_id: _, + verify_id: _, + } => { + unreachable!("TODO(binier)"); + // state.verify_result(meta.time(), peer_id, *verify_id, Err(())); + + // // TODO(binier): blacklist peer + // let dispatcher = state_context.into_dispatcher(); + // let peer_id = *peer_id; + // dispatcher.push(P2pDisconnectionAction::Init { + // peer_id, + // reason: P2pDisconnectionReason::TransactionPoolVerifyError, + // }); + } + TransactionPoolCandidateAction::VerifySuccess { peer_id, verify_id } => { + state.verify_result(meta.time(), peer_id, *verify_id, Ok(())); + } + TransactionPoolCandidateAction::PeerPrune { peer_id } => { + state.peer_remove(*peer_id); + } + } + } +} diff --git a/node/src/transaction_pool/candidate/transaction_pool_candidate_state.rs b/node/src/transaction_pool/candidate/transaction_pool_candidate_state.rs new file mode 100644 index 0000000000..bcb33c1326 --- /dev/null +++ b/node/src/transaction_pool/candidate/transaction_pool_candidate_state.rs @@ -0,0 +1,380 @@ +#![allow(clippy::unit_arg)] + +use std::collections::{BTreeMap, BTreeSet}; + +use mina_p2p_messages::v2; +use redux::Timestamp; +use serde::{Deserialize, Serialize}; + +use crate::core::transaction::{ + Transaction, TransactionHash, TransactionInfo, TransactionWithHash, +}; +use crate::p2p::channels::rpc::P2pRpcId; +use crate::p2p::PeerId; + +static EMPTY_PEER_TX_CANDIDATES: BTreeMap = + BTreeMap::new(); + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct TransactionPoolCandidatesState { + by_peer: BTreeMap>, + by_hash: BTreeMap>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum TransactionPoolCandidateState { + InfoReceived { + time: Timestamp, + info: TransactionInfo, + }, + FetchPending { + time: Timestamp, + info: TransactionInfo, + rpc_id: P2pRpcId, + }, + Received { + time: Timestamp, + transaction: TransactionWithHash, + }, + VerifyPending { + time: Timestamp, + transaction: TransactionWithHash, + verify_id: (), + }, + VerifyError { + time: Timestamp, + transaction: TransactionWithHash, + }, + VerifySuccess { + time: Timestamp, + transaction: TransactionWithHash, + }, +} + +impl TransactionPoolCandidatesState { + pub fn new() -> Self { + Self::default() + } + + pub fn transactions_count(&self) -> usize { + self.by_hash.len() + } + + pub fn peer_transaction_count(&self, peer_id: &PeerId) -> usize { + self.by_peer.get(peer_id).map(|v| v.len()).unwrap_or(0) + } + + pub fn contains(&self, hash: &TransactionHash) -> bool { + self.by_hash.contains_key(hash) + } + + pub fn peer_contains(&self, peer_id: PeerId, hash: &TransactionHash) -> bool { + self.by_peer + .get(&peer_id) + .map_or(false, |txs| txs.contains_key(hash)) + } + + pub fn get( + &self, + peer_id: PeerId, + hash: &TransactionHash, + ) -> Option<&TransactionPoolCandidateState> { + self.by_peer.get(&peer_id)?.get(hash) + } + + fn transactions_from_peer_or_empty( + &self, + peer_id: PeerId, + ) -> &BTreeMap { + self.by_peer + .get(&peer_id) + .unwrap_or(&EMPTY_PEER_TX_CANDIDATES) + } + + pub fn candidates_from_peer_iter( + &self, + peer_id: PeerId, + ) -> impl Iterator { + self.transactions_from_peer_or_empty(peer_id).iter() + } + + pub fn candidates_from_peer_with_hashes<'a, I>( + &'a self, + peer_id: PeerId, + transaction_hashes: I, + ) -> impl Iterator< + Item = ( + &'a TransactionHash, + Option<&'a TransactionPoolCandidateState>, + ), + > + where + I: IntoIterator, + { + let transactions = self.transactions_from_peer_or_empty(peer_id); + transaction_hashes + .into_iter() + .map(|hash| (hash, transactions.get(hash))) + } + + pub fn info_received(&mut self, time: Timestamp, peer_id: PeerId, info: TransactionInfo) { + self.by_hash + .entry(info.hash.clone()) + .or_default() + .insert(peer_id); + + let hash = info.hash.clone(); + let state = TransactionPoolCandidateState::InfoReceived { time, info }; + self.by_peer.entry(peer_id).or_default().insert(hash, state); + } + + pub fn peers_next_transactions_to_fetch( + &self, + peers: I, + get_order: F, + ) -> Vec<(PeerId, TransactionHash)> + where + I: IntoIterator, + F: Copy + Fn(&TransactionHash) -> usize, + { + let mut needs_fetching = peers + .into_iter() + .filter_map(|peer_id| Some((peer_id, self.by_peer.get(&peer_id)?))) + .flat_map(|(peer_id, transactions)| { + transactions + .iter() + .filter(|(_, state)| { + matches!(state, TransactionPoolCandidateState::InfoReceived { .. }) + }) + .map(move |(hash, state)| (get_order(hash), state.fee(), peer_id, hash)) + }) + .collect::>(); + needs_fetching + .sort_by(|(ord1, fee1, ..), (ord2, fee2, ..)| ord1.cmp(ord2).then(fee1.cmp(fee2))); + + needs_fetching + .into_iter() + .scan(None, |last_ord, (ord, _, peer_id, hash)| { + if *last_ord == Some(ord) { + return Some(None); + } + *last_ord = Some(ord); + Some(Some((peer_id, hash.clone()))) + }) + .flatten() + .collect() + } + + pub fn fetch_pending( + &mut self, + time: Timestamp, + peer_id: &PeerId, + hash: &TransactionHash, + rpc_id: P2pRpcId, + ) { + if let Some(state) = self + .by_peer + .get_mut(peer_id) + .and_then(|transactions| transactions.get_mut(hash)) + { + if let TransactionPoolCandidateState::InfoReceived { info, .. } = state { + *state = TransactionPoolCandidateState::FetchPending { + time, + info: info.clone(), + rpc_id, + }; + } + } + } + + pub fn transaction_received( + &mut self, + time: Timestamp, + peer_id: PeerId, + transaction: TransactionWithHash, + ) { + let hash = transaction.hash().clone(); + self.by_hash + .entry(hash.clone()) + .or_default() + .insert(peer_id); + + let state = TransactionPoolCandidateState::Received { time, transaction }; + self.by_peer.entry(peer_id).or_default().insert(hash, state); + } + + pub fn get_batch_to_verify(&self) -> Option<(PeerId, Vec)> { + for hash in self.by_hash.keys() { + if let Some(res) = None.or_else(|| { + for peer_id in self.by_hash.get(hash)? { + let peer_transactions = self.by_peer.get(peer_id)?; + if peer_transactions.get(hash)?.transaction().is_some() { + let transactions = peer_transactions + .iter() + .filter_map(|(_, v)| match v { + TransactionPoolCandidateState::Received { transaction, .. } => { + Some(transaction) + } + _ => None, + }) + .cloned() + .collect(); + return Some((*peer_id, transactions)); + } + } + None + }) { + return Some(res); + } + } + None + } + + pub fn verify_pending( + &mut self, + time: Timestamp, + peer_id: &PeerId, + verify_id: (), + transaction_hashes: &[TransactionHash], + ) { + let Some(peer_transactions) = self.by_peer.get_mut(peer_id) else { + return; + }; + + for hash in transaction_hashes { + if let Some(job_state) = peer_transactions.get_mut(hash) { + if let TransactionPoolCandidateState::Received { transaction, .. } = job_state { + *job_state = TransactionPoolCandidateState::VerifyPending { + time, + transaction: transaction.clone(), + verify_id, + }; + } + } + } + } + + pub fn verify_result( + &mut self, + _time: Timestamp, + peer_id: &PeerId, + verify_id: (), + _result: Result<(), ()>, + ) { + if let Some(peer_transactions) = self.by_peer.get_mut(peer_id) { + let txs_to_remove = peer_transactions + .iter() + .filter(|(_, job_state)| job_state.pending_verify_id() == Some(verify_id)) + .map(|(hash, _)| hash.clone()) + .collect::>(); + + for hash in txs_to_remove { + self.transaction_remove(&hash); + } + } + } + + pub fn peer_remove(&mut self, peer_id: PeerId) { + if let Some(txs) = self.by_peer.remove(&peer_id) { + for hash in txs.into_keys() { + if let Some(peers) = self.by_hash.get_mut(&hash) { + peers.remove(&peer_id); + if peers.is_empty() { + self.by_hash.remove(&hash); + } + } + } + } + } + + pub fn peer_transaction_remove(&mut self, peer_id: PeerId, hash: &TransactionHash) { + if let Some(txs) = self.by_peer.get_mut(&peer_id) { + txs.remove(hash); + if let Some(peers) = self.by_hash.get_mut(hash) { + peers.remove(&peer_id); + if peers.is_empty() { + self.by_hash.remove(hash); + } + } + } + } + + fn transaction_remove(&mut self, hash: &TransactionHash) { + if let Some(peers) = self.by_hash.remove(hash) { + for peer_id in peers { + if let Some(txs) = self.by_peer.get_mut(&peer_id) { + txs.remove(hash); + } + } + } + } + + pub fn remove_inferior_transactions(&mut self, transaction: &Transaction) { + // TODO(binier) + match transaction.hash() { + Err(err) => { + openmina_core::bug_condition!("tx hashing failed: {err}"); + } + Ok(hash) => self.transaction_remove(&hash), + }; + } + + pub fn retain(&mut self, mut predicate: F1) + where + F1: FnMut(&TransactionHash) -> F2, + F2: FnMut(&TransactionPoolCandidateState) -> bool, + { + let by_peer = &mut self.by_peer; + self.by_hash.retain(|hash, peers| { + let mut predicate = predicate(hash); + peers.retain(|peer_id| { + if let Some(peer_txs) = by_peer.get_mut(peer_id) { + match peer_txs.get(hash) { + Some(s) if predicate(s) => true, + Some(_) => { + peer_txs.remove(hash); + false + } + None => false, + } + } else { + false + } + }); + !peers.is_empty() + }) + } +} + +impl TransactionPoolCandidateState { + pub fn fee(&self) -> u64 { + match self { + Self::InfoReceived { info, .. } | Self::FetchPending { info, .. } => info.fee, + Self::Received { transaction, .. } + | Self::VerifyPending { transaction, .. } + | Self::VerifyError { transaction, .. } + | Self::VerifySuccess { transaction, .. } => match transaction.body() { + v2::MinaBaseUserCommandStableV2::SignedCommand(v) => v.payload.common.fee.as_u64(), + v2::MinaBaseUserCommandStableV2::ZkappCommand(v) => v.fee_payer.body.fee.as_u64(), + }, + } + } + + pub fn transaction(&self) -> Option<&TransactionWithHash> { + match self { + Self::InfoReceived { .. } => None, + Self::FetchPending { .. } => None, + Self::Received { transaction, .. } => Some(transaction), + Self::VerifyPending { transaction, .. } => Some(transaction), + Self::VerifyError { transaction, .. } => Some(transaction), + Self::VerifySuccess { transaction, .. } => Some(transaction), + } + } + + pub fn pending_verify_id(&self) -> Option<()> { + match self { + Self::VerifyPending { verify_id, .. } => Some(*verify_id), + _ => None, + } + } +} diff --git a/node/src/transaction_pool/mod.rs b/node/src/transaction_pool/mod.rs index 4f4ddfc220..8712f701a4 100644 --- a/node/src/transaction_pool/mod.rs +++ b/node/src/transaction_pool/mod.rs @@ -1,476 +1,14 @@ -use ledger::{ - scan_state::{ - currency::{Amount, Nonce, Slot}, - transaction_logic::{verifiable, UserCommand, WithStatus}, - }, - transaction_pool::{ - diff::{self, DiffVerified}, - transaction_hash, ApplyDecision, Config, TransactionPoolErrors, ValidCommandWithHash, - }, - Account, AccountId, -}; -use mina_p2p_messages::v2; -use openmina_core::{ - bug_condition, consensus::ConsensusConstants, constants::constraint_constants, requests::RpcId, -}; -use p2p::channels::transaction::P2pChannelsTransactionAction; -use redux::callback; -use snark::{user_command_verify::SnarkUserCommandVerifyId, TransactionVerifier, VerifierSRS}; -use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, - sync::Arc, -}; -use transaction_pool_actions::TransactionPoolActionWithMetaRef; +pub mod candidate; -pub mod transaction_pool_actions; +mod transaction_pool_state; +pub use transaction_pool_state::*; -pub use transaction_pool_actions::{TransactionPoolAction, TransactionPoolEffectfulAction}; +mod transaction_pool_actions; +pub use transaction_pool_actions::*; -use crate::{BlockProducerAction, RpcAction}; +mod transaction_pool_reducer; -type PendingId = u32; +mod transaction_pool_effects; -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct TransactionPoolState { - pool: ledger::transaction_pool::TransactionPool, - pending_actions: BTreeMap, - pending_id: PendingId, - best_tip_hash: Option, - /// For debug only - #[serde(skip)] - file: Option, -} - -impl Clone for TransactionPoolState { - fn clone(&self) -> Self { - Self { - pool: self.pool.clone(), - pending_actions: self.pending_actions.clone(), - pending_id: self.pending_id, - best_tip_hash: self.best_tip_hash.clone(), - file: None, - } - } -} - -impl TransactionPoolState { - pub fn new(config: Config, consensus_constants: &ConsensusConstants) -> Self { - Self { - pool: ledger::transaction_pool::TransactionPool::new(config, consensus_constants), - pending_actions: Default::default(), - pending_id: 0, - best_tip_hash: None, - file: None, - } - } - - pub fn size(&self) -> usize { - self.pool.size() - } - - pub fn transactions(&mut self, limit: usize) -> Vec { - self.pool.transactions(limit) - } - - pub fn list_includable_transactions(&self, limit: usize) -> Vec { - self.pool.list_includable_transactions(limit) - } - - pub fn get_all_transactions(&self) -> Vec { - self.pool.get_all_transactions() - } - - pub fn get_pending_amount_and_nonce(&self) -> HashMap, Amount)> { - self.pool.get_pending_amount_and_nonce() - } - - fn next_pending_id(&mut self) -> PendingId { - let id = self.pending_id; - self.pending_id = self.pending_id.wrapping_add(1); - id - } - - fn make_action_pending(&mut self, action: &TransactionPoolAction) -> PendingId { - let id = self.next_pending_id(); - self.pending_actions.insert(id, action.clone()); - id - } - - #[allow(dead_code)] - fn save_actions(state: &mut crate::Substate) { - let substate = state.get_substate_mut().unwrap(); - if substate.file.is_none() { - let mut file = std::fs::File::create("/tmp/pool.bin").unwrap(); - postcard::to_io(&state.unsafe_get_state(), &mut file).unwrap(); - let substate = state.get_substate_mut().unwrap(); - substate.file = Some(file); - } - } - - pub fn reducer(mut state: crate::Substate, action: TransactionPoolActionWithMetaRef<'_>) { - // Uncoment following line to save actions to `/tmp/pool.bin` - // Self::save_actions(&mut state); - - let substate = state.get_substate_mut().unwrap(); - if let Some(file) = substate.file.as_mut() { - postcard::to_io(&action, file).unwrap(); - }; - - Self::handle_action(state, action) - } - - fn global_slots(state: &crate::State) -> Option<(Slot, Slot)> { - Some(( - Slot::from_u32(state.cur_global_slot()?), - Slot::from_u32(state.cur_global_slot_since_genesis()?), - )) - } - - fn handle_action( - mut state: crate::Substate, - action: TransactionPoolActionWithMetaRef<'_>, - ) { - let (action, meta) = action.split(); - let Some((global_slot, global_slot_from_genesis)) = - // TODO: remove usage of `unsafe_get_state` - Self::global_slots(state.unsafe_get_state()) - else { - return; - }; - let substate = state.get_substate_mut().unwrap(); - - match action { - TransactionPoolAction::StartVerify { commands, from_rpc } => { - let Ok(commands) = commands - .iter() - .map(UserCommand::try_from) - .collect::, _>>() - else { - // ignore all commands if one is invalid - return; - }; - - let account_ids = commands - .iter() - .flat_map(UserCommand::accounts_referenced) - .collect::>(); - let best_tip_hash = substate.best_tip_hash.clone().unwrap(); - let pending_id = substate.make_action_pending(action); - - let dispatcher = state.into_dispatcher(); - dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { - account_ids, - ledger_hash: best_tip_hash.clone(), - on_result: callback!(fetch_to_verify((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { - TransactionPoolAction::StartVerifyWithAccounts { accounts, pending_id: id.unwrap(), from_rpc } - }), - pending_id: Some(pending_id), - from_rpc: *from_rpc, - }); - } - TransactionPoolAction::StartVerifyWithAccounts { - accounts, - pending_id, - from_rpc, - } => { - let TransactionPoolAction::StartVerify { commands, .. } = - substate.pending_actions.remove(pending_id).unwrap() - else { - panic!() - }; - - // TODO: Convert those commands only once - let Ok(commands) = commands - .iter() - .map(UserCommand::try_from) - .collect::, _>>() - else { - return; - }; - let diff = diff::Diff { list: commands }; - - match substate.pool.verify(diff, accounts) { - Ok(valids) => { - let valids = valids - .into_iter() - .map(transaction_hash::hash_command) - .collect::>(); - let best_tip_hash = substate.best_tip_hash.clone().unwrap(); - let diff = DiffVerified { list: valids }; - - let dispatcher = state.into_dispatcher(); - dispatcher.push(TransactionPoolAction::ApplyVerifiedDiff { - best_tip_hash, - diff, - is_sender_local: from_rpc.is_some(), - from_rpc: *from_rpc, - }); - } - Err(e) => { - let dispatch_errors = |errors: Vec| { - let dispatcher = state.into_dispatcher(); - dispatcher.push(TransactionPoolAction::VerifyError { - errors: errors.clone(), - }); - if let Some(rpc_id) = from_rpc { - dispatcher.push(RpcAction::TransactionInjectFailure { - rpc_id: *rpc_id, - errors, - }) - } - }; - match e { - TransactionPoolErrors::BatchedErrors(errors) => { - let errors: Vec<_> = - errors.into_iter().map(|e| e.to_string()).collect(); - dispatch_errors(errors); - } - TransactionPoolErrors::LoadingVK(error) => dispatch_errors(vec![error]), - TransactionPoolErrors::Unexpected(es) => { - panic!("{es}") - } - } - } - } - } - TransactionPoolAction::VerifyError { .. } => { - // just logging the errors - } - TransactionPoolAction::BestTipChanged { best_tip_hash } => { - let account_ids = substate.pool.get_accounts_to_revalidate_on_new_best_tip(); - substate.best_tip_hash = Some(best_tip_hash.clone()); - - let dispatcher = state.into_dispatcher(); - dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { - account_ids, - ledger_hash: best_tip_hash.clone(), - on_result: callback!(fetch_for_best_tip((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { - TransactionPoolAction::BestTipChangedWithAccounts { accounts } - }), - pending_id: None, - from_rpc: None, - }); - } - TransactionPoolAction::BestTipChangedWithAccounts { accounts } => { - if let Err(e) = substate - .pool - .on_new_best_tip(global_slot_from_genesis, accounts) - { - bug_condition!("transaction pool::on_new_best_tip failed: {:?}", e); - } - } - TransactionPoolAction::ApplyVerifiedDiff { - best_tip_hash, - diff, - is_sender_local: _, - from_rpc, - } => { - let account_ids = substate.pool.get_accounts_to_apply_diff(diff); - let pending_id = substate.make_action_pending(action); - - let dispatcher = state.into_dispatcher(); - dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { - account_ids, - ledger_hash: best_tip_hash.clone(), - on_result: callback!(fetch_for_apply((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { - TransactionPoolAction::ApplyVerifiedDiffWithAccounts { - accounts, - pending_id: id.unwrap(), - } - }), - pending_id: Some(pending_id), - from_rpc: *from_rpc, - }); - } - TransactionPoolAction::ApplyVerifiedDiffWithAccounts { - accounts, - pending_id, - } => { - let TransactionPoolAction::ApplyVerifiedDiff { - best_tip_hash: _, - diff, - is_sender_local, - from_rpc, - } = substate.pending_actions.remove(pending_id).unwrap() - else { - panic!() - }; - - // Note(adonagy): Action for rebroadcast, in his action we can use forget_check - match substate.pool.unsafe_apply( - meta.time(), - global_slot_from_genesis, - global_slot, - &diff, - accounts, - is_sender_local, - ) { - Ok((ApplyDecision::Accept, accepted, rejected)) => { - if let Some(rpc_id) = from_rpc { - let dispatcher = state.into_dispatcher(); - - dispatcher.push(RpcAction::TransactionInjectSuccess { - rpc_id, - response: accepted.clone(), - }); - dispatcher - .push(TransactionPoolAction::Rebroadcast { accepted, rejected }); - } - } - Ok((ApplyDecision::Reject, accepted, rejected)) => { - if let Some(rpc_id) = from_rpc { - let dispatcher = state.into_dispatcher(); - - dispatcher.push(RpcAction::TransactionInjectRejected { - rpc_id, - response: rejected.clone(), - }); - dispatcher - .push(TransactionPoolAction::Rebroadcast { accepted, rejected }); - } - } - Err(e) => eprintln!("unsafe_apply error: {:?}", e), - } - } - TransactionPoolAction::ApplyTransitionFrontierDiff { - best_tip_hash, - diff, - } => { - assert_eq!(substate.best_tip_hash.as_ref().unwrap(), best_tip_hash); - - let (account_ids, uncommitted) = - substate.pool.get_accounts_to_handle_transition_diff(diff); - let pending_id = substate.make_action_pending(action); - - let dispatcher = state.into_dispatcher(); - dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { - account_ids: account_ids.union(&uncommitted).cloned().collect(), - ledger_hash: best_tip_hash.clone(), - on_result: callback!(fetch_for_diff((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { - TransactionPoolAction::ApplyTransitionFrontierDiffWithAccounts { - accounts, - pending_id: id.unwrap(), - } - }), - pending_id: Some(pending_id), - from_rpc: None, - }); - } - TransactionPoolAction::ApplyTransitionFrontierDiffWithAccounts { - accounts, - pending_id, - } => { - let TransactionPoolAction::ApplyTransitionFrontierDiff { - best_tip_hash: _, - diff, - } = substate.pending_actions.remove(pending_id).unwrap() - else { - panic!() - }; - - let collect = |set: &BTreeSet| { - set.iter() - .filter_map(|id| { - let account = accounts.get(id).cloned()?; - Some((id.clone(), account)) - }) - .collect::>() - }; - - let (account_ids, uncommitted) = - substate.pool.get_accounts_to_handle_transition_diff(&diff); - - let in_cmds = collect(&account_ids); - let uncommitted = collect(&uncommitted); - - if let Err(e) = substate.pool.handle_transition_frontier_diff( - global_slot_from_genesis, - global_slot, - &diff, - &account_ids, - &in_cmds, - &uncommitted, - ) { - bug_condition!( - "transaction pool::handle_transition_frontier_diff failed: {:?}", - e - ); - } - } - TransactionPoolAction::Rebroadcast { accepted, rejected } => { - let rejected = rejected.iter().map(|(cmd, _)| cmd.data.forget_check()); - - let all_commands = accepted - .iter() - .map(|cmd| cmd.data.forget_check()) - .chain(rejected) - .collect::>(); - - let dispatcher = state.into_dispatcher(); - - for cmd in all_commands { - dispatcher.push(P2pChannelsTransactionAction::Libp2pBroadcast { - transaction: Box::new((&cmd).into()), - nonce: 0, - }); - } - } - TransactionPoolAction::CollectTransactionsByFee => { - let transaction_capacity = - 2u64.pow(constraint_constants().transaction_capacity_log_2 as u32); - let transactions_by_fee = substate - .pool - .list_includable_transactions(transaction_capacity as usize) - .into_iter() - .map(|cmd| cmd.data) - .collect::>(); - - let dispatcher = state.into_dispatcher(); - - dispatcher.push(BlockProducerAction::WonSlotTransactionsSuccess { - transactions_by_fee, - }); - } - } - } -} - -pub trait VerifyUserCommandsService: redux::Service { - fn verify_init( - &mut self, - req_id: SnarkUserCommandVerifyId, - commands: Vec>, - verifier_index: TransactionVerifier, - verifier_srs: Arc, - ); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::State; - use redux::Dispatcher; - use transaction_pool_actions::TransactionPoolActionWithMeta; - - #[allow(unused)] - #[test] - fn test_replay_pool() { - let vec = std::fs::read("/tmp/pool.bin").unwrap(); - let slice = vec.as_slice(); - - let (mut state, rest) = postcard::take_from_bytes::(slice).unwrap(); - let mut slice = rest; - - while let Ok((action, rest)) = - postcard::take_from_bytes::(slice) - { - slice = rest; - - let mut dispatcher = Dispatcher::new(); - let state = crate::Substate::::new(&mut state, &mut dispatcher); - let (action, meta) = action.split(); - - TransactionPoolState::handle_action(state, meta.with_action(&action)); - } - } -} +mod transaction_pool_service; +pub use transaction_pool_service::*; diff --git a/node/src/transaction_pool/transaction_pool_actions.rs b/node/src/transaction_pool/transaction_pool_actions.rs index 0bffbbd799..6b6c952e1c 100644 --- a/node/src/transaction_pool/transaction_pool_actions.rs +++ b/node/src/transaction_pool/transaction_pool_actions.rs @@ -15,9 +15,7 @@ use openmina_core::{requests::RpcId, ActionEvent}; use redux::Callback; use serde::{Deserialize, Serialize}; -use crate::ledger::LedgerService; - -use super::PendingId; +use super::{candidate::TransactionPoolCandidateAction, PendingId}; pub type TransactionPoolActionWithMeta = redux::ActionWithMeta; pub type TransactionPoolActionWithMetaRef<'a> = redux::ActionWithMeta<&'a TransactionPoolAction>; @@ -25,6 +23,7 @@ pub type TransactionPoolActionWithMetaRef<'a> = redux::ActionWithMeta<&'a Transa #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] #[action_event(level = info)] pub enum TransactionPoolAction { + Candidate(TransactionPoolCandidateAction), StartVerify { commands: List, from_rpc: Option, @@ -70,9 +69,48 @@ pub enum TransactionPoolAction { rejected: Vec<(ValidCommandWithHash, diff::Error)>, }, CollectTransactionsByFee, + #[action_event(level = trace)] + P2pSendAll, + #[action_event(level = debug)] + P2pSend { + peer_id: p2p::PeerId, + }, } -impl redux::EnablingCondition for TransactionPoolAction {} +impl redux::EnablingCondition for TransactionPoolAction { + fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool { + match self { + TransactionPoolAction::Candidate(a) => a.is_enabled(state, time), + TransactionPoolAction::P2pSendAll => true, + TransactionPoolAction::P2pSend { peer_id } => state + .p2p + .get_ready_peer(peer_id) + // can't propagate empty transaction pool + .filter(|_| !state.transaction_pool.dpool.is_empty()) + // Only send transactions if peer has the same best tip, + // or its best tip is extension of our best tip. + .and_then(|p| { + let peer_best_tip = p.best_tip.as_ref()?; + let our_best_tip = state.transition_frontier.best_tip()?.hash(); + Some(p).filter(|_| { + peer_best_tip.hash() == our_best_tip + || peer_best_tip.pred_hash() == our_best_tip + }) + }) + .map_or(false, |p| { + let check = + |(next_index, limit), last_index| limit > 0 && next_index <= last_index; + let last_index = state.transaction_pool.dpool.last_index(); + + check( + p.channels.transaction.next_send_index_and_limit(), + last_index, + ) + }), + _ => true, + } + } +} type TransactionPoolEffectfulActionCallback = Callback<( BTreeMap, @@ -92,53 +130,3 @@ pub enum TransactionPoolEffectfulAction { } impl redux::EnablingCondition for TransactionPoolEffectfulAction {} - -impl TransactionPoolEffectfulAction { - pub fn effects(self, store: &mut Store) - where - Store: snark::SnarkStore, - Store::Service: LedgerService, - { - match self { - TransactionPoolEffectfulAction::FetchAccounts { - account_ids, - ledger_hash, - on_result, - pending_id, - from_rpc, - } => { - openmina_core::log::info!( - openmina_core::log::system_time(); - kind = "TransactionPoolEffectfulFetchAccounts", - summary = "fetching accounts for tx pool"); - // FIXME: the ledger ctx `get_accounts` function doesn't ensure that every account we - // asked for is included in the result. - // TODO: should be asynchronous. Once asynchronous, watch out for race - // conditions between tx pool and transition frontier. By the time the - // accounts have been fetched the best tip may have changed already. - let accounts = match store - .service() - .ledger_manager() - .get_accounts(&ledger_hash, account_ids.iter().cloned().collect()) - { - Ok(accounts) => accounts, - Err(err) => { - openmina_core::log::error!( - openmina_core::log::system_time(); - kind = "Error", - summary = "failed to fetch accounts for tx pool", - error = format!("ledger {:?}, error: {:?}", ledger_hash, err)); - return; - } - }; - - let accounts = accounts - .into_iter() - .map(|account| (account.id(), account)) - .collect::>(); - - store.dispatch_callback(on_result, (accounts, pending_id, from_rpc)); - } - } - } -} diff --git a/node/src/transaction_pool/transaction_pool_effects.rs b/node/src/transaction_pool/transaction_pool_effects.rs new file mode 100644 index 0000000000..f508b140b6 --- /dev/null +++ b/node/src/transaction_pool/transaction_pool_effects.rs @@ -0,0 +1,56 @@ +use std::collections::BTreeMap; + +use crate::ledger::LedgerService; +use crate::snark::SnarkStore; + +use super::TransactionPoolEffectfulAction; + +impl TransactionPoolEffectfulAction { + pub fn effects(self, store: &mut Store) + where + Store: SnarkStore, + Store::Service: LedgerService, + { + match self { + TransactionPoolEffectfulAction::FetchAccounts { + account_ids, + ledger_hash, + on_result, + pending_id, + from_rpc, + } => { + openmina_core::log::info!( + openmina_core::log::system_time(); + kind = "TransactionPoolEffectfulFetchAccounts", + summary = "fetching accounts for tx pool"); + // FIXME: the ledger ctx `get_accounts` function doesn't ensure that every account we + // asked for is included in the result. + // TODO: should be asynchronous. Once asynchronous, watch out for race + // conditions between tx pool and transition frontier. By the time the + // accounts have been fetched the best tip may have changed already. + let accounts = match store + .service() + .ledger_manager() + .get_accounts(&ledger_hash, account_ids.iter().cloned().collect()) + { + Ok(accounts) => accounts, + Err(err) => { + openmina_core::log::error!( + openmina_core::log::system_time(); + kind = "Error", + summary = "failed to fetch accounts for tx pool", + error = format!("ledger {:?}, error: {:?}", ledger_hash, err)); + return; + } + }; + + let accounts = accounts + .into_iter() + .map(|account| (account.id(), account)) + .collect::>(); + + store.dispatch_callback(on_result, (accounts, pending_id, from_rpc)); + } + } + } +} diff --git a/node/src/transaction_pool/transaction_pool_reducer.rs b/node/src/transaction_pool/transaction_pool_reducer.rs new file mode 100644 index 0000000000..b84e32fc2a --- /dev/null +++ b/node/src/transaction_pool/transaction_pool_reducer.rs @@ -0,0 +1,396 @@ +use ledger::{ + scan_state::transaction_logic::{GenericCommand, UserCommand}, + transaction_pool::{ + diff::{self, DiffVerified}, + transaction_hash, ApplyDecision, TransactionPoolErrors, + }, + Account, AccountId, +}; +use openmina_core::{ + bug_condition, constants::constraint_constants, requests::RpcId, transaction::Transaction, +}; +use p2p::channels::transaction::P2pChannelsTransactionAction; +use redux::callback; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::{BlockProducerAction, RpcAction}; + +use super::{ + PendingId, TransactionPoolAction, TransactionPoolActionWithMetaRef, + TransactionPoolEffectfulAction, TransactionPoolState, TransactionState, +}; + +impl TransactionPoolState { + pub fn reducer(mut state: crate::Substate, action: TransactionPoolActionWithMetaRef<'_>) { + // Uncoment following line to save actions to `/tmp/pool.bin` + // Self::save_actions(&mut state); + + let substate = state.get_substate_mut().unwrap(); + if let Some(file) = substate.file.as_mut() { + postcard::to_io(&action, file).unwrap(); + }; + + Self::handle_action(state, action) + } + + pub(super) fn handle_action( + mut state: crate::Substate, + action: TransactionPoolActionWithMetaRef<'_>, + ) { + let (action, meta) = action.split(); + let Some((global_slot, global_slot_from_genesis)) = + // TODO: remove usage of `unsafe_get_state` + Self::global_slots(state.unsafe_get_state()) + else { + return; + }; + let substate = state.get_substate_mut().unwrap(); + + match action { + TransactionPoolAction::Candidate(a) => { + super::candidate::TransactionPoolCandidatesState::reducer( + openmina_core::Substate::from_compatible_substate(state), + meta.with_action(a), + ); + } + TransactionPoolAction::StartVerify { commands, from_rpc } => { + let Ok(commands) = commands + .iter() + .map(UserCommand::try_from) + .collect::, _>>() + else { + // ignore all commands if one is invalid + return; + }; + + let account_ids = commands + .iter() + .flat_map(UserCommand::accounts_referenced) + .collect::>(); + let best_tip_hash = substate.best_tip_hash.clone().unwrap(); + let pending_id = substate.make_action_pending(action); + + let dispatcher = state.into_dispatcher(); + dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { + account_ids, + ledger_hash: best_tip_hash.clone(), + on_result: callback!(fetch_to_verify((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { + TransactionPoolAction::StartVerifyWithAccounts { accounts, pending_id: id.unwrap(), from_rpc } + }), + pending_id: Some(pending_id), + from_rpc: *from_rpc, + }); + } + TransactionPoolAction::StartVerifyWithAccounts { + accounts, + pending_id, + from_rpc, + } => { + let TransactionPoolAction::StartVerify { commands, .. } = + substate.pending_actions.remove(pending_id).unwrap() + else { + panic!() + }; + + // TODO: Convert those commands only once + let Ok(commands) = commands + .iter() + .map(UserCommand::try_from) + .collect::, _>>() + else { + return; + }; + let diff = diff::Diff { list: commands }; + + match substate.pool.verify(diff, accounts) { + Ok(valids) => { + let valids = valids + .into_iter() + .map(transaction_hash::hash_command) + .collect::>(); + let best_tip_hash = substate.best_tip_hash.clone().unwrap(); + let diff = DiffVerified { list: valids }; + + let dispatcher = state.into_dispatcher(); + dispatcher.push(TransactionPoolAction::ApplyVerifiedDiff { + best_tip_hash, + diff, + is_sender_local: from_rpc.is_some(), + from_rpc: *from_rpc, + }); + } + Err(e) => { + let dispatch_errors = |errors: Vec| { + let dispatcher = state.into_dispatcher(); + dispatcher.push(TransactionPoolAction::VerifyError { + errors: errors.clone(), + }); + if let Some(rpc_id) = from_rpc { + dispatcher.push(RpcAction::TransactionInjectFailure { + rpc_id: *rpc_id, + errors, + }) + } + }; + match e { + TransactionPoolErrors::BatchedErrors(errors) => { + let errors: Vec<_> = + errors.into_iter().map(|e| e.to_string()).collect(); + dispatch_errors(errors); + } + TransactionPoolErrors::LoadingVK(error) => dispatch_errors(vec![error]), + TransactionPoolErrors::Unexpected(es) => { + panic!("{es}") + } + } + } + } + } + TransactionPoolAction::VerifyError { .. } => { + // just logging the errors + } + TransactionPoolAction::BestTipChanged { best_tip_hash } => { + let account_ids = substate.pool.get_accounts_to_revalidate_on_new_best_tip(); + substate.best_tip_hash = Some(best_tip_hash.clone()); + + let dispatcher = state.into_dispatcher(); + dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { + account_ids, + ledger_hash: best_tip_hash.clone(), + on_result: callback!(fetch_for_best_tip((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { + TransactionPoolAction::BestTipChangedWithAccounts { accounts } + }), + pending_id: None, + from_rpc: None, + }); + } + TransactionPoolAction::BestTipChangedWithAccounts { accounts } => { + match substate + .pool + .on_new_best_tip(global_slot_from_genesis, accounts) + { + Err(e) => bug_condition!("transaction pool::on_new_best_tip failed: {:?}", e), + Ok(dropped) => { + for tx in dropped { + substate.dpool.remove(&tx.hash); + } + } + } + } + TransactionPoolAction::ApplyVerifiedDiff { + best_tip_hash, + diff, + is_sender_local: _, + from_rpc, + } => { + let account_ids = substate.pool.get_accounts_to_apply_diff(diff); + let pending_id = substate.make_action_pending(action); + + let dispatcher = state.into_dispatcher(); + dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { + account_ids, + ledger_hash: best_tip_hash.clone(), + on_result: callback!(fetch_for_apply((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { + TransactionPoolAction::ApplyVerifiedDiffWithAccounts { + accounts, + pending_id: id.unwrap(), + } + }), + pending_id: Some(pending_id), + from_rpc: *from_rpc, + }); + } + TransactionPoolAction::ApplyVerifiedDiffWithAccounts { + accounts, + pending_id, + } => { + let TransactionPoolAction::ApplyVerifiedDiff { + best_tip_hash: _, + diff, + is_sender_local, + from_rpc, + } = substate.pending_actions.remove(pending_id).unwrap() + else { + panic!() + }; + + // Note(adonagy): Action for rebroadcast, in his action we can use forget_check + let (rpc_action, accepted, rejected) = match substate.pool.unsafe_apply( + meta.time(), + global_slot_from_genesis, + global_slot, + &diff, + accounts, + is_sender_local, + ) { + Ok((ApplyDecision::Accept, accepted, rejected, dropped)) => { + for hash in dropped { + substate.dpool.remove(&hash); + } + for tx in &accepted { + substate.dpool.insert(TransactionState { + time: meta.time(), + hash: tx.hash.clone(), + }); + } + let rpc_action = + from_rpc.map(|rpc_id| RpcAction::TransactionInjectSuccess { + rpc_id, + response: accepted.clone(), + }); + (rpc_action, accepted, rejected) + } + Ok((ApplyDecision::Reject, accepted, rejected, _)) => { + let rpc_action = + from_rpc.map(|rpc_id| RpcAction::TransactionInjectRejected { + rpc_id, + response: rejected.clone(), + }); + (rpc_action, accepted, rejected) + } + Err(e) => { + crate::core::warn!(meta.time(); kind = "TransactionPoolUnsafeApplyError", summary = e); + return; + } + }; + + let dispatcher = state.into_dispatcher(); + if let Some(rpc_action) = rpc_action { + dispatcher.push(rpc_action); + } + dispatcher.push(TransactionPoolAction::Rebroadcast { accepted, rejected }); + } + TransactionPoolAction::ApplyTransitionFrontierDiff { + best_tip_hash, + diff, + } => { + assert_eq!(substate.best_tip_hash.as_ref().unwrap(), best_tip_hash); + + let (account_ids, uncommitted) = + substate.pool.get_accounts_to_handle_transition_diff(diff); + let pending_id = substate.make_action_pending(action); + + let dispatcher = state.into_dispatcher(); + dispatcher.push(TransactionPoolEffectfulAction::FetchAccounts { + account_ids: account_ids.union(&uncommitted).cloned().collect(), + ledger_hash: best_tip_hash.clone(), + on_result: callback!(fetch_for_diff((accounts: BTreeMap, id: Option, from_rpc: Option)) -> crate::Action { + TransactionPoolAction::ApplyTransitionFrontierDiffWithAccounts { + accounts, + pending_id: id.unwrap(), + } + }), + pending_id: Some(pending_id), + from_rpc: None, + }); + } + TransactionPoolAction::ApplyTransitionFrontierDiffWithAccounts { + accounts, + pending_id, + } => { + let TransactionPoolAction::ApplyTransitionFrontierDiff { + best_tip_hash: _, + diff, + } = substate.pending_actions.remove(pending_id).unwrap() + else { + panic!() + }; + + let collect = |set: &BTreeSet| { + set.iter() + .filter_map(|id| { + let account = accounts.get(id).cloned()?; + Some((id.clone(), account)) + }) + .collect::>() + }; + + let (account_ids, uncommitted) = + substate.pool.get_accounts_to_handle_transition_diff(&diff); + + let in_cmds = collect(&account_ids); + let uncommitted = collect(&uncommitted); + + if let Err(e) = substate.pool.handle_transition_frontier_diff( + global_slot_from_genesis, + global_slot, + &diff, + &account_ids, + &in_cmds, + &uncommitted, + ) { + bug_condition!( + "transaction pool::handle_transition_frontier_diff failed: {:?}", + e + ); + } + } + TransactionPoolAction::Rebroadcast { accepted, rejected } => { + let rejected = rejected.iter().map(|(cmd, _)| cmd.data.forget_check()); + + let all_commands = accepted + .iter() + .map(|cmd| cmd.data.forget_check()) + .chain(rejected) + .collect::>(); + + let dispatcher = state.into_dispatcher(); + + for cmd in all_commands { + dispatcher.push(P2pChannelsTransactionAction::Libp2pBroadcast { + transaction: Box::new((&cmd).into()), + nonce: 0, + }); + } + } + TransactionPoolAction::CollectTransactionsByFee => { + let transaction_capacity = + 2u64.pow(constraint_constants().transaction_capacity_log_2 as u32); + let transactions_by_fee = substate + .pool + .list_includable_transactions(transaction_capacity as usize) + .into_iter() + .map(|cmd| cmd.data) + .collect::>(); + + let dispatcher = state.into_dispatcher(); + + dispatcher.push(BlockProducerAction::WonSlotTransactionsSuccess { + transactions_by_fee, + }); + } + TransactionPoolAction::P2pSendAll => { + let (dispatcher, global_state) = state.into_dispatcher_and_state(); + for peer_id in global_state.p2p.ready_peers() { + dispatcher.push(TransactionPoolAction::P2pSend { peer_id }); + } + } + TransactionPoolAction::P2pSend { peer_id } => { + let peer_id = *peer_id; + let (dispatcher, global_state) = state.into_dispatcher_and_state(); + let Some(peer) = global_state.p2p.get_ready_peer(&peer_id) else { + return; + }; + + // Send commitments. + let index_and_limit = peer.channels.transaction.next_send_index_and_limit(); + let (transactions, first_index, last_index) = global_state + .transaction_pool + .dpool + .next_messages_to_send(index_and_limit, |state| { + let tx = global_state.transaction_pool.get(&state.hash)?; + let tx = tx.clone().forget(); + // TODO(binier): avoid conversion + Some((&Transaction::from(&tx)).into()) + }); + + dispatcher.push(P2pChannelsTransactionAction::ResponseSend { + peer_id, + transactions, + first_index, + last_index, + }); + } + } + } +} diff --git a/node/src/transaction_pool/transaction_pool_service.rs b/node/src/transaction_pool/transaction_pool_service.rs new file mode 100644 index 0000000000..c4721cfe29 --- /dev/null +++ b/node/src/transaction_pool/transaction_pool_service.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; + +use crate::snark::{ + user_command_verify::SnarkUserCommandVerifyId, TransactionVerifier, VerifierSRS, +}; +use ledger::scan_state::transaction_logic::{verifiable, WithStatus}; + +pub trait VerifyUserCommandsService: redux::Service { + fn verify_init( + &mut self, + req_id: SnarkUserCommandVerifyId, + commands: Vec>, + verifier_index: TransactionVerifier, + verifier_srs: Arc, + ); +} diff --git a/node/src/transaction_pool/transaction_pool_state.rs b/node/src/transaction_pool/transaction_pool_state.rs new file mode 100644 index 0000000000..6b72fe77fc --- /dev/null +++ b/node/src/transaction_pool/transaction_pool_state.rs @@ -0,0 +1,162 @@ +use ledger::{ + scan_state::{ + currency::{Amount, Nonce, Slot}, + transaction_logic::valid::UserCommand, + }, + transaction_pool::{Config, ValidCommandWithHash}, + AccountId, +}; +use mina_p2p_messages::v2::{self, TransactionHash}; +use openmina_core::{consensus::ConsensusConstants, distributed_pool::DistributedPool}; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; + +use super::{candidate::TransactionPoolCandidatesState, TransactionPoolAction}; + +pub(super) type PendingId = u32; + +#[derive(Serialize, Deserialize, Debug)] +pub struct TransactionPoolState { + pub candidates: TransactionPoolCandidatesState, + // TODO(binier): ideally this and `.pool` should be merged together. + pub(super) dpool: DistributedPool, + pub(super) pool: ledger::transaction_pool::TransactionPool, + pub(super) pending_actions: BTreeMap, + pub(super) pending_id: PendingId, + pub(super) best_tip_hash: Option, + /// For debug only + #[serde(skip)] + pub(super) file: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TransactionState { + pub time: redux::Timestamp, + pub hash: TransactionHash, +} + +impl AsRef for TransactionState { + fn as_ref(&self) -> &TransactionHash { + &self.hash + } +} + +impl Clone for TransactionPoolState { + fn clone(&self) -> Self { + Self { + candidates: self.candidates.clone(), + dpool: self.dpool.clone(), + pool: self.pool.clone(), + pending_actions: self.pending_actions.clone(), + pending_id: self.pending_id, + best_tip_hash: self.best_tip_hash.clone(), + file: None, + } + } +} + +impl TransactionPoolState { + pub fn new(config: Config, consensus_constants: &ConsensusConstants) -> Self { + Self { + candidates: Default::default(), + dpool: Default::default(), + pool: ledger::transaction_pool::TransactionPool::new(config, consensus_constants), + pending_actions: Default::default(), + pending_id: 0, + best_tip_hash: None, + file: None, + } + } + + pub fn size(&self) -> usize { + self.pool.size() + } + + pub fn for_propagation_size(&self) -> usize { + self.dpool.len() + } + + pub fn contains(&self, hash: &TransactionHash) -> bool { + self.get(hash).is_some() + } + + pub fn get(&self, hash: &TransactionHash) -> Option<&UserCommand> { + self.pool.pool.get(hash).map(|v| &v.data) + } + + pub fn transactions(&mut self, limit: usize) -> Vec { + self.pool.transactions(limit) + } + + pub fn list_includable_transactions(&self, limit: usize) -> Vec { + self.pool.list_includable_transactions(limit) + } + + pub fn get_all_transactions(&self) -> Vec { + self.pool.get_all_transactions() + } + + pub fn get_pending_amount_and_nonce(&self) -> HashMap, Amount)> { + self.pool.get_pending_amount_and_nonce() + } + + fn next_pending_id(&mut self) -> PendingId { + let id = self.pending_id; + self.pending_id = self.pending_id.wrapping_add(1); + id + } + + pub(super) fn make_action_pending(&mut self, action: &TransactionPoolAction) -> PendingId { + let id = self.next_pending_id(); + self.pending_actions.insert(id, action.clone()); + id + } + + #[allow(dead_code)] + fn save_actions(state: &mut crate::Substate) { + let substate = state.get_substate_mut().unwrap(); + if substate.file.is_none() { + let mut file = std::fs::File::create("/tmp/pool.bin").unwrap(); + postcard::to_io(&state.unsafe_get_state(), &mut file).unwrap(); + let substate = state.get_substate_mut().unwrap(); + substate.file = Some(file); + } + } + + pub(super) fn global_slots(state: &crate::State) -> Option<(Slot, Slot)> { + Some(( + Slot::from_u32(state.cur_global_slot()?), + Slot::from_u32(state.cur_global_slot_since_genesis()?), + )) + } +} + +#[cfg(test)] +mod tests { + use super::super::TransactionPoolActionWithMeta; + use super::*; + use crate::State; + use redux::Dispatcher; + + #[allow(unused)] + #[test] + fn test_replay_pool() { + let vec = std::fs::read("/tmp/pool.bin").unwrap(); + let slice = vec.as_slice(); + + let (mut state, rest) = postcard::take_from_bytes::(slice).unwrap(); + let mut slice = rest; + + while let Ok((action, rest)) = + postcard::take_from_bytes::(slice) + { + slice = rest; + + let mut dispatcher = Dispatcher::new(); + let state = crate::Substate::::new(&mut state, &mut dispatcher); + let (action, meta) = action.split(); + + TransactionPoolState::handle_action(state, meta.with_action(&action)); + } + } +} diff --git a/node/src/transition_frontier/genesis/transition_frontier_genesis_actions.rs b/node/src/transition_frontier/genesis/transition_frontier_genesis_actions.rs index a53ab8c993..c9d370da60 100644 --- a/node/src/transition_frontier/genesis/transition_frontier_genesis_actions.rs +++ b/node/src/transition_frontier/genesis/transition_frontier_genesis_actions.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use mina_p2p_messages::v2; use openmina_core::ActionEvent; use serde::{Deserialize, Serialize}; @@ -25,7 +27,7 @@ pub enum TransitionFrontierGenesisAction { /// Genesis block proved. #[action_event(level = info)] ProveSuccess { - proof: Box, + proof: Arc, }, // TODO(refactor): add prove error } @@ -51,7 +53,7 @@ impl redux::EnablingCondition for TransitionFrontierGenesisAction TransitionFrontierGenesisState::LedgerLoadSuccess { .. } ), TransitionFrontierGenesisAction::ProveInit => { - state.should_produce_blocks_after_genesis() + state.producing_block_after_genesis() && matches!( genesis_state, TransitionFrontierGenesisState::Produced { .. } diff --git a/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs b/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs index 9275e50e67..3b561d8776 100644 --- a/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs +++ b/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs @@ -78,7 +78,10 @@ pub struct GenesisConfigLoaded { } fn bp_num_delegators(i: usize) -> usize { - (i + 1) * 2 + (i.saturating_add(1)) + .checked_mul(2) + .expect("overflow") + .min(32) } #[derive(Debug, thiserror::Error)] @@ -135,6 +138,17 @@ impl GenesisConfig { } } + pub fn override_genesis_state_timestamp(&mut self, timestamp: v2::BlockTimeTimeStableV1) { + match self { + Self::Counts { constants, .. } + | Self::BalancesDelegateTable { constants, .. } + | Self::AccountsBinProt { constants, .. } => { + constants.genesis_state_timestamp = timestamp; + } + _ => todo!(), + } + } + pub fn load( &self, ) -> anyhow::Result<(Vec, GenesisConfigLoaded), GenesisConfigError> { @@ -146,7 +160,8 @@ impl GenesisConfig { constants, } => { let (whales, fish) = (*whales, *fish); - let delegator_balance = |balance: u64| move |i| balance / i as u64; + let delegator_balance = + |balance: u64| move |i| balance.checked_div(i as u64).expect("division by 0"); let whales = (0..whales).map(|i| { let balance = 8333_u64; let delegators = (1..=bp_num_delegators(i)).map(delegator_balance(50_000_000)); @@ -423,13 +438,13 @@ impl GenesisConfig { let mut account = ledger::Account::create_with(account_id, Balance::from_mina(balance).unwrap()); account.delegate = delegate; - *total_balance += balance; - *counter += 1; + *total_balance = total_balance.checked_add(balance).expect("overflow"); + *counter = counter.checked_add(1).expect("overflow"); // println!("Created account with balance: {}, total_balance: {}", balance, *total_balance); // Debug print account } - let mut accounts = Vec::new(); + let mut accounts = genesis_account_iter().map(Ok).collect::>(); // Process block producers and their delegators for (bp_balance, delegators) in block_producers { @@ -458,12 +473,10 @@ impl GenesisConfig { NonStakers::Count(count) => *std::cmp::min(count, &remaining_accounts), }; - let non_staker_total = total_balance * 20 / 80; - let non_staker_balance = if non_staker_count > 0 { - non_staker_total / non_staker_count as u64 - } else { - 0 - }; + let non_staker_total = total_balance.checked_mul(20).expect("overflow") / 80; + let non_staker_balance = non_staker_total + .checked_div(non_staker_count as u64) + .unwrap_or(0); println!("Non staker total balance: {}", non_staker_total); @@ -476,11 +489,6 @@ impl GenesisConfig { } } - // Add genesis accounts - for genesis_account in genesis_account_iter() { - accounts.push(Ok(genesis_account)); - } - Self::build_ledger_from_accounts(accounts) } @@ -491,10 +499,12 @@ impl GenesisConfig { let mask = ledger::Mask::new_root(db); let (mask, total_currency) = accounts.into_iter().try_fold( (mask, 0), - |(mut mask, mut total_currency), account| { + |(mut mask, mut total_currency): (ledger::Mask, u64), account| { let account = account?; let account_id = account.id(); - total_currency += account.balance.as_u64(); + total_currency = total_currency + .checked_add(account.balance.as_u64()) + .expect("overflow"); mask.get_or_create_account(account_id, account).unwrap(); Ok((mask, total_currency)) }, @@ -640,6 +650,43 @@ impl PrebuiltGenesisConfig { masks.push(staking_ledger_mask); Ok((masks, load_result)) } + + pub fn from_loaded( + (masks, data): (Vec, GenesisConfigLoaded), + ) -> Result { + let find_mask_by_hash = |hash: &v2::LedgerHash| { + masks + .iter() + .find(|&m| m.clone().merkle_root() == hash.to_field().unwrap()) + .ok_or(()) + }; + let inner_hashes = |mask: &ledger::Mask| { + mask.get_raw_inner_hashes() + .into_iter() + .map(|(idx, hash)| (idx, v2::LedgerHash::from_fp(hash))) + .collect() + }; + let genesis_mask = find_mask_by_hash(&data.genesis_ledger_hash)?; + let epoch_data = |hash: v2::LedgerHash, seed: v2::EpochSeed| { + find_mask_by_hash(&hash).map(|mask| PrebuiltGenesisEpochData { + accounts: mask.to_list().into_iter().map(Into::into).collect(), + ledger_hash: hash, + hashes: inner_hashes(mask), + seed, + }) + }; + Ok(Self { + constants: data.constants, + accounts: genesis_mask.to_list().into_iter().map(Into::into).collect(), + ledger_hash: data.genesis_ledger_hash, + hashes: inner_hashes(genesis_mask), + staking_epoch_data: epoch_data( + data.staking_epoch_ledger_hash, + data.staking_epoch_seed, + )?, + next_epoch_data: epoch_data(data.next_epoch_ledger_hash, data.next_epoch_seed)?, + }) + } } #[derive(Debug, Serialize, Deserialize, BinProtRead, BinProtWrite)] diff --git a/node/src/transition_frontier/genesis/transition_frontier_genesis_reducer.rs b/node/src/transition_frontier/genesis/transition_frontier_genesis_reducer.rs index badab2fd6f..e35b62deec 100644 --- a/node/src/transition_frontier/genesis/transition_frontier_genesis_reducer.rs +++ b/node/src/transition_frontier/genesis/transition_frontier_genesis_reducer.rs @@ -138,7 +138,7 @@ impl TransitionFrontierGenesisState { let input = v2::ProverExtendBlockchainInputStableV2 { chain: v2::BlockchainSnarkBlockchainStableV2 { state: negative_one.clone(), - proof: (*dummy_blockchain_proof()).clone(), + proof: dummy_blockchain_proof().clone(), }, next_state: genesis.clone(), block: v2::MinaStateSnarkTransitionValueStableV2 { @@ -212,7 +212,7 @@ impl TransitionFrontierGenesisState { let block = v2::MinaBlockBlockStableV2 { header: v2::MinaBlockHeaderStableV2 { protocol_state: genesis.clone(), - protocol_state_proof: (**proof).clone(), + protocol_state_proof: proof.clone(), delta_block_chain_proof: ( genesis_hash.clone(), std::iter::empty().collect(), diff --git a/node/src/transition_frontier/genesis/transition_frontier_genesis_state.rs b/node/src/transition_frontier/genesis/transition_frontier_genesis_state.rs index 11aa219a82..f6e07cf2c5 100644 --- a/node/src/transition_frontier/genesis/transition_frontier_genesis_state.rs +++ b/node/src/transition_frontier/genesis/transition_frontier_genesis_state.rs @@ -49,7 +49,7 @@ impl TransitionFrontierGenesisState { v2::MinaBlockBlockStableV2 { header: v2::MinaBlockHeaderStableV2 { protocol_state: genesis.clone(), - protocol_state_proof: (*dummy_blockchain_proof()).clone(), + protocol_state_proof: dummy_blockchain_proof().clone(), delta_block_chain_proof: (genesis_hash.clone(), std::iter::empty().collect()), current_protocol_version: PROTOCOL_VERSION.clone(), proposed_protocol_version_opt: None, diff --git a/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_reducer.rs b/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_reducer.rs index 0a8bcd0951..06efff4cdb 100644 --- a/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_reducer.rs +++ b/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_reducer.rs @@ -43,6 +43,7 @@ impl TransitionFrontierSyncLedgerSnarkedState { TransitionFrontierSyncLedgerSnarkedAction::PeersQuery => { let mut retry_addresses: Vec<_> = state.sync_address_retry_iter().collect(); let mut addresses: Vec<_> = state.sync_address_query_iter().collect(); + let is_num_accounts_pending = matches!(state, Self::NumAccountsPending { .. }); let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); @@ -55,24 +56,26 @@ impl TransitionFrontierSyncLedgerSnarkedState { .collect::>(); peer_ids.sort_by(|(_, t1), (_, t2)| t2.cmp(t1)); - // If this dispatches, we can avoid even trying the following steps because we will - // not query address unless we have completed the Num_accounts request first. - if let Some((peer_id, _)) = peer_ids.first() { - if dispatcher.push_if_enabled( - TransitionFrontierSyncLedgerSnarkedAction::PeerQueryNumAccountsInit { - peer_id: *peer_id, - }, - global_state, - meta.time(), - ) || dispatcher.push_if_enabled( - TransitionFrontierSyncLedgerSnarkedAction::PeerQueryNumAccountsRetry { - peer_id: *peer_id, - }, - global_state, - meta.time(), - ) { - return; + if is_num_accounts_pending { + for (peer_id, _) in peer_ids { + if dispatcher.push_if_enabled( + TransitionFrontierSyncLedgerSnarkedAction::PeerQueryNumAccountsInit { + peer_id, + }, + global_state, + meta.time(), + ) || dispatcher.push_if_enabled( + TransitionFrontierSyncLedgerSnarkedAction::PeerQueryNumAccountsRetry { + peer_id, + }, + global_state, + meta.time(), + ) { + return; + } } + // we will not query addresses unless we have num accounts. + return; } for (peer_id, _) in peer_ids { @@ -316,7 +319,7 @@ impl TransitionFrontierSyncLedgerSnarkedState { // We know at which node to begin querying, so we skip all the intermediary depths let first_node_address = ledger::Address::first( - LEDGER_DEPTH - tree_height_for_num_accounts(*num_accounts), + LEDGER_DEPTH.saturating_sub(tree_height_for_num_accounts(*num_accounts)), ); let expected_hash = contents_hash.clone(); let first_query = (first_node_address, expected_hash); @@ -528,8 +531,16 @@ impl TransitionFrontierSyncLedgerSnarkedState { // from that subtree will be skipped and add them to the count. // Empty node hashes are not counted in the stats. - let empty = ledger_empty_hash_at_depth(address.length() + 1); - *num_hashes_accepted += (*left != empty) as u64 + (*right != empty) as u64; + let empty = + ledger_empty_hash_at_depth(address.length().checked_add(1).expect("overflow")); + + if *left != empty { + *num_hashes_accepted = num_hashes_accepted.checked_add(1).expect("overflow") + } + + if *right != empty { + *num_hashes_accepted = num_hashes_accepted.checked_add(1).expect("overflow") + } if left != previous_left { let previous = queue.insert(address.child_left(), left.clone()); @@ -572,7 +583,8 @@ impl TransitionFrontierSyncLedgerSnarkedState { return; }; - *synced_accounts_count += count; + *synced_accounts_count = + synced_accounts_count.checked_add(*count).expect("overflow"); pending.remove(address); // Dispatch diff --git a/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_state.rs b/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_state.rs index 557e6e2faa..ea85c186be 100644 --- a/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_state.rs +++ b/node/src/transition_frontier/sync/ledger/snarked/transition_frontier_sync_ledger_snarked_state.rs @@ -229,10 +229,12 @@ impl TransitionFrontierSyncLedgerSnarkedState { // would be more accurate (accounts are fetched in groups of 64, hashes of 2). let tree_height = tree_height_for_num_accounts(*total_accounts_expected); let fill_ratio = (*total_accounts_expected as f64) / 2f64.powf(tree_height as f64); - let num_hashes_estimate = 2u64.pow((tree_height - ACCOUNT_SUBTREE_HEIGHT) as u32); + let num_hashes_estimate = 2u64 + .saturating_pow((tree_height.saturating_sub(ACCOUNT_SUBTREE_HEIGHT)) as u32); let num_hashes_estimate = (num_hashes_estimate as f64 * fill_ratio).ceil() as u64; - let fetched = *synced_accounts_count + synced_hashes_count; - let estimation = fetched.max(*total_accounts_expected + num_hashes_estimate); + let fetched = synced_accounts_count.saturating_add(*synced_hashes_count); + let estimation = + fetched.max(total_accounts_expected.saturating_add(num_hashes_estimate)); Some(LedgerSyncProgress { fetched, diff --git a/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_actions.rs b/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_actions.rs index e8a021a09e..c4bb9ac58b 100644 --- a/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_actions.rs +++ b/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_actions.rs @@ -83,8 +83,9 @@ impl redux::EnablingCondition for TransitionFrontierSyncLedgerStag let Some(p2p) = state.p2p.ready() else { return false; }; - let iter = p2p.ready_rpc_peers_iter(); - staged.filter_available_peers(iter).next().is_some() + staged.fetch_attempts().map_or(false, |attempts| { + attempts.is_empty() || attempts.iter().all(|(_, s)| s.is_error()) + }) && p2p.ready_rpc_peers_iter().next().is_some() }), TransitionFrontierSyncLedgerStagedAction::PartsPeerFetchPending { .. } => state .transition_frontier diff --git a/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_reducer.rs b/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_reducer.rs index 010217a9ab..d7f2934f92 100644 --- a/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_reducer.rs +++ b/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_reducer.rs @@ -35,22 +35,73 @@ impl TransitionFrontierSyncLedgerStagedState { } TransitionFrontierSyncLedgerStagedAction::PartsPeerFetchInit => { let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); - let Some(staged_ledger) = - None.or_else(|| global_state.transition_frontier.sync.ledger()?.staged()) + let Some((p2p, target_best_tip, staged_ledger, fetch_attempts)) = + None.or_else(|| { + let staged = global_state.transition_frontier.sync.ledger()?.staged()?; + Some(( + global_state.p2p.ready()?, + global_state.transition_frontier.sync.best_tip()?, + staged, + staged.fetch_attempts()?, + )) + }) else { return; }; - let Some(p2p) = global_state.p2p.ready() else { - return; - }; - let block_hash = staged_ledger.target().staged.block_hash.clone(); - let mut ready_peers = staged_ledger - .filter_available_peers(p2p.ready_rpc_peers_iter()) - .collect::>(); - ready_peers.shuffle(&mut global_state.pseudo_rng()); + let mut peers = + p2p.ready_rpc_peers_iter() + .fold(Vec::new(), |mut list, (peer_id, state)| { + let mut order = 0u8; + // TODO(binier): maybe take into account fetch attempt time. + order = order.saturating_add(if fetch_attempts.contains_key(peer_id) { + 5 + } else { + 0 + }); + order = + order.saturating_add(state.best_tip.as_ref().map_or(2, |tip| { + let k = target_best_tip.constants().k.as_u32(); + if tip.height() > target_best_tip.height() + || tip.height().abs_diff(target_best_tip.height()) > k + { + // can't have the block we need + 10 + } else { + // has common block + if IntoIterator::into_iter([ + tip.hash(), + tip.pred_hash(), + target_best_tip.hash(), + target_best_tip.pred_hash(), + ]) + .collect::>() + .len() + < 4 + { + 0 + } else { + 1 + } + } + })); + + if list + .last() + .map_or(false, |(_, _, ord): &(_, _, u8)| *ord > order) + { + // remove less priority peers + list.clear(); + } + list.push((peer_id, state.channels.next_local_rpc_id(), order)); + + list + }); + + let block_hash = staged_ledger.target().staged.block_hash.clone(); + peers.shuffle(&mut global_state.pseudo_rng()); - for (peer_id, rpc_id) in ready_peers { + for (&peer_id, rpc_id, _) in peers { let enqueued = if p2p.is_libp2p_peer(&peer_id) { // use old heavy rpc for libp2p peers. dispatcher.push_if_enabled( diff --git a/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_state.rs b/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_state.rs index ff6f9b4f75..3aaef96c64 100644 --- a/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_state.rs +++ b/node/src/transition_frontier/sync/ledger/staged/transition_frontier_sync_ledger_staged_state.rs @@ -116,6 +116,7 @@ impl TransitionFrontierSyncLedgerStagedState { Some(match self { Self::PartsFetchSuccess { target, parts, .. } => (target, Some(parts)), Self::ReconstructEmpty { target, .. } => (target, None), + Self::ReconstructPending { target, parts, .. } => (target, parts.as_ref()), _ => return None, }) } @@ -131,19 +132,6 @@ impl TransitionFrontierSyncLedgerStagedState { } } - pub fn filter_available_peers<'a>( - &'a self, - iter: impl 'a + Iterator, - ) -> impl 'a + Iterator { - let attempts = self.fetch_attempts(); - iter.filter(move |(peer_id, _)| { - attempts.map_or(false, |attempts| { - !attempts.contains_key(peer_id) - && (attempts.is_empty() || attempts.iter().all(|(_, s)| s.is_error())) - }) - }) - } - pub fn parts_fetch_rpc_id(&self, peer_id: &PeerId) -> Option { self.fetch_attempts()?.get(peer_id)?.fetch_pending_rpc_id() } diff --git a/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs b/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs index 7ac2b1416c..cb686091d7 100644 --- a/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs +++ b/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs @@ -2,6 +2,7 @@ use mina_p2p_messages::v2::{LedgerHash, StateHash}; use openmina_core::block::ArcBlockWithHash; use openmina_core::consensus::consensus_take; use openmina_core::ActionEvent; +use redux::Callback; use serde::{Deserialize, Serialize}; use crate::ledger::write::CommitResult; @@ -45,6 +46,7 @@ pub enum TransitionFrontierSyncAction { best_tip: ArcBlockWithHash, root_block: ArcBlockWithHash, blocks_inbetween: Vec, + on_success: Option>, }, /// Staking Ledger sync is pending #[action_event(level = info)] @@ -144,7 +146,7 @@ impl redux::EnablingCondition for TransitionFrontierSyncAction { && state .transition_frontier .best_tip() - .map_or(true, |tip| best_tip.hash != tip.hash) + .map_or(false, |tip| best_tip.hash != tip.hash) && state .transition_frontier .sync diff --git a/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs b/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs index 496c71c936..ac92f9569c 100644 --- a/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs +++ b/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs @@ -49,6 +49,7 @@ impl TransitionFrontierSyncAction { TransitionFrontierSyncAction::BestTipUpdate { previous_root_snarked_ledger_hash, best_tip, + on_success, .. } => { // TODO(tizoc): this is currently required because how how complicated the BestTipUpdate reducer is, @@ -72,6 +73,9 @@ impl TransitionFrontierSyncAction { store.dispatch(TransitionFrontierSyncAction::BlocksNextApplyInit); // TODO(binier): cleanup ledgers + if let Some(callback) = on_success { + store.dispatch_callback(callback.clone(), ()); + } } // TODO(tizoc): this action is never called with the current implementation, // either remove it or figure out how to recover it as a reaction to @@ -243,15 +247,24 @@ impl TransitionFrontierSyncAction { }; let hash = block.hash.clone(); - // During catchup, we skip the verificationf of completed work and zkApp txn proofs - // until get closer to the best tip, at which point full verification is enabled. - let skip_verification = super::CATCHUP_BLOCK_VERIFY_TAIL_LENGTH - < store.state().transition_frontier.sync.pending_count(); + let is_our_block; if let Some(stats) = store.service.stats() { stats.block_producer().block_apply_start(meta.time(), &hash); + // TODO(tizoc): try a better approach that doesn't need + // to make use of the collected stats. + is_our_block = stats.block_producer().is_our_just_produced_block(&hash); + } else { + is_our_block = false; } + // During catchup, we skip the verificationf of completed work and zkApp txn proofs + // until get closer to the best tip, at which point full verification is enabled. + // We also skip verification of completed works if we produced this block. + let skip_verification = is_our_block + || super::CATCHUP_BLOCK_VERIFY_TAIL_LENGTH + < store.state().transition_frontier.sync.pending_count(); + store.dispatch(LedgerWriteAction::Init { request: LedgerWriteRequest::BlockApply { block, diff --git a/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs b/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs index e1809a8c19..7f8f742b65 100644 --- a/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs +++ b/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs @@ -44,6 +44,7 @@ impl TransitionFrontierSyncState { best_tip, root_block, blocks_inbetween, + .. } => match state { Self::StakingLedgerPending(substate) | Self::NextEpochLedgerPending(substate) @@ -438,7 +439,7 @@ impl TransitionFrontierSyncState { best_chain.iter().map(|b| (b.hash(), b)).collect(); let k = best_tip.constants().k.as_u32() as usize; - let mut chain = Vec::with_capacity(k + root_block_updates.len()); + let mut chain = Vec::with_capacity(k.saturating_add(root_block_updates.len())); // TODO(binier): maybe time should be when we originally // applied this block? Same for below. @@ -653,7 +654,7 @@ impl TransitionFrontierSyncState { return; }; let mut needed_protocol_states = std::mem::take(needed_protocol_states); - let start_i = chain.len().saturating_sub(k + 1); + let start_i = chain.len().saturating_sub(k.saturating_add(1)); let mut iter = std::mem::take(chain) .into_iter() .filter_map(|v| v.take_applied_block()); diff --git a/node/src/transition_frontier/transition_frontier_actions.rs b/node/src/transition_frontier/transition_frontier_actions.rs index bdd6c4f73e..679cafa3dc 100644 --- a/node/src/transition_frontier/transition_frontier_actions.rs +++ b/node/src/transition_frontier/transition_frontier_actions.rs @@ -1,4 +1,5 @@ use std::collections::BTreeSet; +use std::sync::Arc; use mina_p2p_messages::v2::StateHash; use openmina_core::block::ArcBlockWithHash; @@ -25,6 +26,8 @@ pub enum TransitionFrontierAction { /// block, otherwise we don't need it so we use dummy proof instead. #[action_event(level = info)] GenesisInject, + #[action_event(level = info)] + GenesisProvenInject, Sync(TransitionFrontierSyncAction), /// Transition frontier synced. @@ -44,15 +47,20 @@ impl redux::EnablingCondition for TransitionFrontierAction { TransitionFrontierAction::Genesis(a) => a.is_enabled(state, time), TransitionFrontierAction::GenesisEffect(a) => a.is_enabled(state, time), TransitionFrontierAction::GenesisInject => { - if state.transition_frontier.best_tip().is_some() { + state.transition_frontier.root().is_none() + && state + .transition_frontier + .genesis + .block_with_real_or_dummy_proof() + .is_some() + } + TransitionFrontierAction::GenesisProvenInject => { + let Some(genesis) = state.transition_frontier.genesis.proven_block() else { return false; - } - let genesis_state = &state.transition_frontier.genesis; - if state.should_produce_blocks_after_genesis() { - genesis_state.proven_block().is_some() - } else { - genesis_state.block_with_dummy_proof().is_some() - } + }; + state.transition_frontier.root().map_or(true, |b| { + b.is_genesis() && !Arc::ptr_eq(&genesis.block, &b.block) + }) } TransitionFrontierAction::Sync(a) => a.is_enabled(state, time), TransitionFrontierAction::Synced { .. } => matches!( diff --git a/node/src/transition_frontier/transition_frontier_effects.rs b/node/src/transition_frontier/transition_frontier_effects.rs index 07428a03ae..c8b2ae97fe 100644 --- a/node/src/transition_frontier/transition_frontier_effects.rs +++ b/node/src/transition_frontier/transition_frontier_effects.rs @@ -34,10 +34,12 @@ pub fn transition_frontier_effects( // TODO(refactor): this should be handled by a callback and removed from here // whenever any of these is going to happen, genesisinject must happen first match &a { - TransitionFrontierGenesisAction::Produce - | TransitionFrontierGenesisAction::ProveSuccess { .. } => { + TransitionFrontierGenesisAction::Produce => { store.dispatch(TransitionFrontierAction::GenesisInject); } + TransitionFrontierGenesisAction::ProveSuccess { .. } => { + store.dispatch(TransitionFrontierAction::GenesisProvenInject); + } _ => {} } } @@ -47,6 +49,11 @@ pub fn transition_frontier_effects( TransitionFrontierAction::GenesisInject => { synced_effects(&meta, store); } + TransitionFrontierAction::GenesisProvenInject => { + if store.state().transition_frontier.sync.is_synced() { + synced_effects(&meta, store); + } + } TransitionFrontierAction::Sync(a) => { match a { TransitionFrontierSyncAction::Init { @@ -231,7 +238,9 @@ pub fn transition_frontier_effects( best_tip.height().saturating_sub(b1.height()) as usize; if height_diff == 0 { best_tip.hash() != b1.hash() - } else if let Some(index) = chain.len().checked_sub(height_diff + 1) { + } else if let Some(index) = + chain.len().checked_sub(height_diff.saturating_add(1)) + { chain.get(index).map_or(true, |b2| b1.hash() != b2.hash()) } else { true diff --git a/node/src/transition_frontier/transition_frontier_reducer.rs b/node/src/transition_frontier/transition_frontier_reducer.rs index 284846ff3e..559e2f7e6d 100644 --- a/node/src/transition_frontier/transition_frontier_reducer.rs +++ b/node/src/transition_frontier/transition_frontier_reducer.rs @@ -34,9 +34,22 @@ impl TransitionFrontierState { block: genesis, just_emitted_a_proof: true, }; - let new_chain = vec![genesis]; - state.chain_diff = state.maybe_make_chain_diff(&new_chain); - state.best_chain = new_chain; + state.best_chain = vec![genesis]; + state.sync = TransitionFrontierSyncState::Synced { time: meta.time() }; + } + TransitionFrontierAction::GenesisProvenInject => { + let Some(genesis) = state.genesis.proven_block() else { + return; + }; + if let Some(block) = state.best_chain.get_mut(0) { + block.block = genesis.clone(); + } else { + let genesis = AppliedBlock { + block: genesis.clone(), + just_emitted_a_proof: true, + }; + state.best_chain = vec![genesis]; + } if !state.sync.is_pending() { state.sync = TransitionFrontierSyncState::Synced { time: meta.time() }; } @@ -86,7 +99,10 @@ impl TransitionFrontierState { // into transition frontier anymore due to consensus // reasons. let tip = new_chain.last().unwrap(); - *height + tip.constants().k.as_u32() > tip.height() + height + .checked_add(tip.constants().k.as_u32()) + .expect("overflow") + > tip.height() }); state.chain_diff = state.maybe_make_chain_diff(&new_chain); state.best_chain = new_chain; diff --git a/node/src/transition_frontier/transition_frontier_state.rs b/node/src/transition_frontier/transition_frontier_state.rs index 69811b0c0d..9fdde1e3b5 100644 --- a/node/src/transition_frontier/transition_frontier_state.rs +++ b/node/src/transition_frontier/transition_frontier_state.rs @@ -6,6 +6,7 @@ use mina_p2p_messages::v2::{ TransactionHash, }; use openmina_core::block::{AppliedBlock, ArcBlockWithHash}; +use openmina_core::bug_condition; use serde::{Deserialize, Serialize}; use super::genesis::TransitionFrontierGenesisState; @@ -128,22 +129,39 @@ impl TransitionFrontierState { } Some((new_chain_start_at, _)) => { // `new_chain_start_at` is the index of `new_root` in `old_chain` - let old_chain_advanced = &old_chain[new_chain_start_at..]; + let Some(old_chain_advanced) = &old_chain.get(new_chain_start_at..) else { + bug_condition!("old_chain[{}] out of bounds", new_chain_start_at); + return None; + }; // Common length let len = old_chain_advanced.len().min(new_chain.len()); // Find the first different block, search from the end - let diff_start_at = old_chain_advanced[..len] + let diff_start_at = old_chain_advanced + .get(..len) + .unwrap() // can't fail because len is the minimum len .iter() .rev() - .zip(new_chain[..len].iter().rev()) + .zip( + new_chain + .get(..len) + .unwrap() // can't fail because len is the minimum len + .iter() + .rev(), + ) .position(|(old_block, new_block)| old_block == new_block) - .map(|index| len - index) // we started from the end + .map(|index| len.saturating_sub(index)) // we started from the end .unwrap(); // Never panics because we know there is the common root block - let diff_old_chain = &old_chain_advanced[diff_start_at..]; - let diff_new_chain = &new_chain[diff_start_at..]; + let Some(diff_old_chain) = old_chain_advanced.get(diff_start_at..) else { + bug_condition!("old_chain[{}] out of bounds", diff_start_at); + return None; + }; + let Some(diff_new_chain) = new_chain.get(diff_start_at..) else { + bug_condition!("new_chain[{}] out of bounds", diff_start_at); + return None; + }; (diff_old_chain, diff_new_chain) } diff --git a/node/testing/Cargo.toml b/node/testing/Cargo.toml index c96457c74c..10412190c8 100644 --- a/node/testing/Cargo.toml +++ b/node/testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-node-testing" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -24,8 +24,8 @@ rand = "0.8" tokio = { version = "1.26.0" } num_cpus = "1.0" rayon = "1.5" -axum = "0.6" -tower-http = { version = "0.4.4", features = ["cors"] } +axum = "0.7" +tower-http = { version = "0.6", features = ["cors", "fs"] } strum = "0.26" strum_macros = "0.26" tracing-log = "0.2.0" @@ -39,6 +39,9 @@ vrf = { workspace = true } time = { version = "0.3", features = ["formatting"] } tracing = { version = "0.1", features = ["std"] } multihash = "0.18.1" +hex = "0.4.3" +bs58 = { version = "0.4" } +base64 = "0.22" nix = { version = "0.27.1", features = ["process", "signal"] } ctrlc = "3.4.2" @@ -51,7 +54,6 @@ openmina-core = { path = "../../core" } node = { path = "../../node" } openmina-node-invariants = { path = "../../node/invariants" } openmina-node-native = { path = "../../node/native" } -hex = "0.4.3" [target.'cfg(not(target_family = "wasm"))'.dependencies] redux = { workspace = true, features=["serializable_callbacks"] } diff --git a/node/testing/docker/Dockerfile.openmina b/node/testing/docker/Dockerfile.openmina index 2dcc6d531d..18250403c8 100644 --- a/node/testing/docker/Dockerfile.openmina +++ b/node/testing/docker/Dockerfile.openmina @@ -9,7 +9,7 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y RUN rm /bin/sh && ln -s /bin/bash /bin/sh -RUN source ~/.cargo/env && rustup update 1.80 +RUN source ~/.cargo/env && rustup update 1.83 RUN git clone https://github.com/openmina/openmina diff --git a/node/testing/docker/Dockerfile.test b/node/testing/docker/Dockerfile.test index 9207a02f5c..8227afd053 100644 --- a/node/testing/docker/Dockerfile.test +++ b/node/testing/docker/Dockerfile.test @@ -2,7 +2,7 @@ FROM vladsimplestakingcom/mina-openmina-builder:focal AS builder RUN git fetch && git checkout feat/tests-with-debugger -RUN source ~/.cargo/env && cargo +1.80 build --release -p openmina-node-testing --bin runner --bin openmina-node-testing +RUN source ~/.cargo/env && cargo +1.83 build --release -p openmina-node-testing --bin runner --bin openmina-node-testing FROM vladsimplestakingcom/mina-debugger:2.0.0rampup4-focal diff --git a/node/testing/src/cluster/config.rs b/node/testing/src/cluster/config.rs index 0a815b006e..37848cdef3 100644 --- a/node/testing/src/cluster/config.rs +++ b/node/testing/src/cluster/config.rs @@ -4,11 +4,15 @@ use crate::node::OcamlNodeExecutable; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ClusterConfig { + #[serde(default)] port_range: Option<(u16, u16)>, all_rust_to_rust_use_webrtc: bool, proof_kind: ProofKind, + #[serde(default)] is_replay: bool, + #[serde(default)] use_debugger: bool, + #[serde(default)] ocaml_node_executable: Option, } @@ -21,8 +25,7 @@ pub enum ProofKind { impl Default for ProofKind { fn default() -> Self { - // TODO(binier): change default to `ConstraintsChecked` once - // https://github.com/openmina/openmina/issues/260 is closed + // once it's working, change to Self::ConstraintsChecked Self::Dummy } } @@ -39,7 +42,7 @@ impl ClusterConfig { }) } - pub fn use_debugger(mut self) -> Self { + pub fn use_debugger(&mut self) -> &mut Self { self.use_debugger = true; self } @@ -48,7 +51,7 @@ impl ClusterConfig { self.use_debugger } - pub fn set_replay(mut self) -> Self { + pub fn set_replay(&mut self) -> &mut Self { self.is_replay = true; self } @@ -62,7 +65,7 @@ impl ClusterConfig { (range.0)..=(range.1) } - pub fn set_all_rust_to_rust_use_webrtc(mut self) -> Self { + pub fn set_all_rust_to_rust_use_webrtc(&mut self) -> &mut Self { assert!(cfg!(feature = "p2p-webrtc")); self.all_rust_to_rust_use_webrtc = true; self @@ -72,11 +75,16 @@ impl ClusterConfig { self.all_rust_to_rust_use_webrtc } + pub fn set_proof_kind(&mut self, kind: ProofKind) -> &mut Self { + self.proof_kind = kind; + self + } + pub fn proof_kind(&self) -> ProofKind { self.proof_kind } - pub fn set_ocaml_node_executable(mut self, executable: OcamlNodeExecutable) -> Self { + pub fn set_ocaml_node_executable(&mut self, executable: OcamlNodeExecutable) -> &mut Self { self.ocaml_node_executable = Some(executable); self } diff --git a/node/testing/src/cluster/mod.rs b/node/testing/src/cluster/mod.rs index 8a39c95553..c65d65d242 100644 --- a/node/testing/src/cluster/mod.rs +++ b/node/testing/src/cluster/mod.rs @@ -11,6 +11,7 @@ pub mod runner; use std::collections::BTreeMap; use std::io::Read; use std::path::{Path, PathBuf}; +use std::sync::Mutex as StdMutex; use std::time::Duration; use std::{collections::VecDeque, sync::Arc}; @@ -21,6 +22,7 @@ use node::account::{AccountPublicKey, AccountSecretKey}; use node::core::channels::mpsc; use node::core::consensus::ConsensusConstants; use node::core::constants::constraint_constants; +use node::core::invariants::InvariantsState; use node::core::log::system_time; use node::core::requests::RpcId; use node::core::{thread, warn}; @@ -117,13 +119,6 @@ lazy_static::lazy_static! { static ref VERIFIER_SRS: Arc = get_srs(); } -lazy_static::lazy_static! { - static ref DETERMINISTIC_ACCOUNT_SEC_KEYS: BTreeMap = (0..1000) - .map(AccountSecretKey::deterministic) - .map(|sec_key| (sec_key.public_key(), sec_key)) - .collect(); -} - pub struct Cluster { pub config: ClusterConfig, scenario: ClusterScenarioRun, @@ -141,6 +136,7 @@ pub struct Cluster { work_verifier_index: TransactionVerifier, debugger: Option, + invariants_state: Arc>, } #[derive(Serialize)] @@ -181,6 +177,7 @@ impl Cluster { work_verifier_index: TransactionVerifier::make(), debugger, + invariants_state: Arc::new(StdMutex::new(Default::default())), } } @@ -193,9 +190,9 @@ impl Cluster { } pub fn get_account_sec_key(&self, pub_key: &AccountPublicKey) -> Option<&AccountSecretKey> { - self.account_sec_keys - .get(pub_key) - .or_else(|| DETERMINISTIC_ACCOUNT_SEC_KEYS.get(pub_key)) + self.account_sec_keys.get(pub_key).or_else(|| { + AccountSecretKey::deterministic_iter().find(|sec_key| &sec_key.public_key() == pub_key) + }) } pub fn set_initial_time(&mut self, initial_time: redux::Timestamp) { @@ -282,7 +279,6 @@ impl Cluster { identity_pub_key: p2p_sec_key.public_key(), initial_peers, external_addrs: vec![], - ask_initial_peers_interval: testing_config.ask_initial_peers_interval, enabled_channels: ChannelId::iter_all().collect(), peer_discovery: true, timeouts: testing_config.timeouts, @@ -321,7 +317,7 @@ impl Cluster { if let Some(keypair) = block_producer_sec_key { let provers = BlockProver::make(None, None); - service_builder.block_producer_init(provers, keypair); + service_builder.block_producer_init(keypair, Some(provers)); } let real_service = service_builder @@ -350,7 +346,9 @@ impl Cluster { }) .unwrap(); - let mut service = NodeTestingService::new(real_service, node_id, shutdown_rx); + let invariants_state = self.invariants_state.clone(); + let mut service = + NodeTestingService::new(real_service, node_id, invariants_state, shutdown_rx); service.set_proof_kind(self.config.proof_kind()); if self.config.all_rust_to_rust_use_webrtc() { @@ -373,6 +371,9 @@ impl Cluster { for (invariant, res) in Invariants::check_all(store, &action) { // TODO(binier): record instead of panicing. match res { + InvariantResult::Ignored(reason) => { + unreachable!("No invariant should be ignored! ignore reason: {reason:?}"); + } InvariantResult::Violation(violation) => { panic!( "Invariant({}) violated! violation: {violation}", diff --git a/node/testing/src/cluster/runner/mod.rs b/node/testing/src/cluster/runner/mod.rs index afc240d32d..c21d37210b 100644 --- a/node/testing/src/cluster/runner/mod.rs +++ b/node/testing/src/cluster/runner/mod.rs @@ -23,7 +23,7 @@ use crate::{ pub struct ClusterRunner<'a> { cluster: &'a mut Cluster, - add_step: Box, + add_step: Box, rng: StdRng, latest_advance_time: Option, } @@ -31,7 +31,7 @@ pub struct ClusterRunner<'a> { impl<'a> ClusterRunner<'a> { pub fn new(cluster: &'a mut Cluster, add_step: F) -> Self where - F: 'a + FnMut(&ScenarioStep), + F: 'a + Send + FnMut(&ScenarioStep), { Self { cluster, @@ -319,7 +319,7 @@ impl<'a> ClusterRunner<'a> { where F: Fn(&State, u32, u32) -> bool, { - let now = tokio::time::Instant::now(); + let now = redux::Instant::now(); let mut last_slot: u32 = 0; let mut produced_blocks: u32 = 0; diff --git a/node/testing/src/cluster/runner/run.rs b/node/testing/src/cluster/runner/run.rs index ca5aa9c609..057497d8df 100644 --- a/node/testing/src/cluster/runner/run.rs +++ b/node/testing/src/cluster/runner/run.rs @@ -23,12 +23,13 @@ pub struct RunCfg< advance_time: Option, } -#[derive(derive_more::From, Serialize, Deserialize, Debug, Clone)] +#[derive(derive_more::From, Serialize, Deserialize, Debug, Default, Clone)] pub enum RunCfgAdvanceTime { /// Set the range of time in milliseconds, with which time will be /// advanced during `run` function execution. Rand(std::ops::RangeInclusive), /// Advance time so that node's time matches the real time. + #[default] Real, } diff --git a/node/testing/src/main.rs b/node/testing/src/main.rs index bd51d5a7e0..57b362a036 100644 --- a/node/testing/src/main.rs +++ b/node/testing/src/main.rs @@ -1,5 +1,6 @@ use clap::Parser; +use node::p2p::webrtc::Host; use openmina_node_testing::cluster::{Cluster, ClusterConfig}; use openmina_node_testing::scenario::Scenario; use openmina_node_testing::scenarios::Scenarios; @@ -24,8 +25,13 @@ pub enum Command { #[derive(Debug, clap::Args)] pub struct CommandServer { - #[arg(long, short, env, default_value = "11000")] + #[arg(long, short, default_value = "127.0.0.1")] + pub host: Host, + + #[arg(long, short, default_value = "11000")] pub port: u16, + #[arg(long, short)] + pub ssl_port: Option, } #[derive(Debug, clap::Args)] @@ -66,38 +72,33 @@ impl Command { match self { Self::Server(args) => { - server(args.port); + server(rt, args.host, args.port, args.ssl_port); Ok(()) } Self::ScenariosGenerate(cmd) => { #[cfg(feature = "scenario-generators")] { - let config = ClusterConfig::new(None).map_err(|err| { - anyhow::anyhow!("failed to create cluster configuration: {err}") - })?; - let config = if cmd.use_debugger { - config.use_debugger() - } else { - config + let run_scenario = |scenario: Scenarios| -> Result<_, anyhow::Error> { + let mut config = scenario.default_cluster_config()?; + if cmd.use_debugger { + config.use_debugger(); + } + Ok(scenario.run_only_from_scratch(config)) }; - let fut = async move { if let Some(name) = cmd.name { if let Some(scenario) = Scenarios::find_by_name(&name) { - scenario.run_only_from_scratch(config).await; - // scenario.run_and_save_from_scratch(config).await; + run_scenario(scenario)?.await; } else { anyhow::bail!("no such scenario: \"{name}\""); } } else { for scenario in Scenarios::iter() { - scenario.run_only_from_scratch(config.clone()).await; - // scenario.run_and_save_from_scratch(config.clone()).await; + run_scenario(scenario)?.await; } } Ok(()) }; - rt.block_on(async { tokio::select! { res = fut => res, @@ -113,10 +114,10 @@ impl Command { .into()) } Self::ScenariosRun(cmd) => { - let config = ClusterConfig::new(None).map_err(|err| { + let mut config = ClusterConfig::new(None).map_err(|err| { anyhow::anyhow!("failed to create cluster configuration: {err}") })?; - let config = config.set_replay(); + config.set_replay(); let id = cmd.name.parse()?; let fut = async move { diff --git a/node/testing/src/network_debugger.rs b/node/testing/src/network_debugger.rs index 04f9547cab..e2cf1bbed1 100644 --- a/node/testing/src/network_debugger.rs +++ b/node/testing/src/network_debugger.rs @@ -179,7 +179,7 @@ pub struct ConnectionsHandshaked<'a> { ids_cache: HashSet, } -impl<'a> Iterator for ConnectionsHandshaked<'a> { +impl Iterator for ConnectionsHandshaked<'_> { type Item = u64; fn next(&mut self) -> Option { @@ -199,7 +199,7 @@ pub struct Connections<'a> { buffer: VecDeque<(u64, Connection)>, } -impl<'a> Iterator for Connections<'a> { +impl Iterator for Connections<'_> { type Item = (u64, Connection); fn next(&mut self) -> Option { @@ -225,7 +225,7 @@ pub struct Messages<'a> { buffer: VecDeque<(u64, FullMessage)>, } -impl<'a> Iterator for Messages<'a> { +impl Iterator for Messages<'_> { type Item = (u64, FullMessage); fn next(&mut self) -> Option { diff --git a/node/testing/src/node/rust/config.rs b/node/testing/src/node/rust/config.rs index 1022004f7e..4754ebad74 100644 --- a/node/testing/src/node/rust/config.rs +++ b/node/testing/src/node/rust/config.rs @@ -1,6 +1,6 @@ use std::fs::File; use std::path::Path; -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; use node::account::AccountSecretKey; use node::config::DEVNET_CONFIG; @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::scenario::ListenerNode; -#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub enum TestPeerId { /// NOTE This option results a deterministic private key derived from the /// node index in the cluster. Be aware that when interacting with OCaml @@ -26,18 +26,25 @@ pub struct RustNodeTestingConfig { pub initial_time: redux::Timestamp, pub genesis: Arc, pub max_peers: usize, - pub ask_initial_peers_interval: Duration, + #[serde(default)] pub initial_peers: Vec, + #[serde(default)] pub peer_id: TestPeerId, + #[serde(default)] pub snark_worker: Option, + #[serde(default)] pub block_producer: Option, + #[serde(default)] pub timeouts: P2pTimeouts, + #[serde(default)] pub libp2p_port: Option, + #[serde(default)] pub recorder: Recorder, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub enum Recorder { + #[default] None, StateWithInputActions, } @@ -54,7 +61,6 @@ impl RustNodeTestingConfig { initial_time: redux::Timestamp::ZERO, genesis: DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(10), initial_peers: Vec::new(), peer_id: TestPeerId::default(), block_producer: None, @@ -70,7 +76,6 @@ impl RustNodeTestingConfig { initial_time: redux::Timestamp::ZERO, genesis: DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(10), initial_peers: Vec::new(), peer_id: TestPeerId::default(), block_producer: None, @@ -86,11 +91,6 @@ impl RustNodeTestingConfig { self } - pub fn ask_initial_peers_interval(mut self, d: Duration) -> Self { - self.ask_initial_peers_interval = d; - self - } - pub fn initial_peers(mut self, v: Vec) -> Self { self.initial_peers = v; self @@ -119,9 +119,3 @@ impl RustNodeTestingConfig { self } } - -impl Default for Recorder { - fn default() -> Self { - Self::None - } -} diff --git a/node/testing/src/scenarios/driver.rs b/node/testing/src/scenarios/driver.rs index 2a33c94b12..364168e275 100644 --- a/node/testing/src/scenarios/driver.rs +++ b/node/testing/src/scenarios/driver.rs @@ -734,7 +734,7 @@ pub fn peer_is_ready( ) } -pub fn get_p2p_state<'a>(cluster: &'a ClusterRunner<'a>, node_id: ClusterNodeId) -> &P2pState { +pub fn get_p2p_state<'a>(cluster: &'a ClusterRunner<'a>, node_id: ClusterNodeId) -> &'a P2pState { cluster .node(node_id) .expect("node should exist") diff --git a/node/testing/src/scenarios/mod.rs b/node/testing/src/scenarios/mod.rs index 867a1f7464..9bacc6dbe1 100644 --- a/node/testing/src/scenarios/mod.rs +++ b/node/testing/src/scenarios/mod.rs @@ -18,7 +18,6 @@ pub mod p2p; mod driver; pub use driver::*; -use p2p::signaling::P2pSignaling; pub use crate::cluster::runner::*; @@ -29,13 +28,31 @@ use crate::scenario::{Scenario, ScenarioId, ScenarioStep}; use self::multi_node::basic_connectivity_initial_joining::MultiNodeBasicConnectivityInitialJoining; use self::multi_node::basic_connectivity_peer_discovery::MultiNodeBasicConnectivityPeerDiscovery; +use self::multi_node::connection_discovery::RustNodeAsSeed as P2pConnectionDiscoveryRustNodeAsSeed; +use self::multi_node::connection_discovery::{ + OCamlToRust, OCamlToRustViaSeed, RustToOCaml, RustToOCamlViaSeed, +}; use self::multi_node::pubsub_advanced::MultiNodePubsubPropagateBlock; use self::multi_node::sync_4_block_producers::MultiNodeSync4BlockProducers; use self::multi_node::vrf_correct_ledgers::MultiNodeVrfGetCorrectLedgers; use self::multi_node::vrf_correct_slots::MultiNodeVrfGetCorrectSlots; use self::multi_node::vrf_epoch_bounds_correct_ledgers::MultiNodeVrfEpochBoundsCorrectLedger; use self::multi_node::vrf_epoch_bounds_evaluation::MultiNodeVrfEpochBoundsEvaluation; +use self::p2p::basic_connection_handling::{ + AllNodesConnectionsAreSymmetric, MaxNumberOfPeersIncoming, MaxNumberOfPeersIs1, + SeedConnectionsAreSymmetric, SimultaneousConnections, +}; +use self::p2p::basic_incoming_connections::{ + AcceptIncomingConnection, AcceptMultipleIncomingConnections, +}; +use self::p2p::basic_outgoing_connections::{ + ConnectToInitialPeers, ConnectToInitialPeersBecomeReady, ConnectToUnavailableInitialPeers, + DontConnectToInitialPeerWithSameId, DontConnectToNodeWithSameId, DontConnectToSelfInitialPeer, + MakeMultipleOutgoingConnections, MakeOutgoingConnection, +}; +use self::p2p::kademlia::KademliaBootstrap; use self::p2p::pubsub::P2pReceiveBlock; +use self::p2p::signaling::P2pSignaling; use self::record_replay::block_production::RecordReplayBlockProduction; use self::record_replay::bootstrap::RecordReplayBootstrap; use self::simulation::small::SimulationSmall; @@ -68,9 +85,31 @@ pub enum Scenarios { SimulationSmallForeverRealTime(SimulationSmallForeverRealTime), P2pReceiveBlock(P2pReceiveBlock), P2pSignaling(P2pSignaling), + P2pConnectionDiscoveryRustNodeAsSeed(P2pConnectionDiscoveryRustNodeAsSeed), MultiNodePubsubPropagateBlock(MultiNodePubsubPropagateBlock), RecordReplayBootstrap(RecordReplayBootstrap), RecordReplayBlockProduction(RecordReplayBlockProduction), + + RustToOCaml(RustToOCaml), + OCamlToRust(OCamlToRust), + OCamlToRustViaSeed(OCamlToRustViaSeed), + RustToOCamlViaSeed(RustToOCamlViaSeed), + KademliaBootstrap(KademliaBootstrap), + AcceptIncomingConnection(AcceptIncomingConnection), + MakeOutgoingConnection(MakeOutgoingConnection), + AcceptMultipleIncomingConnections(AcceptMultipleIncomingConnections), + MakeMultipleOutgoingConnections(MakeMultipleOutgoingConnections), + DontConnectToNodeWithSameId(DontConnectToNodeWithSameId), + DontConnectToInitialPeerWithSameId(DontConnectToInitialPeerWithSameId), + DontConnectToSelfInitialPeer(DontConnectToSelfInitialPeer), + SimultaneousConnections(SimultaneousConnections), + ConnectToInitialPeers(ConnectToInitialPeers), + ConnectToUnavailableInitialPeers(ConnectToUnavailableInitialPeers), + AllNodesConnectionsAreSymmetric(AllNodesConnectionsAreSymmetric), + ConnectToInitialPeersBecomeReady(ConnectToInitialPeersBecomeReady), + SeedConnectionsAreSymmetric(SeedConnectionsAreSymmetric), + MaxNumberOfPeersIncoming(MaxNumberOfPeersIncoming), + MaxNumberOfPeersIs1(MaxNumberOfPeersIs1), } impl Scenarios { @@ -152,9 +191,43 @@ impl Scenarios { Self::SimulationSmallForeverRealTime(_) => SimulationSmallForeverRealTime::DOCS, Self::P2pReceiveBlock(_) => P2pReceiveBlock::DOCS, Self::P2pSignaling(_) => P2pSignaling::DOCS, + Self::P2pConnectionDiscoveryRustNodeAsSeed(_) => { + P2pConnectionDiscoveryRustNodeAsSeed::DOCS + } Self::MultiNodePubsubPropagateBlock(_) => MultiNodePubsubPropagateBlock::DOCS, Self::RecordReplayBootstrap(_) => RecordReplayBootstrap::DOCS, Self::RecordReplayBlockProduction(_) => RecordReplayBlockProduction::DOCS, + + Self::RustToOCaml(_) => RustToOCaml::DOCS, + Self::OCamlToRust(_) => OCamlToRust::DOCS, + Self::OCamlToRustViaSeed(_) => OCamlToRustViaSeed::DOCS, + Self::RustToOCamlViaSeed(_) => RustToOCamlViaSeed::DOCS, + Self::KademliaBootstrap(_) => KademliaBootstrap::DOCS, + Self::AcceptIncomingConnection(_) => AcceptIncomingConnection::DOCS, + Self::MakeOutgoingConnection(_) => MakeOutgoingConnection::DOCS, + Self::AcceptMultipleIncomingConnections(_) => AcceptMultipleIncomingConnections::DOCS, + Self::MakeMultipleOutgoingConnections(_) => MakeMultipleOutgoingConnections::DOCS, + Self::DontConnectToNodeWithSameId(_) => DontConnectToNodeWithSameId::DOCS, + Self::DontConnectToInitialPeerWithSameId(_) => DontConnectToInitialPeerWithSameId::DOCS, + Self::DontConnectToSelfInitialPeer(_) => DontConnectToSelfInitialPeer::DOCS, + Self::SimultaneousConnections(_) => SimultaneousConnections::DOCS, + Self::ConnectToInitialPeers(_) => ConnectToInitialPeers::DOCS, + Self::ConnectToUnavailableInitialPeers(_) => ConnectToUnavailableInitialPeers::DOCS, + Self::AllNodesConnectionsAreSymmetric(_) => AllNodesConnectionsAreSymmetric::DOCS, + Self::ConnectToInitialPeersBecomeReady(_) => ConnectToInitialPeersBecomeReady::DOCS, + Self::SeedConnectionsAreSymmetric(_) => SeedConnectionsAreSymmetric::DOCS, + Self::MaxNumberOfPeersIncoming(_) => MaxNumberOfPeersIncoming::DOCS, + Self::MaxNumberOfPeersIs1(_) => MaxNumberOfPeersIs1::DOCS, + } + } + + pub fn default_cluster_config(self) -> Result { + let config = ClusterConfig::new(None) + .map_err(|err| anyhow::anyhow!("failed to create cluster configuration: {err}"))?; + + match self { + Self::P2pSignaling(v) => v.default_cluster_config(config), + _ => Ok(config), } } @@ -168,7 +241,7 @@ impl Scenarios { async fn run(self, cluster: &mut Cluster, add_step: F) where - F: FnMut(&ScenarioStep), + F: Send + FnMut(&ScenarioStep), { let runner = ClusterRunner::new(cluster, add_step); match self { @@ -189,9 +262,31 @@ impl Scenarios { Self::SimulationSmallForeverRealTime(v) => v.run(runner).await, Self::P2pReceiveBlock(v) => v.run(runner).await, Self::P2pSignaling(v) => v.run(runner).await, + Self::P2pConnectionDiscoveryRustNodeAsSeed(v) => v.run(runner).await, Self::MultiNodePubsubPropagateBlock(v) => v.run(runner).await, Self::RecordReplayBootstrap(v) => v.run(runner).await, Self::RecordReplayBlockProduction(v) => v.run(runner).await, + + Self::RustToOCaml(v) => v.run(runner).await, + Self::OCamlToRust(v) => v.run(runner).await, + Self::OCamlToRustViaSeed(v) => v.run(runner).await, + Self::RustToOCamlViaSeed(v) => v.run(runner).await, + Self::KademliaBootstrap(v) => v.run(runner).await, + Self::AcceptIncomingConnection(v) => v.run(runner).await, + Self::MakeOutgoingConnection(v) => v.run(runner).await, + Self::AcceptMultipleIncomingConnections(v) => v.run(runner).await, + Self::MakeMultipleOutgoingConnections(v) => v.run(runner).await, + Self::DontConnectToNodeWithSameId(v) => v.run(runner).await, + Self::DontConnectToInitialPeerWithSameId(v) => v.run(runner).await, + Self::DontConnectToSelfInitialPeer(v) => v.run(runner).await, + Self::SimultaneousConnections(v) => v.run(runner).await, + Self::ConnectToInitialPeers(v) => v.run(runner).await, + Self::ConnectToUnavailableInitialPeers(v) => v.run(runner).await, + Self::AllNodesConnectionsAreSymmetric(v) => v.run(runner).await, + Self::ConnectToInitialPeersBecomeReady(v) => v.run(runner).await, + Self::SeedConnectionsAreSymmetric(v) => v.run(runner).await, + Self::MaxNumberOfPeersIncoming(v) => v.run(runner).await, + Self::MaxNumberOfPeersIs1(v) => v.run(runner).await, } } diff --git a/node/testing/src/scenarios/multi_node/basic_connectivity_initial_joining.rs b/node/testing/src/scenarios/multi_node/basic_connectivity_initial_joining.rs index 73eb32db79..9ae698eec0 100644 --- a/node/testing/src/scenarios/multi_node/basic_connectivity_initial_joining.rs +++ b/node/testing/src/scenarios/multi_node/basic_connectivity_initial_joining.rs @@ -44,8 +44,7 @@ impl MultiNodeBasicConnectivityInitialJoining { let node = runner.add_rust_node( RustNodeTestingConfig::devnet_default() .max_peers(MAX_PEERS_PER_NODE) - .initial_peers(vec![seed_node.into()]) - .ask_initial_peers_interval(Duration::from_secs(10)), + .initial_peers(vec![seed_node.into()]), ); eprintln!("launch Openmina node, id: {node}, connects to {seed_node}"); diff --git a/node/testing/src/scenarios/multi_node/basic_connectivity_peer_discovery.rs b/node/testing/src/scenarios/multi_node/basic_connectivity_peer_discovery.rs index 771b74a018..fb7936288b 100644 --- a/node/testing/src/scenarios/multi_node/basic_connectivity_peer_discovery.rs +++ b/node/testing/src/scenarios/multi_node/basic_connectivity_peer_discovery.rs @@ -67,7 +67,6 @@ impl MultiNodeBasicConnectivityPeerDiscovery { eprintln!("OCaml nodes should be ready now"); let config = RustNodeTestingConfig::devnet_default() - .ask_initial_peers_interval(Duration::from_secs(3600)) //.with_daemon_json("genesis_ledgers/devnet-full.json") .max_peers(100) .initial_peers( diff --git a/node/testing/src/scenarios/multi_node/pubsub_advanced.rs b/node/testing/src/scenarios/multi_node/pubsub_advanced.rs index 743343873e..acf98c3d5b 100644 --- a/node/testing/src/scenarios/multi_node/pubsub_advanced.rs +++ b/node/testing/src/scenarios/multi_node/pubsub_advanced.rs @@ -1,11 +1,20 @@ -use std::time::Duration; +use std::{ + str, + sync::{Arc, Mutex}, + time::Duration, +}; -use mina_p2p_messages::v2; -use node::transition_frontier::genesis::{GenesisConfig, NonStakers}; +use mina_p2p_messages::{binprot::BinProtRead, gossip, v2}; +use node::{ + p2p::{P2pNetworkAction, P2pNetworkPubsubAction, PeerId}, + transition_frontier::genesis::{GenesisConfig, NonStakers}, + Action, ActionWithMeta, P2pAction, +}; use crate::{ node::Recorder, scenarios::{ClusterRunner, RunCfgAdvanceTime}, + service::NodeTestingService, simulator::{Simulator, SimulatorConfig, SimulatorRunUntil}, }; @@ -20,9 +29,58 @@ use crate::{ pub struct MultiNodePubsubPropagateBlock; impl MultiNodePubsubPropagateBlock { - const WORKERS: usize = 20; + const WORKERS: usize = 10; pub async fn run(self, mut runner: ClusterRunner<'_>) { + let graph = Arc::new(Mutex::new("digraph {\n".to_owned())); + let factory = || { + let graph = graph.clone(); + move |_id, + state: &node::State, + _service: &NodeTestingService, + action: &ActionWithMeta| { + let this = state.p2p.my_id(); + + let cut = |peer_id: &PeerId| { + let st = peer_id.to_string(); + let len = st.len(); + st[(len - 6)..len].to_owned() + }; + let this = cut(&this); + + match action.action() { + Action::P2p(P2pAction::Network(P2pNetworkAction::Pubsub( + P2pNetworkPubsubAction::OutgoingMessage { peer_id }, + ))) => { + let pubsub_state = + &state.p2p.ready().unwrap().network.scheduler.broadcast_state; + let msg = &pubsub_state.clients.get(peer_id).unwrap().message; + + for publish_message in &msg.publish { + let mut slice = &publish_message.data()[8..]; + if let Ok(gossip::GossipNetMessageV2::NewState(block)) = + gossip::GossipNetMessageV2::binprot_read(&mut slice) + { + let height = block + .header + .protocol_state + .body + .consensus_state + .global_slot(); + let mut lock = graph.lock().unwrap(); + *lock = format!( + "{lock} \"{this}\" -> \"{}\" [label=\"{height}\"];\n", + cut(peer_id) + ); + } + } + false + } + _ => false, + } + } + }; + let initial_time = redux::Timestamp::global_now(); let mut constants = v2::PROTOCOL_CONSTANTS.clone(); constants.genesis_state_timestamp = @@ -40,11 +98,15 @@ impl MultiNodePubsubPropagateBlock { snark_workers: 1, block_producers: 1, advance_time: RunCfgAdvanceTime::Rand(1..=200), - run_until: SimulatorRunUntil::BlockchainLength(3), + run_until: SimulatorRunUntil::BlockchainLength(4), run_until_timeout: Duration::from_secs(10 * 60), recorder: Recorder::StateWithInputActions, }; let mut simulator = Simulator::new(initial_time, config); - simulator.run(&mut runner).await; + simulator + .setup_and_run_with_listener(&mut runner, factory) + .await; + + println!("{}}}\n", graph.lock().unwrap()); } } diff --git a/node/testing/src/scenarios/multi_node/vrf_correct_ledgers.rs b/node/testing/src/scenarios/multi_node/vrf_correct_ledgers.rs index a9576a7ec4..07fd97d8ff 100644 --- a/node/testing/src/scenarios/multi_node/vrf_correct_ledgers.rs +++ b/node/testing/src/scenarios/multi_node/vrf_correct_ledgers.rs @@ -37,7 +37,6 @@ impl MultiNodeVrfGetCorrectLedgers { initial_time, genesis: node::config::DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(60 * 60), initial_peers: Vec::new(), peer_id: Default::default(), block_producer: Some(RustNodeBlockProducerTestingConfig { diff --git a/node/testing/src/scenarios/multi_node/vrf_correct_slots.rs b/node/testing/src/scenarios/multi_node/vrf_correct_slots.rs index d0f5b88864..97c0e0a8c7 100644 --- a/node/testing/src/scenarios/multi_node/vrf_correct_slots.rs +++ b/node/testing/src/scenarios/multi_node/vrf_correct_slots.rs @@ -43,7 +43,6 @@ impl MultiNodeVrfGetCorrectSlots { initial_time, genesis: node::config::DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(60 * 60), initial_peers: Vec::new(), peer_id: Default::default(), block_producer: Some(RustNodeBlockProducerTestingConfig { diff --git a/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs b/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs index e37b82be3c..886ae23b27 100644 --- a/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs +++ b/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs @@ -32,7 +32,7 @@ pub struct MultiNodeVrfEpochBoundsCorrectLedger; impl MultiNodeVrfEpochBoundsCorrectLedger { pub async fn run(self, mut runner: ClusterRunner<'_>) { - let start = tokio::time::Instant::now(); + let start = redux::Instant::now(); let initial_time = runner.get_initial_time().unwrap(); let (initial_node, _) = runner.nodes_iter().last().unwrap(); @@ -51,7 +51,6 @@ impl MultiNodeVrfEpochBoundsCorrectLedger { initial_time, genesis: node::config::DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(60 * 60), initial_peers: Vec::new(), peer_id: Default::default(), block_producer: None, diff --git a/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_evaluation.rs b/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_evaluation.rs index 673d04496c..77eb09bc8f 100644 --- a/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_evaluation.rs +++ b/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_evaluation.rs @@ -37,7 +37,6 @@ impl MultiNodeVrfEpochBoundsEvaluation { initial_time, genesis: node::config::DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(60 * 60), initial_peers: Vec::new(), peer_id: Default::default(), block_producer: None, diff --git a/node/testing/src/scenarios/p2p/pubsub.rs b/node/testing/src/scenarios/p2p/pubsub.rs index 1e2d1604c3..225e1d5eb5 100644 --- a/node/testing/src/scenarios/p2p/pubsub.rs +++ b/node/testing/src/scenarios/p2p/pubsub.rs @@ -14,7 +14,6 @@ impl P2pReceiveBlock { pub async fn run(self, mut runner: ClusterRunner<'_>) { let config = RustNodeTestingConfig::devnet_default() // make sure it will not ask initial peers - .ask_initial_peers_interval(Duration::from_secs(3600)) .max_peers(1) .initial_peers(vec![hosts::devnet()[0].clone()]); let retransmitter_openmina_node = runner.add_rust_node(config); @@ -27,7 +26,6 @@ impl P2pReceiveBlock { let config = RustNodeTestingConfig::devnet_default() // make sure it will not ask initial peers - .ask_initial_peers_interval(Duration::from_secs(3600)) .max_peers(1) .initial_peers(vec![retransmitter_openmina_node.into()]); let receiver_openmina_node = runner.add_rust_node(config); diff --git a/node/testing/src/scenarios/p2p/signaling.rs b/node/testing/src/scenarios/p2p/signaling.rs index 5124238d6a..288ab075d0 100644 --- a/node/testing/src/scenarios/p2p/signaling.rs +++ b/node/testing/src/scenarios/p2p/signaling.rs @@ -6,6 +6,7 @@ use node::{ }; use crate::{ + cluster::ClusterConfig, node::RustNodeTestingConfig, scenarios::{ClusterRunner, DynEffectsData, RunCfg}, }; @@ -16,6 +17,14 @@ use crate::{ pub struct P2pSignaling; impl P2pSignaling { + pub fn default_cluster_config( + self, + mut config: ClusterConfig, + ) -> Result { + config.set_all_rust_to_rust_use_webrtc(); + Ok(config) + } + pub async fn run(self, mut runner: ClusterRunner<'_>) { const NODES_N: usize = 4; diff --git a/node/testing/src/scenarios/record_replay/block_production.rs b/node/testing/src/scenarios/record_replay/block_production.rs index d6f5025cfb..0b8e82f098 100644 --- a/node/testing/src/scenarios/record_replay/block_production.rs +++ b/node/testing/src/scenarios/record_replay/block_production.rs @@ -39,7 +39,7 @@ impl RecordReplayBlockProduction { recorder: Recorder::StateWithInputActions, }; let mut simulator = Simulator::new(initial_time, cfg); - simulator.run(&mut runner).await; + simulator.setup_and_run(&mut runner).await; // flush the recorded data. node::recorder::Recorder::graceful_shutdown(); diff --git a/node/testing/src/scenarios/simulation/small.rs b/node/testing/src/scenarios/simulation/small.rs index d7bbc879cd..8d77b8b655 100644 --- a/node/testing/src/scenarios/simulation/small.rs +++ b/node/testing/src/scenarios/simulation/small.rs @@ -43,6 +43,6 @@ impl SimulationSmall { recorder: Default::default(), }; let mut simulator = Simulator::new(initial_time, cfg); - simulator.run(&mut runner).await; + simulator.setup_and_run(&mut runner).await; } } diff --git a/node/testing/src/scenarios/simulation/small_forever_real_time.rs b/node/testing/src/scenarios/simulation/small_forever_real_time.rs index 8d77992a2f..1b013a5f49 100644 --- a/node/testing/src/scenarios/simulation/small_forever_real_time.rs +++ b/node/testing/src/scenarios/simulation/small_forever_real_time.rs @@ -43,6 +43,6 @@ impl SimulationSmallForeverRealTime { recorder: Default::default(), }; let mut simulator = Simulator::new(initial_time, cfg); - simulator.run(&mut runner).await; + simulator.setup_and_run(&mut runner).await; } } diff --git a/node/testing/src/scenarios/solo_node/basic_connectivity_accept_incoming.rs b/node/testing/src/scenarios/solo_node/basic_connectivity_accept_incoming.rs index 55bc34059f..626d8b4ad4 100644 --- a/node/testing/src/scenarios/solo_node/basic_connectivity_accept_incoming.rs +++ b/node/testing/src/scenarios/solo_node/basic_connectivity_accept_incoming.rs @@ -35,7 +35,6 @@ impl SoloNodeBasicConnectivityAcceptIncoming { eprintln!("add initial peer: {seed:?}"); } let config = RustNodeTestingConfig::devnet_default() - .ask_initial_peers_interval(Duration::from_secs(3600)) .max_peers(MAX_PEERS_PER_NODE) .initial_peers(initial_peers) .with_peer_id(rand::thread_rng().gen()); diff --git a/node/testing/src/scenarios/solo_node/basic_connectivity_initial_joining.rs b/node/testing/src/scenarios/solo_node/basic_connectivity_initial_joining.rs index b04cd24ac4..cac135ee3c 100644 --- a/node/testing/src/scenarios/solo_node/basic_connectivity_initial_joining.rs +++ b/node/testing/src/scenarios/solo_node/basic_connectivity_initial_joining.rs @@ -35,7 +35,6 @@ impl SoloNodeBasicConnectivityInitialJoining { eprintln!("add initial peer: {seed:?}"); } let config = RustNodeTestingConfig::devnet_default() - .ask_initial_peers_interval(Duration::from_secs(3600)) .max_peers(MAX_PEERS_PER_NODE) .initial_peers(initial_peers); diff --git a/node/testing/src/scenarios/solo_node/bootstrap.rs b/node/testing/src/scenarios/solo_node/bootstrap.rs index 858decf4b3..9ce1101e73 100644 --- a/node/testing/src/scenarios/solo_node/bootstrap.rs +++ b/node/testing/src/scenarios/solo_node/bootstrap.rs @@ -1,7 +1,8 @@ use std::time::Duration; use node::transition_frontier::sync::TransitionFrontierSyncState; -use tokio::time::Instant; +use openmina_core::constants::constraint_constants; +use redux::Instant; use crate::{ hosts, @@ -19,6 +20,24 @@ use crate::{ #[derive(documented::Documented, Default, Clone, Copy)] pub struct SoloNodeBootstrap; +// TODO(tizoc): this is ugly, do a cleaner conversion or figure out a better way. +// This test will fail if we don't start with this as the initial time because +// the time validation for the first block will reject it. +fn first_block_slot_timestamp_nanos(config: &RustNodeTestingConfig) -> u64 { + let first_block_global_slot = 46891; // Update if replay changes + let protocol_constants = config.genesis.protocol_constants().unwrap(); + let genesis_timestamp_ms = protocol_constants.genesis_state_timestamp.0.as_u64(); + let milliseconds_per_slot = constraint_constants().block_window_duration_ms; + let first_block_global_slot_delta_ms = first_block_global_slot * milliseconds_per_slot; + + // Convert to nanos + genesis_timestamp_ms + .checked_add(first_block_global_slot_delta_ms) + .unwrap() + .checked_mul(1_000_000) + .unwrap() +} + impl SoloNodeBootstrap { pub async fn run(self, mut runner: ClusterRunner<'_>) { use self::TransitionFrontierSyncState::*; @@ -27,10 +46,12 @@ impl SoloNodeBootstrap { let replayer = hosts::replayer(); - let node_id = runner.add_rust_node( - RustNodeTestingConfig::devnet_default() - .initial_peers(vec![ListenerNode::Custom(replayer)]), - ); + let mut config = RustNodeTestingConfig::devnet_default(); + + config.initial_time = redux::Timestamp::new(first_block_slot_timestamp_nanos(&config)); + + let node_id = + runner.add_rust_node(config.initial_peers(vec![ListenerNode::Custom(replayer)])); eprintln!("launch Openmina node with default configuration, id: {node_id}"); let mut timeout = TIMEOUT; diff --git a/node/testing/src/scenarios/solo_node/sync_to_genesis.rs b/node/testing/src/scenarios/solo_node/sync_to_genesis.rs index 3d6a127fa2..f5ffc5eb6a 100644 --- a/node/testing/src/scenarios/solo_node/sync_to_genesis.rs +++ b/node/testing/src/scenarios/solo_node/sync_to_genesis.rs @@ -47,7 +47,6 @@ impl SoloNodeSyncToGenesis { initial_time, genesis: node::config::DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(60 * 60), initial_peers: Vec::new(), peer_id: Default::default(), snark_worker: None, diff --git a/node/testing/src/scenarios/solo_node/sync_to_genesis_custom.rs b/node/testing/src/scenarios/solo_node/sync_to_genesis_custom.rs index 9fb673774f..6e795b221f 100644 --- a/node/testing/src/scenarios/solo_node/sync_to_genesis_custom.rs +++ b/node/testing/src/scenarios/solo_node/sync_to_genesis_custom.rs @@ -63,7 +63,6 @@ impl SoloNodeSyncToGenesisCustom { initial_time, genesis: node::config::DEVNET_CONFIG.clone(), max_peers: 100, - ask_initial_peers_interval: Duration::from_secs(60 * 60), initial_peers: Vec::new(), peer_id: Default::default(), block_producer: None, diff --git a/node/testing/src/server.rs b/node/testing/src/server/mod.rs similarity index 61% rename from node/testing/src/server.rs rename to node/testing/src/server/mod.rs index 23f494a73e..102c68e36d 100644 --- a/node/testing/src/server.rs +++ b/node/testing/src/server/mod.rs @@ -1,25 +1,45 @@ +pub mod simulator; +pub mod webnode; + use crate::cluster::{Cluster, ClusterConfig, ClusterNodeId}; use crate::node::NodeTestingConfig; use crate::scenario::{event_details, Scenario, ScenarioId, ScenarioInfo, ScenarioStep}; use crate::service::PendingEventId; +use std::collections::BTreeSet; +use std::path::PathBuf; use std::{collections::BTreeMap, sync::Arc, time::Duration}; +use axum::http::header; +use axum::middleware; +use axum::routing::get_service; use axum::{ extract::{Path, State}, http::StatusCode, routing::{get, post, put}, Json, Router, }; +use node::account::AccountPublicKey; +use node::p2p::connection::outgoing::P2pConnectionOutgoingInitOpts; +use node::p2p::webrtc::{Host, Offer, P2pConnectionResponse, SignalingMethod}; +use node::transition_frontier::genesis::{GenesisConfig, PrebuiltGenesisConfig}; +use openmina_node_native::p2p::webrtc::webrtc_signal_send; use rand::{rngs::StdRng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; +use tokio::net::TcpListener; +use tokio::runtime::Runtime; use tokio::sync::{oneshot, Mutex, MutexGuard, OwnedMutexGuard}; use tower_http::cors::CorsLayer; +use tower_http::services::ServeDir; -pub fn server(port: u16) { +pub fn server(rt: Runtime, host: Host, port: u16, ssl_port: Option) { + let fe_dist_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../") + .join("frontend/dist/frontend/"); eprintln!("scenarios path: {}", Scenario::PATH); + eprintln!("FrontEnd dist path: {fe_dist_dir:?}"); - let state = AppState::new(); + let state = AppState::new(host, ssl_port); let scenarios_router = Router::new() .route("/", get(scenario_list)) @@ -32,12 +52,19 @@ pub fn server(port: u16) { .route("/", get(cluster_list)) .route("/create/:scenario_id", put(cluster_create)) .route("/:cluster_id", get(cluster_get)) + .nest("/:cluster_id/webnode", webnode::router()) .route("/:cluster_id/run", post(cluster_run)) .route("/:cluster_id/run/auto", post(cluster_run_auto)) .route( "/:cluster_id/scenarios/reload", post(cluster_scenarios_reload), ) + .route( + "/:cluster_id/mina/webrtc/signal/:offer", + get(cluster_webrtc_signal), + ) + .route("/:cluster_id/seeds", get(cluster_seeds)) + .route("/:cluster_id/genesis/config", get(cluster_genesis_config)) .route( "/:cluster_id/nodes/events/pending", get(cluster_events_pending), @@ -49,31 +76,53 @@ pub fn server(port: u16) { .route("/:cluster_id/destroy", post(cluster_destroy)); let cors = CorsLayer::very_permissive(); + let coop_coep = middleware::from_fn(|req, next: middleware::Next| async { + let mut resp = next.run(req).await; + resp.headers_mut().insert( + header::HeaderName::from_static("cross-origin-embedder-policy"), + header::HeaderValue::from_static("require-corp"), + ); + resp.headers_mut().insert( + header::HeaderName::from_static("cross-origin-opener-policy"), + header::HeaderValue::from_static("same-origin"), + ); + resp + }); let app = Router::new() .nest("/scenarios", scenarios_router) .nest("/clusters", clusters_router) + .nest("/simulations", simulator::simulations_router()) + .fallback(get_service(ServeDir::new(&fe_dist_dir)).layer(coop_coep.clone())) .with_state(state) .layer(cors); - tokio::runtime::Handle::current().block_on(async { - axum::Server::bind(&([0, 0, 0, 0], port).into()) - .serve(app.into_make_service()) + rt.block_on(async { + let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port)); + let listener = TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app.into_make_service()) .await .unwrap(); }); } pub struct AppStateInner { + host: Host, + ssl_port: Option, rng: StdRng, clusters: BTreeMap>>, + // TODO(binier): move inside cluster state + locked_block_producer_keys: BTreeMap>, } impl AppStateInner { - pub fn new() -> Self { + pub fn new(host: Host, ssl_port: Option) -> Self { Self { + host, + ssl_port, rng: StdRng::seed_from_u64(0), clusters: Default::default(), + locked_block_producer_keys: Default::default(), } } } @@ -82,8 +131,8 @@ impl AppStateInner { pub struct AppState(Arc>); impl AppState { - pub fn new() -> Self { - Self(Arc::new(Mutex::new(AppStateInner::new()))) + pub fn new(host: Host, ssl_port: Option) -> Self { + Self(Arc::new(Mutex::new(AppStateInner::new(host, ssl_port)))) } pub async fn lock(&self) -> MutexGuard<'_, AppStateInner> { @@ -94,20 +143,14 @@ impl AppState { &self, cluster_id: u16, ) -> Result>, (StatusCode, String)> { - let state = self.lock().await; - state.clusters.get(&cluster_id).cloned().ok_or_else(|| { - ( - StatusCode::BAD_REQUEST, - format!("cluster {cluster_id} not found"), - ) - }) + self.lock().await.cluster_mutex(cluster_id) } pub async fn cluster( &self, cluster_id: u16, ) -> Result, (StatusCode, String)> { - Ok(self.cluster_mutex(cluster_id).await?.lock_owned().await) + self.lock().await.cluster(cluster_id).await } pub async fn cluster_create( @@ -140,8 +183,49 @@ impl AppState { Ok((id, cluster_guard)) } + pub async fn cluster_create_empty( + &self, + config: ClusterConfig, + ) -> Result<(u16, OwnedMutexGuard), (StatusCode, String)> { + let cluster = Cluster::new(config); + + let mut state = self.lock().await; + let id = loop { + let id = state.rng.gen(); + if !state.clusters.contains_key(&id) { + break id; + } + }; + + let cluster = Arc::new(Mutex::new(cluster)); + let cluster_guard = cluster.clone().try_lock_owned().unwrap(); + state.clusters.insert(id, cluster); + + Ok((id, cluster_guard)) + } + pub async fn cluster_destroy(&self, cluster_id: u16) -> bool { - self.lock().await.clusters.remove(&cluster_id).is_some() + let mut this = self.lock().await; + this.locked_block_producer_keys.remove(&cluster_id); + this.clusters.remove(&cluster_id).is_some() + } +} + +impl AppStateInner { + fn cluster_mutex(&self, cluster_id: u16) -> Result>, (StatusCode, String)> { + self.clusters.get(&cluster_id).cloned().ok_or_else(|| { + ( + StatusCode::BAD_REQUEST, + format!("cluster {cluster_id} not found"), + ) + }) + } + + pub async fn cluster( + &self, + cluster_id: u16, + ) -> Result, (StatusCode, String)> { + Ok(self.cluster_mutex(cluster_id)?.lock_owned().await) } } @@ -377,6 +461,125 @@ async fn cluster_scenarios_reload( .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string())) } +async fn cluster_webrtc_signal( + State(state): State, + Path((cluster_id, offer)): Path<(u16, String)>, +) -> Result, (StatusCode, Json)> { + let offer: Offer = Err(()) + .or_else(move |_| { + let json = bs58::decode(&offer).into_vec().or(Err(()))?; + serde_json::from_slice(&json).or(Err(())) + }) + .map_err(|_| { + ( + StatusCode::BAD_REQUEST, + Json(P2pConnectionResponse::SignalDecryptionFailed), + ) + })?; + + let internal_err = || { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(P2pConnectionResponse::InternalError), + ) + }; + + let http_url = { + let cluster = state + .cluster(cluster_id) + .await + .map_err(|_| internal_err())?; + let node = cluster + .node_by_peer_id(offer.target_peer_id) + .ok_or_else(internal_err)?; + let http_url = match node.dial_addr() { + P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => signaling.http_url(), + _ => None, + }; + http_url.ok_or_else(internal_err)? + }; + let resp = webrtc_signal_send(&http_url, offer) + .await + .map_err(|_| internal_err())?; + Ok(Json(resp)) +} + +async fn cluster_seeds( + State(state): State, + Path(cluster_id): Path, +) -> Result { + let state = state.lock().await; + let host = state.host.clone(); + let ssl_port = state.ssl_port; + state.cluster(cluster_id).await.map(|cluster| { + let list = cluster + .nodes_iter() + .filter(|(_, node)| node.config().initial_peers.is_empty()) + .map(|(_, node)| { + let mut addr = node.dial_addr(); + if let P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } = &mut addr { + if let SignalingMethod::Http(http) = signaling { + if let Some(port) = ssl_port { + http.host = host.clone(); + http.port = port; + *signaling = SignalingMethod::HttpsProxy(cluster_id, http.clone()); + } + } + } + addr = addr.to_string().parse().unwrap(); + addr.to_string() + }) + .collect::>(); + list.join("\n") + }) +} + +async fn cluster_genesis_config( + State(state): State, + Path(cluster_id): Path, +) -> Result, (StatusCode, String)> { + let cluster = state.cluster(cluster_id).await?; + let genesis = cluster + .nodes_iter() + .next() + .map(|(_, node)| node.config().genesis.clone()); + let genesis = genesis.ok_or_else(|| { + ( + StatusCode::BAD_REQUEST, + "no nodes in the cluster".to_owned(), + ) + })?; + if let GenesisConfig::Prebuilt(encoded) = &*genesis { + return Ok(encoded.clone().into_owned()); + } + tokio::task::spawn_blocking(move || { + let res = genesis.load().map_err(|err| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("failed to load genesis config. err: {err}"), + ) + })?; + let mut encoded = Vec::new(); + PrebuiltGenesisConfig::from_loaded(res) + .map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "failed to build `PrebuiltGenesisConfig` from loaded data".to_owned(), + ) + })? + .store(&mut encoded) + .map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "failed to encode `PrebuiltGenesisConfig`".to_owned(), + ) + })?; + Ok(encoded) + }) + .await + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "join error".to_owned()))? +} + #[derive(Serialize)] struct ClusterNodePendingEvents { node_id: ClusterNodeId, diff --git a/node/testing/src/server/simulator.rs b/node/testing/src/server/simulator.rs new file mode 100644 index 0000000000..f4df9a0df1 --- /dev/null +++ b/node/testing/src/server/simulator.rs @@ -0,0 +1,97 @@ +use std::{sync::Arc, time::Duration}; + +use axum::{ + extract::{Json, State}, + http::StatusCode, + routing::put, +}; +use mina_p2p_messages::v2; +use openmina_core::channels::oneshot; +use serde::{Deserialize, Serialize}; + +use crate::{ + cluster::ClusterConfig, + scenarios::ClusterRunner, + simulator::{Simulator, SimulatorConfig}, +}; + +use super::AppState; + +pub fn simulations_router() -> axum::Router { + axum::Router::new().route("/", put(simulation_create)) +} + +#[derive(Deserialize)] +struct SimulationCreateArgs { + cluster: ClusterConfig, + simulator: SimulatorConfig, + #[serde(default)] + override_genesis_state_timestamp: bool, +} + +#[derive(Serialize)] +struct SimulationCreateResponse { + cluster_id: u16, +} + +async fn simulation_create( + State(state): State, + Json(args): Json, +) -> Result, (StatusCode, String)> { + async fn setup( + state: AppState, + mut args: SimulationCreateArgs, + ) -> Result<(u16, Simulator), (StatusCode, String)> { + let (cluster_id, mut cluster) = state.cluster_create_empty(args.cluster).await?; + + let initial_time = redux::Timestamp::global_now(); + if args.override_genesis_state_timestamp { + Arc::get_mut(&mut args.simulator.genesis) + .unwrap() + .override_genesis_state_timestamp(v2::BlockTimeTimeStableV1( + (u64::from(initial_time) / 1_000_000).into(), + )); + } + let mut simulator = Simulator::new(initial_time, args.simulator); + simulator + .setup(&mut ClusterRunner::new(&mut cluster, |_| {})) + .await; + Ok((cluster_id, simulator)) + } + let (setup_tx, setup_rx) = oneshot::channel(); + let state_clone = state.clone(); + + std::thread::spawn(move || { + let state = state_clone; + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + rt.block_on(async move { + let (cluster_id, mut simulator) = match setup(state.clone(), args).await { + Err(err) => { + let _ = setup_tx.send(Err(err)); + return; + } + Ok((cluster_id, simulator)) => { + let _ = setup_tx.send(Ok(cluster_id)); + (cluster_id, simulator) + } + }; + let cluster_mutex = match state.cluster_mutex(cluster_id).await { + Err(_) => return, + Ok(cluster_mutex) => Arc::downgrade(&cluster_mutex), + }; + while let Some(cluster_mutex) = cluster_mutex.upgrade() { + let mut cluster = cluster_mutex.lock().await; + let mut runner = ClusterRunner::new(&mut cluster, |_| {}); + let _ = + tokio::time::timeout(Duration::from_millis(500), simulator.run(&mut runner)) + .await; + } + }); + }); + let cluster_id = setup_rx.await.unwrap()?; + + Ok(Json(SimulationCreateResponse { cluster_id })) +} diff --git a/node/testing/src/server/webnode.rs b/node/testing/src/server/webnode.rs new file mode 100644 index 0000000000..6fde723bba --- /dev/null +++ b/node/testing/src/server/webnode.rs @@ -0,0 +1,74 @@ +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::Redirect, + routing::get, +}; +use rand::prelude::*; + +use crate::scenarios::ClusterRunner; + +use super::AppState; + +pub fn router() -> axum::Router { + axum::Router::new() + .route("/", get(webnode_get)) + .route("/lock-random-bp", get(webnode_lock_random_bp)) +} + +async fn webnode_get( + State(state): State, + Path(cluster_id): Path, +) -> Result { + use base64::{engine::general_purpose::URL_SAFE, Engine as _}; + // make sure cluster exists + state.cluster_mutex(cluster_id).await?; + + let args = serde_json::json!({ + "network": cluster_id, + }) + .to_string(); + let args = URL_SAFE.encode(&args); + + Ok(Redirect::temporary(&format!("/?a={args}"))) +} + +async fn webnode_lock_random_bp( + State(state): State, + Path(cluster_id): Path, +) -> Result { + use base64::{engine::general_purpose::URL_SAFE, Engine as _}; + let mut state_guard = state.lock().await; + let state = &mut *state_guard; + let mut cluster = state.cluster(cluster_id).await?; + let runner = ClusterRunner::new(&mut cluster, |_| {}); + let locked_keys = state + .locked_block_producer_keys + .entry(cluster_id) + .or_default(); + + let (sec_key, _) = runner + .block_producer_sec_keys(runner.nodes_iter().next().unwrap().0) + .into_iter() + .filter(|(key, _)| !locked_keys.contains(&key.public_key())) + .choose(&mut state.rng) + .ok_or_else(|| { + ( + StatusCode::NOT_ACCEPTABLE, + "no more block producer keys available!".to_owned(), + ) + })?; + locked_keys.insert(sec_key.public_key()); + + let args = serde_json::json!({ + "network": cluster_id, + "block_producer": { + "sec_key": sec_key.to_string(), + "pub_key": sec_key.public_key().to_string(), + }, + }) + .to_string(); + let args = URL_SAFE.encode(&args); + + Ok(Redirect::temporary(&format!("/?a={args}"))) +} diff --git a/node/testing/src/service/mod.rs b/node/testing/src/service/mod.rs index e5cae724ce..6455052bd0 100644 --- a/node/testing/src/service/mod.rs +++ b/node/testing/src/service/mod.rs @@ -1,6 +1,7 @@ mod rpc_service; use std::collections::VecDeque; +use std::sync::Mutex as StdMutex; use std::time::Duration; use std::{collections::BTreeMap, sync::Arc}; @@ -20,8 +21,9 @@ use node::account::AccountPublicKey; use node::block_producer::vrf_evaluator::VrfEvaluatorInput; use node::block_producer::BlockProducerEvent; use node::core::channels::mpsc; +use node::core::invariants::InvariantsState; use node::core::snark::{Snark, SnarkJobId}; -use node::external_snark_worker::ExternalSnarkWorkerEvent; +use node::external_snark_worker_effectful::ExternalSnarkWorkerEvent; use node::p2p::service_impl::webrtc_with_libp2p::P2pServiceWebrtcWithLibp2p; use node::p2p::P2pCryptoService; use node::recorder::Recorder; @@ -40,7 +42,8 @@ use node::stats::Stats; use node::transition_frontier::genesis::GenesisConfig; use node::{ event_source::Event, - external_snark_worker::{ExternalSnarkWorkerService, SnarkWorkSpec}, + external_snark_worker::SnarkWorkSpec, + external_snark_worker_effectful::ExternalSnarkWorkerService, p2p::{ connection::outgoing::P2pConnectionOutgoingInitOpts, service_impl::webrtc::{Cmd, P2pServiceWebrtc, PeerState}, @@ -133,12 +136,19 @@ pub struct NodeTestingService { dyn_effects: Option, snarker_sok_digest: Option, + + cluster_invariants_state: Arc>, /// Once dropped, it will cause all threads associated to shutdown. _shutdown: mpsc::Receiver<()>, } impl NodeTestingService { - pub fn new(real: NodeService, id: ClusterNodeId, _shutdown: mpsc::Receiver<()>) -> Self { + pub fn new( + real: NodeService, + id: ClusterNodeId, + cluster_invariants_state: Arc>, + _shutdown: mpsc::Receiver<()>, + ) -> Self { Self { real, id, @@ -149,6 +159,7 @@ impl NodeTestingService { pending_events: PendingEvents::new(), dyn_effects: None, snarker_sok_digest: None, + cluster_invariants_state, _shutdown, } } @@ -364,7 +375,7 @@ impl P2pServiceWebrtc for NodeTestingService { &mut self, other_pk: &node::p2p::identity::PublicKey, message: &T, - ) -> Result { + ) -> Result> { self.real.encrypt(other_pk, message) } @@ -372,7 +383,7 @@ impl P2pServiceWebrtc for NodeTestingService { &mut self, other_pub_key: &node::p2p::identity::PublicKey, encrypted: &T::Encrypted, - ) -> Result { + ) -> Result> { self.real.decrypt(other_pub_key, encrypted) } @@ -490,7 +501,7 @@ impl BlockProducerVrfEvaluatorService for NodeTestingService { use std::cell::RefCell; thread_local! { - static GENESIS_PROOF: RefCell)>> = const { RefCell::new(None)}; + static GENESIS_PROOF: RefCell)>> = const { RefCell::new(None)}; } impl BlockProducerService for NodeTestingService { @@ -642,7 +653,24 @@ impl ExternalSnarkWorkerService for NodeTestingService { } impl node::core::invariants::InvariantService for NodeTestingService { - fn invariants_state(&mut self) -> &mut openmina_core::invariants::InvariantsState { + type ClusterInvariantsState<'a> = std::sync::MutexGuard<'a, InvariantsState>; + + fn node_id(&self) -> usize { + self.node_id().index() + } + + fn invariants_state(&mut self) -> &mut InvariantsState { node::core::invariants::InvariantService::invariants_state(&mut self.real) } + + fn cluster_invariants_state<'a>(&'a mut self) -> Option> + where + Self: 'a, + { + Some( + self.cluster_invariants_state.try_lock().expect( + "locking should never fail, since we are running all nodes in the same thread", + ), + ) + } } diff --git a/node/testing/src/simulator/config.rs b/node/testing/src/simulator/config.rs index a8b5c2d730..11913b1c90 100644 --- a/node/testing/src/simulator/config.rs +++ b/node/testing/src/simulator/config.rs @@ -12,15 +12,24 @@ pub struct SimulatorConfig { pub normal_nodes: usize, pub snark_workers: usize, pub block_producers: usize, + #[serde(default)] pub advance_time: RunCfgAdvanceTime, + #[serde(default)] pub run_until: SimulatorRunUntil, + #[serde(default = "duration_max")] pub run_until_timeout: Duration, + #[serde(default)] pub recorder: Recorder, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub enum SimulatorRunUntil { + #[default] Forever, Epoch(u32), BlockchainLength(u32), } + +fn duration_max() -> Duration { + Duration::MAX +} diff --git a/node/testing/src/simulator/mod.rs b/node/testing/src/simulator/mod.rs index a5e4a2b85e..af3c5c555f 100644 --- a/node/testing/src/simulator/mod.rs +++ b/node/testing/src/simulator/mod.rs @@ -1,23 +1,28 @@ mod config; pub use config::*; + use mina_p2p_messages::v2::{ CurrencyFeeStableV1, UnsignedExtendedUInt64Int64ForVersionTagsStableV1, }; use std::{collections::BTreeSet, time::Duration}; -use node::{ActionKind, BlockProducerConfig, SnarkerConfig, SnarkerStrategy, State}; +use node::{ + ActionKind, ActionWithMeta, BlockProducerConfig, SnarkerConfig, SnarkerStrategy, State, +}; use crate::{ cluster::ClusterNodeId, node::{Node, RustNodeBlockProducerTestingConfig, RustNodeTestingConfig}, scenario::ListenerNode, scenarios::{ClusterRunner, RunCfg}, + service::NodeTestingService, }; pub struct Simulator { initial_time: redux::Timestamp, config: SimulatorConfig, + start_t: Option, } impl Simulator { @@ -25,6 +30,7 @@ impl Simulator { Self { initial_time, config, + start_t: None, } } @@ -37,7 +43,6 @@ impl Simulator { initial_time: self.initial_time(), genesis: self.config.genesis.clone(), max_peers: 1000, - ask_initial_peers_interval: Duration::from_secs(60), initial_peers: Vec::new(), peer_id: Default::default(), block_producer: None, @@ -98,6 +103,10 @@ impl Simulator { } async fn set_up_normal_nodes(&mut self, runner: &mut ClusterRunner<'_>) { + if self.config.normal_nodes == 0 { + return; + } + eprintln!("setting up normal nodes: {}", self.config.normal_nodes); let node_config = RustNodeTestingConfig { @@ -114,6 +123,10 @@ impl Simulator { } async fn set_up_snark_worker_nodes(&mut self, runner: &mut ClusterRunner<'_>) { + if self.config.snark_workers == 0 { + return; + } + eprintln!( "setting up rust snark worker nodes: {}", self.config.snark_workers @@ -163,6 +176,10 @@ impl Simulator { } async fn set_up_block_producer_nodes(&mut self, runner: &mut ClusterRunner<'_>) { + if self.config.block_producers == 0 { + return; + } + let block_producers = runner.block_producer_sec_keys(ClusterNodeId::new_unchecked(0)); assert!(self.config.block_producers <= block_producers.len()); @@ -203,27 +220,59 @@ impl Simulator { self.wait_for_all_nodes_synced(runner).await; } - pub async fn run<'a>(&mut self, runner: &mut ClusterRunner<'a>) { + pub async fn setup_and_run_with_listener<'a, AL, ALF>( + &mut self, + runner: &mut ClusterRunner<'a>, + listener: ALF, + ) where + ALF: FnMut() -> AL, + AL: 'static + + Send + + FnMut(ClusterNodeId, &State, &NodeTestingService, &ActionWithMeta) -> bool, + { + self.setup(runner).await; + self.run_with_listener(runner, listener).await; + } + + pub async fn setup_and_run<'a>(&mut self, runner: &mut ClusterRunner<'a>) { + self.setup(runner).await; + self.run_with_listener(runner, || |_, _, _, _| false).await; + } + + pub async fn setup<'a>(&mut self, runner: &mut ClusterRunner<'a>) { self.set_up_seed_nodes(runner).await; self.set_up_normal_nodes(runner).await; self.set_up_snark_worker_nodes(runner).await; self.set_up_block_producer_nodes(runner).await; + } + + pub async fn run<'a>(&mut self, runner: &mut ClusterRunner<'a>) { + self.run_with_listener(runner, || |_, _, _, _| false).await; + } + pub async fn run_with_listener<'a, AL, ALF>( + &mut self, + runner: &mut ClusterRunner<'a>, + mut listener: ALF, + ) where + ALF: FnMut() -> AL, + AL: 'static + + Send + + FnMut(ClusterNodeId, &State, &NodeTestingService, &ActionWithMeta) -> bool, + { let run_until = self.config.run_until.clone(); let advance_time = self.config.advance_time.clone(); - let start_t = redux::Instant::now(); + let start_t = *self.start_t.get_or_insert_with(redux::Instant::now); let mut last_printed_slot = 0; let virtual_initial_time = self.initial_time(); while start_t.elapsed() < self.config.run_until_timeout { tokio::task::yield_now().await; - let _ = runner - .run( - RunCfg::default() - .advance_time(advance_time.clone()) - .timeout(Duration::ZERO), - ) - .await; + let cfg = RunCfg::default() + .advance_time(advance_time.clone()) + .timeout(Duration::ZERO) + .action_handler(listener()); + let _ = runner.run(cfg).await; let printed_elapsed_time = { let state = runner.nodes_iter().next().unwrap().1.state(); diff --git a/node/testing/tests/common.rs b/node/testing/tests/common.rs index f4c9e3e8e9..e62bb8caf2 100644 --- a/node/testing/tests/common.rs +++ b/node/testing/tests/common.rs @@ -18,8 +18,8 @@ macro_rules! scenario_test { $(#[$meta])? async fn $name() { use openmina_node_testing::{ - cluster::{Cluster, ClusterConfig}, - scenarios::ClusterRunner, + cluster::Cluster, + scenarios::{ClusterRunner, Scenarios}, setup_without_rt, wait_for_other_tests, }; use std::io::Write; @@ -57,16 +57,15 @@ macro_rules! scenario_test { })); } + let scenario = $scenario_instance; #[allow(unused_mut)] - let mut config = ClusterConfig::new(None).unwrap(); + let mut config = Scenarios::from(scenario).default_cluster_config().unwrap(); #[cfg(feature = "p2p-webrtc")] if $can_test_webrtc { - eprintln!("All rust to rust connections will be over webrtc transport"); - config = config.set_all_rust_to_rust_use_webrtc(); + config.set_all_rust_to_rust_use_webrtc(); } let mut cluster = Cluster::new(config); let runner = ClusterRunner::new(&mut cluster, |_| {}); - let scenario = $scenario_instance; scenario.run(runner).await; if let Some(summary) = std::env::var_os("GITHUB_STEP_SUMMARY") { diff --git a/node/testing/tests/node_libp2p_with_rust_to_rust_webrtc.rs b/node/testing/tests/node_libp2p_with_rust_to_rust_webrtc.rs index 914e2b116c..492932f6f3 100644 --- a/node/testing/tests/node_libp2p_with_rust_to_rust_webrtc.rs +++ b/node/testing/tests/node_libp2p_with_rust_to_rust_webrtc.rs @@ -1,18 +1,17 @@ #[cfg(feature = "p2p-webrtc")] -use openmina_node_testing::{cluster::ClusterConfig, scenarios::Scenarios, setup}; +use openmina_node_testing::{scenarios::Scenarios, setup}; #[cfg(feature = "p2p-webrtc")] #[test] fn node_libp2p_with_rust_to_rust_webrtc_all_scenarios() { let rt = setup(); - let config = ClusterConfig::new(None) - .unwrap() - .set_all_rust_to_rust_use_webrtc(); for scenario in Scenarios::iter() { eprintln!("running scenario: {}", scenario.to_str()); - rt.block_on(async { - scenario.run_only_from_scratch(config.clone()).await; + let mut config = scenario.default_cluster_config().unwrap(); + config.set_all_rust_to_rust_use_webrtc(); + rt.block_on(async move { + scenario.run_only_from_scratch(config).await; }); } } diff --git a/node/web/Cargo.toml b/node/web/Cargo.toml index cfe6643e87..9427c4ba62 100644 --- a/node/web/Cargo.toml +++ b/node/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-node-web" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -28,6 +28,7 @@ jsonpath-rust = "0.5.0" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" console_error_panic_hook = "0.1" +gloo-utils = "0.2" openmina-node-common = { path = "../common" } node = { path = "../../node" } diff --git a/node/web/src/lib.rs b/node/web/src/lib.rs index 5d8c0a5f89..727c3f64fd 100644 --- a/node/web/src/lib.rs +++ b/node/web/src/lib.rs @@ -1,5 +1,6 @@ #![cfg(target_family = "wasm")] +use ::node::transition_frontier::genesis::GenesisConfig; pub use openmina_node_common::*; mod rayon; @@ -12,6 +13,7 @@ use ::node::account::AccountSecretKey; use ::node::core::thread; use ::node::snark::{BlockVerifier, TransactionVerifier}; use anyhow::Context; +use gloo_utils::format::JsValueSerdeExt; use ledger::proofs::provers::BlockProver; use openmina_node_common::rpc::RpcSender; use wasm_bindgen::prelude::*; @@ -31,7 +33,16 @@ fn main() { } #[wasm_bindgen] -pub async fn run(block_producer: Option) -> RpcSender { +pub fn build_env() -> JsValue { + JsValue::from_serde(&::node::BuildEnv::get()).unwrap_or_default() +} + +#[wasm_bindgen] +pub async fn run( + block_producer: Option, + seed_nodes_url: Option, + genesis_config_url: Option, +) -> RpcSender { let block_producer: Option = block_producer.map(|key| { key.parse() .expect("failed to parse passed block producer keys") @@ -40,12 +51,12 @@ pub async fn run(block_producer: Option) -> RpcSender { let (rpc_sender_tx, rpc_sender_rx) = ::node::core::channels::oneshot::channel(); let _ = thread::spawn(move || { wasm_bindgen_futures::spawn_local(async move { - let mut node = setup_node(block_producer).await; + let mut node = setup_node(block_producer, seed_nodes_url, genesis_config_url).await; let _ = rpc_sender_tx.send(node.rpc()); node.run_forever().await; }); - wasm_bindgen::throw_str("Cursed hack to keep workers alive. See https://github.com/rustwasm/wasm-bindgen/issues/2945"); + keep_worker_alive_cursed_hack(); }); rpc_sender_rx.await.unwrap() @@ -53,20 +64,44 @@ pub async fn run(block_producer: Option) -> RpcSender { async fn setup_node( block_producer: Option, + seed_nodes_url: Option, + genesis_config_url: Option, ) -> openmina_node_common::Node { let block_verifier_index = BlockVerifier::make().await; let work_verifier_index = TransactionVerifier::make().await; - let genesis_config = ::node::config::DEVNET_CONFIG.clone(); + let genesis_config = if let Some(genesis_config_url) = genesis_config_url { + let bytes = ::node::core::http::get_bytes(&genesis_config_url) + .await + .expect("failed to fetch genesis config"); + GenesisConfig::Prebuilt(bytes.into()).into() + } else { + ::node::config::DEVNET_CONFIG.clone() + }; + let mut node_builder: NodeBuilder = NodeBuilder::new(None, genesis_config); node_builder .block_verifier_index(block_verifier_index.clone()) .work_verifier_index(work_verifier_index.clone()); + // TODO(binier): refactor + if let Some(seed_nodes_url) = seed_nodes_url { + let peers = ::node::core::http::get_bytes(&seed_nodes_url) + .await + .expect("failed to fetch seed nodes"); + node_builder.initial_peers( + String::from_utf8_lossy(&peers) + .split("\n") + .filter(|s| !s.trim().is_empty()) + .map(|s| s.trim().parse().expect("failed to parse seed node addr")), + ); + } + if let Some(bp_key) = block_producer { - let provers = - BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)).await; - node_builder.block_producer(provers, bp_key); + thread::spawn(move || { + BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); + }); + node_builder.block_producer(bp_key, None); } node_builder @@ -75,3 +110,7 @@ async fn setup_node( node_builder.gather_stats(); node_builder.build().context("node build failed!").unwrap() } + +fn keep_worker_alive_cursed_hack() { + wasm_bindgen::throw_str("Cursed hack to keep workers alive. See https://github.com/rustwasm/wasm-bindgen/issues/2945"); +} diff --git a/node/web/src/node/builder.rs b/node/web/src/node/builder.rs index 70c9a119c8..5942877f60 100644 --- a/node/web/src/node/builder.rs +++ b/node/web/src/node/builder.rs @@ -117,14 +117,18 @@ impl NodeBuilder { } /// Set up block producer. - pub fn block_producer(&mut self, provers: BlockProver, key: AccountSecretKey) -> &mut Self { + pub fn block_producer( + &mut self, + key: AccountSecretKey, + provers: Option, + ) -> &mut Self { let config = BlockProducerConfig { pub_key: key.public_key().into(), custom_coinbase_receiver: None, proposed_protocol_version: None, }; self.block_producer = Some(config); - self.service.block_producer_init(provers, key); + self.service.block_producer_init(key, provers); self } @@ -227,7 +231,6 @@ impl NodeBuilder { identity_pub_key: p2p_sec_key.public_key(), initial_peers, external_addrs: vec![], - ask_initial_peers_interval: Duration::from_secs(3600), enabled_channels: ChannelId::iter_all().collect(), peer_discovery: !self.p2p_no_discovery, meshsub: P2pMeshsubConfig { @@ -273,7 +276,7 @@ impl NodeBuilder { } fn default_peers() -> Vec { - ["/2cBFzmUmkYgMUrxdv5S2Udyv8eiuhokAFS4WnYfHiAJLWoQ3yL9/https/webrtc3.webnode.openmina.com/443"] + ["/2bjYBqn45MmtismsAYP9rZ6Xns9snCcNsN1eDgQZB5s6AzY2CR2/https/webrtc3.webnode.openmina.com/443"] .into_iter() .map(|s| s.parse().unwrap()) .collect() diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index cf4ad92f10..c6317ef742 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p2p" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -15,6 +15,7 @@ derive_more = "0.99.17" rand = { version = "0.8", features = [ "small_rng" ] } bytes = "*" bs58 = "0.4.0" +base64 = "0.22" binprot = { git = "https://github.com/openmina/binprot-rs", rev = "2b5a909" } binprot_derive = { git = "https://github.com/openmina/binprot-rs", rev = "2b5a909" } anyhow = "1.0.70" @@ -68,7 +69,7 @@ p2p-testing = { path = "testing" } redux = { workspace = true, features=["serializable_callbacks"] } tokio = { version = "1.26", features = ["rt"] } webrtc = { git = "https://github.com/openmina/webrtc.git", branch = "openmina-v0.11.0", optional = true } -hyper = { version = "0.14.25", features = ["client", "http1", "tcp"] } +reqwest = { version = "0.11", features = ["json"] } mio = { version = "0.8.11", features = ["os-poll", "net"] } libc = { version = "0.2.151" } local-ip-address = "0.6.1" diff --git a/p2p/libp2p-rpc-behaviour/Cargo.toml b/p2p/libp2p-rpc-behaviour/Cargo.toml index ae94d31ed5..f76077210f 100644 --- a/p2p/libp2p-rpc-behaviour/Cargo.toml +++ b/p2p/libp2p-rpc-behaviour/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-rpc-behaviour" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/p2p/src/channels/best_tip/p2p_channels_best_tip_reducer.rs b/p2p/src/channels/best_tip/p2p_channels_best_tip_reducer.rs index c4bdf794d5..ea350fee6c 100644 --- a/p2p/src/channels/best_tip/p2p_channels_best_tip_reducer.rs +++ b/p2p/src/channels/best_tip/p2p_channels_best_tip_reducer.rs @@ -2,11 +2,14 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ - channels::best_tip_effectful::P2pChannelsBestTipEffectfulAction, P2pNetworkPubsubAction, - P2pPeerAction, P2pState, + channels::{ChannelId, ChannelMsg, MsgId, P2pChannelsEffectfulAction}, + P2pPeerAction, P2pState, PeerId, }; -use super::{BestTipPropagationState, P2pChannelsBestTipAction, P2pChannelsBestTipState}; +use super::{ + BestTipPropagationChannelMsg, BestTipPropagationState, P2pChannelsBestTipAction, + P2pChannelsBestTipState, +}; impl P2pChannelsBestTipState { /// Substate is accessed @@ -33,7 +36,16 @@ impl P2pChannelsBestTipState { *best_tip_state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsBestTipEffectfulAction::Init { peer_id }); + + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::BestTipPropagation, + on_success: redux::callback!( + on_best_tip_channel_init(peer_id: PeerId) -> crate::P2pAction { + P2pChannelsBestTipAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsBestTipAction::Pending { .. } => { @@ -69,7 +81,11 @@ impl P2pChannelsBestTipState { *local = BestTipPropagationState::Requested { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsBestTipEffectfulAction::RequestSend { peer_id }); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg: ChannelMsg::BestTipPropagation(BestTipPropagationChannelMsg::GetNext), + }); Ok(()) } P2pChannelsBestTipAction::Received { best_tip, .. } => { @@ -136,30 +152,16 @@ impl P2pChannelsBestTipState { let dispatcher = state_context.into_dispatcher(); if !is_libp2p { - dispatcher.push(P2pChannelsBestTipEffectfulAction::ResponseSend { + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - best_tip, + msg_id: MsgId::first(), + msg: ChannelMsg::BestTipPropagation(BestTipPropagationChannelMsg::BestTip( + best_tip.block, + )), }); return Ok(()); } - #[cfg(feature = "p2p-libp2p")] - { - use mina_p2p_messages::gossip::GossipNetMessageV2; - let block = (*best_tip.block).clone(); - let message = Box::new(GossipNetMessageV2::NewState(block)); - // TODO(vlad): `P2pChannelsBestTipAction::ResponseSend` - // action is dispatched for each peer. So `P2pNetworkPubsubAction::Broadcast` - // will be called many times causing many duplicate - // broadcasts. Either in pubsub state machine, we - // need to filter out duplicate messages, or better, - // have a simple action to send pubsub message to a - // specific peer instead of sending to everyone. - // That way we can avoid duplicate state, since we - // already store last sent best tip here and we make - // sure we don't send same block to same peer again. - dispatcher.push(P2pNetworkPubsubAction::Broadcast { message }); - } Ok(()) } } diff --git a/p2p/src/channels/best_tip_effectful/mod.rs b/p2p/src/channels/best_tip_effectful/mod.rs deleted file mode 100644 index 010e97c36c..0000000000 --- a/p2p/src/channels/best_tip_effectful/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod p2p_channels_best_tip_effectful_actions; -pub use p2p_channels_best_tip_effectful_actions::P2pChannelsBestTipEffectfulAction; - -mod p2p_channels_best_tip_effectful_effects; diff --git a/p2p/src/channels/best_tip_effectful/p2p_channels_best_tip_effectful_actions.rs b/p2p/src/channels/best_tip_effectful/p2p_channels_best_tip_effectful_actions.rs deleted file mode 100644 index efb08ca2fa..0000000000 --- a/p2p/src/channels/best_tip_effectful/p2p_channels_best_tip_effectful_actions.rs +++ /dev/null @@ -1,31 +0,0 @@ -use openmina_core::{block::ArcBlockWithHash, ActionEvent}; -use serde::{Deserialize, Serialize}; - -use crate::{channels::P2pChannelsEffectfulAction, P2pState, PeerId}; - -#[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] -#[action_event(fields(display(peer_id), best_tip = display(&best_tip.hash)))] -pub enum P2pChannelsBestTipEffectfulAction { - Init { - peer_id: PeerId, - }, - RequestSend { - peer_id: PeerId, - }, - ResponseSend { - peer_id: PeerId, - best_tip: ArcBlockWithHash, - }, -} - -impl redux::EnablingCondition for P2pChannelsBestTipEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(action: P2pChannelsBestTipEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::BestTip(action)) - } -} diff --git a/p2p/src/channels/best_tip_effectful/p2p_channels_best_tip_effectful_effects.rs b/p2p/src/channels/best_tip_effectful/p2p_channels_best_tip_effectful_effects.rs deleted file mode 100644 index 76fc9f3451..0000000000 --- a/p2p/src/channels/best_tip_effectful/p2p_channels_best_tip_effectful_effects.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::P2pChannelsBestTipEffectfulAction; -use crate::channels::{ - best_tip::{BestTipPropagationChannelMsg, P2pChannelsBestTipAction}, - ChannelId, MsgId, P2pChannelsService, -}; -use redux::ActionMeta; - -impl P2pChannelsBestTipEffectfulAction { - pub fn effects(self, _meta: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsBestTipEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::BestTipPropagation); - store.dispatch(P2pChannelsBestTipAction::Pending { peer_id }); - } - P2pChannelsBestTipEffectfulAction::RequestSend { peer_id } => { - let msg = BestTipPropagationChannelMsg::GetNext; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - P2pChannelsBestTipEffectfulAction::ResponseSend { peer_id, best_tip } => { - let msg = BestTipPropagationChannelMsg::BestTip(best_tip.block); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - } - } -} diff --git a/p2p/src/channels/mod.rs b/p2p/src/channels/mod.rs index 78b35cf3b3..a66b92323b 100644 --- a/p2p/src/channels/mod.rs +++ b/p2p/src/channels/mod.rs @@ -1,16 +1,10 @@ pub mod best_tip; -pub mod best_tip_effectful; pub mod rpc; -pub mod rpc_effectful; pub mod signaling; pub mod snark; -pub mod snark_effectful; pub mod snark_job_commitment; -pub mod snark_job_commitment_effectful; pub mod streaming_rpc; -pub mod streaming_rpc_effectful; pub mod transaction; -pub mod transaction_effectful; mod p2p_channels_state; pub use p2p_channels_state::*; @@ -23,6 +17,8 @@ mod p2p_channels_reducer; mod p2p_channels_service; pub use p2p_channels_service::*; +mod p2p_channels_effectful_effects; + use binprot::{BinProtRead, BinProtWrite}; use binprot_derive::{BinProtRead, BinProtWrite}; use derive_more::From; diff --git a/p2p/src/channels/p2p_channels_actions.rs b/p2p/src/channels/p2p_channels_actions.rs index 30e9ec7065..d456bce283 100644 --- a/p2p/src/channels/p2p_channels_actions.rs +++ b/p2p/src/channels/p2p_channels_actions.rs @@ -1,28 +1,25 @@ use openmina_core::log::ActionEvent; +use redux::Callback; use serde::{Deserialize, Serialize}; -use crate::{P2pState, PeerId}; +use crate::{ + identity::PublicKey, + webrtc::{EncryptedAnswer, EncryptedOffer, Offer, P2pConnectionResponse}, + P2pState, PeerId, +}; use super::{ best_tip::P2pChannelsBestTipAction, - best_tip_effectful::P2pChannelsBestTipEffectfulAction, rpc::P2pChannelsRpcAction, - rpc_effectful::P2pChannelsRpcEffectfulAction, signaling::{ discovery::P2pChannelsSignalingDiscoveryAction, - discovery_effectful::P2pChannelsSignalingDiscoveryEffectfulAction, exchange::P2pChannelsSignalingExchangeAction, - exchange_effectful::P2pChannelsSignalingExchangeEffectfulAction, }, snark::P2pChannelsSnarkAction, - snark_effectful::P2pChannelsSnarkEffectfulAction, snark_job_commitment::P2pChannelsSnarkJobCommitmentAction, - snark_job_commitment_effectful::P2pChannelsSnarkJobCommitmentEffectfulAction, streaming_rpc::P2pChannelsStreamingRpcAction, - streaming_rpc_effectful::P2pChannelsStreamingRpcEffectfulAction, transaction::P2pChannelsTransactionAction, - transaction_effectful::P2pChannelsTransactionEffectfulAction, - ChannelMsg, + ChannelId, ChannelMsg, MsgId, }; #[derive(Serialize, Deserialize, Debug, Clone, openmina_core::ActionEvent)] @@ -40,14 +37,36 @@ pub enum P2pChannelsAction { #[derive(Serialize, Deserialize, Debug, Clone, openmina_core::ActionEvent)] pub enum P2pChannelsEffectfulAction { - SignalingDiscovery(P2pChannelsSignalingDiscoveryEffectfulAction), - SignalingExchange(P2pChannelsSignalingExchangeEffectfulAction), - BestTip(P2pChannelsBestTipEffectfulAction), - Rpc(P2pChannelsRpcEffectfulAction), - Snark(P2pChannelsSnarkEffectfulAction), - SnarkJobCommitment(P2pChannelsSnarkJobCommitmentEffectfulAction), - StreamingRpc(P2pChannelsStreamingRpcEffectfulAction), - Transaction(P2pChannelsTransactionEffectfulAction), + InitChannel { + peer_id: PeerId, + id: ChannelId, + on_success: Callback, + }, + MessageSend { + peer_id: PeerId, + msg_id: MsgId, + msg: ChannelMsg, + }, + SignalingDiscoveryAnswerDecrypt { + peer_id: PeerId, + pub_key: PublicKey, + answer: EncryptedAnswer, + }, + SignalingDiscoveryOfferEncryptAndSend { + peer_id: PeerId, + pub_key: PublicKey, + offer: Box, + }, + SignalingExchangeOfferDecrypt { + peer_id: PeerId, + pub_key: PublicKey, + offer: EncryptedOffer, + }, + SignalingExchangeAnswerEncryptAndSend { + peer_id: PeerId, + pub_key: PublicKey, + answer: Option, + }, } impl P2pChannelsAction { @@ -83,17 +102,8 @@ impl redux::EnablingCondition for P2pChannelsAction { } impl redux::EnablingCondition for P2pChannelsEffectfulAction { - fn is_enabled(&self, state: &crate::P2pState, time: redux::Timestamp) -> bool { - match self { - P2pChannelsEffectfulAction::SignalingDiscovery(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::SignalingExchange(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::BestTip(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::Transaction(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::StreamingRpc(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::SnarkJobCommitment(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::Rpc(a) => a.is_enabled(state, time), - P2pChannelsEffectfulAction::Snark(a) => a.is_enabled(state, time), - } + fn is_enabled(&self, _state: &crate::P2pState, _time: redux::Timestamp) -> bool { + true } } diff --git a/p2p/src/channels/p2p_channels_effectful_effects.rs b/p2p/src/channels/p2p_channels_effectful_effects.rs new file mode 100644 index 0000000000..275157e419 --- /dev/null +++ b/p2p/src/channels/p2p_channels_effectful_effects.rs @@ -0,0 +1,134 @@ +use openmina_core::bug_condition; +use redux::ActionMeta; + +use crate::webrtc::{Offer, P2pConnectionResponse}; + +use super::{ + signaling::{ + discovery::{P2pChannelsSignalingDiscoveryAction, SignalingDiscoveryChannelMsg}, + exchange::{P2pChannelsSignalingExchangeAction, SignalingExchangeChannelMsg}, + }, + ChannelMsg, MsgId, P2pChannelsEffectfulAction, P2pChannelsService, +}; + +impl P2pChannelsEffectfulAction { + pub fn effects(self, meta: &ActionMeta, store: &mut Store) + where + Store: crate::P2pStore, + Store::Service: P2pChannelsService, + { + match self { + P2pChannelsEffectfulAction::InitChannel { + peer_id, + id, + on_success, + } => { + store.service().channel_open(peer_id, id); + store.dispatch_callback(on_success, peer_id); + } + P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id, + msg, + } => { + store.service().channel_send(peer_id, msg_id, msg); + } + P2pChannelsEffectfulAction::SignalingDiscoveryAnswerDecrypt { + peer_id, + pub_key, + answer, + } => { + match store + .service() + .decrypt::(&pub_key, &answer) + { + Err(_) => { + store.dispatch(P2pChannelsSignalingDiscoveryAction::AnswerDecrypted { + peer_id, + answer: P2pConnectionResponse::SignalDecryptionFailed, + }); + } + Ok(answer) => { + store.dispatch(P2pChannelsSignalingDiscoveryAction::AnswerDecrypted { + peer_id, + answer, + }); + } + } + } + P2pChannelsEffectfulAction::SignalingDiscoveryOfferEncryptAndSend { + peer_id, + pub_key, + offer, + } => match store.service().encrypt(&pub_key, offer.as_ref()) { + Err(_) => { + // TODO: handle + openmina_core::error!( + meta.time(); + summary = "Failed to encrypt webrtc offer", + peer_id = peer_id.to_string() + ); + } + Ok(offer) => { + let message = SignalingDiscoveryChannelMsg::DiscoveredAccept(offer); + store + .service() + .channel_send(peer_id, super::MsgId::first(), message.into()); + } + }, + P2pChannelsEffectfulAction::SignalingExchangeOfferDecrypt { + peer_id, + pub_key, + offer, + } => { + match store.service().decrypt::(&pub_key, &offer) { + Err(_) => { + store.dispatch(P2pChannelsSignalingExchangeAction::OfferDecryptError { + peer_id, + }); + } + Ok(offer) if offer.identity_pub_key != pub_key => { + // TODO(binier): propagate specific error. + // This is invalid behavior either from relayer or offerer. + store.dispatch(P2pChannelsSignalingExchangeAction::OfferDecryptError { + peer_id, + }); + } + Ok(offer) => { + store.dispatch(P2pChannelsSignalingExchangeAction::OfferDecryptSuccess { + peer_id, + offer, + }); + } + } + } + P2pChannelsEffectfulAction::SignalingExchangeAnswerEncryptAndSend { + peer_id, + pub_key, + answer, + } => { + let Some(answer) = answer else { + let message = SignalingExchangeChannelMsg::Answer(None); + store.service().channel_send( + peer_id, + MsgId::first(), + ChannelMsg::SignalingExchange(message), + ); + return; + }; + + match store.service().encrypt(&pub_key, &answer) { + Err(_) => bug_condition!("Failed to encrypt webrtc answer. Shouldn't happen since we managed to decrypt sent offer."), + Ok(answer) => { + let message = SignalingExchangeChannelMsg::Answer(Some(answer)); + store.service().channel_send( + peer_id, + MsgId::first(), + ChannelMsg::SignalingExchange(message), + ); + } + } + } + } + } +} diff --git a/p2p/src/channels/p2p_channels_service.rs b/p2p/src/channels/p2p_channels_service.rs index 38a6eae8d7..faf4be6663 100644 --- a/p2p/src/channels/p2p_channels_service.rs +++ b/p2p/src/channels/p2p_channels_service.rs @@ -12,10 +12,10 @@ pub trait P2pChannelsService: redux::Service { &mut self, other_pk: &PublicKey, message: &T, - ) -> Result; + ) -> Result>; fn decrypt( &mut self, other_pk: &PublicKey, encrypted: &T::Encrypted, - ) -> Result; + ) -> Result>; } diff --git a/p2p/src/channels/rpc/mod.rs b/p2p/src/channels/rpc/mod.rs index 15d0d74d7b..0f47f02b9d 100644 --- a/p2p/src/channels/rpc/mod.rs +++ b/p2p/src/channels/rpc/mod.rs @@ -22,6 +22,7 @@ use mina_p2p_messages::{ use openmina_core::{ block::ArcBlock, snark::{Snark, SnarkJobId}, + transaction::{Transaction, TransactionHash}, }; use serde::{Deserialize, Serialize}; @@ -51,6 +52,7 @@ pub enum P2pRpcKind { StagedLedgerAuxAndPendingCoinbasesAtBlock, Block, Snark, + Transaction, InitialPeers, } @@ -64,6 +66,7 @@ impl P2pRpcKind { } Self::Block => config.block, Self::Snark => config.snark, + Self::Transaction => config.transaction, Self::InitialPeers => config.initial_peers, } } @@ -75,6 +78,7 @@ impl P2pRpcKind { Self::StagedLedgerAuxAndPendingCoinbasesAtBlock => true, Self::Block => true, Self::Snark => false, + Self::Transaction => false, Self::InitialPeers => true, } } @@ -87,6 +91,7 @@ pub enum P2pRpcRequest { StagedLedgerAuxAndPendingCoinbasesAtBlock(StateHash), Block(StateHash), Snark(SnarkJobId), + Transaction(TransactionHash), InitialPeers, } @@ -100,6 +105,7 @@ impl P2pRpcRequest { } Self::Block(_) => P2pRpcKind::Block, Self::Snark(_) => P2pRpcKind::Snark, + Self::Transaction(_) => P2pRpcKind::Transaction, Self::InitialPeers => P2pRpcKind::InitialPeers, } } @@ -153,6 +159,9 @@ impl std::fmt::Display for P2pRpcRequest { Self::Snark(job_id) => { write!(f, ", {job_id}") } + Self::Transaction(hash) => { + write!(f, ", {hash}") + } Self::InitialPeers => Ok(()), } } @@ -180,6 +189,7 @@ pub enum P2pRpcResponse { StagedLedgerAuxAndPendingCoinbasesAtBlock(Arc), Block(ArcBlock), Snark(Snark), + Transaction(Transaction), InitialPeers(List), } @@ -193,6 +203,7 @@ impl P2pRpcResponse { } Self::Block(_) => P2pRpcKind::Block, Self::Snark(_) => P2pRpcKind::Snark, + Self::Transaction(_) => P2pRpcKind::Transaction, Self::InitialPeers(_) => P2pRpcKind::InitialPeers, } } @@ -283,6 +294,10 @@ mod libp2p { // should use gossipsub to broadcast None } + P2pRpcResponse::Transaction(_) => { + // should use gossipsub to broadcast + None + } P2pRpcResponse::InitialPeers(peers) => { type Method = rpc::GetSomeInitialPeersV1ForV2; type Payload = ResponsePayload<::Response>; @@ -379,6 +394,11 @@ mod libp2p { // libp2p cannot fulfill this request None } + P2pRpcRequest::Transaction(hash) => { + let _ = hash; + // libp2p cannot fulfill this request + None + } P2pRpcRequest::InitialPeers => { type Method = rpc::GetSomeInitialPeersV1ForV2; type Payload = QueryPayload<::Query>; diff --git a/p2p/src/channels/rpc/p2p_channels_rpc_reducer.rs b/p2p/src/channels/rpc/p2p_channels_rpc_reducer.rs index 04d4fa5cfe..62d93a34c9 100644 --- a/p2p/src/channels/rpc/p2p_channels_rpc_reducer.rs +++ b/p2p/src/channels/rpc/p2p_channels_rpc_reducer.rs @@ -1,17 +1,14 @@ -use std::collections::VecDeque; - -use openmina_core::{block::BlockWithHash, bug_condition, error, Substate}; -use redux::ActionWithMeta; - -use crate::{ - channels::rpc_effectful::P2pChannelsRpcEffectfulAction, P2pNetworkRpcAction, P2pPeerAction, - P2pState, -}; - use super::{ P2pChannelsRpcAction, P2pChannelsRpcState, P2pRpcLocalState, P2pRpcRemotePendingRequestState, - P2pRpcRemoteState, P2pRpcResponse, MAX_P2P_RPC_REMOTE_CONCURRENT_REQUESTS, + P2pRpcRemoteState, P2pRpcResponse, RpcChannelMsg, MAX_P2P_RPC_REMOTE_CONCURRENT_REQUESTS, }; +use crate::{ + channels::{ChannelId, ChannelMsg, MsgId, P2pChannelsEffectfulAction}, + P2pNetworkRpcAction, P2pPeerAction, P2pState, +}; +use openmina_core::{block::BlockWithHash, bug_condition, error, Substate}; +use redux::ActionWithMeta; +use std::collections::VecDeque; impl P2pChannelsRpcState { /// Substate is accessed @@ -40,7 +37,16 @@ impl P2pChannelsRpcState { *rpc_state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsRpcEffectfulAction::Init { peer_id }); + + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::Rpc, + on_success: redux::callback!( + on_rpc_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsRpcAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsRpcAction::Pending { .. } => { @@ -107,12 +113,15 @@ impl P2pChannelsRpcState { return Ok(()); } - dispatcher.push(P2pChannelsRpcEffectfulAction::RequestSend { + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - id, - request, - on_init, + msg_id: MsgId::first(), + msg: ChannelMsg::Rpc(RpcChannelMsg::Request(id, *request.clone())), }); + + if let Some(callback) = on_init { + dispatcher.push_callback(callback, (peer_id, id, *request)); + } Ok(()) } P2pChannelsRpcAction::Timeout { id, .. } => { @@ -238,10 +247,10 @@ impl P2pChannelsRpcState { return Ok(()); } - dispatcher.push(P2pChannelsRpcEffectfulAction::ResponseSend { + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - id, - response, + msg_id: MsgId::first(), + msg: ChannelMsg::Rpc(RpcChannelMsg::Response(id, response.map(|v| *v))), }); Ok(()) } diff --git a/p2p/src/channels/rpc_effectful/mod.rs b/p2p/src/channels/rpc_effectful/mod.rs deleted file mode 100644 index 302be5eef2..0000000000 --- a/p2p/src/channels/rpc_effectful/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod p2p_channels_rpc_effectful_actions; -pub use p2p_channels_rpc_effectful_actions::P2pChannelsRpcEffectfulAction; -mod p2p_channels_rpc_effectful_effects; diff --git a/p2p/src/channels/rpc_effectful/p2p_channels_rpc_effectful_actions.rs b/p2p/src/channels/rpc_effectful/p2p_channels_rpc_effectful_actions.rs deleted file mode 100644 index 57fb90b0f8..0000000000 --- a/p2p/src/channels/rpc_effectful/p2p_channels_rpc_effectful_actions.rs +++ /dev/null @@ -1,42 +0,0 @@ -use openmina_core::ActionEvent; -use redux::Timestamp; -use serde::{Deserialize, Serialize}; - -use crate::{ - channels::{ - rpc::{P2pRpcId, P2pRpcRequest, P2pRpcResponse}, - P2pChannelsEffectfulAction, - }, - P2pState, PeerId, -}; - -#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsRpcEffectfulAction { - Init { - peer_id: PeerId, - }, - RequestSend { - peer_id: PeerId, - id: P2pRpcId, - request: Box, - on_init: Option>, - }, - ResponseSend { - peer_id: PeerId, - id: P2pRpcId, - response: Option>, - }, -} - -impl redux::EnablingCondition for P2pChannelsRpcEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(a: P2pChannelsRpcEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::Rpc(a)) - } -} diff --git a/p2p/src/channels/rpc_effectful/p2p_channels_rpc_effectful_effects.rs b/p2p/src/channels/rpc_effectful/p2p_channels_rpc_effectful_effects.rs deleted file mode 100644 index 098f43093f..0000000000 --- a/p2p/src/channels/rpc_effectful/p2p_channels_rpc_effectful_effects.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::P2pChannelsRpcEffectfulAction; -use crate::channels::{ - rpc::{P2pChannelsRpcAction, RpcChannelMsg}, - ChannelId, MsgId, P2pChannelsService, -}; -use redux::ActionMeta; - -impl P2pChannelsRpcEffectfulAction { - pub fn effects(self, _meta: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsRpcEffectfulAction::Init { peer_id } => { - store.service().channel_open(peer_id, ChannelId::Rpc); - // TODO(akoptelov): open a new stream, if we decide not to forcibly do that on connection established - store.dispatch(P2pChannelsRpcAction::Pending { peer_id }); - } - P2pChannelsRpcEffectfulAction::RequestSend { - peer_id, - id, - request, - on_init, - } => { - let msg = RpcChannelMsg::Request(id, *request.clone()); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - - if let Some(on_init) = on_init { - store.dispatch_callback(on_init, (peer_id, id, *request)); - } - } - P2pChannelsRpcEffectfulAction::ResponseSend { - peer_id, - id, - response, - } => { - let msg = RpcChannelMsg::Response(id, response.map(|v| *v)); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - } - } -} diff --git a/p2p/src/channels/signaling/discovery/p2p_channels_signaling_discovery_reducer.rs b/p2p/src/channels/signaling/discovery/p2p_channels_signaling_discovery_reducer.rs index b934647c95..2f5c91142f 100644 --- a/p2p/src/channels/signaling/discovery/p2p_channels_signaling_discovery_reducer.rs +++ b/p2p/src/channels/signaling/discovery/p2p_channels_signaling_discovery_reducer.rs @@ -2,9 +2,9 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ - channels::signaling::{ - discovery_effectful::P2pChannelsSignalingDiscoveryEffectfulAction, - exchange::P2pChannelsSignalingExchangeAction, + channels::{ + signaling::exchange::P2pChannelsSignalingExchangeAction, ChannelId, MsgId, + P2pChannelsEffectfulAction, }, connection::{ outgoing::{P2pConnectionOutgoingAction, P2pConnectionOutgoingInitOpts}, @@ -44,7 +44,15 @@ impl P2pChannelsSignalingDiscoveryState { *state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSignalingDiscoveryEffectfulAction::Init { peer_id }); + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::SignalingDiscovery, + on_success: redux::callback!( + on_signaling_discovery_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsSignalingDiscoveryAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsSignalingDiscoveryAction::Pending { .. } => { @@ -73,10 +81,12 @@ impl P2pChannelsSignalingDiscoveryState { *local = SignalingDiscoveryState::Requested { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - let message = SignalingDiscoveryChannelMsg::GetNext; - dispatcher.push(P2pChannelsSignalingDiscoveryEffectfulAction::MessageSend { + + let msg = SignalingDiscoveryChannelMsg::GetNext.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message, + msg_id: MsgId::first(), + msg, }); Ok(()) } @@ -112,11 +122,12 @@ impl P2pChannelsSignalingDiscoveryState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSignalingDiscoveryEffectfulAction::MessageSend { + + let msg = SignalingDiscoveryChannelMsg::Discovered { target_public_key }.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message: SignalingDiscoveryChannelMsg::Discovered { - target_public_key: target_public_key.clone(), - }, + msg_id: MsgId::first(), + msg, }); Ok(()) } @@ -195,10 +206,12 @@ impl P2pChannelsSignalingDiscoveryState { *local = SignalingDiscoveryState::Answered { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - let message = SignalingDiscoveryChannelMsg::Answer(answer.clone()); - dispatcher.push(P2pChannelsSignalingDiscoveryEffectfulAction::MessageSend { + + let msg = SignalingDiscoveryChannelMsg::Answer(answer.clone()).into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message, + msg_id: MsgId::first(), + msg, }); Ok(()) } @@ -223,10 +236,12 @@ impl P2pChannelsSignalingDiscoveryState { *remote = SignalingDiscoveryState::DiscoveryRequested { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - let message = SignalingDiscoveryChannelMsg::Discover; - dispatcher.push(P2pChannelsSignalingDiscoveryEffectfulAction::MessageSend { + + let msg = SignalingDiscoveryChannelMsg::Discover.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message, + msg_id: MsgId::first(), + msg, }); Ok(()) } @@ -254,6 +269,7 @@ impl P2pChannelsSignalingDiscoveryState { }, }, rpc_id: None, + on_success: None, }; let accepted = redux::EnablingCondition::is_enabled(&action, state, meta.time()); if accepted { @@ -289,10 +305,12 @@ impl P2pChannelsSignalingDiscoveryState { target_public_key, }; let dispatcher = state_context.into_dispatcher(); - let message = SignalingDiscoveryChannelMsg::DiscoveredReject; - dispatcher.push(P2pChannelsSignalingDiscoveryEffectfulAction::MessageSend { + + let msg = SignalingDiscoveryChannelMsg::DiscoveredReject.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message, + msg_id: MsgId::first(), + msg, }); Ok(()) } @@ -327,10 +345,10 @@ impl P2pChannelsSignalingDiscoveryState { peer_id: target_public_key.peer_id(), }); dispatcher.push( - P2pChannelsSignalingDiscoveryEffectfulAction::OfferEncryptAndSend { + P2pChannelsEffectfulAction::SignalingDiscoveryOfferEncryptAndSend { peer_id, pub_key: target_public_key, - offer: offer.clone(), + offer, }, ); Ok(()) @@ -363,7 +381,7 @@ impl P2pChannelsSignalingDiscoveryState { error: P2pConnectionErrorResponse::InternalError, }), Some(answer) => dispatcher.push( - P2pChannelsSignalingDiscoveryEffectfulAction::AnswerDecrypt { + P2pChannelsEffectfulAction::SignalingDiscoveryAnswerDecrypt { peer_id, pub_key: target_public_key, answer: answer.clone(), diff --git a/p2p/src/channels/signaling/discovery_effectful/mod.rs b/p2p/src/channels/signaling/discovery_effectful/mod.rs deleted file mode 100644 index ae681677f3..0000000000 --- a/p2p/src/channels/signaling/discovery_effectful/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod p2p_channels_signaling_discovery_effectful_actions; -pub use p2p_channels_signaling_discovery_effectful_actions::P2pChannelsSignalingDiscoveryEffectfulAction; - -mod p2p_channels_signaling_discovery_effectful_effects; diff --git a/p2p/src/channels/signaling/discovery_effectful/p2p_channels_signaling_discovery_effectful_actions.rs b/p2p/src/channels/signaling/discovery_effectful/p2p_channels_signaling_discovery_effectful_actions.rs deleted file mode 100644 index 20e133e489..0000000000 --- a/p2p/src/channels/signaling/discovery_effectful/p2p_channels_signaling_discovery_effectful_actions.rs +++ /dev/null @@ -1,44 +0,0 @@ -use openmina_core::ActionEvent; -use serde::{Deserialize, Serialize}; - -use crate::{ - channels::{signaling::discovery::SignalingDiscoveryChannelMsg, P2pChannelsEffectfulAction}, - connection::Offer, - identity::PublicKey, - webrtc::EncryptedAnswer, - P2pState, PeerId, -}; - -#[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsSignalingDiscoveryEffectfulAction { - Init { - peer_id: PeerId, - }, - MessageSend { - peer_id: PeerId, - message: SignalingDiscoveryChannelMsg, - }, - OfferEncryptAndSend { - peer_id: PeerId, - pub_key: PublicKey, - offer: Box, - }, - AnswerDecrypt { - peer_id: PeerId, - pub_key: PublicKey, - answer: EncryptedAnswer, - }, -} - -impl redux::EnablingCondition for P2pChannelsSignalingDiscoveryEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(action: P2pChannelsSignalingDiscoveryEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::SignalingDiscovery(action)) - } -} diff --git a/p2p/src/channels/signaling/discovery_effectful/p2p_channels_signaling_discovery_effectful_effects.rs b/p2p/src/channels/signaling/discovery_effectful/p2p_channels_signaling_discovery_effectful_effects.rs deleted file mode 100644 index b95e028cfd..0000000000 --- a/p2p/src/channels/signaling/discovery_effectful/p2p_channels_signaling_discovery_effectful_effects.rs +++ /dev/null @@ -1,75 +0,0 @@ -use redux::ActionMeta; - -use crate::{ - channels::{ - signaling::discovery::{P2pChannelsSignalingDiscoveryAction, SignalingDiscoveryChannelMsg}, - ChannelId, MsgId, - }, - connection::P2pConnectionResponse, - P2pChannelsService, -}; - -use super::P2pChannelsSignalingDiscoveryEffectfulAction; - -impl P2pChannelsSignalingDiscoveryEffectfulAction { - pub fn effects(self, _meta: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsSignalingDiscoveryEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::SignalingDiscovery); - store.dispatch(P2pChannelsSignalingDiscoveryAction::Pending { peer_id }); - } - P2pChannelsSignalingDiscoveryEffectfulAction::MessageSend { peer_id, message } => { - message_send(store.service(), peer_id, message); - } - P2pChannelsSignalingDiscoveryEffectfulAction::OfferEncryptAndSend { - peer_id, - pub_key, - offer, - } => match store.service().encrypt(&pub_key, &*offer) { - Err(()) => { - // todo!("Failed to encrypt webrtc offer. Handle it.") - } - Ok(offer) => { - let message = SignalingDiscoveryChannelMsg::DiscoveredAccept(offer); - message_send(store.service(), peer_id, message); - } - }, - P2pChannelsSignalingDiscoveryEffectfulAction::AnswerDecrypt { - peer_id, - pub_key, - answer, - } => { - match store - .service() - .decrypt::(&pub_key, &answer) - { - Err(()) => { - store.dispatch(P2pChannelsSignalingDiscoveryAction::AnswerDecrypted { - peer_id, - answer: P2pConnectionResponse::SignalDecryptionFailed, - }); - } - Ok(answer) => { - store.dispatch(P2pChannelsSignalingDiscoveryAction::AnswerDecrypted { - peer_id, - answer, - }); - } - } - } - } - } -} - -fn message_send(service: &mut S, peer_id: crate::PeerId, message: SignalingDiscoveryChannelMsg) -where - S: P2pChannelsService, -{ - service.channel_send(peer_id, MsgId::first(), message.into()) -} diff --git a/p2p/src/channels/signaling/exchange/p2p_channels_signaling_exchange_reducer.rs b/p2p/src/channels/signaling/exchange/p2p_channels_signaling_exchange_reducer.rs index 737d6e2e78..37620086c4 100644 --- a/p2p/src/channels/signaling/exchange/p2p_channels_signaling_exchange_reducer.rs +++ b/p2p/src/channels/signaling/exchange/p2p_channels_signaling_exchange_reducer.rs @@ -2,9 +2,9 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ - channels::signaling::{ - discovery::P2pChannelsSignalingDiscoveryAction, - exchange_effectful::P2pChannelsSignalingExchangeEffectfulAction, + channels::{ + signaling::discovery::P2pChannelsSignalingDiscoveryAction, ChannelId, MsgId, + P2pChannelsEffectfulAction, }, connection::{ incoming::{ @@ -45,7 +45,15 @@ impl P2pChannelsSignalingExchangeState { *state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSignalingExchangeEffectfulAction::Init { peer_id }); + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::SignalingExchange, + on_success: redux::callback!( + on_signaling_exchange_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsSignalingExchangeAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsSignalingExchangeAction::Pending { .. } => { @@ -74,10 +82,12 @@ impl P2pChannelsSignalingExchangeState { *local = SignalingExchangeState::Requested { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - let message = SignalingExchangeChannelMsg::GetNext; - dispatcher.push(P2pChannelsSignalingExchangeEffectfulAction::MessageSend { + + let msg = SignalingExchangeChannelMsg::GetNext.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message, + msg_id: MsgId::first(), + msg, }); Ok(()) } @@ -100,7 +110,7 @@ impl P2pChannelsSignalingExchangeState { let dispatcher = state_context.into_dispatcher(); let offer = offer.clone(); - dispatcher.push(P2pChannelsSignalingExchangeEffectfulAction::OfferDecrypt { + dispatcher.push(P2pChannelsEffectfulAction::SignalingExchangeOfferDecrypt { peer_id, pub_key: offerer_pub_key.clone(), offer, @@ -162,7 +172,7 @@ impl P2pChannelsSignalingExchangeState { let answer = answer.clone(); let dispatcher = state_context.into_dispatcher(); dispatcher.push( - P2pChannelsSignalingExchangeEffectfulAction::AnswerEncryptAndSend { + P2pChannelsEffectfulAction::SignalingExchangeAnswerEncryptAndSend { peer_id, pub_key: offerer_pub_key.clone(), answer: Some(answer), @@ -205,13 +215,16 @@ impl P2pChannelsSignalingExchangeState { offerer_pub_key: offerer_pub_key.clone(), }; let dispatcher = state_context.into_dispatcher(); - let message = SignalingExchangeChannelMsg::OfferToYou { - offerer_pub_key: offerer_pub_key.clone(), - offer: offer.clone(), - }; - dispatcher.push(P2pChannelsSignalingExchangeEffectfulAction::MessageSend { + let msg = SignalingExchangeChannelMsg::OfferToYou { + offerer_pub_key, + offer, + } + .into(); + + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - message, + msg_id: MsgId::first(), + msg, }); Ok(()) } diff --git a/p2p/src/channels/signaling/exchange_effectful/mod.rs b/p2p/src/channels/signaling/exchange_effectful/mod.rs deleted file mode 100644 index 89a36e2c5b..0000000000 --- a/p2p/src/channels/signaling/exchange_effectful/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod p2p_channels_signaling_exchange_effectful_actions; -pub use p2p_channels_signaling_exchange_effectful_actions::P2pChannelsSignalingExchangeEffectfulAction; - -mod p2p_channels_signaling_exchange_effectful_effects; diff --git a/p2p/src/channels/signaling/exchange_effectful/p2p_channels_signaling_exchange_effectful_actions.rs b/p2p/src/channels/signaling/exchange_effectful/p2p_channels_signaling_exchange_effectful_actions.rs deleted file mode 100644 index 6daa07af78..0000000000 --- a/p2p/src/channels/signaling/exchange_effectful/p2p_channels_signaling_exchange_effectful_actions.rs +++ /dev/null @@ -1,44 +0,0 @@ -use openmina_core::ActionEvent; -use serde::{Deserialize, Serialize}; - -use crate::{ - channels::{signaling::exchange::SignalingExchangeChannelMsg, P2pChannelsEffectfulAction}, - connection::P2pConnectionResponse, - identity::PublicKey, - webrtc::EncryptedOffer, - P2pState, PeerId, -}; - -#[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsSignalingExchangeEffectfulAction { - Init { - peer_id: PeerId, - }, - MessageSend { - peer_id: PeerId, - message: SignalingExchangeChannelMsg, - }, - OfferDecrypt { - peer_id: PeerId, - pub_key: PublicKey, - offer: EncryptedOffer, - }, - AnswerEncryptAndSend { - peer_id: PeerId, - pub_key: PublicKey, - answer: Option, - }, -} - -impl redux::EnablingCondition for P2pChannelsSignalingExchangeEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(action: P2pChannelsSignalingExchangeEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::SignalingExchange(action)) - } -} diff --git a/p2p/src/channels/signaling/exchange_effectful/p2p_channels_signaling_exchange_effectful_effects.rs b/p2p/src/channels/signaling/exchange_effectful/p2p_channels_signaling_exchange_effectful_effects.rs deleted file mode 100644 index d7fa962bc4..0000000000 --- a/p2p/src/channels/signaling/exchange_effectful/p2p_channels_signaling_exchange_effectful_effects.rs +++ /dev/null @@ -1,96 +0,0 @@ -use openmina_core::bug_condition; -use redux::ActionMeta; - -use crate::{ - channels::{ - signaling::exchange::{P2pChannelsSignalingExchangeAction, SignalingExchangeChannelMsg}, - ChannelId, MsgId, - }, - webrtc::{EncryptedAnswer, Offer}, - P2pChannelsService, -}; - -use super::P2pChannelsSignalingExchangeEffectfulAction; - -impl P2pChannelsSignalingExchangeEffectfulAction { - pub fn effects(self, _meta: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsSignalingExchangeEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::SignalingExchange); - store.dispatch(P2pChannelsSignalingExchangeAction::Pending { peer_id }); - } - P2pChannelsSignalingExchangeEffectfulAction::MessageSend { peer_id, message } => { - message_send(store.service(), peer_id, message); - } - P2pChannelsSignalingExchangeEffectfulAction::OfferDecrypt { - peer_id, - pub_key, - offer, - } => { - match store.service().decrypt::(&pub_key, &offer) { - Err(()) => { - store.dispatch(P2pChannelsSignalingExchangeAction::OfferDecryptError { - peer_id, - }); - } - Ok(offer) if offer.identity_pub_key != pub_key => { - // TODO(binier): propagate specific error. - // This is invalid behavior either from relayer or offerer. - store.dispatch(P2pChannelsSignalingExchangeAction::OfferDecryptError { - peer_id, - }); - } - Ok(offer) => { - store.dispatch(P2pChannelsSignalingExchangeAction::OfferDecryptSuccess { - peer_id, - offer, - }); - } - } - } - P2pChannelsSignalingExchangeEffectfulAction::AnswerEncryptAndSend { - peer_id, - pub_key, - answer, - } => { - let answer = match answer { - None => { - answer_message_send(store.service(), peer_id, None); - return; - } - Some(v) => v, - }; - match store.service().encrypt(&pub_key, &answer) { - Err(()) => bug_condition!("Failed to encrypt webrtc answer. Shouldn't happen since we managed to decrypt sent offer."), - Ok(answer) => { - answer_message_send(store.service(), peer_id, Some(answer)); - } - } - } - } - } -} - -fn answer_message_send(service: &mut S, peer_id: crate::PeerId, answer: Option) -where - S: P2pChannelsService, -{ - message_send( - service, - peer_id, - SignalingExchangeChannelMsg::Answer(answer), - ) -} - -fn message_send(service: &mut S, peer_id: crate::PeerId, message: SignalingExchangeChannelMsg) -where - S: P2pChannelsService, -{ - service.channel_send(peer_id, MsgId::first(), message.into()) -} diff --git a/p2p/src/channels/signaling/mod.rs b/p2p/src/channels/signaling/mod.rs index 3dd52d4f66..84fa98a268 100644 --- a/p2p/src/channels/signaling/mod.rs +++ b/p2p/src/channels/signaling/mod.rs @@ -16,9 +16,7 @@ //! 7. [discovery] Relayer relays the answer to the dialer. pub mod discovery; -pub mod discovery_effectful; pub mod exchange; -pub mod exchange_effectful; mod p2p_channels_signaling_state; pub use p2p_channels_signaling_state::*; diff --git a/p2p/src/channels/snark/p2p_channels_snark_actions.rs b/p2p/src/channels/snark/p2p_channels_snark_actions.rs index 5a272c9201..cfe2171350 100644 --- a/p2p/src/channels/snark/p2p_channels_snark_actions.rs +++ b/p2p/src/channels/snark/p2p_channels_snark_actions.rs @@ -20,10 +20,12 @@ pub enum P2pChannelsSnarkAction { Ready { peer_id: PeerId, }, + #[action_event(level = debug, fields(display(peer_id), limit))] RequestSend { peer_id: PeerId, limit: u8, }, + #[action_event(level = debug, fields(display(peer_id), promised_count))] PromiseReceived { peer_id: PeerId, promised_count: u8, @@ -32,10 +34,12 @@ pub enum P2pChannelsSnarkAction { peer_id: PeerId, snark: Box, }, + #[action_event(level = debug, fields(display(peer_id), limit))] RequestReceived { peer_id: PeerId, limit: u8, }, + #[action_event(level = debug, fields(display(peer_id), snarks = snarks.len(), first_index, last_index))] ResponseSend { peer_id: PeerId, snarks: Vec, @@ -144,7 +148,7 @@ impl redux::EnablingCondition for P2pChannelsSnarkAction { last_index, } => { !snarks.is_empty() - && first_index < last_index + && first_index <= last_index && state .get_ready_peer(peer_id) .map_or(false, |p| match &p.channels.snark { diff --git a/p2p/src/channels/snark/p2p_channels_snark_reducer.rs b/p2p/src/channels/snark/p2p_channels_snark_reducer.rs index 8fa06efa63..b92755a073 100644 --- a/p2p/src/channels/snark/p2p_channels_snark_reducer.rs +++ b/p2p/src/channels/snark/p2p_channels_snark_reducer.rs @@ -2,10 +2,14 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ - channels::snark_effectful::P2pChannelsSnarkEffectfulAction, P2pNetworkPubsubAction, P2pState, + channels::{ChannelId, MsgId, P2pChannelsEffectfulAction}, + P2pNetworkPubsubAction, P2pState, }; -use super::{P2pChannelsSnarkAction, P2pChannelsSnarkState, SnarkPropagationState}; +use super::{ + P2pChannelsSnarkAction, P2pChannelsSnarkState, SnarkPropagationChannelMsg, + SnarkPropagationState, +}; use mina_p2p_messages::{gossip::GossipNetMessageV2, v2}; impl P2pChannelsSnarkState { @@ -32,7 +36,16 @@ impl P2pChannelsSnarkState { *state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSnarkEffectfulAction::Init { peer_id }); + + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::SnarkPropagation, + on_success: redux::callback!( + on_snark_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsSnarkAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsSnarkAction::Pending { .. } => { @@ -65,7 +78,11 @@ impl P2pChannelsSnarkState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSnarkEffectfulAction::RequestSend { peer_id, limit }); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg: SnarkPropagationChannelMsg::GetNext { limit }.into(), + }); Ok(()) } P2pChannelsSnarkAction::PromiseReceived { promised_count, .. } => { @@ -178,7 +195,19 @@ impl P2pChannelsSnarkState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSnarkEffectfulAction::ResponseSend { peer_id, snarks }); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg: SnarkPropagationChannelMsg::WillSend { count }.into(), + }); + + for snark in snarks { + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg: SnarkPropagationChannelMsg::Snark(snark).into(), + }); + } Ok(()) } #[cfg(feature = "p2p-libp2p")] @@ -187,7 +216,7 @@ impl P2pChannelsSnarkState { let message = Box::new((snark.statement(), (&snark).into())); let message = v2::NetworkPoolSnarkPoolDiffVersionedStableV2::AddSolvedWork(message); let nonce = nonce.into(); - let message = Box::new(GossipNetMessageV2::SnarkPoolDiff { message, nonce }); + let message = GossipNetMessageV2::SnarkPoolDiff { message, nonce }; dispatcher.push(P2pNetworkPubsubAction::Broadcast { message }); Ok(()) } diff --git a/p2p/src/channels/snark_effectful/mod.rs b/p2p/src/channels/snark_effectful/mod.rs deleted file mode 100644 index 8f1d95bc4b..0000000000 --- a/p2p/src/channels/snark_effectful/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod p2p_channels_snark_effectful_actions; -pub use p2p_channels_snark_effectful_actions::P2pChannelsSnarkEffectfulAction; -mod p2p_channels_snark_effectful_effects; diff --git a/p2p/src/channels/snark_effectful/p2p_channels_snark_effectful_actions.rs b/p2p/src/channels/snark_effectful/p2p_channels_snark_effectful_actions.rs deleted file mode 100644 index a3214d7e26..0000000000 --- a/p2p/src/channels/snark_effectful/p2p_channels_snark_effectful_actions.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::{channels::P2pChannelsEffectfulAction, P2pState, PeerId}; -use openmina_core::{snark::SnarkInfo, ActionEvent}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsSnarkEffectfulAction { - Init { - peer_id: PeerId, - }, - RequestSend { - peer_id: PeerId, - limit: u8, - }, - ResponseSend { - peer_id: PeerId, - snarks: Vec, - }, -} - -impl redux::EnablingCondition for P2pChannelsSnarkEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(action: P2pChannelsSnarkEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::Snark(action)) - } -} diff --git a/p2p/src/channels/snark_effectful/p2p_channels_snark_effectful_effects.rs b/p2p/src/channels/snark_effectful/p2p_channels_snark_effectful_effects.rs deleted file mode 100644 index 4ef59ccbb6..0000000000 --- a/p2p/src/channels/snark_effectful/p2p_channels_snark_effectful_effects.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::P2pChannelsSnarkEffectfulAction; -use crate::channels::{ - snark::{P2pChannelsSnarkAction, SnarkPropagationChannelMsg}, - ChannelId, MsgId, P2pChannelsService, -}; -use redux::ActionMeta; - -impl P2pChannelsSnarkEffectfulAction { - pub fn effects(self, _: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsSnarkEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::SnarkPropagation); - store.dispatch(P2pChannelsSnarkAction::Pending { peer_id }); - } - P2pChannelsSnarkEffectfulAction::RequestSend { peer_id, limit } => { - let msg = SnarkPropagationChannelMsg::GetNext { limit }; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - P2pChannelsSnarkEffectfulAction::ResponseSend { - peer_id, snarks, .. - } => { - let msg = SnarkPropagationChannelMsg::WillSend { - count: snarks.len() as u8, - }; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - - for snark in snarks { - let msg = SnarkPropagationChannelMsg::Snark(snark); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - } - } - } -} diff --git a/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_actions.rs b/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_actions.rs index b8a69fbbe2..eae9c84e1a 100644 --- a/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_actions.rs +++ b/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_actions.rs @@ -145,7 +145,7 @@ impl redux::EnablingCondition for P2pChannelsSnarkJobCommitmentAction last_index, } => { !commitments.is_empty() - && first_index < last_index + && first_index <= last_index && state.get_ready_peer(peer_id).map_or(false, |p| { match &p.channels.snark_job_commitment { P2pChannelsSnarkJobCommitmentState::Ready { diff --git a/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_reducer.rs b/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_reducer.rs index 14586ab34e..236a67ae93 100644 --- a/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_reducer.rs +++ b/p2p/src/channels/snark_job_commitment/p2p_channels_snark_job_commitment_reducer.rs @@ -2,13 +2,13 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ - channels::snark_job_commitment_effectful::P2pChannelsSnarkJobCommitmentEffectfulAction, + channels::{ChannelId, MsgId, P2pChannelsEffectfulAction}, P2pState, }; use super::{ P2pChannelsSnarkJobCommitmentAction, P2pChannelsSnarkJobCommitmentState, - SnarkJobCommitmentPropagationState, + SnarkJobCommitmentPropagationChannelMsg, SnarkJobCommitmentPropagationState, }; const LIMIT: u8 = 16; @@ -37,7 +37,15 @@ impl P2pChannelsSnarkJobCommitmentState { *snark_job_state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSnarkJobCommitmentAction::Pending { peer_id }); + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::SnarkJobCommitmentPropagation, + on_success: redux::callback!( + on_snark_job_commitment_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsSnarkJobCommitmentAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsSnarkJobCommitmentAction::Pending { .. } => { @@ -197,10 +205,23 @@ impl P2pChannelsSnarkJobCommitmentState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsSnarkJobCommitmentEffectfulAction::ResponseSend { + let msg = SnarkJobCommitmentPropagationChannelMsg::WillSend { count }.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - commitments, + msg_id: MsgId::first(), + msg, }); + + for commitment in commitments { + let msg = + SnarkJobCommitmentPropagationChannelMsg::Commitment(commitment).into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg, + }); + } + Ok(()) } } diff --git a/p2p/src/channels/snark_job_commitment_effectful/mod.rs b/p2p/src/channels/snark_job_commitment_effectful/mod.rs deleted file mode 100644 index 91a01082ef..0000000000 --- a/p2p/src/channels/snark_job_commitment_effectful/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod p2p_channels_snark_job_commitment_effectful_actions; -pub use p2p_channels_snark_job_commitment_effectful_actions::P2pChannelsSnarkJobCommitmentEffectfulAction; -mod p2p_channels_snark_job_commitment_effectful_effects; diff --git a/p2p/src/channels/snark_job_commitment_effectful/p2p_channels_snark_job_commitment_effectful_actions.rs b/p2p/src/channels/snark_job_commitment_effectful/p2p_channels_snark_job_commitment_effectful_actions.rs deleted file mode 100644 index d3836687f8..0000000000 --- a/p2p/src/channels/snark_job_commitment_effectful/p2p_channels_snark_job_commitment_effectful_actions.rs +++ /dev/null @@ -1,32 +0,0 @@ -use openmina_core::{snark::SnarkJobCommitment, ActionEvent}; -use serde::{Deserialize, Serialize}; - -use crate::{channels::P2pChannelsEffectfulAction, P2pState, PeerId}; - -#[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsSnarkJobCommitmentEffectfulAction { - Init { - peer_id: PeerId, - }, - RequestSend { - peer_id: PeerId, - limit: u8, - }, - ResponseSend { - peer_id: PeerId, - commitments: Vec, - }, -} - -impl redux::EnablingCondition for P2pChannelsSnarkJobCommitmentEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(action: P2pChannelsSnarkJobCommitmentEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::SnarkJobCommitment(action)) - } -} diff --git a/p2p/src/channels/snark_job_commitment_effectful/p2p_channels_snark_job_commitment_effectful_effects.rs b/p2p/src/channels/snark_job_commitment_effectful/p2p_channels_snark_job_commitment_effectful_effects.rs deleted file mode 100644 index 94741f1b4c..0000000000 --- a/p2p/src/channels/snark_job_commitment_effectful/p2p_channels_snark_job_commitment_effectful_effects.rs +++ /dev/null @@ -1,55 +0,0 @@ -use redux::ActionMeta; - -use crate::channels::{ - snark_job_commitment::{ - P2pChannelsSnarkJobCommitmentAction, SnarkJobCommitmentPropagationChannelMsg, - }, - ChannelId, MsgId, P2pChannelsService, -}; - -use super::p2p_channels_snark_job_commitment_effectful_actions::P2pChannelsSnarkJobCommitmentEffectfulAction; - -impl P2pChannelsSnarkJobCommitmentEffectfulAction { - pub fn effects(self, _: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsSnarkJobCommitmentEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::SnarkJobCommitmentPropagation); - store.dispatch(P2pChannelsSnarkJobCommitmentAction::Pending { peer_id }); - } - P2pChannelsSnarkJobCommitmentEffectfulAction::RequestSend { peer_id, limit } => { - let msg = SnarkJobCommitmentPropagationChannelMsg::GetNext { limit }; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - P2pChannelsSnarkJobCommitmentEffectfulAction::ResponseSend { - peer_id, - commitments, - } => { - if commitments.is_empty() { - return; - } - - let msg = SnarkJobCommitmentPropagationChannelMsg::WillSend { - count: commitments.len() as u8, - }; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - - for commitment in commitments { - let msg = SnarkJobCommitmentPropagationChannelMsg::Commitment(commitment); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - } - } - } -} diff --git a/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_reducer.rs b/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_reducer.rs index 3340b327ec..463b5436f6 100644 --- a/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_reducer.rs +++ b/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_reducer.rs @@ -1,13 +1,16 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; -use crate::{channels::streaming_rpc_effectful::P2pChannelsStreamingRpcEffectfulAction, P2pState}; +use crate::{ + channels::{ChannelId, ChannelMsg, MsgId, P2pChannelsEffectfulAction}, + P2pState, +}; use super::{ staged_ledger_parts::{StagedLedgerPartsReceiveProgress, StagedLedgerPartsSendProgress}, P2pChannelsStreamingRpcAction, P2pChannelsStreamingRpcState, P2pStreamingRpcLocalState, P2pStreamingRpcRemoteState, P2pStreamingRpcRequest, P2pStreamingRpcResponseFull, - P2pStreamingRpcSendProgress, + P2pStreamingRpcSendProgress, StreamingRpcChannelMsg, }; impl P2pChannelsStreamingRpcState { @@ -36,7 +39,15 @@ impl P2pChannelsStreamingRpcState { *streaming_rpc_state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsStreamingRpcEffectfulAction::Init { peer_id }); + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::StreamingRpc, + on_success: redux::callback!( + on_streaming_rpc_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsStreamingRpcAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsStreamingRpcAction::Pending { .. } => { @@ -88,12 +99,14 @@ impl P2pChannelsStreamingRpcState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsStreamingRpcEffectfulAction::RequestSend { + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - id, - request, - on_init, + msg_id: MsgId::first(), + msg: StreamingRpcChannelMsg::Request(id, *request.clone()).into(), }); + if let Some(callback) = on_init { + dispatcher.push_callback(callback, (peer_id, id, *request)); + } Ok(()) } P2pChannelsStreamingRpcAction::Timeout { id, .. } => { @@ -125,9 +138,11 @@ impl P2pChannelsStreamingRpcState { } let dispatcher = state_context.into_dispatcher(); - dispatcher.push( - P2pChannelsStreamingRpcEffectfulAction::ResponseNextPartGet { peer_id, id }, - ); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg: ChannelMsg::StreamingRpc(StreamingRpcChannelMsg::Next(id)), + }); Ok(()) } P2pChannelsStreamingRpcAction::ResponsePartReceived { response, id, .. } => { @@ -269,11 +284,19 @@ impl P2pChannelsStreamingRpcState { } let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsStreamingRpcEffectfulAction::ResponseSendInit { - peer_id, - id, - response, - }); + if response.is_none() { + let msg = StreamingRpcChannelMsg::Response(id, None).into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg, + }); + dispatcher.push(P2pChannelsStreamingRpcAction::ResponseSent { peer_id, id }); + return Ok(()); + } + + dispatcher + .push(P2pChannelsStreamingRpcAction::ResponsePartNextSend { peer_id, id }); Ok(()) } P2pChannelsStreamingRpcAction::ResponsePartNextSend { id, .. } => { @@ -368,11 +391,14 @@ impl P2pChannelsStreamingRpcState { } let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsStreamingRpcEffectfulAction::ResponsePartSend { + + let msg = StreamingRpcChannelMsg::Response(id, Some(*response)).into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - id, - response, + msg_id: MsgId::first(), + msg, }); + dispatcher.push(P2pChannelsStreamingRpcAction::ResponseSent { peer_id, id }); Ok(()) } P2pChannelsStreamingRpcAction::ResponseSent { id, .. } => { diff --git a/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_state.rs b/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_state.rs index 613f731697..12c606ce0a 100644 --- a/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_state.rs +++ b/p2p/src/channels/streaming_rpc/p2p_channels_streaming_rpc_state.rs @@ -135,6 +135,16 @@ impl P2pChannelsStreamingRpcState { self.pending_local_rpc().map(|req| req.kind()) } + pub fn pending_local_rpc_progress(&self) -> Option<&P2pStreamingRpcReceiveProgress> { + match self { + Self::Ready { + local: P2pStreamingRpcLocalState::Requested { progress, .. }, + .. + } => Some(progress), + _ => None, + } + } + pub(super) fn local_done_response(&self) -> Option { match self { Self::Ready { diff --git a/p2p/src/channels/streaming_rpc/rpcs/mod.rs b/p2p/src/channels/streaming_rpc/rpcs/mod.rs index 4330b3c431..5b2daaf9e5 100644 --- a/p2p/src/channels/streaming_rpc/rpcs/mod.rs +++ b/p2p/src/channels/streaming_rpc/rpcs/mod.rs @@ -47,7 +47,7 @@ impl P2pStreamingRpcKind { pub fn timeout(self, _config: &P2pTimeouts) -> Option { match self { // TODO(binier): use config - Self::StagedLedgerParts => Some(Duration::from_secs(20)), + Self::StagedLedgerParts => Some(Duration::from_secs(30)), } } } diff --git a/p2p/src/channels/streaming_rpc/rpcs/staged_ledger_parts.rs b/p2p/src/channels/streaming_rpc/rpcs/staged_ledger_parts.rs index 00816fa03d..03d72c921b 100644 --- a/p2p/src/channels/streaming_rpc/rpcs/staged_ledger_parts.rs +++ b/p2p/src/channels/streaming_rpc/rpcs/staged_ledger_parts.rs @@ -366,6 +366,43 @@ impl StagedLedgerPartsReceiveProgress { } } } + + pub fn progress(&self) -> (u64, u64) { + const EXPECTED_FETCH_TOTAL: u64 = 27; + let total = |trees: u32| (trees as u64) + 3; + match self { + Self::BasePending { .. } => (0, EXPECTED_FETCH_TOTAL), + Self::BaseSuccess { .. } | Self::ScanStateBasePending { .. } => { + (1, EXPECTED_FETCH_TOTAL) + } + Self::ScanStateBaseSuccess { + scan_state_base, .. + } + | Self::PreviousIncompleteZkappUpdatesPending { + scan_state_base, .. + } => (2, total(scan_state_base.trees.as_u32())), + Self::PreviousIncompleteZkappUpdatesSuccess { + scan_state_base, .. + } => (3, total(scan_state_base.trees.as_u32())), + Self::ScanStateTreesPending { + scan_state_base, + trees, + .. + } => ( + 3 + trees.len() as u64, + total(scan_state_base.trees.as_u32()), + ), + Self::Success { data, .. } => { + let total = calc_total_pieces_to_transfer(data); + (total, total) + } + } + } +} + +pub fn calc_total_pieces_to_transfer(parts: &StagedLedgerAuxAndPendingCoinbases) -> u64 { + let total_trees = parts.scan_state.scan_state.trees.1.len() + 1; + 3 + total_trees as u64 } impl Default for StagedLedgerPartsReceiveProgress { diff --git a/p2p/src/channels/streaming_rpc_effectful/mod.rs b/p2p/src/channels/streaming_rpc_effectful/mod.rs deleted file mode 100644 index 864b055264..0000000000 --- a/p2p/src/channels/streaming_rpc_effectful/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod p2p_channels_streaming_rpc_effectful_actions; -pub use p2p_channels_streaming_rpc_effectful_actions::P2pChannelsStreamingRpcEffectfulAction; -mod p2p_channels_streaming_rpc_effectful_effects; diff --git a/p2p/src/channels/streaming_rpc_effectful/p2p_channels_streaming_rpc_effectful_actions.rs b/p2p/src/channels/streaming_rpc_effectful/p2p_channels_streaming_rpc_effectful_actions.rs deleted file mode 100644 index cc5fac9b05..0000000000 --- a/p2p/src/channels/streaming_rpc_effectful/p2p_channels_streaming_rpc_effectful_actions.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{ - channels::{ - streaming_rpc::{ - P2pStreamingRpcId, P2pStreamingRpcRequest, P2pStreamingRpcResponse, - P2pStreamingRpcResponseFull, - }, - P2pChannelsEffectfulAction, - }, - P2pState, PeerId, -}; -use openmina_core::ActionEvent; -use redux::Timestamp; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsStreamingRpcEffectfulAction { - Init { - peer_id: PeerId, - }, - RequestSend { - peer_id: PeerId, - id: P2pStreamingRpcId, - request: Box, - on_init: Option>, - }, - ResponseNextPartGet { - peer_id: PeerId, - id: P2pStreamingRpcId, - }, - ResponseSendInit { - peer_id: PeerId, - id: P2pStreamingRpcId, - response: Option, - }, - ResponsePartSend { - peer_id: PeerId, - id: P2pStreamingRpcId, - response: Box, - }, -} - -impl redux::EnablingCondition for P2pChannelsStreamingRpcEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(a: P2pChannelsStreamingRpcEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::StreamingRpc(a)) - } -} diff --git a/p2p/src/channels/streaming_rpc_effectful/p2p_channels_streaming_rpc_effectful_effects.rs b/p2p/src/channels/streaming_rpc_effectful/p2p_channels_streaming_rpc_effectful_effects.rs deleted file mode 100644 index 244cd3db6a..0000000000 --- a/p2p/src/channels/streaming_rpc_effectful/p2p_channels_streaming_rpc_effectful_effects.rs +++ /dev/null @@ -1,72 +0,0 @@ -use redux::ActionMeta; - -use crate::channels::{ - streaming_rpc::{P2pChannelsStreamingRpcAction, StreamingRpcChannelMsg}, - ChannelId, MsgId, P2pChannelsService, -}; - -use super::P2pChannelsStreamingRpcEffectfulAction; - -impl P2pChannelsStreamingRpcEffectfulAction { - pub fn effects(self, _: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsStreamingRpcEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::StreamingRpc); - store.dispatch(P2pChannelsStreamingRpcAction::Pending { peer_id }); - } - P2pChannelsStreamingRpcEffectfulAction::RequestSend { - peer_id, - id, - request, - on_init, - } => { - let msg = StreamingRpcChannelMsg::Request(id, *request.clone()); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - if let Some(on_init) = on_init { - store.dispatch_callback(on_init, (peer_id, id, *request)); - } - } - P2pChannelsStreamingRpcEffectfulAction::ResponseNextPartGet { peer_id, id } => { - let msg = StreamingRpcChannelMsg::Next(id); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - P2pChannelsStreamingRpcEffectfulAction::ResponseSendInit { - peer_id, - id, - response, - } => { - if response.is_none() { - let msg = StreamingRpcChannelMsg::Response(id, None); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - - store.dispatch(P2pChannelsStreamingRpcAction::ResponseSent { peer_id, id }); - return; - } - store.dispatch(P2pChannelsStreamingRpcAction::ResponsePartNextSend { peer_id, id }); - } - P2pChannelsStreamingRpcEffectfulAction::ResponsePartSend { - peer_id, - id, - response, - } => { - let msg = StreamingRpcChannelMsg::Response(id, Some(*response)); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - store.dispatch(P2pChannelsStreamingRpcAction::ResponseSent { peer_id, id }); - } - } - } -} diff --git a/p2p/src/channels/transaction/p2p_channels_transaction_actions.rs b/p2p/src/channels/transaction/p2p_channels_transaction_actions.rs index e4f9851b7b..bd4365c1b3 100644 --- a/p2p/src/channels/transaction/p2p_channels_transaction_actions.rs +++ b/p2p/src/channels/transaction/p2p_channels_transaction_actions.rs @@ -155,7 +155,7 @@ impl redux::EnablingCondition for P2pChannelsTransactionAction { last_index, } => { !transactions.is_empty() - && first_index < last_index + && first_index <= last_index && state.get_ready_peer(peer_id).map_or(false, |p| { match &p.channels.transaction { P2pChannelsTransactionState::Ready { diff --git a/p2p/src/channels/transaction/p2p_channels_transaction_reducer.rs b/p2p/src/channels/transaction/p2p_channels_transaction_reducer.rs index c7f7a8c60a..872102a843 100644 --- a/p2p/src/channels/transaction/p2p_channels_transaction_reducer.rs +++ b/p2p/src/channels/transaction/p2p_channels_transaction_reducer.rs @@ -1,9 +1,10 @@ use super::{ - P2pChannelsTransactionAction, P2pChannelsTransactionState, TransactionPropagationState, + P2pChannelsTransactionAction, P2pChannelsTransactionState, TransactionPropagationChannelMsg, + TransactionPropagationState, }; use crate::{ - channels::transaction_effectful::P2pChannelsTransactionEffectfulAction, P2pNetworkPubsubAction, - P2pState, + channels::{ChannelId, MsgId, P2pChannelsEffectfulAction}, + P2pNetworkPubsubAction, P2pState, }; use mina_p2p_messages::{gossip::GossipNetMessageV2, v2}; use openmina_core::{bug_condition, Substate}; @@ -33,7 +34,15 @@ impl P2pChannelsTransactionState { *state = Self::Init { time: meta.time() }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsTransactionEffectfulAction::Init { peer_id }); + dispatcher.push(P2pChannelsEffectfulAction::InitChannel { + peer_id, + id: ChannelId::TransactionPropagation, + on_success: redux::callback!( + on_transaction_channel_init(peer_id: crate::PeerId) -> crate::P2pAction { + P2pChannelsTransactionAction::Pending { peer_id } + } + ), + }); Ok(()) } P2pChannelsTransactionAction::Pending { .. } => { @@ -66,8 +75,11 @@ impl P2pChannelsTransactionState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher - .push(P2pChannelsTransactionEffectfulAction::RequestSend { peer_id, limit }); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg: TransactionPropagationChannelMsg::GetNext { limit }.into(), + }); Ok(()) } P2pChannelsTransactionAction::PromiseReceived { promised_count, .. } => { @@ -97,7 +109,10 @@ impl P2pChannelsTransactionState { }; Ok(()) } - P2pChannelsTransactionAction::Received { .. } => { + P2pChannelsTransactionAction::Received { + peer_id, + transaction, + } => { let state = transaction_state.inspect_err(|error| bug_condition!("{}", error))?; let Self::Ready { local, .. } = state else { bug_condition!( @@ -123,6 +138,14 @@ impl P2pChannelsTransactionState { count: *current_count, }; } + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let p2p_state: &P2pState = state.substate()?; + + if let Some(callback) = &p2p_state.callbacks.on_p2p_channels_transaction_received { + dispatcher.push_callback(callback.clone(), (peer_id, transaction)); + } + Ok(()) } P2pChannelsTransactionAction::RequestReceived { limit, .. } => { @@ -172,10 +195,21 @@ impl P2pChannelsTransactionState { }; let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pChannelsTransactionEffectfulAction::ResponseSend { + let msg = TransactionPropagationChannelMsg::WillSend { count }.into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { peer_id, - transactions, + msg_id: MsgId::first(), + msg, }); + + for tx in transactions { + let msg = TransactionPropagationChannelMsg::Transaction(tx).into(); + dispatcher.push(P2pChannelsEffectfulAction::MessageSend { + peer_id, + msg_id: MsgId::first(), + msg, + }); + } Ok(()) } P2pChannelsTransactionAction::Libp2pReceived { transaction, .. } => { @@ -200,7 +234,7 @@ impl P2pChannelsTransactionState { std::iter::once(*transaction).collect(), ); let nonce = nonce.into(); - let message = Box::new(GossipNetMessageV2::TransactionPoolDiff { message, nonce }); + let message = GossipNetMessageV2::TransactionPoolDiff { message, nonce }; dispatcher.push(P2pNetworkPubsubAction::Broadcast { message }); Ok(()) } diff --git a/p2p/src/channels/transaction_effectful/mod.rs b/p2p/src/channels/transaction_effectful/mod.rs deleted file mode 100644 index 470d454098..0000000000 --- a/p2p/src/channels/transaction_effectful/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod p2p_channels_transaction_effectful_actions; -pub use p2p_channels_transaction_effectful_actions::P2pChannelsTransactionEffectfulAction; -mod p2p_channels_transaction_effectful_effects; diff --git a/p2p/src/channels/transaction_effectful/p2p_channels_transaction_effectful_actions.rs b/p2p/src/channels/transaction_effectful/p2p_channels_transaction_effectful_actions.rs deleted file mode 100644 index 82728a0e0e..0000000000 --- a/p2p/src/channels/transaction_effectful/p2p_channels_transaction_effectful_actions.rs +++ /dev/null @@ -1,33 +0,0 @@ -use openmina_core::transaction::TransactionInfo; -use openmina_core::ActionEvent; -use serde::{Deserialize, Serialize}; - -use crate::{channels::P2pChannelsEffectfulAction, P2pState, PeerId}; - -#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id)))] -pub enum P2pChannelsTransactionEffectfulAction { - Init { - peer_id: PeerId, - }, - RequestSend { - peer_id: PeerId, - limit: u8, - }, - ResponseSend { - peer_id: PeerId, - transactions: Vec, - }, -} - -impl redux::EnablingCondition for P2pChannelsTransactionEffectfulAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true - } -} - -impl From for crate::P2pEffectfulAction { - fn from(action: P2pChannelsTransactionEffectfulAction) -> crate::P2pEffectfulAction { - crate::P2pEffectfulAction::Channels(P2pChannelsEffectfulAction::Transaction(action)) - } -} diff --git a/p2p/src/channels/transaction_effectful/p2p_channels_transaction_effectful_effects.rs b/p2p/src/channels/transaction_effectful/p2p_channels_transaction_effectful_effects.rs deleted file mode 100644 index 94d90e621c..0000000000 --- a/p2p/src/channels/transaction_effectful/p2p_channels_transaction_effectful_effects.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::P2pChannelsTransactionEffectfulAction; -use crate::channels::{ - transaction::{P2pChannelsTransactionAction, TransactionPropagationChannelMsg}, - ChannelId, MsgId, P2pChannelsService, -}; -use redux::ActionMeta; - -impl P2pChannelsTransactionEffectfulAction { - pub fn effects(self, _: &ActionMeta, store: &mut Store) - where - Store: crate::P2pStore, - Store::Service: P2pChannelsService, - { - match self { - P2pChannelsTransactionEffectfulAction::Init { peer_id } => { - store - .service() - .channel_open(peer_id, ChannelId::TransactionPropagation); - store.dispatch(P2pChannelsTransactionAction::Pending { peer_id }); - } - P2pChannelsTransactionEffectfulAction::RequestSend { peer_id, limit } => { - let msg = TransactionPropagationChannelMsg::GetNext { limit }; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - P2pChannelsTransactionEffectfulAction::ResponseSend { - peer_id, - transactions, - } => { - if transactions.is_empty() { - return; - } - - let msg = TransactionPropagationChannelMsg::WillSend { - count: transactions.len() as u8, - }; - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - - for tx in transactions { - let msg = TransactionPropagationChannelMsg::Transaction(tx); - store - .service() - .channel_send(peer_id, MsgId::first(), msg.into()); - } - } - } - } -} diff --git a/p2p/src/connection/incoming/p2p_connection_incoming_actions.rs b/p2p/src/connection/incoming/p2p_connection_incoming_actions.rs index 00e6f45710..aefdc64f4c 100644 --- a/p2p/src/connection/incoming/p2p_connection_incoming_actions.rs +++ b/p2p/src/connection/incoming/p2p_connection_incoming_actions.rs @@ -56,6 +56,7 @@ pub enum P2pConnectionIncomingAction { peer_id: PeerId, }, /// Error establishing incoming connection. + #[action_event(level = warn, fields(display(peer_id), display(error)))] Error { peer_id: PeerId, error: P2pConnectionIncomingError, diff --git a/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs b/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs index a47c5c14a6..aa30217363 100644 --- a/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs +++ b/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs @@ -252,7 +252,8 @@ impl P2pConnectionIncomingState { rpc_id: rpc_id.take(), }; - state_context.into_dispatcher().push(P2pConnectionIncomingEffectfulAction::ConnectionAuthorizationEncryptAndSend { peer_id, other_pub_key, auth }); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pConnectionIncomingEffectfulAction::ConnectionAuthorizationEncryptAndSend { peer_id, other_pub_key, auth }); } else { bug_condition!( "Invalid state for `P2pConnectionIncomingAction::FinalizePending`: {:?}", @@ -362,6 +363,7 @@ impl P2pConnectionIncomingState { dispatcher.push_callback(callback.clone(), (rpc_id, str_error)); } } + dispatcher.push(P2pDisconnectionAction::FailedCleanup { peer_id }); Ok(()) } diff --git a/p2p/src/connection/outgoing/mod.rs b/p2p/src/connection/outgoing/mod.rs index 6bed5c356e..58dbb7d75d 100644 --- a/p2p/src/connection/outgoing/mod.rs +++ b/p2p/src/connection/outgoing/mod.rs @@ -6,11 +6,13 @@ pub use p2p_connection_outgoing_actions::*; mod p2p_connection_outgoing_reducer; +use std::net::IpAddr; #[cfg(feature = "p2p-libp2p")] use std::net::SocketAddr; use std::{fmt, str::FromStr}; use binprot_derive::{BinProtRead, BinProtWrite}; +use multiaddr::{Multiaddr, Protocol}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -44,6 +46,42 @@ pub struct P2pConnectionOutgoingInitLibp2pOpts { pub port: u16, } +impl P2pConnectionOutgoingInitLibp2pOpts { + /// If the current host is local and there is a better host among the `addrs`, + /// replace the current one with the better one. + pub fn update_host_if_needed<'a>(&mut self, mut addrs: impl Iterator) { + fn is_local(ip: impl Into) -> bool { + match ip.into() { + IpAddr::V4(ip) => ip.is_loopback() || ip.is_private(), + IpAddr::V6(ip) => ip.is_loopback(), + } + } + + // if current dial opts is not good enough + let update = match &self.host { + Host::Domain(_) => false, + Host::Ipv4(ip) => is_local(*ip), + Host::Ipv6(ip) => is_local(*ip), + }; + if update { + // if new options is better + let new = addrs.find_map(|x| { + x.iter().find_map(|x| match x { + Protocol::Dns4(hostname) | Protocol::Dns6(hostname) => { + Some(Host::Domain(hostname.into_owned())) + } + Protocol::Ip4(ip) if !is_local(ip) => Some(Host::Ipv4(ip)), + Protocol::Ip6(ip) if !is_local(ip) => Some(Host::Ipv6(ip)), + _ => None, + }) + }); + if let Some(new) = new { + self.host = new; + } + } + } +} + pub(crate) mod libp2p_opts { use std::net::{IpAddr, SocketAddr}; @@ -240,6 +278,17 @@ impl P2pConnectionOutgoingInitOpts { (*peer_id).to_string().into_bytes().into(), ), }), + SignalingMethod::HttpsProxy(cluster_id, info) => { + Some(v2::NetworkPeerPeerStableV1 { + host: format!("https://{}/clusters/{cluster_id}", info.host) + .as_bytes() + .into(), + libp2p_port: (info.port as u64).into(), + peer_id: v2::NetworkPeerPeerIdStableV1( + (*peer_id).to_string().into_bytes().into(), + ), + }) + } SignalingMethod::P2p { .. } => None, }, } diff --git a/p2p/src/connection/outgoing/p2p_connection_outgoing_actions.rs b/p2p/src/connection/outgoing/p2p_connection_outgoing_actions.rs index 126ec50a78..ae50f9b742 100644 --- a/p2p/src/connection/outgoing/p2p_connection_outgoing_actions.rs +++ b/p2p/src/connection/outgoing/p2p_connection_outgoing_actions.rs @@ -1,4 +1,5 @@ use openmina_core::ActionEvent; +use redux::Callback; use serde::{Deserialize, Serialize}; use openmina_core::requests::RpcId; @@ -18,6 +19,7 @@ pub enum P2pConnectionOutgoingAction { Init { opts: P2pConnectionOutgoingInitOpts, rpc_id: Option, + on_success: Option)>>, }, /// Reconnect to an existing peer. // TODO: rename `Init` and `Reconnect` to `New` and `Connect` or something diff --git a/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs b/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs index 4994b9bd49..05ffc40c0a 100644 --- a/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs +++ b/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs @@ -9,6 +9,7 @@ use crate::{ outgoing_effectful::P2pConnectionOutgoingEffectfulAction, P2pConnectionErrorResponse, P2pConnectionState, }, + disconnection::P2pDisconnectionAction, webrtc::Host, P2pNetworkKadRequestAction, P2pNetworkSchedulerAction, P2pPeerAction, P2pPeerState, P2pPeerStatus, P2pState, @@ -36,11 +37,17 @@ impl P2pConnectionOutgoingState { match action { P2pConnectionOutgoingAction::RandomInit => { - let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pConnectionOutgoingEffectfulAction::RandomInit); + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let p2p_state: &P2pState = state.substate()?; + let peers = p2p_state.disconnected_peers().collect::>(); + dispatcher.push(P2pConnectionOutgoingEffectfulAction::RandomInit { peers }); Ok(()) } - P2pConnectionOutgoingAction::Init { opts, rpc_id } => { + P2pConnectionOutgoingAction::Init { + opts, + rpc_id, + on_success, + } => { let peer_state = p2p_state .peers @@ -59,6 +66,7 @@ impl P2pConnectionOutgoingState { time, opts: opts.clone(), rpc_id, + on_success, })); let dispatcher = state_context.into_dispatcher(); @@ -98,6 +106,7 @@ impl P2pConnectionOutgoingState { time, opts: opts.clone(), rpc_id, + on_success: None, })); let dispatcher = state_context.into_dispatcher(); @@ -131,11 +140,18 @@ impl P2pConnectionOutgoingState { .outgoing_peer_connection_mut(&peer_id) .ok_or("Missing connection state for: `P2pConnectionOutgoingAction::OfferSdpCreatePending`")?; - if let Self::Init { opts, rpc_id, .. } = state { + if let Self::Init { + opts, + rpc_id, + on_success, + .. + } = state + { *state = Self::OfferSdpCreatePending { time, opts: opts.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; } else { bug_condition!("Invalid state for `P2pConnectionOutgoingAction::OfferSdpCreatePending`: {:?}", state); @@ -157,12 +173,19 @@ impl P2pConnectionOutgoingState { .outgoing_peer_connection_mut(&peer_id) .ok_or("Missing peer connection for `P2pConnectionOutgoingAction::OfferSdpCreateSuccess`")?; - if let Self::OfferSdpCreatePending { opts, rpc_id, .. } = state { + if let Self::OfferSdpCreatePending { + opts, + rpc_id, + on_success, + .. + } = state + { *state = Self::OfferSdpCreateSuccess { time, opts: opts.clone(), sdp: sdp.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; } else { bug_condition!("Invalid state for `P2pConnectionOutgoingAction::OfferSdpCreateSuccess`: {:?}", state); @@ -187,7 +210,13 @@ impl P2pConnectionOutgoingState { .outgoing_peer_connection_mut(&peer_id) .ok_or("Invalid state for `P2pConnectionOutgoingAction::OfferReady`")?; - let Self::OfferSdpCreateSuccess { opts, rpc_id, .. } = state else { + let Self::OfferSdpCreateSuccess { + opts, + rpc_id, + on_success, + .. + } = state + else { bug_condition!( "Invalid state for `P2pConnectionOutgoingAction::OfferReady`: {:?}", state @@ -200,6 +229,7 @@ impl P2pConnectionOutgoingState { opts: opts.clone(), offer: offer.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; let dispatcher = state_context.into_dispatcher(); @@ -207,12 +237,19 @@ impl P2pConnectionOutgoingState { if let Some(relay_peer_id) = opts.webrtc_p2p_relay_peer_id() { dispatcher.push(P2pChannelsSignalingDiscoveryAction::DiscoveredAccept { peer_id: relay_peer_id, - offer: offer.clone(), + offer, }); } else { + let signaling_method = match opts { + P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => signaling, + #[allow(unreachable_patterns)] + _ => return Ok(()), + }; + dispatcher.push(P2pConnectionOutgoingEffectfulAction::OfferSend { peer_id, - offer: offer.clone(), + offer, + signaling_method, }); } Ok(()) @@ -225,6 +262,7 @@ impl P2pConnectionOutgoingState { opts, offer, rpc_id, + on_success, .. } = state { @@ -233,6 +271,7 @@ impl P2pConnectionOutgoingState { opts: opts.clone(), offer: offer.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; } else { bug_condition!( @@ -254,6 +293,7 @@ impl P2pConnectionOutgoingState { opts, offer, rpc_id, + on_success, .. } = state { @@ -262,6 +302,7 @@ impl P2pConnectionOutgoingState { opts: opts.clone(), offer: offer.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; } else { bug_condition!( @@ -299,6 +340,7 @@ impl P2pConnectionOutgoingState { opts, offer, rpc_id, + on_success, .. } = state { @@ -308,6 +350,7 @@ impl P2pConnectionOutgoingState { offer: offer.clone(), answer: answer.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; } else { bug_condition!( @@ -315,8 +358,9 @@ impl P2pConnectionOutgoingState { state ); } - state_context - .into_dispatcher() + + let dispatcher = state_context.into_dispatcher(); + dispatcher .push(P2pConnectionOutgoingEffectfulAction::AnswerSet { peer_id, answer }); Ok(()) } @@ -326,13 +370,19 @@ impl P2pConnectionOutgoingState { .ok_or_else(|| format!("Invalid state: {:?}", action))?; let (auth, other_pub_key) = match state { - Self::Init { opts, rpc_id, .. } => { + Self::Init { + opts, + rpc_id, + on_success, + .. + } => { *state = Self::FinalizePending { time, opts: opts.clone(), offer: None, answer: None, rpc_id: rpc_id.take(), + on_success: on_success.take(), }; return Ok(()); } @@ -341,6 +391,7 @@ impl P2pConnectionOutgoingState { offer, answer, rpc_id, + on_success, .. } => { let auth = offer.conn_auth(answer); @@ -352,6 +403,7 @@ impl P2pConnectionOutgoingState { offer: Some(offer.clone()), answer: Some(answer.clone()), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; (auth, other_pub_key) @@ -362,7 +414,8 @@ impl P2pConnectionOutgoingState { } }; - state_context.into_dispatcher().push( + let dispatcher = state_context.into_dispatcher(); + dispatcher.push( P2pConnectionOutgoingEffectfulAction::ConnectionAuthorizationEncryptAndSend { peer_id, other_pub_key, @@ -394,6 +447,7 @@ impl P2pConnectionOutgoingState { offer, answer, rpc_id, + on_success, .. } = state { @@ -411,6 +465,7 @@ impl P2pConnectionOutgoingState { offer: offer.clone(), answer: answer.clone(), rpc_id: rpc_id.take(), + on_success: on_success.take(), }; values } else { @@ -481,6 +536,8 @@ impl P2pConnectionOutgoingState { dispatcher.push_callback(callback.clone(), (rpc_id, error)); } } + dispatcher.push(P2pDisconnectionAction::FailedCleanup { peer_id }); + Ok(()) } P2pConnectionOutgoingAction::Success { peer_id } => { @@ -488,40 +545,49 @@ impl P2pConnectionOutgoingState { .outgoing_peer_connection_mut(&peer_id) .ok_or_else(|| format!("Invalid state: {:?}", action))?; - if let Self::FinalizeSuccess { + let Self::FinalizeSuccess { offer, answer, rpc_id, + on_success, .. } = state - { - *state = Self::Success { - time, - offer: offer.clone(), - answer: answer.clone(), - rpc_id: rpc_id.take(), - }; - } else { + else { bug_condition!( "Invalid state for `P2pConnectionOutgoingAction::Success`: {:?}", state ); return Ok(()); - } + }; + + let callback = on_success.take(); + + *state = Self::Success { + time, + offer: offer.clone(), + answer: answer.clone(), + rpc_id: rpc_id.take(), + }; let (dispatcher, state) = state_context.into_dispatcher_and_state(); let p2p_state: &P2pState = state.substate()?; + dispatcher.push(P2pPeerAction::Ready { peer_id, incoming: false, }); - if let Some(rpc_id) = p2p_state.peer_connection_rpc_id(&peer_id) { + let rpc_id = p2p_state.peer_connection_rpc_id(&peer_id); + if let Some(rpc_id) = rpc_id { if let Some(callback) = &p2p_state.callbacks.on_p2p_connection_outgoing_success { dispatcher.push_callback(callback.clone(), rpc_id); } } + + if let Some(callback) = callback { + dispatcher.push_callback(callback, (peer_id, rpc_id)); + } Ok(()) } } diff --git a/p2p/src/connection/outgoing/p2p_connection_outgoing_state.rs b/p2p/src/connection/outgoing/p2p_connection_outgoing_state.rs index 07fdfc7948..385ba82dff 100644 --- a/p2p/src/connection/outgoing/p2p_connection_outgoing_state.rs +++ b/p2p/src/connection/outgoing/p2p_connection_outgoing_state.rs @@ -1,9 +1,9 @@ -use redux::Timestamp; +use redux::{Callback, Timestamp}; use serde::{Deserialize, Serialize}; use openmina_core::requests::RpcId; -use crate::{connection::RejectionReason, webrtc, P2pTimeouts}; +use crate::{connection::RejectionReason, webrtc, P2pTimeouts, PeerId}; use super::P2pConnectionOutgoingInitOpts; @@ -13,35 +13,41 @@ pub enum P2pConnectionOutgoingState { time: Timestamp, opts: P2pConnectionOutgoingInitOpts, rpc_id: Option, + on_success: Option)>>, }, OfferSdpCreatePending { time: Timestamp, opts: P2pConnectionOutgoingInitOpts, rpc_id: Option, + on_success: Option)>>, }, OfferSdpCreateSuccess { time: Timestamp, opts: P2pConnectionOutgoingInitOpts, sdp: String, rpc_id: Option, + on_success: Option)>>, }, OfferReady { time: Timestamp, opts: P2pConnectionOutgoingInitOpts, offer: Box, rpc_id: Option, + on_success: Option)>>, }, OfferSendSuccess { time: Timestamp, opts: P2pConnectionOutgoingInitOpts, offer: Box, rpc_id: Option, + on_success: Option)>>, }, AnswerRecvPending { time: Timestamp, opts: P2pConnectionOutgoingInitOpts, offer: Box, rpc_id: Option, + on_success: Option)>>, }, AnswerRecvSuccess { time: Timestamp, @@ -49,6 +55,7 @@ pub enum P2pConnectionOutgoingState { offer: Box, answer: Box, rpc_id: Option, + on_success: Option)>>, }, FinalizePending { time: Timestamp, @@ -56,6 +63,7 @@ pub enum P2pConnectionOutgoingState { offer: Option>, answer: Option>, rpc_id: Option, + on_success: Option)>>, }, FinalizeSuccess { time: Timestamp, @@ -63,6 +71,7 @@ pub enum P2pConnectionOutgoingState { offer: Option>, answer: Option>, rpc_id: Option, + on_success: Option)>>, }, Error { time: Timestamp, diff --git a/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_actions.rs b/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_actions.rs index ff46ee349d..3049d9c0b3 100644 --- a/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_actions.rs +++ b/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_actions.rs @@ -6,7 +6,7 @@ use openmina_core::requests::RpcId; use crate::{ connection::{outgoing::P2pConnectionOutgoingInitOpts, P2pConnectionEffectfulAction}, identity::PublicKey, - webrtc::{self, ConnectionAuth}, + webrtc::{self, ConnectionAuth, SignalingMethod}, P2pState, PeerId, }; @@ -15,7 +15,9 @@ use crate::{ pub enum P2pConnectionOutgoingEffectfulAction { /// Initialize connection to a random peer. #[action_event(level = trace)] - RandomInit, + RandomInit { + peers: Vec, + }, /// Initialize connection to a new peer. #[action_event(level = info)] Init { @@ -25,6 +27,7 @@ pub enum P2pConnectionOutgoingEffectfulAction { OfferSend { peer_id: PeerId, offer: Box, + signaling_method: SignalingMethod, }, AnswerSet { peer_id: PeerId, diff --git a/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_effects.rs b/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_effects.rs index 435ae8408d..8fd75164fc 100644 --- a/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_effects.rs +++ b/p2p/src/connection/outgoing_effectful/p2p_connection_outgoing_effectful_effects.rs @@ -3,13 +3,10 @@ use redux::ActionMeta; use crate::{ connection::{ - outgoing::{ - P2pConnectionOutgoingAction, P2pConnectionOutgoingError, P2pConnectionOutgoingInitOpts, - P2pConnectionOutgoingState, - }, - P2pConnectionService, P2pConnectionState, + outgoing::{P2pConnectionOutgoingAction, P2pConnectionOutgoingError}, + P2pConnectionService, }, - webrtc, P2pPeerStatus, + webrtc, }; use super::P2pConnectionOutgoingEffectfulAction; @@ -21,8 +18,7 @@ impl P2pConnectionOutgoingEffectfulAction { Store::Service: P2pConnectionService, { match self { - P2pConnectionOutgoingEffectfulAction::RandomInit => { - let peers = store.state().disconnected_peers().collect::>(); + P2pConnectionOutgoingEffectfulAction::RandomInit { peers } => { let picked_peer = store.service().random_pick(&peers); if let Some(picked_peer) = picked_peer { store.dispatch(P2pConnectionOutgoingAction::Reconnect { @@ -38,28 +34,19 @@ impl P2pConnectionOutgoingEffectfulAction { store.service().outgoing_init(opts); store.dispatch(P2pConnectionOutgoingAction::OfferSdpCreatePending { peer_id }); } - P2pConnectionOutgoingEffectfulAction::OfferSend { peer_id, offer } => { - let (state, service) = store.state_and_service(); - let Some(peer) = state.peers.get(&peer_id) else { - return; - }; - let P2pPeerStatus::Connecting(P2pConnectionState::Outgoing( - P2pConnectionOutgoingState::OfferReady { opts, .. }, - )) = &peer.status - else { - return; - }; - let signaling_method = match opts { - P2pConnectionOutgoingInitOpts::WebRTC { signaling, .. } => signaling, - #[allow(unreachable_patterns)] - _ => return, - }; + P2pConnectionOutgoingEffectfulAction::OfferSend { + peer_id, + offer, + signaling_method, + } => { match signaling_method { - webrtc::SignalingMethod::Http(_) | webrtc::SignalingMethod::Https(_) => { + webrtc::SignalingMethod::Http(_) + | webrtc::SignalingMethod::Https(_) + | webrtc::SignalingMethod::HttpsProxy(_, _) => { let Some(url) = signaling_method.http_url() else { return; }; - service.http_signaling_request(url, *offer); + store.service().http_signaling_request(url, *offer); } webrtc::SignalingMethod::P2p { .. } => { bug_condition!("`P2pConnectionOutgoingEffectfulAction::OfferSend` shouldn't be called for `webrtc::SignalingMethod::P2p`"); diff --git a/p2p/src/connection/p2p_connection_state.rs b/p2p/src/connection/p2p_connection_state.rs index 6c956ec073..30046f7ccb 100644 --- a/p2p/src/connection/p2p_connection_state.rs +++ b/p2p/src/connection/p2p_connection_state.rs @@ -20,6 +20,7 @@ impl P2pConnectionState { time: Timestamp::ZERO, opts: opts.clone(), rpc_id: None, + on_success: None, }) } diff --git a/p2p/src/disconnection/p2p_disconnection_actions.rs b/p2p/src/disconnection/p2p_disconnection_actions.rs index aec90cef41..86946f62b2 100644 --- a/p2p/src/disconnection/p2p_disconnection_actions.rs +++ b/p2p/src/disconnection/p2p_disconnection_actions.rs @@ -7,7 +7,7 @@ use crate::{P2pPeerStatus, P2pState, PeerId}; pub type P2pDisconnectionActionWithMetaRef<'a> = redux::ActionWithMeta<&'a P2pDisconnectionAction>; #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id), display(reason)), level = info)] +#[action_event(level = debug)] pub enum P2pDisconnectionAction { /// Initialize disconnection. #[action_event(fields(display(peer_id), display(reason)), level = info)] @@ -15,8 +15,13 @@ pub enum P2pDisconnectionAction { peer_id: PeerId, reason: P2pDisconnectionReason, }, + /// Peer disconnection. + #[action_event(fields(display(peer_id)), level = info)] + PeerClosed { peer_id: PeerId }, + #[action_event(fields(display(peer_id)), level = info)] + FailedCleanup { peer_id: PeerId }, /// Finish disconnecting from a peer. - #[action_event(level = debug)] + #[action_event(fields(display(peer_id)), level = debug)] Finish { peer_id: PeerId }, } @@ -24,11 +29,17 @@ impl redux::EnablingCondition for P2pDisconnectionAction { fn is_enabled(&self, state: &P2pState, _time: redux::Timestamp) -> bool { match self { P2pDisconnectionAction::Init { peer_id, .. } + | P2pDisconnectionAction::PeerClosed { peer_id, .. } | P2pDisconnectionAction::Finish { peer_id } => { state.peers.get(peer_id).map_or(false, |peer| { !matches!(peer.status, P2pPeerStatus::Disconnected { .. }) + && !peer.status.is_error() }) } + P2pDisconnectionAction::FailedCleanup { peer_id } => state + .peers + .get(peer_id) + .map_or(false, |peer| !peer.is_libp2p() && peer.status.is_error()), } } } diff --git a/p2p/src/disconnection/p2p_disconnection_reducer.rs b/p2p/src/disconnection/p2p_disconnection_reducer.rs index 9b4e55860f..517aa5d9ff 100644 --- a/p2p/src/disconnection/p2p_disconnection_reducer.rs +++ b/p2p/src/disconnection/p2p_disconnection_reducer.rs @@ -24,23 +24,30 @@ impl P2pDisconnectedState { P2pDisconnectionAction::Init { peer_id, reason } => { #[cfg(feature = "p2p-libp2p")] if p2p_state.is_libp2p_peer(&peer_id) { - if let Some((&addr, _)) = p2p_state + let connections = p2p_state .network .scheduler .connections .iter() - .find(|(_, conn_state)| conn_state.peer_id() == Some(&peer_id)) - { - let Some(peer) = p2p_state.peers.get_mut(&peer_id) else { - bug_condition!("Invalid state for: `P2pDisconnectionAction::Finish`"); - return Ok(()); - }; - peer.status = P2pPeerStatus::Disconnecting { time: meta.time() }; + .filter(|(_, conn_state)| conn_state.peer_id() == Some(&peer_id)) + .map(|(addr, _)| *addr) + .collect::>(); - let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pNetworkSchedulerAction::Disconnect { addr, reason }); - dispatcher.push(P2pDisconnectionAction::Finish { peer_id }); + let Some(peer) = p2p_state.peers.get_mut(&peer_id) else { + bug_condition!("Invalid state for: `P2pDisconnectionAction::Finish`"); + return Ok(()); + }; + peer.status = P2pPeerStatus::Disconnecting { time: meta.time() }; + + let dispatcher = state_context.into_dispatcher(); + for addr in connections { + dispatcher.push(P2pNetworkSchedulerAction::Disconnect { + addr, + reason: reason.clone(), + }); } + + dispatcher.push(P2pDisconnectionAction::Finish { peer_id }); return Ok(()); } @@ -48,41 +55,34 @@ impl P2pDisconnectedState { dispatcher.push(P2pDisconnectionEffectfulAction::Init { peer_id }); Ok(()) } - #[cfg(not(feature = "p2p-libp2p"))] + P2pDisconnectionAction::PeerClosed { peer_id } => { + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pDisconnectionEffectfulAction::Init { peer_id }); + Ok(()) + } + P2pDisconnectionAction::FailedCleanup { peer_id } => { + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pDisconnectionEffectfulAction::Init { peer_id }); + Ok(()) + } P2pDisconnectionAction::Finish { peer_id } => { let Some(peer) = p2p_state.peers.get_mut(&peer_id) else { bug_condition!("Invalid state for: `P2pDisconnectionAction::Finish`"); return Ok(()); }; - peer.status = P2pPeerStatus::Disconnected { time: meta.time() }; - - let (dispatcher, state) = state_context.into_dispatcher_and_state(); - let p2p_state: &P2pState = state.substate()?; - dispatcher.push(P2pPeerAction::Remove { peer_id }); - - if let Some(callback) = &p2p_state.callbacks.on_p2p_disconnection_finish { - dispatcher.push_callback(callback.clone(), peer_id); - } - Ok(()) - } - #[cfg(feature = "p2p-libp2p")] - P2pDisconnectionAction::Finish { peer_id } => { - if p2p_state - .network - .scheduler - .connections - .iter() - .any(|(_addr, conn_state)| { - conn_state.peer_id() == Some(&peer_id) && conn_state.closed.is_none() - }) + if peer.is_libp2p() + && p2p_state + .network + .scheduler + .connections + .iter() + .any(|(_addr, conn_state)| { + conn_state.peer_id() == Some(&peer_id) && conn_state.closed.is_none() + }) { return Ok(()); } - let Some(peer) = p2p_state.peers.get_mut(&peer_id) else { - bug_condition!("Invalid state for: `P2pDisconnectionAction::Finish`"); - return Ok(()); - }; peer.status = P2pPeerStatus::Disconnected { time: meta.time() }; let (dispatcher, state) = state_context.into_dispatcher_and_state(); @@ -92,7 +92,6 @@ impl P2pDisconnectedState { if let Some(callback) = &p2p_state.callbacks.on_p2p_disconnection_finish { dispatcher.push_callback(callback.clone(), peer_id); } - Ok(()) } } diff --git a/p2p/src/disconnection_effectful/p2p_disconnection_effectful_actions.rs b/p2p/src/disconnection_effectful/p2p_disconnection_effectful_actions.rs index 205aca2bea..5bd7a1e550 100644 --- a/p2p/src/disconnection_effectful/p2p_disconnection_effectful_actions.rs +++ b/p2p/src/disconnection_effectful/p2p_disconnection_effectful_actions.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{P2pPeerStatus, P2pState, PeerId}; #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id), display(reason)), level = debug)] +#[action_event(fields(display(peer_id)), level = debug)] pub enum P2pDisconnectionEffectfulAction { /// Initialize disconnection. Init { peer_id: PeerId }, diff --git a/p2p/src/identify/p2p_identify_reducer.rs b/p2p/src/identify/p2p_identify_reducer.rs index d0a5c1f0d4..0b887f2268 100644 --- a/p2p/src/identify/p2p_identify_reducer.rs +++ b/p2p/src/identify/p2p_identify_reducer.rs @@ -2,6 +2,7 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ + connection::outgoing::P2pConnectionOutgoingInitOpts, disconnection::{P2pDisconnectionAction, P2pDisconnectionReason}, token::{BroadcastAlgorithm, DiscoveryAlgorithm, IdentifyAlgorithm, RpcAlgorithm, StreamKind}, P2pNetworkConnectionMuxState, P2pNetworkKadRequestAction, P2pNetworkKadState, @@ -60,6 +61,9 @@ impl P2pState { let info = *info; if let Some(peer) = p2p_state.peers.get_mut(&peer_id) { peer.identify = Some(info.clone()); + if let Some(P2pConnectionOutgoingInitOpts::LibP2P(opts)) = &mut peer.dial_opts { + opts.update_host_if_needed(info.listen_addrs.iter()); + } } else { bug_condition!( "Peer state not found for `P2pIdentifyAction::UpdatePeerInformation`" diff --git a/p2p/src/identity/secret_key.rs b/p2p/src/identity/secret_key.rs index 5237391cc6..9b4b272f4f 100644 --- a/p2p/src/identity/secret_key.rs +++ b/p2p/src/identity/secret_key.rs @@ -1,6 +1,8 @@ -use std::{fmt, str::FromStr}; +use std::{fmt, path::Path, str::FromStr}; +use base64::Engine; use ed25519_dalek::SigningKey as Ed25519SecretKey; +use openmina_core::{EncryptedSecretKey, EncryptedSecretKeyFile, EncryptionError}; use rand::{CryptoRng, Rng}; use serde::{Deserialize, Serialize}; @@ -50,17 +52,70 @@ impl SecretKey { pub fn to_x25519(&self) -> x25519_dalek::StaticSecret { self.0.to_scalar_bytes().into() } + + pub fn from_encrypted_file( + path: impl AsRef, + password: &str, + ) -> Result { + let encrypted = EncryptedSecretKeyFile::new(path)?; + let decrypted = Self::try_decrypt(&encrypted, password)?; + + let keypair_string = String::from_utf8(decrypted.to_vec()) + .map_err(|e| EncryptionError::Other(e.to_string()))?; + + let parts: Vec<&str> = keypair_string.split(',').collect(); + + if parts.len() != 3 { + return Err(EncryptionError::Other( + "libp2p keypair string must have 3 parts".to_string(), + )); + } + + let (secret_key_base64, _public_key_base64, _peer_id) = (parts[0], parts[1], parts[2]); + + let key_bytes = base64::engine::general_purpose::STANDARD + .decode(secret_key_base64.as_bytes()) + .map_err(|e| EncryptionError::Other(e.to_string()))?; + + let key_bytes = key_bytes[4..36] + .try_into() + .map_err(|_| EncryptionError::Other("Invalid secret key length".to_string()))?; + Ok(Self::from_bytes(key_bytes)) + } + + pub fn to_encrypted_file( + &self, + _password: &str, + _path: impl AsRef, + ) -> Result<(), EncryptionError> { + todo!() + } } +impl EncryptedSecretKey for SecretKey {} + use aes_gcm::{ aead::{Aead, AeadCore}, Aes256Gcm, KeyInit, }; + +// TODO: provide more detailed errors +#[derive(Debug, Clone)] +pub struct EncryptError(); + +impl std::fmt::Display for EncryptError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Encryption error occurred") + } +} + +impl std::error::Error for EncryptError {} + impl SecretKey { - fn shared_key(&self, other_pk: &PublicKey) -> Result { + fn shared_key(&self, other_pk: &PublicKey) -> Result { let key = self.to_x25519().diffie_hellman(&other_pk.to_x25519()); if !key.was_contributory() { - return Err(()); + return Err(EncryptError()); } let key = key.to_bytes(); // eprintln!("[shared_key] {} & {} = {}", self.public_key(), other_pk, hex::encode(&key)); @@ -73,11 +128,15 @@ impl SecretKey { other_pk: &PublicKey, rng: impl Rng + CryptoRng, data: &[u8], - ) -> Result, ()> { + ) -> Result, Box> { let shared_key = self.shared_key(other_pk)?; let nonce = Aes256Gcm::generate_nonce(rng); let mut buffer = Vec::from(AsRef::<[u8]>::as_ref(&nonce)); - buffer.extend(shared_key.encrypt(&nonce, data).or(Err(()))?); + buffer.extend( + shared_key + .encrypt(&nonce, data) + .or(Err(Box::new(EncryptError())))?, + ); Ok(buffer) } @@ -86,24 +145,30 @@ impl SecretKey { other_pk: &PublicKey, rng: impl Rng + CryptoRng, data: &M, - ) -> Result { - let data = serde_json::to_vec(data).or(Err(()))?; + ) -> Result> { + let data = serde_json::to_vec(data).map_err(|_| EncryptError())?; self.encrypt_raw(other_pk, rng, &data).map(Into::into) } - pub fn decrypt_raw(&self, other_pk: &PublicKey, ciphertext: &[u8]) -> Result, ()> { + pub fn decrypt_raw( + &self, + other_pk: &PublicKey, + ciphertext: &[u8], + ) -> Result, EncryptError> { let shared_key = self.shared_key(other_pk)?; - let (nonce, ciphertext) = ciphertext.split_at_checked(12).ok_or(())?; - shared_key.decrypt(nonce.into(), ciphertext).or(Err(())) + let (nonce, ciphertext) = ciphertext.split_at_checked(12).ok_or(EncryptError())?; + shared_key + .decrypt(nonce.into(), ciphertext) + .or(Err(EncryptError())) } pub fn decrypt( &self, other_pk: &PublicKey, ciphertext: &M::Encrypted, - ) -> Result { - let data = self.decrypt_raw(other_pk, ciphertext.as_ref())?; - serde_json::from_slice(&data).or(Err(())) + ) -> Result> { + let data: Vec = self.decrypt_raw(other_pk, ciphertext.as_ref())?; + serde_json::from_slice(&data).map_err(Box::::from) } } @@ -195,4 +260,18 @@ mod tests { let unparsed = sk.to_string(); assert_eq!(s, &unparsed); } + + #[test] + fn test_libp2p_key_decrypt() { + let password = "total-secure-pass"; + let key_path = "../tests/files/accounts/libp2p-key"; + + let expected_peer_id = "12D3KooWDxyuJKSsVEwNR13UVwf4PEfs4yHkk3ecZipBPv3Y3Sac"; + + let decrypted = SecretKey::from_encrypted_file(key_path, password) + .expect("Failed to decrypt secret key file"); + + let peer_id = decrypted.public_key().peer_id().to_libp2p_string(); + assert_eq!(expected_peer_id, peer_id); + } } diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index 92000312c4..ed642d2939 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -7,23 +7,16 @@ pub mod identity; use bootstrap::P2pNetworkKadBootstrapState; use channels::{ best_tip::P2pChannelsBestTipAction, - best_tip_effectful::P2pChannelsBestTipEffectfulAction, rpc::P2pChannelsRpcAction, - rpc_effectful::P2pChannelsRpcEffectfulAction, signaling::{ discovery::P2pChannelsSignalingDiscoveryAction, - discovery_effectful::P2pChannelsSignalingDiscoveryEffectfulAction, exchange::P2pChannelsSignalingExchangeAction, - exchange_effectful::P2pChannelsSignalingExchangeEffectfulAction, }, snark::P2pChannelsSnarkAction, - snark_effectful::P2pChannelsSnarkEffectfulAction, snark_job_commitment::P2pChannelsSnarkJobCommitmentAction, - snark_job_commitment_effectful::P2pChannelsSnarkJobCommitmentEffectfulAction, streaming_rpc::P2pChannelsStreamingRpcAction, - streaming_rpc_effectful::P2pChannelsStreamingRpcEffectfulAction, transaction::P2pChannelsTransactionAction, - transaction_effectful::P2pChannelsTransactionEffectfulAction, + P2pChannelsEffectfulAction, }; use connection::{ incoming::P2pConnectionIncomingAction, @@ -136,9 +129,7 @@ pub trait P2pActionTrait: + From + From + From - + From + From - + From + From + From + From @@ -151,12 +142,9 @@ pub trait P2pActionTrait: + From + From + From - + From - + From - + From - + From - + From - + From + From + + From { } + +pub trait Action: From {} diff --git a/p2p/src/network/identify/p2p_network_identify_reducer.rs b/p2p/src/network/identify/p2p_network_identify_reducer.rs index 76fc2d9157..b7ba6684d2 100644 --- a/p2p/src/network/identify/p2p_network_identify_reducer.rs +++ b/p2p/src/network/identify/p2p_network_identify_reducer.rs @@ -1,6 +1,6 @@ use super::{stream::P2pNetworkIdentifyStreamState, P2pNetworkIdentifyAction}; use crate::P2pLimits; -use openmina_core::{Substate, SubstateAccess}; +use openmina_core::Substate; use redux::ActionWithMeta; impl super::P2pNetworkIdentifyState { @@ -10,7 +10,7 @@ impl super::P2pNetworkIdentifyState { limits: &P2pLimits, ) -> Result<(), String> where - State: SubstateAccess, + State: crate::P2pStateTrait, Action: crate::P2pActionTrait, { let (action, meta) = action.split(); diff --git a/p2p/src/network/identify/stream/p2p_network_identify_stream_actions.rs b/p2p/src/network/identify/stream/p2p_network_identify_stream_actions.rs index bd0e15705a..2d22901b9a 100644 --- a/p2p/src/network/identify/stream/p2p_network_identify_stream_actions.rs +++ b/p2p/src/network/identify/stream/p2p_network_identify_stream_actions.rs @@ -1,4 +1,5 @@ use crate::{ConnectionAddr, Data, P2pAction, P2pState, PeerId, StreamId}; +use multiaddr::Multiaddr; use openmina_core::ActionEvent; use redux::EnablingCondition; use serde::{Deserialize, Serialize}; @@ -38,6 +39,12 @@ pub enum P2pNetworkIdentifyStreamAction { peer_id: PeerId, stream_id: StreamId, }, + SendIdentify { + addr: ConnectionAddr, + peer_id: PeerId, + stream_id: StreamId, + addresses: Vec, + }, } macro_rules! enum_field { @@ -48,6 +55,7 @@ macro_rules! enum_field { | P2pNetworkIdentifyStreamAction::IncomingData { $field, .. } | P2pNetworkIdentifyStreamAction::Close { $field, .. } | P2pNetworkIdentifyStreamAction::RemoteClose { $field, .. } + | P2pNetworkIdentifyStreamAction::SendIdentify { $field, .. } | P2pNetworkIdentifyStreamAction::Prune { $field, .. } => $field, } } diff --git a/p2p/src/network/identify/stream/p2p_network_identify_stream_reducer.rs b/p2p/src/network/identify/stream/p2p_network_identify_stream_reducer.rs index dd6c04dc1a..6206a6e553 100644 --- a/p2p/src/network/identify/stream/p2p_network_identify_stream_reducer.rs +++ b/p2p/src/network/identify/stream/p2p_network_identify_stream_reducer.rs @@ -4,14 +4,16 @@ use super::{ use crate::{ identify::P2pIdentifyAction, network::identify::{ - pb::Identify, stream::P2pNetworkIdentifyStreamError, - stream_effectful::P2pNetworkIdentifyStreamEffectfulAction, P2pNetworkIdentify, - P2pNetworkIdentifyState, + pb::{self, Identify}, + stream::P2pNetworkIdentifyStreamError, + stream_effectful::P2pNetworkIdentifyStreamEffectfulAction, + P2pNetworkIdentify, P2pNetworkIdentifyState, }, - ConnectionAddr, Data, P2pLimits, P2pNetworkConnectionError, P2pNetworkSchedulerAction, - P2pNetworkStreamProtobufError, P2pNetworkYamuxAction, PeerId, YamuxFlags, + token, ConnectionAddr, Data, P2pLimits, P2pNetworkConnectionError, P2pNetworkSchedulerAction, + P2pNetworkStreamProtobufError, P2pNetworkYamuxAction, P2pState, PeerId, YamuxFlags, }; -use openmina_core::{bug_condition, warn, Substate, SubstateAccess}; +use multiaddr::Multiaddr; +use openmina_core::{bug_condition, fuzzed_maybe, warn, Substate, SubstateAccess}; use prost::Message; use quick_protobuf::BytesReader; use redux::{ActionWithMeta, Dispatcher}; @@ -23,7 +25,7 @@ impl P2pNetworkIdentifyStreamState { limits: &P2pLimits, ) -> Result<(), String> where - State: SubstateAccess, + State: crate::P2pStateTrait, Action: crate::P2pActionTrait, { let (action, meta) = action.split(); @@ -77,13 +79,25 @@ impl P2pNetworkIdentifyStreamState { }; if matches!(stream_state, P2pNetworkIdentifyStreamState::SendIdentify) { - let dispatcher = state_context.into_dispatcher(); + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let p2p_state: &P2pState = state.substate()?; + + let addresses = p2p_state + .network + .scheduler + .listeners + .iter() + .cloned() + .collect::>(); - dispatcher.push(P2pNetworkIdentifyStreamEffectfulAction::SendIdentify { - addr, - peer_id, - stream_id, - }); + dispatcher.push( + P2pNetworkIdentifyStreamEffectfulAction::GetListenAddresses { + addr, + peer_id, + stream_id, + addresses, + }, + ); } Ok(()) @@ -260,6 +274,17 @@ impl P2pNetworkIdentifyStreamState { let dispatcher = state_context.into_dispatcher(); Self::disconnect(dispatcher, addr, peer_id, stream_id) } + P2pNetworkIdentifyStreamAction::SendIdentify { + addr, + peer_id, + stream_id, + addresses, + } => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let state = state.substate()?; + Self::send_identify(dispatcher, state, addr, peer_id, stream_id, addresses); + Ok(()) + } action => { // State and connection cleanup should be handled by timeout bug_condition!("Received action {:?} in SendIdentify state", action); @@ -337,4 +362,82 @@ impl P2pNetworkIdentifyStreamState { }); Ok(()) } + + fn send_identify( + dispatcher: &mut Dispatcher, + state: &P2pState, + addr: ConnectionAddr, + peer_id: PeerId, + stream_id: u32, + mut listen_addrs: Vec, + ) where + Action: crate::P2pActionTrait, + State: crate::P2pStateTrait, + { + let config = &state.config; + let ips = &config.external_addrs; + let port = config.libp2p_port.unwrap_or(8302); + + listen_addrs.extend( + ips.iter() + .map(|ip| Multiaddr::from(*ip).with(multiaddr::Protocol::Tcp(port))), + ); + + let public_key = Some(state.config.identity_pub_key.clone()); + + let mut protocols = vec![ + token::StreamKind::Identify(token::IdentifyAlgorithm::Identify1_0_0), + token::StreamKind::Broadcast(token::BroadcastAlgorithm::Meshsub1_1_0), + token::StreamKind::Rpc(token::RpcAlgorithm::Rpc0_0_1), + ]; + if state.network.scheduler.discovery_state.is_some() { + protocols.push(token::StreamKind::Discovery( + token::DiscoveryAlgorithm::Kademlia1_0_0, + )); + } + let identify_msg = P2pNetworkIdentify { + protocol_version: Some("ipfs/0.1.0".to_string()), + // TODO: include build info from GlobalConfig (?) + agent_version: Some("openmina".to_owned()), + public_key, + listen_addrs, + // TODO: other peers seem to report inaccurate information, should we implement this? + observed_addr: None, + protocols, + }; + + let mut out = Vec::new(); + let identify_msg_proto: pb::Identify = match (&identify_msg).try_into() { + Ok(identify_msg_proto) => identify_msg_proto, + Err(err) => { + bug_condition!("error encoding message {:?}", err); + return; + } + }; + + if let Err(err) = prost::Message::encode_length_delimited(&identify_msg_proto, &mut out) { + bug_condition!("error serializing message {:?}", err); + return; + } + + let data = fuzzed_maybe!( + Data(out.into_boxed_slice()), + crate::fuzzer::mutate_identify_msg + ); + + let flags = fuzzed_maybe!(Default::default(), crate::fuzzer::mutate_yamux_flags); + + dispatcher.push(P2pNetworkYamuxAction::OutgoingData { + addr, + stream_id, + data, + flags, + }); + + dispatcher.push(P2pNetworkIdentifyStreamAction::Close { + addr, + peer_id, + stream_id, + }); + } } diff --git a/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_actions.rs b/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_actions.rs index 6c3fa76df0..3e669c314c 100644 --- a/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_actions.rs +++ b/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_actions.rs @@ -1,38 +1,23 @@ +use std::net::SocketAddr; + use crate::{ConnectionAddr, P2pState, PeerId, StreamId}; use openmina_core::ActionEvent; use redux::EnablingCondition; use serde::{Deserialize, Serialize}; -/// Identify stream related actions. +/// Identify stream effectful actions. #[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] pub enum P2pNetworkIdentifyStreamEffectfulAction { - /// Creates a new stream state. - SendIdentify { + GetListenAddresses { addr: ConnectionAddr, peer_id: PeerId, stream_id: StreamId, + addresses: Vec, }, } -macro_rules! enum_field { - ($field:ident: $field_type:ty) => { - pub fn $field(&self) -> &$field_type { - match self { - P2pNetworkIdentifyStreamEffectfulAction::SendIdentify { $field, .. } => $field, - } - } - }; -} - -impl P2pNetworkIdentifyStreamEffectfulAction { - enum_field!(addr: ConnectionAddr); - enum_field!(peer_id: PeerId); - enum_field!(stream_id: StreamId); -} - impl EnablingCondition for P2pNetworkIdentifyStreamEffectfulAction { fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - // TODO true } } diff --git a/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_effects.rs b/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_effects.rs index 177046a065..25ce52c2ab 100644 --- a/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_effects.rs +++ b/p2p/src/network/identify/stream_effectful/p2p_network_identify_stream_effectful_effects.rs @@ -1,16 +1,10 @@ use multiaddr::Multiaddr; -use openmina_core::{bug_condition, error, fuzzed_maybe, log::system_time}; +use openmina_core::{error, log::system_time}; use redux::ActionMeta; use std::net::SocketAddr; -use super::{ - super::{pb, P2pNetworkIdentify}, - P2pNetworkIdentifyStreamEffectfulAction, -}; -use crate::{ - network::identify::P2pNetworkIdentifyStreamAction, token, Data, P2pNetworkService, - P2pNetworkYamuxAction, -}; +use super::P2pNetworkIdentifyStreamEffectfulAction; +use crate::{network::identify::P2pNetworkIdentifyStreamAction, P2pNetworkService}; fn get_addrs(addr: &SocketAddr, net_svc: &mut S) -> I where @@ -44,89 +38,22 @@ impl P2pNetworkIdentifyStreamEffectfulAction { Store: crate::P2pStore, { match self { - P2pNetworkIdentifyStreamEffectfulAction::SendIdentify { + P2pNetworkIdentifyStreamEffectfulAction::GetListenAddresses { addr, peer_id, stream_id, + addresses, } => { - let config = &store.state().config; - let ips = &config.external_addrs; - let port = config.libp2p_port.unwrap_or(8302); - - let mut listen_addrs = ips - .iter() - .map(|ip| Multiaddr::from(*ip).with(multiaddr::Protocol::Tcp(port))) - .collect::>(); - - for addr in store - .state() - .network - .scheduler - .listeners - .iter() - .cloned() - .collect::>() - { - listen_addrs.extend(get_addrs::, _>(&addr, store.service())) - } - - let public_key = Some(store.state().config.identity_pub_key.clone()); - - let mut protocols = vec![ - token::StreamKind::Identify(token::IdentifyAlgorithm::Identify1_0_0), - token::StreamKind::Broadcast(token::BroadcastAlgorithm::Meshsub1_1_0), - token::StreamKind::Rpc(token::RpcAlgorithm::Rpc0_0_1), - ]; - if store.state().network.scheduler.discovery_state.is_some() { - protocols.push(token::StreamKind::Discovery( - token::DiscoveryAlgorithm::Kademlia1_0_0, - )); - } - let identify_msg = P2pNetworkIdentify { - protocol_version: Some("ipfs/0.1.0".to_string()), - // TODO: include build info from GlobalConfig (?) - agent_version: Some("openmina".to_owned()), - public_key, - listen_addrs, - // TODO: other peers seem to report inaccurate information, should we implement this? - observed_addr: None, - protocols, - }; - - let mut out = Vec::new(); - let identify_msg_proto: pb::Identify = match (&identify_msg).try_into() { - Ok(identify_msg_proto) => identify_msg_proto, - Err(err) => { - bug_condition!("error encoding message {:?}", err); - return; - } - }; - - if let Err(err) = - prost::Message::encode_length_delimited(&identify_msg_proto, &mut out) - { - bug_condition!("error serializing message {:?}", err); - return; + let mut listen_addresses = Vec::new(); + for addr in addresses { + listen_addresses.extend(get_addrs::, _>(&addr, store.service())) } - let data = fuzzed_maybe!( - Data(out.into_boxed_slice()), - crate::fuzzer::mutate_identify_msg - ); - - let flags = fuzzed_maybe!(Default::default(), crate::fuzzer::mutate_yamux_flags); - - store.dispatch(P2pNetworkYamuxAction::OutgoingData { - addr, - stream_id, - data, - flags, - }); - - store.dispatch(P2pNetworkIdentifyStreamAction::Close { + store.dispatch(P2pNetworkIdentifyStreamAction::SendIdentify { addr, peer_id, stream_id, + addresses: listen_addresses, }); } } diff --git a/p2p/src/network/kad/p2p_network_kad_internals.rs b/p2p/src/network/kad/p2p_network_kad_internals.rs index 3246db4126..8ad5b92b62 100644 --- a/p2p/src/network/kad/p2p_network_kad_internals.rs +++ b/p2p/src/network/kad/p2p_network_kad_internals.rs @@ -66,6 +66,12 @@ mod u256_serde { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct P2pNetworkKadKey(#[serde(with = "u256_serde")] U256); +impl P2pNetworkKadKey { + pub fn distance(self, rhs: &Self) -> P2pNetworkKadDist { + P2pNetworkKadDist(self.0 ^ rhs.0) + } +} + #[derive(Clone, Debug, Serialize, PartialEq, Deserialize, thiserror::Error)] pub enum P2pNetworkKadKeyError { #[error("decoding error")] @@ -110,7 +116,7 @@ impl Debug for P2pNetworkKadKey { impl Add<&P2pNetworkKadDist> for &P2pNetworkKadKey { type Output = P2pNetworkKadKey; - #[allow(clippy::suspicious_arithmetic_impl)] + #[allow(clippy::suspicious_arithmetic_impl, clippy::arithmetic_side_effects)] fn add(self, rhs: &P2pNetworkKadDist) -> Self::Output { P2pNetworkKadKey(self.0 ^ rhs.0) } @@ -119,36 +125,36 @@ impl Add<&P2pNetworkKadDist> for &P2pNetworkKadKey { impl Sub for P2pNetworkKadKey { type Output = P2pNetworkKadDist; - #[allow(clippy::suspicious_arithmetic_impl)] + #[allow(clippy::suspicious_arithmetic_impl, clippy::arithmetic_side_effects)] fn sub(self, rhs: Self) -> Self::Output { - P2pNetworkKadDist(self.0 ^ rhs.0) + self.distance(&rhs) } } impl Sub<&P2pNetworkKadKey> for &P2pNetworkKadKey { type Output = P2pNetworkKadDist; - #[allow(clippy::suspicious_arithmetic_impl)] + #[allow(clippy::suspicious_arithmetic_impl, clippy::arithmetic_side_effects)] fn sub(self, rhs: &P2pNetworkKadKey) -> Self::Output { - P2pNetworkKadDist(self.0 ^ rhs.0) + self.distance(rhs) } } impl Sub for &P2pNetworkKadKey { type Output = P2pNetworkKadDist; - #[allow(clippy::suspicious_arithmetic_impl)] + #[allow(clippy::suspicious_arithmetic_impl, clippy::arithmetic_side_effects)] fn sub(self, rhs: P2pNetworkKadKey) -> Self::Output { - P2pNetworkKadDist(self.0 ^ rhs.0) + self.distance(&rhs) } } impl Sub<&P2pNetworkKadKey> for P2pNetworkKadKey { type Output = P2pNetworkKadDist; - #[allow(clippy::suspicious_arithmetic_impl)] + #[allow(clippy::suspicious_arithmetic_impl, clippy::arithmetic_side_effects)] fn sub(self, rhs: &P2pNetworkKadKey) -> Self::Output { - P2pNetworkKadDist(self.0 ^ rhs.0) + self.distance(rhs) } } @@ -560,11 +566,18 @@ impl P2pNetworkKadBucket { ); } - for addr in entry.addrs { + let addrs = entry + .addrs + .into_iter() + .filter(|addr| !e.addrs.contains(addr)) + .collect::>(); + + for addr in addrs { if e.addrs.len() >= P2pNetworkKadEntry::MAX_ADDRS { openmina_core::warn!( openmina_core::log::system_time(); kind = "P2pNetworkKadBucket insert", + peer_id = e.peer_id.to_string(), summary = format!("Skipping updates to Kad entry multiaddress list"), ); break; diff --git a/p2p/src/network/kad/p2p_network_kad_protocol.rs b/p2p/src/network/kad/p2p_network_kad_protocol.rs index c2b5a18f9e..2077f72cbd 100644 --- a/p2p/src/network/kad/p2p_network_kad_protocol.rs +++ b/p2p/src/network/kad/p2p_network_kad_protocol.rs @@ -119,7 +119,7 @@ pub(super) fn peer_id_try_from_bytes( Ok((libp2p_identity::PeerId::from_bytes(bytes.as_ref())?).try_into()?) } -impl<'a> TryFrom<&PeerId> for Cow<'a, [u8]> { +impl TryFrom<&PeerId> for Cow<'_, [u8]> { type Error = DecodingError; fn try_from(value: &PeerId) -> Result { diff --git a/p2p/src/network/kad/request/p2p_network_kad_request_actions.rs b/p2p/src/network/kad/request/p2p_network_kad_request_actions.rs index c9476213b2..020ad42977 100644 --- a/p2p/src/network/kad/request/p2p_network_kad_request_actions.rs +++ b/p2p/src/network/kad/request/p2p_network_kad_request_actions.rs @@ -1,10 +1,20 @@ use std::net::SocketAddr; use openmina_core::ActionEvent; -use redux::EnablingCondition; +use redux::{Callback, EnablingCondition}; use serde::{Deserialize, Serialize}; -use crate::{ConnectionAddr, P2pAction, P2pNetworkKadEntry, P2pState, PeerId, StreamId}; +use crate::{ + ConnectionAddr, P2pAction, P2pNetworkKadEntry, P2pNetworkKademliaRpcRequest, P2pState, PeerId, + StreamId, +}; + +type StreamReadyCallback = Callback<( + ConnectionAddr, + PeerId, + StreamId, + P2pNetworkKademliaRpcRequest, +)>; #[derive(Clone, Debug, Serialize, Deserialize, ActionEvent)] #[action_event(fields(display(peer_id), display(addr), display(key), stream_id, error))] @@ -29,6 +39,7 @@ pub enum P2pNetworkKadRequestAction { peer_id: PeerId, stream_id: StreamId, addr: ConnectionAddr, + callback: StreamReadyCallback, }, RequestSent { peer_id: PeerId, diff --git a/p2p/src/network/kad/request/p2p_network_kad_request_reducer.rs b/p2p/src/network/kad/request/p2p_network_kad_request_reducer.rs index 941350c9c2..8917120ede 100644 --- a/p2p/src/network/kad/request/p2p_network_kad_request_reducer.rs +++ b/p2p/src/network/kad/request/p2p_network_kad_request_reducer.rs @@ -1,11 +1,11 @@ -use openmina_core::{bug_condition, Substate, SubstateAccess}; +use openmina_core::{bug_condition, requests::RpcId, Substate, SubstateAccess}; use redux::{ActionWithMeta, Dispatcher}; use crate::{ connection::outgoing::P2pConnectionOutgoingAction, ConnectionAddr, P2pNetworkConnectionMuxState, P2pNetworkKadBootstrapAction, P2pNetworkKadEffectfulAction, P2pNetworkKadState, P2pNetworkKademliaRpcRequest, P2pNetworkKademliaStreamAction, - P2pNetworkYamuxAction, P2pPeerState, P2pState, + P2pNetworkYamuxAction, P2pPeerState, P2pState, PeerId, }; use super::{P2pNetworkKadRequestAction, P2pNetworkKadRequestState, P2pNetworkKadRequestStatus}; @@ -47,14 +47,19 @@ impl P2pNetworkKadRequestState { let peer_state = p2p_state.peers.get(&peer_id); let on_initialize_connection = |dispatcher: &mut Dispatcher| { - // initialize connection to the peer. - // when connection is establised and yamux layer is ready, we will continue with TODO - // TODO: add callbacks let opts = crate::connection::outgoing::P2pConnectionOutgoingInitOpts::LibP2P( (peer_id, addr).into(), ); - dispatcher.push(P2pConnectionOutgoingAction::Init { opts, rpc_id: None }); - dispatcher.push(P2pNetworkKadRequestAction::PeerIsConnecting { peer_id }); + let callback = redux::callback!( + on_p2p_connection_outgoing_kad_connection_success((peer_id: PeerId, _rpc_id: Option)) -> crate::P2pAction { + P2pNetworkKadRequestAction::PeerIsConnecting { peer_id } + } + ); + dispatcher.push(P2pConnectionOutgoingAction::Init { + opts, + rpc_id: None, + on_success: Some(callback), + }); Ok(()) }; @@ -172,6 +177,7 @@ impl P2pNetworkKadRequestState { peer_id, stream_id, addr, + callback, } => { let find_node = match P2pNetworkKademliaRpcRequest::find_node(request_state.key) { Ok(find_node) => find_node, @@ -193,20 +199,8 @@ impl P2pNetworkKadRequestState { super::P2pNetworkKadRequestStatus::Request, ); - let key = request_state.key; - let dispatcher = state_context.into_dispatcher(); - let data = - P2pNetworkKademliaRpcRequest::find_node(key).map_err(|e| e.to_string())?; - - // TODO: move action bellow to callback - dispatcher.push(P2pNetworkKademliaStreamAction::SendRequest { - addr, - peer_id, - stream_id, - data, - }); - dispatcher.push(P2pNetworkKadRequestAction::RequestSent { peer_id }); + dispatcher.push_callback(callback, (addr, peer_id, stream_id, find_node)); Ok(()) } P2pNetworkKadRequestAction::RequestSent { .. } => { diff --git a/p2p/src/network/kad/stream/mod.rs b/p2p/src/network/kad/stream/mod.rs index 3a3ed7e350..64ef134dda 100644 --- a/p2p/src/network/kad/stream/mod.rs +++ b/p2p/src/network/kad/stream/mod.rs @@ -1,8 +1,64 @@ mod p2p_network_kad_stream_state; +use redux::Callback; +use serde::{Deserialize, Serialize}; + +use crate::ConnectionAddr; +use crate::P2pNetworkKademliaAction; +use crate::PeerId; +use crate::StreamId; + pub use self::p2p_network_kad_stream_state::*; mod p2p_network_kad_stream_actions; pub use self::p2p_network_kad_stream_actions::*; +use super::P2pNetworkKadEntry; +use super::CID; + #[cfg(feature = "p2p-libp2p")] mod p2p_network_kad_stream_reducer; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub enum P2pNetworkKademliaStreamWaitOutgoingCallback { + AnswerFindNodeRequest { + callback: Callback<(ConnectionAddr, PeerId, StreamId, CID)>, + args: CID, + }, + UpdateFindNodeRequest { + callback: Callback<(ConnectionAddr, PeerId, StreamId, Vec)>, + args: Vec, + }, +} + +impl P2pNetworkKademliaStreamWaitOutgoingCallback { + pub fn answer_find_node_request(cid: CID) -> Self { + Self::AnswerFindNodeRequest { + callback: redux::callback!( + on_p2p_network_stream_wait_outgoing_answer_find_node_request(( + addr: ConnectionAddr, + peer_id: PeerId, + stream_id: StreamId, + cid: CID + )) -> crate::P2pAction{ + P2pNetworkKademliaAction::AnswerFindNodeRequest { addr, peer_id, stream_id, key: cid } + } + ), + args: cid, + } + } + pub fn update_find_node_request(peers: Vec) -> Self { + Self::UpdateFindNodeRequest { + callback: redux::callback!( + on_p2p_network_stream_wait_outgoing_answer_find_node_request(( + addr: ConnectionAddr, + peer_id: PeerId, + stream_id: StreamId, + closest_peers: Vec + )) -> crate::P2pAction{ + P2pNetworkKademliaAction::UpdateFindNodeRequest { addr, peer_id, stream_id, closest_peers } + } + ), + args: peers, + } + } +} diff --git a/p2p/src/network/kad/stream/p2p_network_kad_stream_actions.rs b/p2p/src/network/kad/stream/p2p_network_kad_stream_actions.rs index cd5cd3deea..28d838bea4 100644 --- a/p2p/src/network/kad/stream/p2p_network_kad_stream_actions.rs +++ b/p2p/src/network/kad/stream/p2p_network_kad_stream_actions.rs @@ -7,6 +7,8 @@ use crate::{ P2pState, PeerId, StreamId, }; +use super::P2pNetworkKademliaStreamWaitOutgoingCallback; + /// Kademlia stream related actions. #[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)] #[action_event(fields(display(addr), display(peer_id), stream_id, incoming, debug(data)))] @@ -44,6 +46,7 @@ pub enum P2pNetworkKademliaStreamAction { addr: ConnectionAddr, peer_id: PeerId, stream_id: StreamId, + callback: P2pNetworkKademliaStreamWaitOutgoingCallback, }, /// Sends request to the stream. diff --git a/p2p/src/network/kad/stream/p2p_network_kad_stream_reducer.rs b/p2p/src/network/kad/stream/p2p_network_kad_stream_reducer.rs index c69a779477..ed8f2d3bdb 100644 --- a/p2p/src/network/kad/stream/p2p_network_kad_stream_reducer.rs +++ b/p2p/src/network/kad/stream/p2p_network_kad_stream_reducer.rs @@ -4,7 +4,7 @@ use redux::ActionWithMeta; use crate::{ stream::P2pNetworkKadOutgoingStreamError, Data, P2pLimits, P2pNetworkConnectionError, - P2pNetworkKadState, P2pNetworkKademliaAction, P2pNetworkKademliaRpcReply, + P2pNetworkKadRequestAction, P2pNetworkKadState, P2pNetworkKademliaRpcReply, P2pNetworkKademliaRpcRequest, P2pNetworkSchedulerAction, P2pNetworkStreamProtobufError, P2pNetworkYamuxAction, P2pState, YamuxFlags, }; @@ -12,6 +12,7 @@ use crate::{ use super::{ super::Message, P2pNetworkKadIncomingStreamError, P2pNetworkKadIncomingStreamState, P2pNetworkKadOutgoingStreamState, P2pNetworkKadStreamState, P2pNetworkKademliaStreamAction, + P2pNetworkKademliaStreamWaitOutgoingCallback, }; impl P2pNetworkKadIncomingStreamState { @@ -95,17 +96,11 @@ impl P2pNetworkKadIncomingStreamState { P2pNetworkKadIncomingStreamState::RequestIsReady { data: P2pNetworkKademliaRpcRequest::FindNode { key }, } => { - // TODO: add callback dispatcher.push(P2pNetworkKademliaStreamAction::WaitOutgoing { addr, peer_id, stream_id, - }); - dispatcher.push(P2pNetworkKademliaAction::AnswerFindNodeRequest { - addr, - peer_id, - stream_id, - key, + callback: P2pNetworkKademliaStreamWaitOutgoingCallback::answer_find_node_request(key) }); } P2pNetworkKadIncomingStreamState::Error(error) => { @@ -149,17 +144,11 @@ impl P2pNetworkKadIncomingStreamState { P2pNetworkKadIncomingStreamState::RequestIsReady { data: P2pNetworkKademliaRpcRequest::FindNode { key }, } => { - // TODO: add callbacks dispatcher.push(P2pNetworkKademliaStreamAction::WaitOutgoing { addr, peer_id, stream_id, - }); - dispatcher.push(P2pNetworkKademliaAction::AnswerFindNodeRequest { - addr, - peer_id, - stream_id, - key, + callback: P2pNetworkKademliaStreamWaitOutgoingCallback::answer_find_node_request(key) }); } P2pNetworkKadIncomingStreamState::Error(error) => { @@ -178,9 +167,25 @@ impl P2pNetworkKadIncomingStreamState { } ( P2pNetworkKadIncomingStreamState::RequestIsReady { .. }, - P2pNetworkKademliaStreamAction::WaitOutgoing { .. }, + P2pNetworkKademliaStreamAction::WaitOutgoing { + addr, + peer_id, + stream_id, + callback, + }, ) => { *state = P2pNetworkKadIncomingStreamState::WaitingForReply; + let dispatcher = state_context.into_dispatcher(); + match callback { + P2pNetworkKademliaStreamWaitOutgoingCallback::AnswerFindNodeRequest { + callback, + args, + } => dispatcher.push_callback(callback, (addr, peer_id, stream_id, args)), + P2pNetworkKademliaStreamWaitOutgoingCallback::UpdateFindNodeRequest { + callback, + args, + } => dispatcher.push_callback(callback, (addr, peer_id, stream_id, args)), + }; Ok(()) } ( @@ -338,6 +343,7 @@ impl P2pNetworkKadOutgoingStreamState { peer_id, stream_id, }); + dispatcher.push(P2pNetworkKadRequestAction::RequestSent { peer_id }); Ok(()) } ( @@ -409,12 +415,7 @@ impl P2pNetworkKadOutgoingStreamState { addr, peer_id, stream_id, - }); - dispatcher.push(P2pNetworkKademliaAction::UpdateFindNodeRequest { - addr, - peer_id, - stream_id, - closest_peers, + callback: P2pNetworkKademliaStreamWaitOutgoingCallback::update_find_node_request(closest_peers) }); } P2pNetworkKadOutgoingStreamState::Error(error) => { @@ -466,12 +467,7 @@ impl P2pNetworkKadOutgoingStreamState { addr, peer_id, stream_id, - }); - dispatcher.push(P2pNetworkKademliaAction::UpdateFindNodeRequest { - addr, - peer_id, - stream_id, - closest_peers, + callback: P2pNetworkKademliaStreamWaitOutgoingCallback::update_find_node_request(closest_peers) }); } P2pNetworkKadOutgoingStreamState::Error(error) => { @@ -490,9 +486,25 @@ impl P2pNetworkKadOutgoingStreamState { } ( P2pNetworkKadOutgoingStreamState::ResponseIsReady { .. }, - P2pNetworkKademliaStreamAction::WaitOutgoing { .. }, + P2pNetworkKademliaStreamAction::WaitOutgoing { + addr, + peer_id, + stream_id, + callback, + }, ) => { *state = P2pNetworkKadOutgoingStreamState::WaitingForRequest { expect_close: true }; + let dispatcher = state_context.into_dispatcher(); + match callback { + P2pNetworkKademliaStreamWaitOutgoingCallback::AnswerFindNodeRequest { + callback, + args, + } => dispatcher.push_callback(callback, (addr, peer_id, stream_id, args)), + P2pNetworkKademliaStreamWaitOutgoingCallback::UpdateFindNodeRequest { + callback, + args, + } => dispatcher.push_callback(callback, (addr, peer_id, stream_id, args)), + }; Ok(()) } ( diff --git a/p2p/src/network/noise/p2p_network_noise_state.rs b/p2p/src/network/noise/p2p_network_noise_state.rs index ce714edfd8..af5c255573 100644 --- a/p2p/src/network/noise/p2p_network_noise_state.rs +++ b/p2p/src/network/noise/p2p_network_noise_state.rs @@ -507,7 +507,7 @@ mod wrapper { use serde::{Deserialize, Serialize}; use zeroize::Zeroize; - impl<'a, 'b> Mul<&'b Pk> for &'a Sk { + impl<'b> Mul<&'b Pk> for &Sk { type Output = [u8; 32]; fn mul(self, rhs: &'b Pk) -> Self::Output { diff --git a/p2p/src/network/p2p_network_service.rs b/p2p/src/network/p2p_network_service.rs index 093b2c3c75..81743513de 100644 --- a/p2p/src/network/p2p_network_service.rs +++ b/p2p/src/network/p2p_network_service.rs @@ -14,8 +14,8 @@ pub enum MioCmd { Refuse(SocketAddr), /// Create a new outgoing connection to the socket. Connect(SocketAddr), - /// Receive some data from the connection in the buffer. - Recv(ConnectionAddr, Box<[u8]>), + /// Receive some data from the connection. + Recv(ConnectionAddr, usize), /// Send the data in the connection. Send(ConnectionAddr, Box<[u8]>), /// Disconnect the remote peer. diff --git a/p2p/src/network/pubsub/p2p_network_pubsub_actions.rs b/p2p/src/network/pubsub/p2p_network_pubsub_actions.rs index e49b773203..ee12863b77 100644 --- a/p2p/src/network/pubsub/p2p_network_pubsub_actions.rs +++ b/p2p/src/network/pubsub/p2p_network_pubsub_actions.rs @@ -4,8 +4,23 @@ use mina_p2p_messages::gossip::GossipNetMessageV2; use openmina_core::ActionEvent; use serde::{Deserialize, Serialize}; +/// Actions that can occur within the P2P Network PubSub system. +/// +/// Managing pubsub streams, handling incoming and outgoing messages, +/// and maintaining the mesh network topology. +/// +/// **Common Fields:** +/// - `peer_id`: The identifier of the peer associated with the action. +/// - `addr`: The connection address of the peer. +/// - `stream_id`: The unique identifier of the stream. +/// - `topic_id`: The identifier of the topic involved in the action. #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] pub enum P2pNetworkPubsubAction { + /// Create a new stream, either incoming or outgoing. + /// + /// **Fields:** + /// - `incoming`: Indicates if the stream is incoming (`true`) or outgoing (`false`). + /// - `protocol`: The broadcast algorithm used for the stream. NewStream { incoming: bool, peer_id: PeerId, @@ -13,6 +28,12 @@ pub enum P2pNetworkPubsubAction { stream_id: StreamId, protocol: BroadcastAlgorithm, }, + + /// Process incoming raw data from a peer. + /// + /// **Fields:** + /// - `data`: The raw data payload received. + /// - `seen_limit`: The limit for tracking seen messages to prevent duplication. IncomingData { peer_id: PeerId, addr: ConnectionAddr, @@ -20,49 +41,82 @@ pub enum P2pNetworkPubsubAction { data: Data, seen_limit: usize, }, - IncomingMessage { + + /// Validate a batch of decoded incoming messages. + ValidateIncomingMessages { peer_id: PeerId, - message: pb::Message, seen_limit: usize, + addr: ConnectionAddr, }, - Graft { - peer_id: PeerId, - topic_id: String, - }, - Prune { + + /// Handle a fully decoded and validated message received from a peer. + /// + /// **Fields:** + /// - `message`: The decoded protobuf message. + /// - `seen_limit`: The limit for tracking seen messages to prevent duplication. + IncomingMessage { peer_id: PeerId, - topic_id: String, - }, - Broadcast { - message: Box, + message: pb::Message, + seen_limit: usize, }, + + /// Clean up temporary states after processing an incoming message. + IncomingMessageCleanup { peer_id: PeerId }, + + /// Add a peer to the mesh network for a specific topic. + Graft { peer_id: PeerId, topic_id: String }, + + /// Remove a peer from the mesh network for a specific topic. + Prune { peer_id: PeerId, topic_id: String }, + + /// Initiate the broadcasting of a message to all subscribed peers. + /// + /// **Fields:** + /// - `message`: The gossip network message to broadcast. + Broadcast { message: GossipNetMessageV2 }, + + /// Prepare a message for signing before broadcasting. + /// + /// **Fields:** + /// - `seqno`: The sequence number of the message. + /// - `author`: The identifier of the peer authoring the message. + /// - `data`: The data payload of the message. + /// - `topic`: The topic under which the message is published. Sign { seqno: u64, author: PeerId, data: Data, topic: String, }, + + /// An error occured during the signing process. #[action_event(level = warn, fields(display(author), display(topic)))] - SignError { - author: PeerId, - topic: String, - }, - BroadcastSigned { - signature: Data, - }, - OutgoingMessage { - msg: pb::Rpc, - peer_id: PeerId, - }, + SignError { author: PeerId, topic: String }, + + /// Finalize the broadcasting of a signed message by attaching the signature. + /// + /// **Fields:** + /// - `signature`: The cryptographic signature of the message. + BroadcastSigned { signature: Data }, + + /// Prepare an outgoing message to send to a specific peer. + OutgoingMessage { peer_id: PeerId }, + + /// Clear the outgoing message state for a specific peer after sending. + OutgoingMessageClear { peer_id: PeerId }, + + /// An error occured during the sending of an outgoing message. + /// + /// **Fields:** + /// - `msg`: The protobuf message that failed to send. #[action_event(level = warn, fields(display(peer_id), debug(msg)))] - OutgoingMessageError { - msg: pb::Rpc, - peer_id: PeerId, - }, - OutgoingData { - data: Data, - peer_id: PeerId, - }, + OutgoingMessageError { msg: pb::Rpc, peer_id: PeerId }, + + /// Send encoded data over an outgoing stream to a specific peer. + /// + /// **Fields:** + /// - `data`: The encoded data to be sent. + OutgoingData { data: Data, peer_id: PeerId }, } impl From for crate::P2pAction { @@ -72,7 +126,16 @@ impl From for crate::P2pAction { } impl redux::EnablingCondition for P2pNetworkPubsubAction { - fn is_enabled(&self, _state: &P2pState, _time: redux::Timestamp) -> bool { - true + fn is_enabled(&self, state: &P2pState, _time: redux::Timestamp) -> bool { + match self { + P2pNetworkPubsubAction::OutgoingMessage { peer_id } => state + .network + .scheduler + .broadcast_state + .clients + .get(peer_id) + .map_or(false, |s| !s.message_is_empty()), + _ => true, + } } } diff --git a/p2p/src/network/pubsub/p2p_network_pubsub_reducer.rs b/p2p/src/network/pubsub/p2p_network_pubsub_reducer.rs index 506c9393d6..7782ad9965 100644 --- a/p2p/src/network/pubsub/p2p_network_pubsub_reducer.rs +++ b/p2p/src/network/pubsub/p2p_network_pubsub_reducer.rs @@ -1,4 +1,4 @@ -use std::{collections::btree_map::Entry, sync::Arc}; +use std::collections::btree_map::Entry; use binprot::BinProtRead; use mina_p2p_messages::{gossip, v2}; @@ -14,8 +14,8 @@ use crate::{ use super::{ p2p_network_pubsub_state::P2pNetworkPubsubClientMeshAddingState, pb::{self, Message}, - P2pNetworkPubsubAction, P2pNetworkPubsubClientState, P2pNetworkPubsubClientTopicState, - P2pNetworkPubsubEffectfulAction, P2pNetworkPubsubState, TOPIC, + P2pNetworkPubsubAction, P2pNetworkPubsubClientState, P2pNetworkPubsubEffectfulAction, + P2pNetworkPubsubState, TOPIC, }; impl P2pNetworkPubsubState { @@ -53,6 +53,7 @@ impl P2pNetworkPubsubState { publish: vec![], control: None, }, + cache: Default::default(), buffer: vec![], incoming_messages: vec![], }); @@ -61,7 +62,9 @@ impl P2pNetworkPubsubState { pubsub_state .topics - .insert(super::TOPIC.to_owned(), Default::default()); + .entry(super::TOPIC.to_owned()) + .or_default() + .insert(peer_id, Default::default()); Ok(()) } @@ -82,6 +85,7 @@ impl P2pNetworkPubsubState { publish: vec![], control: None, }, + cache: Default::default(), buffer: vec![], incoming_messages: vec![], } @@ -90,6 +94,19 @@ impl P2pNetworkPubsubState { state.protocol = protocol; state.addr = addr; + pubsub_state + .topics + .entry(TOPIC.to_owned()) + .or_default() + .insert(peer_id, Default::default()); + + if let Some(state) = pubsub_state.clients.get_mut(&peer_id) { + state.message.subscriptions.push(pb::rpc::SubOpts { + subscribe: Some(true), + topic_id: Some(TOPIC.to_owned()), + }); + } + let (dispatcher, state) = state_context.into_dispatcher_and_state(); let config: &P2pConfig = state.substate()?; let state: &P2pNetworkPubsubState = state.substate()?; @@ -98,17 +115,7 @@ impl P2pNetworkPubsubState { // must have this topic already return Ok(()); }; - dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { - msg: pb::Rpc { - subscriptions: vec![pb::rpc::SubOpts { - subscribe: Some(true), - topic_id: Some(TOPIC.to_owned()), - }], - publish: vec![], - control: None, - }, - peer_id, - }); + dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { peer_id }); let mesh_size = map.values().filter(|s| s.on_mesh()).count(); if mesh_size < config.meshsub.outbound_degree_desired { dispatcher.push(P2pNetworkPubsubAction::Graft { @@ -123,14 +130,40 @@ impl P2pNetworkPubsubState { peer_id, data, seen_limit, + addr, .. } => { pubsub_state.reduce_incoming_data(&peer_id, data, meta.time())?; - let dispatcher: &mut Dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pNetworkPubsubEffectfulAction::IncomingData { + let dispatcher = state_context.into_dispatcher(); + + dispatcher.push(P2pNetworkPubsubAction::ValidateIncomingMessages { + peer_id, + seen_limit, + addr, + }); + + Ok(()) + } + P2pNetworkPubsubAction::ValidateIncomingMessages { + peer_id, + seen_limit, + addr, + } => { + let Some(state) = pubsub_state.clients.get_mut(&peer_id) else { + // TODO: investigate, cannot reproduce this + // bug_condition!("{:?} not found in state.clients", peer_id); + return Ok(()); + }; + let messages = std::mem::take(&mut state.incoming_messages); + + let dispatcher = state_context.into_dispatcher(); + + dispatcher.push(P2pNetworkPubsubEffectfulAction::ValidateIncomingMessages { peer_id, seen_limit, + addr, + messages, }); Ok(()) @@ -140,51 +173,73 @@ impl P2pNetworkPubsubState { message, seen_limit, } => { - pubsub_state.reduce_incoming_message(peer_id, message, seen_limit)?; + // Check result later to ensure we always dispatch the cleanup action + let reduce_incoming_result = + pubsub_state.reduce_incoming_message(peer_id, message, seen_limit); let (dispatcher, global_state) = state_context.into_dispatcher_and_state(); + + dispatcher.push(P2pNetworkPubsubAction::IncomingMessageCleanup { peer_id }); + + reduce_incoming_result?; + let state: &Self = global_state.substate()?; let config: &P2pConfig = global_state.substate()?; - let incoming_block = state.incoming_block.as_ref().cloned(); - let incoming_transactions = state.incoming_transactions.clone(); - let incoming_snarks = state.incoming_snarks.clone(); - let topics = state.topics.clone(); - - for (topic_id, map) in topics { + for (topic_id, map) in &state.topics { let mesh_size = map.values().filter(|s| s.on_mesh()).count(); let could_accept = mesh_size < config.meshsub.outbound_degree_high; if !could_accept { if let Some(topic_state) = map.get(&peer_id) { if topic_state.on_mesh() { + let topic_id = topic_id.clone(); dispatcher.push(P2pNetworkPubsubAction::Prune { peer_id, topic_id }) } } } } - broadcast(dispatcher, global_state)?; - if let Some((_, block)) = incoming_block { - let best_tip = BlockWithHash::try_new(Arc::new(block))?; + if let Err(error) = Self::broadcast(dispatcher, global_state) { + bug_condition!( + "Failure when trying to broadcast incoming pubsub message: {error}" + ); + }; + + if let Some((_, block)) = state.incoming_block.as_ref() { + let best_tip = BlockWithHash::try_new(block.clone())?; dispatcher.push(P2pPeerAction::BestTipUpdate { peer_id, best_tip }); } - for (transaction, nonce) in incoming_transactions { + for (transaction, nonce) in &state.incoming_transactions { dispatcher.push(P2pChannelsTransactionAction::Libp2pReceived { peer_id, - transaction: Box::new(transaction), - nonce, + transaction: Box::new(transaction.clone()), + nonce: *nonce, }); } - for (snark, nonce) in incoming_snarks { + for (snark, nonce) in &state.incoming_snarks { dispatcher.push(P2pChannelsSnarkAction::Libp2pReceived { peer_id, - snark: Box::new(snark), - nonce, + snark: Box::new(snark.clone()), + nonce: *nonce, }); } Ok(()) } + P2pNetworkPubsubAction::IncomingMessageCleanup { peer_id } => { + pubsub_state.clear_incoming(); + + let Some(client_state) = pubsub_state.clients.get_mut(&peer_id) else { + bug_condition!( + "State not found for action P2pNetworkPubsubAction::IncomingMessageCleanup" + ); + return Ok(()); + }; + + client_state.clear_incoming(); + + Ok(()) + } // we want to add peer to our mesh P2pNetworkPubsubAction::Graft { peer_id, topic_id } => { let Some(state) = pubsub_state @@ -194,24 +249,25 @@ impl P2pNetworkPubsubState { else { return Ok(()); }; - state.mesh = P2pNetworkPubsubClientMeshAddingState::Added; - let dispatcher = state_context.into_dispatcher(); - let msg = pb::Rpc { - subscriptions: vec![], - publish: vec![], - control: Some(pb::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![pb::ControlGraft { - topic_id: Some(topic_id), - }], - prune: vec![], - }), - }; + if let Some(state) = pubsub_state.clients.get_mut(&peer_id) { + let control = state + .message + .control + .get_or_insert_with(|| pb::ControlMessage { + ihave: vec![], + iwant: vec![], + graft: vec![], + prune: vec![], + }); + control.graft.push(pb::ControlGraft { + topic_id: Some(topic_id), + }); + } - dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { msg, peer_id }); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { peer_id }); Ok(()) } P2pNetworkPubsubAction::Prune { peer_id, topic_id } => { @@ -223,64 +279,83 @@ impl P2pNetworkPubsubState { bug_condition!("State not found for action: `P2pNetworkPubsubAction::Prune`"); return Ok(()); }; - state.mesh = P2pNetworkPubsubClientMeshAddingState::WeRefused; - let dispatcher = state_context.into_dispatcher(); - let msg = pb::Rpc { - subscriptions: vec![], - publish: vec![], - control: Some(pb::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![], - prune: vec![pb::ControlPrune { - topic_id: Some(topic_id), - peers: vec![pb::PeerInfo { - peer_id: None, - signed_peer_record: None, - }], - backoff: None, + if let Some(state) = pubsub_state.clients.get_mut(&peer_id) { + let control = state + .message + .control + .get_or_insert_with(|| pb::ControlMessage { + ihave: vec![], + iwant: vec![], + graft: vec![], + prune: vec![], + }); + control.prune.push(pb::ControlPrune { + topic_id: Some(topic_id), + peers: vec![pb::PeerInfo { + peer_id: None, + signed_peer_record: None, }], - }), - }; + backoff: None, + }); + } - dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { msg, peer_id }); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { peer_id }); Ok(()) } - P2pNetworkPubsubAction::OutgoingMessage { peer_id, msg } => { - if let Some(v) = pubsub_state.clients.get_mut(&peer_id) { - v.message.subscriptions.clear(); - v.message.publish.clear(); - v.message.control = None; + P2pNetworkPubsubAction::OutgoingMessage { peer_id } => { + let msg = if let Some(v) = pubsub_state.clients.get_mut(&peer_id) { + &v.message } else { bug_condition!( "Invalid state for action: `P2pNetworkPubsubAction::OutgoingMessage`" ); - } + return Ok(()); + }; + + let mut data = vec![]; + let result = prost::Message::encode_length_delimited(msg, &mut data) + .map(|_| data) + .map_err(|_| msg.clone()); let dispatcher = state_context.into_dispatcher(); - if !message_is_empty(&msg) { - let mut data = vec![]; - if prost::Message::encode_length_delimited(&msg, &mut data).is_err() { + + match result { + Err(msg) => { dispatcher .push(P2pNetworkPubsubAction::OutgoingMessageError { msg, peer_id }); - } else { + } + Ok(data) => { dispatcher.push(P2pNetworkPubsubAction::OutgoingData { data: Data::from(data), peer_id, }); } } + + // Important to avoid leaking state + dispatcher.push(P2pNetworkPubsubAction::OutgoingMessageClear { peer_id }); + + Ok(()) + } + P2pNetworkPubsubAction::OutgoingMessageClear { peer_id } => { + if let Some(v) = pubsub_state.clients.get_mut(&peer_id) { + v.message = pb::Rpc { + subscriptions: vec![], + publish: vec![], + control: None, + }; + } else { + bug_condition!( + "Invalid state for action: `P2pNetworkPubsubAction::OutgoingMessageClear`" + ); + }; Ok(()) } P2pNetworkPubsubAction::OutgoingMessageError { .. } => Ok(()), P2pNetworkPubsubAction::Broadcast { message } => { - let mut seqno = pubsub_state.seq; - let (dispatcher, state) = state_context.into_dispatcher_and_state(); - let config: &P2pConfig = state.substate()?; - seqno += config.meshsub.initial_time.as_nanos() as u64; - let mut buffer = vec![0; 8]; if binprot::BinProtWrite::binprot_write(&message, &mut buffer).is_err() { @@ -291,14 +366,7 @@ impl P2pNetworkPubsubState { let len = buffer.len() - 8; buffer[..8].clone_from_slice(&(len as u64).to_le_bytes()); - dispatcher.push(P2pNetworkPubsubAction::Sign { - seqno, - author: config.identity_pub_key.peer_id(), - data: buffer.into(), - topic: super::TOPIC.to_owned(), - }); - - Ok(()) + Self::prepare_to_sign(state_context, buffer) } P2pNetworkPubsubAction::Sign { seqno, @@ -319,8 +387,18 @@ impl P2pNetworkPubsubState { key: None, }); + let to_sign = pubsub_state.to_sign.front().cloned(); + let Some(message) = to_sign else { + bug_condition!("Message not found"); + return Ok(()); + }; + let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pNetworkPubsubEffectfulAction::Sign { author, topic }); + dispatcher.push(P2pNetworkPubsubEffectfulAction::Sign { + author, + topic, + message, + }); Ok(()) } P2pNetworkPubsubAction::SignError { .. } => { @@ -333,11 +411,11 @@ impl P2pNetworkPubsubState { pubsub_state .clients .iter_mut() - .for_each(|(_, state)| state.message.publish.push(message.clone())); + .for_each(|(_, state)| state.publish(&message)); } let (dispatcher, state) = state_context.into_dispatcher_and_state(); - broadcast(dispatcher, state) + Self::broadcast(dispatcher, state) } P2pNetworkPubsubAction::OutgoingData { mut data, peer_id } => { let (dispatcher, state) = state_context.into_dispatcher_and_state(); @@ -365,23 +443,38 @@ impl P2pNetworkPubsubState { } } + fn prepare_to_sign( + mut state_context: Substate, + buffer: Vec, + ) -> Result<(), String> + where + State: crate::P2pStateTrait, + Action: crate::P2pActionTrait, + { + let pubsub_state = state_context.get_substate_mut()?; + + let mut seqno = pubsub_state.seq; + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let config: &P2pConfig = state.substate()?; + seqno += config.meshsub.initial_time.as_nanos() as u64; + + dispatcher.push(P2pNetworkPubsubAction::Sign { + seqno, + author: config.identity_pub_key.peer_id(), + data: buffer.into(), + topic: super::TOPIC.to_owned(), + }); + + Ok(()) + } + + #[inline(never)] fn reduce_incoming_message( &mut self, peer_id: PeerId, message: Message, seen_limit: usize, ) -> Result<(), String> { - self.incoming_transactions.clear(); - self.incoming_snarks.clear(); - - let Some(state) = self.clients.get_mut(&peer_id) else { - bug_condition!("State not found for action P2pNetworkPubsubAction::IncomingMessage"); - return Ok(()); - }; - state.incoming_messages.clear(); - - let message_id = self.mcache.put(message.clone()); - let topic = self.topics.entry(message.topic.clone()).or_default(); if let Some(signature) = &message.signature { @@ -397,27 +490,6 @@ impl P2pNetworkPubsubState { } } - self.clients - .iter_mut() - .filter(|(c, _)| { - // don't send back to who sent this - *c != &peer_id - }) - .for_each(|(c, state)| { - let Some(topic_state) = topic.get(c) else { - return; - }; - if topic_state.on_mesh() { - state.message.publish.push(message.clone()) - } else { - let ctr = state.message.control.get_or_insert_with(Default::default); - ctr.ihave.push(pb::ControlIHave { - topic_id: Some(message.topic.clone()), - message_ids: message_id.clone().into_iter().collect(), - }) - } - }); - if let Some(data) = &message.data { if data.len() > 8 { let mut slice = &data[8..]; @@ -444,68 +516,108 @@ impl P2pNetworkPubsubState { } } + let message_id = self.mcache.put(message.clone()); + + // TODO: this should only happen after the contents have been validated. + // The only validation that has happened so far is that the message can be parsed. + self.clients + .iter_mut() + .filter(|(c, _)| { + // don't send back to who sent this + *c != &peer_id + }) + .for_each(|(c, state)| { + let Some(topic_state) = topic.get(c) else { + return; + }; + if topic_state.on_mesh() { + state.publish(&message) + } else { + let ctr = state.message.control.get_or_insert_with(Default::default); + ctr.ihave.push(pb::ControlIHave { + topic_id: Some(message.topic.clone()), + message_ids: message_id.clone().into_iter().collect(), + }) + } + }); + Ok(()) } + fn combined_with_pending_buffer<'a>(buffer: &'a mut Vec, data: &'a [u8]) -> &'a [u8] { + if buffer.is_empty() { + // Nothing pending, we can use the data directly + data + } else { + buffer.extend_from_slice(data); + buffer.as_slice() + } + } + + /// Processes incoming data from a peer, handling subscriptions, control messages, + /// and message broadcasting within the P2P pubsub system. fn reduce_incoming_data( &mut self, peer_id: &PeerId, data: Data, timestamp: Timestamp, ) -> Result<(), String> { - let Some(state) = self.clients.get_mut(peer_id) else { + let Some(client_state) = self.clients.get_mut(peer_id) else { // TODO: investigate, cannot reproduce this // bug_condition!("State not found for action: P2pNetworkPubsubAction::IncomingData"); return Ok(()); }; - let slice = if state.buffer.is_empty() { - &*data - } else { - state.buffer.extend_from_slice(&data); - &state.buffer - }; - let mut subscriptions = vec![]; - let mut control = pb::ControlMessage::default(); + + // Data may be part of a partial message we received before. + let slice = Self::combined_with_pending_buffer(&mut client_state.buffer, &data); + match ::decode_length_delimited(slice) { - Ok(v) => { - state.buffer.clear(); - // println!( - // "(pubsub) this <- {peer_id}, {:?}, {:?}, {}", - // v.subscriptions, - // v.control, - // v.publish.len() - // ); - - subscriptions.extend_from_slice(&v.subscriptions); - state.incoming_messages.extend_from_slice(&v.publish); - if let Some(v) = v.control { - control.graft.extend_from_slice(&v.graft); - control.prune.extend_from_slice(&v.prune); - control.ihave.extend_from_slice(&v.ihave); - control.iwant.extend_from_slice(&v.iwant); - } + Ok(decoded) => { + client_state.clear_buffer(); + client_state.incoming_messages.extend(decoded.publish); + + let subscriptions = decoded.subscriptions; + let control = decoded.control.unwrap_or_default(); + + self.update_subscriptions(peer_id, subscriptions); + self.apply_control_commands(peer_id, &control); + self.respond_to_iwant_requests(peer_id, &control.iwant); + self.process_ihave_messages(peer_id, control.ihave, timestamp); } Err(err) => { - // bad way to check the error, but `prost` doesn't provide better - if err.to_string().contains("buffer underflow") && state.buffer.is_empty() { - state.buffer = data.to_vec(); + // NOTE: not the ideal way to check for errors, but `prost` doesn't provide + // a better alternative, so we must check the message contents. + if err.to_string().contains("buffer underflow") && client_state.buffer.is_empty() { + // Incomplete data, keep in buffer, should be completed later + client_state.buffer = data.to_vec(); + } else { + // Clear the buffer for other decoding errors, otherwise this will cause issues + // with any data we receive later. + client_state.clear_buffer(); } } } + Ok(()) + } + + fn update_subscriptions(&mut self, peer_id: &PeerId, subscriptions: Vec) { + // Update subscription status based on incoming subscription requests. for subscription in &subscriptions { let topic_id = subscription.topic_id().to_owned(); let topic = self.topics.entry(topic_id).or_default(); if subscription.subscribe() { - if let Entry::Vacant(v) = topic.entry(*peer_id) { - v.insert(P2pNetworkPubsubClientTopicState::default()); - } + topic.entry(*peer_id).or_default(); } else { topic.remove(peer_id); } } + } + /// Applies control commands (`graft` and `prune`) to manage the peer's mesh states within topics. + fn apply_control_commands(&mut self, peer_id: &PeerId, control: &pb::ControlMessage) { + // Apply graft commands to add the peer to specific topic meshes. for graft in &control.graft { if let Some(mesh_state) = self .topics @@ -515,6 +627,8 @@ impl P2pNetworkPubsubState { mesh_state.mesh = P2pNetworkPubsubClientMeshAddingState::Added; } } + + // Apply prune commands to remove the peer from specific topic meshes. for prune in &control.prune { if let Some(mesh_state) = self .topics @@ -524,17 +638,29 @@ impl P2pNetworkPubsubState { mesh_state.mesh = P2pNetworkPubsubClientMeshAddingState::TheyRefused; } } - for iwant in &control.iwant { + } + + fn respond_to_iwant_requests(&mut self, peer_id: &PeerId, iwant_requests: &[pb::ControlIWant]) { + // Respond to iwant requests by publishing available messages from the cache. + for iwant in iwant_requests { for msg_id in &iwant.message_ids { if let Some(msg) = self.mcache.map.get(msg_id) { if let Some(client) = self.clients.get_mut(peer_id) { - client.message.publish.push(msg.clone()); + client.publish(msg); } } } } + } - for ihave in control.ihave { + fn process_ihave_messages( + &mut self, + peer_id: &PeerId, + ihave_messages: Vec, + timestamp: Timestamp, + ) { + // Process ihave messages by determining which available messages the client wants. + for ihave in ihave_messages { if self.clients.contains_key(peer_id) { let message_ids = ihave .message_ids @@ -543,41 +669,36 @@ impl P2pNetworkPubsubState { .collect::>(); let Some(client) = self.clients.get_mut(peer_id) else { - bug_condition!("State not found for {}", peer_id); - return Ok(()); + bug_condition!("process_ihave_messages: State not found for {}", peer_id); + return; }; + // Queue the desired message IDs for the client to request. let ctr = client.message.control.get_or_insert_with(Default::default); ctr.iwant.push(pb::ControlIWant { message_ids }) } } - Ok(()) } -} -fn message_is_empty(msg: &pb::Rpc) -> bool { - msg.subscriptions.is_empty() && msg.publish.is_empty() && msg.control.is_none() -} + fn broadcast( + dispatcher: &mut Dispatcher, + state: &State, + ) -> Result<(), String> + where + State: crate::P2pStateTrait, + Action: crate::P2pActionTrait, + { + let state: &P2pNetworkPubsubState = state.substate()?; + + for peer_id in state + .clients + .iter() + .filter(|(_, s)| !s.message_is_empty()) + .map(|(peer_id, _)| *peer_id) + { + dispatcher.push(P2pNetworkPubsubAction::OutgoingMessage { peer_id }); + } -fn broadcast( - dispatcher: &mut Dispatcher, - state: &State, -) -> Result<(), String> -where - State: crate::P2pStateTrait, - Action: crate::P2pActionTrait, -{ - let state: &P2pNetworkPubsubState = state.substate()?; - - state - .clients - .iter() - .filter(|(_, state)| !message_is_empty(&state.message)) - .map(|(peer_id, state)| P2pNetworkPubsubAction::OutgoingMessage { - msg: state.message.clone(), - peer_id: *peer_id, - }) - .for_each(|action| dispatcher.push(action)); - - Ok(()) + Ok(()) + } } diff --git a/p2p/src/network/pubsub/p2p_network_pubsub_state.rs b/p2p/src/network/pubsub/p2p_network_pubsub_state.rs index e3abeaa971..8ece03753e 100644 --- a/p2p/src/network/pubsub/p2p_network_pubsub_state.rs +++ b/p2p/src/network/pubsub/p2p_network_pubsub_state.rs @@ -2,7 +2,8 @@ use super::pb; use crate::{token::BroadcastAlgorithm, ConnectionAddr, PeerId, StreamId}; use std::{ - collections::{BTreeMap, VecDeque}, + collections::{BTreeMap, BTreeSet, VecDeque}, + sync::Arc, time::Duration, }; @@ -13,17 +14,49 @@ use serde::{Deserialize, Serialize}; pub const IWANT_TIMEOUT_DURATION: Duration = Duration::from_secs(5); +/// State of the P2P Network PubSub system. +/// +/// This struct maintains information about connected peers, message sequencing, +/// message caching, and topic subscriptions. It handles incoming and outgoing +/// messages, manages the mesh network topology, and ensures efficient message +/// broadcasting across the network. #[derive(Default, Serialize, Deserialize, Debug, Clone)] pub struct P2pNetworkPubsubState { + /// State of each connected peer. pub clients: BTreeMap, + + /// Current message sequence number. + /// + /// Increments with each new message to ensure proper ordering and uniqueness. pub seq: u64, + + /// Messages awaiting cryptographic signing. pub to_sign: VecDeque, + + /// Recently seen message identifiers to prevent duplication. + /// + /// Keeps a limited history of message signatures to avoid processing + /// the same message multiple times. pub seen: VecDeque>, + + /// Cache of published messages for efficient retrieval and broadcasting. + /// + /// For quick access and reducing redundant data transmission across peers. pub mcache: P2pNetworkPubsubMessageCache, - pub incoming_block: Option<(PeerId, v2::MinaBlockBlockStableV2)>, + + /// Incoming block from a peer, if any. + pub incoming_block: Option<(PeerId, Arc)>, + + /// Incoming transactions from peers along with their nonces. pub incoming_transactions: Vec<(Transaction, u32)>, + + /// Incoming snarks from peers along with their nonces. pub incoming_snarks: Vec<(Snark, u32)>, + + /// Topics and their subscribed peers. pub topics: BTreeMap>, + + /// `iwant` requests, tracking the number of times peers have expressed interest in specific messages. pub iwant: VecDeque, } @@ -83,18 +116,94 @@ impl P2pNetworkPubsubState { } } } + + pub fn clear_incoming(&mut self) { + self.incoming_transactions.clear(); + self.incoming_snarks.clear(); + + self.incoming_transactions.shrink_to(0x20); + self.incoming_snarks.shrink_to(0x20); + + self.incoming_block = None; + } } +/// State of a pubsub client connected to a peer. +/// +/// This struct maintains essential information about the client's protocol, +/// connection details, message buffers, and caching mechanisms. It facilitates +/// efficient message handling and broadcasting within the pubsub system. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct P2pNetworkPubsubClientState { + /// Broadcast algorithm used for this client. pub protocol: BroadcastAlgorithm, + + /// Connection address of the peer. pub addr: ConnectionAddr, + + /// Outgoing stream identifier, if any. + /// + /// - `Some(StreamId)`: Indicates an active outgoing stream. + /// - `None`: No outgoing stream is currently established. pub outgoing_stream_id: Option, + + /// Current RPC message being constructed or processed. + /// + /// - `subscriptions`: List of subscription options for various topics. + /// - `publish`: Messages queued for publishing. + /// - `control`: Control commands for managing the mesh network. pub message: pb::Rpc, + + /// Cache of recently published messages. + pub cache: P2pNetworkPubsubRecentlyPublishCache, + + /// Buffer for incoming data fragments. + /// + /// Stores partial data received from peers, facilitating the assembly of complete + /// messages when all fragments are received. pub buffer: Vec, + + /// Collection of incoming messages from the peer. + /// + /// Holds fully decoded `pb::Message` instances received from the peer, + /// ready for further handling such as validation, caching, and broadcasting. pub incoming_messages: Vec, } +impl P2pNetworkPubsubClientState { + pub fn publish(&mut self, message: &pb::Message) { + let Some(id) = compute_message_id(message) else { + self.message.publish.push(message.clone()); + return; + }; + if self.cache.map.insert(id.clone()) { + self.message.publish.push(message.clone()); + } + self.cache.queue.push_back(id); + if self.cache.queue.len() > 50 { + if let Some(id) = self.cache.queue.pop_front() { + self.cache.map.remove(&id); + } + } + } + + pub fn clear_buffer(&mut self) { + self.buffer.clear(); + self.buffer.shrink_to(0x2000); + } + + pub fn clear_incoming(&mut self) { + self.incoming_messages.clear(); + self.incoming_messages.shrink_to(0x20) + } +} + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +pub struct P2pNetworkPubsubRecentlyPublishCache { + pub map: BTreeSet>, + pub queue: VecDeque>, +} + // TODO: store blocks, snarks and txs separately #[derive(Default, Serialize, Deserialize, Debug, Clone)] pub struct P2pNetworkPubsubMessageCache { @@ -110,7 +219,7 @@ impl P2pNetworkPubsubMessageCache { self.map.insert(id.clone(), message); self.queue.push_back(id.clone()); if self.queue.len() > Self::CAPACITY { - if let Some(id) = self.queue.pop_back() { + if let Some(id) = self.queue.pop_front() { self.map.remove(&id); } } @@ -155,6 +264,14 @@ pub enum P2pNetworkPubsubClientMeshAddingState { Added, } +impl P2pNetworkPubsubClientState { + pub fn message_is_empty(&self) -> bool { + self.message.subscriptions.is_empty() + && self.message.publish.is_empty() + && self.message.control.is_none() + } +} + impl P2pNetworkPubsubClientTopicState { pub fn on_mesh(&self) -> bool { matches!(&self.mesh, P2pNetworkPubsubClientMeshAddingState::Added) diff --git a/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_actions.rs b/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_actions.rs index 4b9acbddc1..3120fa3452 100644 --- a/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_actions.rs +++ b/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_actions.rs @@ -1,11 +1,35 @@ -use crate::{P2pState, PeerId}; +use crate::{pubsub::pb::Message, ConnectionAddr, P2pState, PeerId}; use openmina_core::ActionEvent; use serde::{Deserialize, Serialize}; +/// Eeffectful actions within the P2P Network PubSub system. #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] pub enum P2pNetworkPubsubEffectfulAction { - Sign { author: PeerId, topic: String }, - IncomingData { peer_id: PeerId, seen_limit: usize }, + /// Initiate the signing of a message before broadcasting. + /// + /// **Fields:** + /// - `author`: The identifier of the peer authoring the message. + /// - `topic`: The topic under which the message is published. + /// - `message`: The protobuf message to be signed. + Sign { + author: PeerId, + topic: String, + message: Message, + }, + + /// Validate a batch of incoming messages from a peer. + /// + /// **Fields:** + /// - `peer_id`: The identifier of the peer sending the messages. + /// - `seen_limit`: The limit for tracking seen messages to prevent duplication. + /// - `addr`: The connection address of the peer. + /// - `messages`: Decoded protobuf messages. + ValidateIncomingMessages { + peer_id: PeerId, + seen_limit: usize, + addr: ConnectionAddr, + messages: Vec, + }, } impl From for crate::P2pEffectfulAction { diff --git a/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_effects.rs b/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_effects.rs index de7b4dfec7..d47bce28ee 100644 --- a/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_effects.rs +++ b/p2p/src/network/pubsub/pubsub_effectful/p2p_network_pubsub_effectful_effects.rs @@ -1,5 +1,3 @@ -use openmina_core::bug_condition; - use libp2p_identity::{DecodingError, PublicKey}; use super::super::pb; @@ -10,88 +8,103 @@ use crate::{ use super::P2pNetworkPubsubEffectfulAction; +#[derive(Debug, thiserror::Error)] +pub enum PubSubError { + #[error("Message does not contain a signature.")] + MissingSignature, + #[error("Message does not contain a verifying key.")] + MissingVerifyingKey, + #[error("Failed to retrieve the originator's public key.")] + OriginatorFailed, + #[error("Message serialization failed.")] + SerializationError, + #[error("Message's signature is invalid.")] + InvalidSignature, +} + impl P2pNetworkPubsubEffectfulAction { pub fn effects(self, _meta: &redux::ActionMeta, store: &mut Store) where Store: crate::P2pStore, Store::Service: P2pCryptoService, { - let state = &store.state().network.scheduler.broadcast_state; - match self { - P2pNetworkPubsubEffectfulAction::Sign { author, topic } => { - if let Some(to_sign) = state.to_sign.front() { - let mut publication = vec![]; - if prost::Message::encode(to_sign, &mut publication).is_err() { - store.dispatch(P2pNetworkPubsubAction::SignError { author, topic }); - } else { - let signature = store.service().sign_publication(&publication).into(); - store.dispatch(P2pNetworkPubsubAction::BroadcastSigned { signature }); - } + P2pNetworkPubsubEffectfulAction::Sign { + author, + topic, + message, + } => { + let mut publication = vec![]; + if prost::Message::encode(&message, &mut publication).is_err() { + store.dispatch(P2pNetworkPubsubAction::SignError { author, topic }); + } else { + let signature = store.service().sign_publication(&publication).into(); + store.dispatch(P2pNetworkPubsubAction::BroadcastSigned { signature }); } } - P2pNetworkPubsubEffectfulAction::IncomingData { + P2pNetworkPubsubEffectfulAction::ValidateIncomingMessages { peer_id, seen_limit, + addr, + messages, } => { - let Some(state) = state.clients.get(&peer_id) else { - // TODO: investigate, cannot reproduce this - // bug_condition!("{:?} not found in state.clients", peer_id); - return; - }; - let messages = state.incoming_messages.clone(); + let mut valid_messages = Vec::with_capacity(messages.len()); for message in messages { - let result = if let Some(signature) = message.signature.clone() { - if let Ok(Some(pk)) = originator(&message) { - // the message is mutable only in this function - let mut message = message; - let Ok(data) = encode_without_signature_and_key(&mut message) else { - // should never happen; - // we just decode this message, so it should encode without error - bug_condition!("serialization error"); - return; - }; - // keep the binding immutable - let message = message; - - if store.service().verify_publication(&pk, &data, &signature) { - store.dispatch(P2pNetworkPubsubAction::IncomingMessage { - peer_id, - message, - seen_limit, - }); - Ok(()) - } else { - Err("invalid signature") - } - } else { - Err("message doesn't contain verifying key") + match validate_message(message, store) { + Ok(valid_msg) => valid_messages.push(valid_msg), + Err(error) => { + store.dispatch(P2pNetworkSchedulerAction::Error { + addr, + error: P2pNetworkConnectionError::PubSubError(error.to_string()), + }); + + return; // Early exit, no need to process the rest } - } else { - Err("message doesn't contain signature") - }; - - if let Err(error) = result { - let Some((addr, _)) = store.state().network.scheduler.find_peer(&peer_id) - else { - bug_condition!("{:?} not found in scheduler state", peer_id); - return; - }; - - store.dispatch(P2pNetworkSchedulerAction::Error { - addr: *addr, - error: P2pNetworkConnectionError::PubSubError(error.to_string()), - }); - - return; } } + + // All good, we can continue with these validated messages + for message in valid_messages { + store.dispatch(P2pNetworkPubsubAction::IncomingMessage { + peer_id, + message, + seen_limit, + }); + } } } } } +fn validate_message( + message: pb::Message, + store: &mut Store, +) -> Result +where + Store: crate::P2pStore, + Store::Service: P2pCryptoService, +{ + let signature = message + .signature + .clone() + .ok_or(PubSubError::MissingSignature)?; + + let pk = originator(&message) + .map_err(|_| PubSubError::OriginatorFailed)? + .ok_or(PubSubError::MissingVerifyingKey)?; + + let mut message = message; + let data = encode_without_signature_and_key(&mut message) + .map_err(|_| PubSubError::SerializationError)?; + + if store.service().verify_publication(&pk, &data, &signature) { + Ok(message) + } else { + Err(PubSubError::InvalidSignature) + } +} + pub fn originator(message: &pb::Message) -> Result, DecodingError> { message .key diff --git a/p2p/src/network/scheduler/p2p_network_scheduler_actions.rs b/p2p/src/network/scheduler/p2p_network_scheduler_actions.rs index 8c5140c679..45539aa20d 100644 --- a/p2p/src/network/scheduler/p2p_network_scheduler_actions.rs +++ b/p2p/src/network/scheduler/p2p_network_scheduler_actions.rs @@ -11,10 +11,7 @@ use super::{ p2p_network_scheduler_state::{P2pNetworkConnectionCloseReason, P2pNetworkConnectionError}, }; -use crate::{ - disconnection::P2pDisconnectionReason, ConnectionAddr, P2pPeerStatus, P2pState, PeerId, - StreamId, -}; +use crate::{disconnection::P2pDisconnectionReason, ConnectionAddr, P2pState, PeerId, StreamId}; #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] #[action_event(fields(display(ip), display(listener), display(addr), debug(result), select_kind = debug(kind), display(error)))] @@ -107,10 +104,6 @@ pub enum P2pNetworkSchedulerAction { addr: ConnectionAddr, }, /// Prune streams. - PruneStreams { - peer_id: PeerId, - }, - /// Prune streams. PruneStream { peer_id: PeerId, stream_id: StreamId, @@ -180,12 +173,6 @@ impl redux::EnablingCondition for P2pNetworkSchedulerAction { .connections .get(addr) .map_or(false, |conn_state| conn_state.closed.is_some()), - P2pNetworkSchedulerAction::PruneStreams { peer_id } => { - state.peers.get(peer_id).map_or(false, |peer_state| { - peer_state.status.is_error() - || matches!(peer_state.status, P2pPeerStatus::Disconnected { .. }) - }) - } P2pNetworkSchedulerAction::PruneStream { peer_id, stream_id } => state .network .scheduler diff --git a/p2p/src/network/scheduler/p2p_network_scheduler_reducer.rs b/p2p/src/network/scheduler/p2p_network_scheduler_reducer.rs index 9a318055e7..ccf05f7081 100644 --- a/p2p/src/network/scheduler/p2p_network_scheduler_reducer.rs +++ b/p2p/src/network/scheduler/p2p_network_scheduler_reducer.rs @@ -213,7 +213,7 @@ impl P2pNetworkSchedulerState { Self::forward_select_done(dispatcher, p2p_state, protocol, addr, incoming, kind); Ok(()) } - P2pNetworkSchedulerAction::SelectError { addr, kind, error } => { + P2pNetworkSchedulerAction::SelectError { addr, kind, .. } => { let dispatcher = state_context.into_dispatcher(); match kind { @@ -239,10 +239,11 @@ impl P2pNetworkSchedulerState { } } - dispatcher.push(P2pNetworkSchedulerEffectfulAction::SelectError { + dispatcher.push(P2pNetworkSchedulerEffectfulAction::Disconnect { addr, - kind, - error, + reason: P2pNetworkConnectionCloseReason::Error( + P2pNetworkConnectionError::SelectError, + ), }); Ok(()) @@ -296,7 +297,10 @@ impl P2pNetworkSchedulerState { conn_state.closed = Some(reason.clone().into()); let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pNetworkSchedulerEffectfulAction::Disconnect { addr, reason }); + dispatcher.push(P2pNetworkSchedulerEffectfulAction::Disconnect { + addr, + reason: P2pNetworkConnectionCloseReason::Disconnect(reason), + }); Ok(()) } @@ -316,7 +320,10 @@ impl P2pNetworkSchedulerState { conn_state.closed = Some(error.clone().into()); let dispatcher = state_context.into_dispatcher(); - dispatcher.push(P2pNetworkSchedulerEffectfulAction::Error { addr, error }); + dispatcher.push(P2pNetworkSchedulerEffectfulAction::Disconnect { + addr, + reason: P2pNetworkConnectionCloseReason::Error(error), + }); Ok(()) } P2pNetworkSchedulerAction::Disconnected { addr, reason } => { @@ -328,7 +335,7 @@ impl P2pNetworkSchedulerState { }; if cn.closed.is_none() { bug_condition!( - "P2pNetworkSchedulerAction::Disconnect: {addr} is not disconnecting" + "P2pNetworkSchedulerAction::Disconnect: {addr} is not disconnected" ); } @@ -376,7 +383,6 @@ impl P2pNetworkSchedulerState { dispatcher.push(P2pDisconnectionAction::Finish { peer_id }); } } - dispatcher.push(P2pNetworkSchedulerAction::PruneStreams { peer_id }); } None => { // sanity check, should be incoming connection @@ -397,8 +403,6 @@ impl P2pNetworkSchedulerState { } Ok(()) } - // TODO: remove the action - P2pNetworkSchedulerAction::PruneStreams { .. } => Ok(()), P2pNetworkSchedulerAction::PruneStream { peer_id, stream_id } => { let Some((_, conn_state)) = scheduler_state .connections @@ -416,9 +420,17 @@ impl P2pNetworkSchedulerState { Ok(()) } P2pNetworkSchedulerAction::IncomingConnectionIsReady { listener } => { - let dispatcher = state_context.into_dispatcher(); + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let p2p_state: &P2pState = state.substate()?; + + let should_accept = p2p_state.network.scheduler.connections.len() + < p2p_state.config.limits.max_connections(); + dispatcher.push( - P2pNetworkSchedulerEffectfulAction::IncomingConnectionIsReady { listener }, + P2pNetworkSchedulerEffectfulAction::IncomingConnectionIsReady { + listener, + should_accept, + }, ); Ok(()) } @@ -563,6 +575,21 @@ impl P2pNetworkSchedulerState { peer_id, addr, stream_id, + callback: redux::callback!( + on_p2p_network_kad_request_stream_ready(( + addr: ConnectionAddr, + peer_id: PeerId, + stream_id: StreamId, + data: P2pNetworkKademliaRpcRequest + )) -> crate::P2pAction{ + P2pNetworkKademliaStreamAction::SendRequest { + addr, + peer_id, + stream_id, + data + } + } + ), }); } } diff --git a/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_actions.rs b/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_actions.rs index e0b23194e8..b4cfba7ae0 100644 --- a/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_actions.rs +++ b/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_actions.rs @@ -3,10 +3,7 @@ use std::net::{IpAddr, SocketAddr}; use openmina_core::ActionEvent; use serde::{Deserialize, Serialize}; -use crate::{ - disconnection::P2pDisconnectionReason, select::SelectKind, ConnectionAddr, - P2pNetworkConnectionError, P2pState, -}; +use crate::{ConnectionAddr, P2pNetworkConnectionCloseReason, P2pState}; #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] #[action_event(fields(display(ip), display(listener), display(addr), debug(result), select_kind = debug(kind), display(error)))] @@ -17,6 +14,7 @@ pub enum P2pNetworkSchedulerEffectfulAction { }, IncomingConnectionIsReady { listener: SocketAddr, + should_accept: bool, }, #[action_event(fields(debug(addr), debug(result)))] IncomingDidAccept { @@ -39,26 +37,12 @@ pub enum P2pNetworkSchedulerEffectfulAction { addr: ConnectionAddr, incoming: bool, }, - SelectError { - addr: ConnectionAddr, - kind: SelectKind, - error: String, - }, /// Action that initiate the specified peer disconnection. Disconnect { /// Connection address. addr: ConnectionAddr, /// Reason why disconnection is triggered. - reason: P2pDisconnectionReason, - }, - - /// Fatal connection error. - #[action_event(level = debug)] - Error { - /// Connection address. - addr: ConnectionAddr, - /// Reason why disconnection is triggered. - error: P2pNetworkConnectionError, + reason: P2pNetworkConnectionCloseReason, }, } diff --git a/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_effects.rs b/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_effects.rs index b4ff0e6d08..343bb53e14 100644 --- a/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_effects.rs +++ b/p2p/src/network/scheduler_effectful/p2p_network_scheduler_effectful_effects.rs @@ -17,14 +17,14 @@ impl P2pNetworkSchedulerEffectfulAction { .service() .send_mio_cmd(MioCmd::ListenOn(SocketAddr::new(ip, port))); } - P2pNetworkSchedulerEffectfulAction::IncomingConnectionIsReady { listener } => { - let state = store.state(); - if state.network.scheduler.connections.len() - >= state.config.limits.max_connections() - { - store.service().send_mio_cmd(MioCmd::Refuse(listener)); - } else { + P2pNetworkSchedulerEffectfulAction::IncomingConnectionIsReady { + listener, + should_accept, + } => { + if should_accept { store.service().send_mio_cmd(MioCmd::Accept(listener)); + } else { + store.service().send_mio_cmd(MioCmd::Refuse(listener)); } } P2pNetworkSchedulerEffectfulAction::IncomingDidAccept { addr, .. } => { @@ -47,9 +47,7 @@ impl P2pNetworkSchedulerEffectfulAction { }); } P2pNetworkSchedulerEffectfulAction::IncomingDataIsReady { addr, limit } => { - store - .service() - .send_mio_cmd(MioCmd::Recv(addr, vec![0; limit].into_boxed_slice())); + store.service().send_mio_cmd(MioCmd::Recv(addr, limit)); } P2pNetworkSchedulerEffectfulAction::NoiseSelectDone { addr, incoming } => { let ephemeral_sk = Sk::from_random(store.service().ephemeral_sk()); @@ -66,16 +64,9 @@ impl P2pNetworkSchedulerEffectfulAction { signature, }); } - // TODO: remove state access - P2pNetworkSchedulerEffectfulAction::SelectError { addr, .. } - | P2pNetworkSchedulerEffectfulAction::Disconnect { addr, .. } - | P2pNetworkSchedulerEffectfulAction::Error { addr, .. } => { - if let Some(conn_state) = store.state().network.scheduler.connections.get(&addr) { - if let Some(reason) = conn_state.closed.clone() { - store.service().send_mio_cmd(MioCmd::Disconnect(addr)); - store.dispatch(P2pNetworkSchedulerAction::Disconnected { addr, reason }); - } - } + P2pNetworkSchedulerEffectfulAction::Disconnect { addr, reason } => { + store.service().send_mio_cmd(MioCmd::Disconnect(addr)); + store.dispatch(P2pNetworkSchedulerAction::Disconnected { addr, reason }); } } } diff --git a/p2p/src/network/select/p2p_network_select_reducer.rs b/p2p/src/network/select/p2p_network_select_reducer.rs index eac32b761a..9757f79ae6 100644 --- a/p2p/src/network/select/p2p_network_select_reducer.rs +++ b/p2p/src/network/select/p2p_network_select_reducer.rs @@ -122,6 +122,7 @@ impl P2pNetworkSelectState { addr, fin, data, .. } => { select_state.recv.buffer.clear(); + select_state.recv.buffer.shrink_to(0x2000); P2pNetworkSelectState::handle_negotiated_token( state_context, @@ -253,6 +254,7 @@ impl P2pNetworkSelectState { Err(ParseTokenError) => { self.inner = P2pNetworkSelectStateInner::Error("parse_token".to_owned()); self.recv.buffer.clear(); + self.recv.buffer.shrink_to(0x2000); break; } Ok(None) => break, diff --git a/p2p/src/p2p_config.rs b/p2p/src/p2p_config.rs index 9a425a8b7e..5206168321 100644 --- a/p2p/src/p2p_config.rs +++ b/p2p/src/p2p_config.rs @@ -25,10 +25,6 @@ pub struct P2pConfig { /// External addresses pub external_addrs: Vec, - /// The time interval that must elapse before the next peer discovery request. - /// The node periodically polls peers for their connections to keep our list up to date. - pub ask_initial_peers_interval: Duration, - pub enabled_channels: BTreeSet, pub timeouts: P2pTimeouts, @@ -76,6 +72,7 @@ pub struct P2pTimeouts { pub staged_ledger_aux_and_pending_coinbases_at_block: Option, pub block: Option, pub snark: Option, + pub transaction: Option, pub initial_peers: Option, pub kademlia_bootstrap: Option, pub kademlia_initial_bootstrap: Option, @@ -103,7 +100,7 @@ impl Default for P2pTimeouts { ), outgoing_connection_timeout: from_env_or( "OUTGOING_CONNECTION_TIMEOUT", - Some(Duration::from_secs(10)), + Some(Duration::from_secs(15)), ), reconnect_timeout: from_env_or("RECONNECT_TIMEOUT", Some(Duration::from_secs(1))), incoming_error_reconnect_timeout: from_env_or( @@ -116,15 +113,16 @@ impl Default for P2pTimeouts { ), best_tip_with_proof: from_env_or( "BEST_TIP_WITH_PROOF_TIMEOUT", - Some(Duration::from_secs(10)), + Some(Duration::from_secs(15)), ), - ledger_query: from_env_or("LEDGER_QUERY_TIMEOUT", Some(Duration::from_secs(2))), + ledger_query: from_env_or("LEDGER_QUERY_TIMEOUT", Some(Duration::from_secs(4))), staged_ledger_aux_and_pending_coinbases_at_block: from_env_or( "STAGED_LEDGER_AUX_AND_PENDING_COINBASES_AT_BLOCK_TIMEOUT", - Some(Duration::from_secs(120)), + Some(Duration::from_secs(180)), ), - block: from_env_or("BLOCK_TIMEOUT", Some(Duration::from_secs(5))), - snark: from_env_or("SNARK_TIMEOUT", Some(Duration::from_secs(5))), + block: from_env_or("BLOCK_TIMEOUT", Some(Duration::from_secs(8))), + snark: from_env_or("SNARK_TIMEOUT", Some(Duration::from_secs(8))), + transaction: from_env_or("TRANSACTION_TIMEOUT", Some(Duration::from_secs(8))), initial_peers: from_env_or("INITIAL_PEERS_TIMEOUT", Some(Duration::from_secs(5))), kademlia_bootstrap: from_env_or( "KADEMLIA_BOOTSTRAP_TIMEOUT", diff --git a/p2p/src/p2p_effects.rs b/p2p/src/p2p_effects.rs index 5a360e5722..f29d9f5625 100644 --- a/p2p/src/p2p_effects.rs +++ b/p2p/src/p2p_effects.rs @@ -1,7 +1,4 @@ -use crate::{ - channels::P2pChannelsEffectfulAction, connection::P2pConnectionEffectfulAction, - P2pEffectfulAction, P2pStore, -}; +use crate::{connection::P2pConnectionEffectfulAction, P2pEffectfulAction, P2pStore}; use redux::ActionMeta; impl P2pEffectfulAction { @@ -12,22 +9,7 @@ impl P2pEffectfulAction { { match self { P2pEffectfulAction::Initialize => {} - P2pEffectfulAction::Channels(action) => match action { - P2pChannelsEffectfulAction::SignalingDiscovery(action) => { - action.effects(&meta, store) - } - P2pChannelsEffectfulAction::SignalingExchange(action) => { - action.effects(&meta, store) - } - P2pChannelsEffectfulAction::BestTip(action) => action.effects(&meta, store), - P2pChannelsEffectfulAction::Transaction(action) => action.effects(&meta, store), - P2pChannelsEffectfulAction::StreamingRpc(action) => action.effects(&meta, store), - P2pChannelsEffectfulAction::SnarkJobCommitment(action) => { - action.effects(&meta, store) - } - P2pChannelsEffectfulAction::Rpc(action) => action.effects(&meta, store), - P2pChannelsEffectfulAction::Snark(action) => action.effects(&meta, store), - }, + P2pEffectfulAction::Channels(action) => action.effects(&meta, store), P2pEffectfulAction::Connection(action) => match action { P2pConnectionEffectfulAction::Outgoing(action) => action.effects(&meta, store), P2pConnectionEffectfulAction::Incoming(action) => action.effects(&meta, store), diff --git a/p2p/src/p2p_state.rs b/p2p/src/p2p_state.rs index 16c7a26811..4a7a44058d 100644 --- a/p2p/src/p2p_state.rs +++ b/p2p/src/p2p_state.rs @@ -3,6 +3,7 @@ use openmina_core::{ impl_substate_access, requests::RpcId, snark::{Snark, SnarkInfo, SnarkJobCommitment}, + transaction::TransactionInfo, ChainId, SubstateAccess, }; use redux::{Callback, Timestamp}; @@ -170,10 +171,11 @@ impl P2pState { .filter_map(|(id, p)| Some((id, p.status.as_ready()?))) } - pub fn ready_rpc_peers_iter(&self) -> impl '_ + Iterator { + pub fn ready_rpc_peers_iter( + &self, + ) -> impl '_ + Iterator { self.ready_peers_iter() .filter(|(_, p)| p.channels.rpc.can_send_request()) - .map(|(peer_id, p)| (*peer_id, p.channels.next_local_rpc_id())) } pub fn ready_peers(&self) -> Vec { @@ -491,6 +493,8 @@ type OptionalCallback = Option>; #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct P2pCallbacks { + /// Callback for [`P2pChannelsTransactionAction::Received`] + pub on_p2p_channels_transaction_received: OptionalCallback<(PeerId, Box)>, /// Callback for [`P2pChannelsTransactionAction::Libp2pReceived`] pub on_p2p_channels_transaction_libp2p_received: OptionalCallback>, diff --git a/p2p/src/service_impl/mio/mod.rs b/p2p/src/service_impl/mio/mod.rs index 84d034aa26..0ab78ab33a 100644 --- a/p2p/src/service_impl/mio/mod.rs +++ b/p2p/src/service_impl/mio/mod.rs @@ -144,6 +144,7 @@ impl MioRunningService { tokens, listeners: BTreeMap::default(), connections: BTreeMap::default(), + recv_buf: vec![0; 0x8000], }; std::thread::Builder::new() @@ -177,6 +178,7 @@ struct MioServiceInner { tokens: TokenRegistry, listeners: BTreeMap, connections: BTreeMap, + recv_buf: Vec, } struct Listener { @@ -476,20 +478,29 @@ where )), }; } - Recv(addr, mut buf) => { + Recv(addr, limit) => { if let Some(mut connection) = self.connections.remove(&addr) { + // Ensure the buffer has enough space for the requested limit + if limit > self.recv_buf.len() { + // TODO: upper bound? resize to `limit` or try to allocate some extra space too? + self.recv_buf.resize(limit, 0); + } + let mut keep = false; - match connection.stream.read(&mut buf) { + match connection.stream.read(&mut self.recv_buf[..limit]) { Ok(0) => self.send(MioEvent::ConnectionDidClose(addr, Ok(()))), Ok(read) => { self.send(MioEvent::IncomingDataDidReceive( addr, - Ok(buf[..read].to_vec().into()), + Ok(self.recv_buf[..read].to_vec().into()), )); self.send(MioEvent::IncomingDataIsReady(addr)); keep = true; } - Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + Err(err) + if err.kind() == io::ErrorKind::WouldBlock + || err.kind() == io::ErrorKind::Interrupted => + { connection.incoming_ready = false; keep = true; } diff --git a/p2p/src/service_impl/mod.rs b/p2p/src/service_impl/mod.rs index bd5b14f0ed..c6cbf390f3 100644 --- a/p2p/src/service_impl/mod.rs +++ b/p2p/src/service_impl/mod.rs @@ -77,13 +77,13 @@ pub mod webrtc { &mut self, other_pk: &PublicKey, message: &T, - ) -> Result; + ) -> Result>; fn decrypt( &mut self, other_pub_key: &PublicKey, encrypted: &T::Encrypted, - ) -> Result; + ) -> Result>; fn auth_encrypt_and_send( &mut self, diff --git a/p2p/src/service_impl/webrtc/mod.rs b/p2p/src/service_impl/webrtc/mod.rs index 0fd03da2f9..e57589ae4d 100644 --- a/p2p/src/service_impl/webrtc/mod.rs +++ b/p2p/src/service_impl/webrtc/mod.rs @@ -28,13 +28,20 @@ use crate::{ }; #[cfg(not(target_arch = "wasm32"))] -use self::native::{ - webrtc_signal_send, RTCChannel, RTCConnection, RTCConnectionState, RTCSignalingError, -}; +mod imports { + pub use super::native::{ + webrtc_signal_send, RTCChannel, RTCConnection, RTCConnectionState, RTCSignalingError, + }; +} #[cfg(target_arch = "wasm32")] -use self::web::{ - webrtc_signal_send, RTCChannel, RTCConnection, RTCConnectionState, RTCSignalingError, -}; +mod imports { + pub use super::web::{ + webrtc_signal_send, RTCChannel, RTCConnection, RTCConnectionState, RTCSignalingError, + }; +} + +use imports::*; +pub use imports::{webrtc_signal_send, RTCSignalingError}; use super::TaskSpawner; @@ -42,7 +49,10 @@ use super::TaskSpawner; const CHUNK_SIZE: usize = 16 * 1024; pub enum Cmd { - PeerAdd(PeerAddArgs), + PeerAdd { + args: PeerAddArgs, + abort: oneshot::Receiver<()>, + }, } #[derive(Debug)] @@ -83,6 +93,7 @@ pub enum PeerConnectionKind { pub struct PeerState { pub cmd_sender: mpsc::UnboundedSender, + pub abort: oneshot::Sender<()>, } #[derive(thiserror::Error, derive_more::From, Debug)] @@ -138,11 +149,11 @@ pub struct RTCChannelConfig { impl Default for RTCConfigIceServers { fn default() -> Self { Self(vec![ - // RTCConfigIceServer { - // urls: vec!["stun:65.109.110.75:3478".to_owned()], - // username: Some("openmina".to_owned()), - // credential: Some("webrtc".to_owned()), - // }, + RTCConfigIceServer { + urls: vec!["stun:65.109.110.75:3478".to_owned()], + username: Some("openmina".to_owned()), + credential: Some("webrtc".to_owned()), + }, RTCConfigIceServer { urls: vec![ "stun:stun.l.google.com:19302".to_owned(), @@ -250,6 +261,15 @@ async fn peer_start(args: PeerAddArgs) { } }; + let (main_channel_open_tx, main_channel_open) = oneshot::channel::<()>(); + let mut main_channel_open_tx = Some(main_channel_open_tx); + main_channel.on_open(move || { + if let Some(tx) = main_channel_open_tx.take() { + let _ = tx.send(()); + } + std::future::ready(()) + }); + let answer = if is_outgoing { let answer_fut = async { let sdp = pc.local_sdp().await.unwrap(); @@ -357,6 +377,8 @@ async fn peer_start(args: PeerAddArgs) { return; } Some(PeerCmd::ConnectionAuthorizationSend(Some(auth))) => { + let _ = main_channel_open.await; + // Add a delay for sending messages after channel // was opened. Some initial messages get lost otherwise. // TODO(binier): find deeper cause and fix it. @@ -691,8 +713,13 @@ pub trait P2pServiceWebrtc: redux::Service { spawner.spawn_main("webrtc", async move { while let Some(cmd) = cmd_receiver.recv().await { match cmd { - Cmd::PeerAdd(args) => { - spawn_local(peer_start(args)); + Cmd::PeerAdd { args, abort } => { + spawn_local(async move { + tokio::select! { + _ = abort => {} + _ = peer_start(args) => {} + } + }); } } } @@ -706,42 +733,52 @@ pub trait P2pServiceWebrtc: redux::Service { fn outgoing_init(&mut self, peer_id: PeerId) { let (peer_cmd_sender, peer_cmd_receiver) = mpsc::unbounded_channel(); + let (abort_sender, abort_receiver) = oneshot::channel(); self.peers().insert( peer_id, PeerState { cmd_sender: peer_cmd_sender, + abort: abort_sender, }, ); let event_sender = self.event_sender().clone(); let event_sender = Arc::new(move |p2p_event: P2pEvent| event_sender.send(p2p_event.into()).ok()); - let _ = self.cmd_sender().send(Cmd::PeerAdd(PeerAddArgs { - peer_id, - kind: PeerConnectionKind::Outgoing, - event_sender, - cmd_receiver: peer_cmd_receiver, - })); + let _ = self.cmd_sender().send(Cmd::PeerAdd { + args: PeerAddArgs { + peer_id, + kind: PeerConnectionKind::Outgoing, + event_sender, + cmd_receiver: peer_cmd_receiver, + }, + abort: abort_receiver, + }); } fn incoming_init(&mut self, peer_id: PeerId, offer: webrtc::Offer) { let (peer_cmd_sender, peer_cmd_receiver) = mpsc::unbounded_channel(); + let (abort_sender, abort_receiver) = oneshot::channel(); self.peers().insert( peer_id, PeerState { cmd_sender: peer_cmd_sender, + abort: abort_sender, }, ); let event_sender = self.event_sender().clone(); let event_sender = Arc::new(move |p2p_event: P2pEvent| event_sender.send(p2p_event.into()).ok()); - let _ = self.cmd_sender().send(Cmd::PeerAdd(PeerAddArgs { - peer_id, - kind: PeerConnectionKind::Incoming(Box::new(offer)), - event_sender, - cmd_receiver: peer_cmd_receiver, - })); + let _ = self.cmd_sender().send(Cmd::PeerAdd { + args: PeerAddArgs { + peer_id, + kind: PeerConnectionKind::Incoming(Box::new(offer)), + event_sender, + cmd_receiver: peer_cmd_receiver, + }, + abort: abort_receiver, + }); } fn set_answer(&mut self, peer_id: PeerId, answer: webrtc::Answer) { @@ -778,13 +815,13 @@ pub trait P2pServiceWebrtc: redux::Service { &mut self, other_pk: &PublicKey, message: &T, - ) -> Result; + ) -> Result>; fn decrypt( &mut self, other_pub_key: &PublicKey, encrypted: &T::Encrypted, - ) -> Result; + ) -> Result>; fn auth_encrypt_and_send( &mut self, diff --git a/p2p/src/service_impl/webrtc/native.rs b/p2p/src/service_impl/webrtc/native.rs index 1cbd06db8c..703e3a349f 100644 --- a/p2p/src/service_impl/webrtc/native.rs +++ b/p2p/src/service_impl/webrtc/native.rs @@ -35,9 +35,7 @@ pub enum RTCSignalingError { #[error("serialization failed: {0}")] Serialize(serde_json::Error), #[error("http request failed: {0}")] - Hyper(hyper::Error), - #[error("http request failed: {0}")] - Http(hyper::http::Error), + Http(reqwest::Error), } impl RTCConnection { @@ -165,11 +163,15 @@ pub async fn webrtc_signal_send( url: &str, offer: Offer, ) -> std::result::Result { - let client = hyper::Client::new(); - let req = hyper::Request::post(url).body(serde_json::to_string(&offer)?.into())?; - let body = client.request(req).await?.into_body(); - let bytes = hyper::body::to_bytes(body).await?; - Ok(serde_json::from_slice(bytes.as_ref())?) + let client = reqwest::Client::new(); + let res = client + .post(url) + .body(serde_json::to_string(&offer)?) + .send() + .await? + .json() + .await?; + Ok(res) } impl Clone for RTCConnection { diff --git a/p2p/src/service_impl/webrtc_with_libp2p.rs b/p2p/src/service_impl/webrtc_with_libp2p.rs index 7c16c32141..2bab626dcf 100644 --- a/p2p/src/service_impl/webrtc_with_libp2p.rs +++ b/p2p/src/service_impl/webrtc_with_libp2p.rs @@ -172,7 +172,7 @@ impl P2pChannelsService for T { &mut self, other_pk: &crate::identity::PublicKey, message: &M, - ) -> Result { + ) -> Result> { P2pServiceWebrtc::encrypt(self, other_pk, message) } @@ -180,7 +180,7 @@ impl P2pChannelsService for T { &mut self, other_pk: &crate::identity::PublicKey, encrypted: &M::Encrypted, - ) -> Result { + ) -> Result> { P2pServiceWebrtc::decrypt(self, other_pk, encrypted) } } diff --git a/p2p/src/webrtc/signaling_method/mod.rs b/p2p/src/webrtc/signaling_method/mod.rs index ff09d5a7d8..73ae4e23d9 100644 --- a/p2p/src/webrtc/signaling_method/mod.rs +++ b/p2p/src/webrtc/signaling_method/mod.rs @@ -13,13 +13,17 @@ use crate::PeerId; pub enum SignalingMethod { Http(HttpSignalingInfo), Https(HttpSignalingInfo), - P2p { relay_peer_id: PeerId }, + /// Proxy used as an SSL gateway to the actual signaling server. + HttpsProxy(u16, HttpSignalingInfo), + P2p { + relay_peer_id: PeerId, + }, } impl SignalingMethod { pub fn can_connect_directly(&self) -> bool { match self { - Self::Http(_) | Self::Https(_) => true, + Self::Http(_) | Self::Https(_) | Self::HttpsProxy(_, _) => true, Self::P2p { .. } => false, } } @@ -30,6 +34,12 @@ impl SignalingMethod { let (http, info) = match self { Self::Http(info) => ("http", info), Self::Https(info) => ("https", info), + Self::HttpsProxy(cluster_id, info) => { + return Some(format!( + "https://{}:{}/clusters/{}/mina/webrtc/signal", + info.host, info.port, cluster_id + )); + } _ => return None, }; Some(format!( @@ -57,6 +67,10 @@ impl fmt::Display for SignalingMethod { write!(f, "/https")?; signaling.fmt(f) } + Self::HttpsProxy(cluster_id, signaling) => { + write!(f, "/https_proxy/{cluster_id}")?; + signaling.fmt(f) + } Self::P2p { relay_peer_id } => { write!(f, "/p2p/{relay_peer_id}") } @@ -70,6 +84,8 @@ pub enum SignalingMethodParseError { NotEnoughArgs, #[error("unknown signaling method: `{0}`")] UnknownSignalingMethod(String), + #[error("invalid cluster id")] + InvalidClusterId, #[error("host parse error: {0}")] HostParseError(String), #[error("host parse error: {0}")] @@ -90,9 +106,23 @@ impl FromStr for SignalingMethod { .filter(|i| s.len() > *i) .ok_or(SignalingMethodParseError::NotEnoughArgs)?; + let rest = &s[method_end_index..]; match &s[1..method_end_index] { - "http" => Ok(Self::Http(s[method_end_index..].parse()?)), - "https" => Ok(Self::Https(s[method_end_index..].parse()?)), + "http" => Ok(Self::Http(rest.parse()?)), + "https" => Ok(Self::Https(rest.parse()?)), + "https_proxy" => { + let mut iter = rest.splitn(3, '/').filter(|v| !v.trim().is_empty()); + let (cluster_id, rest) = ( + iter.next() + .ok_or(SignalingMethodParseError::NotEnoughArgs)?, + iter.next() + .ok_or(SignalingMethodParseError::NotEnoughArgs)?, + ); + let cluster_id: u16 = cluster_id + .parse() + .or(Err(SignalingMethodParseError::InvalidClusterId))?; + Ok(Self::HttpsProxy(cluster_id, rest.parse()?)) + } method => Err(SignalingMethodParseError::UnknownSignalingMethod( method.to_owned(), )), diff --git a/p2p/testing/Cargo.toml b/p2p/testing/Cargo.toml index 222b2fde67..2cd6fbbe24 100644 --- a/p2p/testing/Cargo.toml +++ b/p2p/testing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p2p-testing" -version = "0.11.0" +version = "0.12.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/p2p/testing/src/cluster.rs b/p2p/testing/src/cluster.rs index 671f55887e..a199cecd8c 100644 --- a/p2p/testing/src/cluster.rs +++ b/p2p/testing/src/cluster.rs @@ -355,7 +355,6 @@ impl Cluster { identity_pub_key: secret_key.public_key(), initial_peers, external_addrs: vec![], - ask_initial_peers_interval: Duration::from_secs(5), enabled_channels: p2p::channels::ChannelId::for_libp2p().collect(), peer_discovery: config.discovery, timeouts: config.timeouts, @@ -486,6 +485,7 @@ impl Cluster { .dispatch_action(P2pConnectionOutgoingAction::Init { opts: dial_opts, rpc_id: None, + on_success: None, }); } NodeId::Libp2p(id) => { diff --git a/p2p/testing/src/redux.rs b/p2p/testing/src/redux.rs index 674680705c..bff9051f43 100644 --- a/p2p/testing/src/redux.rs +++ b/p2p/testing/src/redux.rs @@ -13,23 +13,16 @@ use p2p::{ bootstrap::P2pNetworkKadBootstrapState, channels::{ best_tip::P2pChannelsBestTipAction, - best_tip_effectful::P2pChannelsBestTipEffectfulAction, rpc::P2pChannelsRpcAction, - rpc_effectful::P2pChannelsRpcEffectfulAction, signaling::{ discovery::P2pChannelsSignalingDiscoveryAction, - discovery_effectful::P2pChannelsSignalingDiscoveryEffectfulAction, exchange::P2pChannelsSignalingExchangeAction, - exchange_effectful::P2pChannelsSignalingExchangeEffectfulAction, }, snark::P2pChannelsSnarkAction, - snark_effectful::P2pChannelsSnarkEffectfulAction, snark_job_commitment::P2pChannelsSnarkJobCommitmentAction, - snark_job_commitment_effectful::P2pChannelsSnarkJobCommitmentEffectfulAction, streaming_rpc::P2pChannelsStreamingRpcAction, - streaming_rpc_effectful::P2pChannelsStreamingRpcEffectfulAction, transaction::P2pChannelsTransactionAction, - transaction_effectful::P2pChannelsTransactionEffectfulAction, + P2pChannelsEffectfulAction, }, connection::{ incoming_effectful::P2pConnectionIncomingEffectfulAction, @@ -146,7 +139,10 @@ pub enum Action { impl From for Action { fn from(action: redux::AnyAction) -> Self { - *action.0.downcast::().expect("Downcast failed") + match action.0.downcast() { + Ok(action) => *action, + Err(action) => Self::P2p(*action.downcast().expect("Downcast failed")), + } } } @@ -317,13 +313,6 @@ impl_from_p2p!(effectful p2p::P2pNetworkPubsubEffectfulAction); impl_from_p2p!(effectful P2pNetworkIdentifyStreamEffectfulAction); impl_from_p2p!(effectful P2pConnectionOutgoingEffectfulAction); impl_from_p2p!(effectful P2pDisconnectionEffectfulAction); -impl_from_p2p!(effectful P2pChannelsSignalingDiscoveryEffectfulAction); -impl_from_p2p!(effectful P2pChannelsSignalingExchangeEffectfulAction); -impl_from_p2p!(effectful P2pChannelsBestTipEffectfulAction); -impl_from_p2p!(effectful P2pChannelsStreamingRpcEffectfulAction); -impl_from_p2p!(effectful P2pChannelsTransactionEffectfulAction); -impl_from_p2p!(effectful P2pChannelsSnarkJobCommitmentEffectfulAction); -impl_from_p2p!(effectful P2pChannelsRpcEffectfulAction); -impl_from_p2p!(effectful P2pChannelsSnarkEffectfulAction); +impl_from_p2p!(effectful P2pChannelsEffectfulAction); impl p2p::P2pActionTrait for Action {} diff --git a/p2p/testing/src/service.rs b/p2p/testing/src/service.rs index 58681a347e..8ffe1f1a30 100644 --- a/p2p/testing/src/service.rs +++ b/p2p/testing/src/service.rs @@ -64,7 +64,7 @@ impl ClusterService { impl TimeService for ClusterService { fn monotonic_time(&mut self) -> redux::Instant { - self.time + self.time.into() } } @@ -104,7 +104,7 @@ impl P2pServiceWebrtc for ClusterService { &mut self, _other_pk: &p2p::identity::PublicKey, _message: &T, - ) -> Result { + ) -> Result> { unreachable!("this is webrtc only and this crate tests libp2p only") } @@ -112,7 +112,7 @@ impl P2pServiceWebrtc for ClusterService { &mut self, _other_pub_key: &p2p::identity::PublicKey, _encrypted: &T::Encrypted, - ) -> Result { + ) -> Result> { unreachable!("this is webrtc only and this crate tests libp2p only") } diff --git a/p2p/tests/identify.rs b/p2p/tests/identify.rs index 2753caece4..36d1f56a27 100644 --- a/p2p/tests/identify.rs +++ b/p2p/tests/identify.rs @@ -5,13 +5,11 @@ use std::{ use multiaddr::{multiaddr, Multiaddr}; use p2p::{ - identity::SecretKey, network::identify::{ - stream_effectful::P2pNetworkIdentifyStreamEffectfulAction, P2pNetworkIdentify, + stream_effectful::P2pNetworkIdentifyStreamEffectfulAction, P2pNetworkIdentifyEffectfulAction, P2pNetworkIdentifyStreamAction, }, - token::{self, DiscoveryAlgorithm}, - Data, P2pEffectfulAction, P2pNetworkEffectfulAction, P2pNetworkYamuxAction, PeerId, + P2pEffectfulAction, P2pNetworkEffectfulAction, PeerId, }; use p2p_testing::{ cluster::{Cluster, ClusterBuilder, ClusterEvent, Listener}, @@ -181,14 +179,15 @@ fn bad_node_effects( } Action::P2pEffectful(P2pEffectfulAction::Network( P2pNetworkEffectfulAction::Identify(P2pNetworkIdentifyEffectfulAction::Stream( - P2pNetworkIdentifyStreamEffectfulAction::SendIdentify { + P2pNetworkIdentifyStreamEffectfulAction::GetListenAddresses { addr, peer_id, stream_id, + addresses: _, }, )), )) => { - let listen_addrs = vec![ + let addresses = vec![ multiaddr!(Ip4([127, 0, 0, 1]), Tcp(10500u16)), multiaddr!(Ip4([127, 0, 0, 1]), Tcp(10500u16)), multiaddr!(Ip6([0; 16]), Tcp(10500u16)), @@ -203,46 +202,12 @@ fn bad_node_effects( multiaddr!(Https), ]; - let public_key = Some(SecretKey::rand().public_key()); - - let protocols = vec![ - token::StreamKind::Identify(token::IdentifyAlgorithm::Identify1_0_0), - token::StreamKind::Broadcast(p2p::token::BroadcastAlgorithm::Meshsub1_1_0), - p2p::token::StreamKind::Rpc(token::RpcAlgorithm::Rpc0_0_1), - p2p::token::StreamKind::Discovery(DiscoveryAlgorithm::Kademlia1_0_0), - ]; - - let identify_msg = P2pNetworkIdentify { - protocol_version: Some("ipfs/0.1.0".to_string()), - agent_version: Some("openmina".to_owned()), - public_key, - listen_addrs, - observed_addr: None, - protocols, - }; - - let mut out = Vec::new(); - let identify_msg_proto = - identify_msg.to_proto_message().expect("serialized message"); - - prost::Message::encode_length_delimited(&identify_msg_proto, &mut out) - .expect("Error converting message"); - - store.dispatch(Action::P2p( - P2pNetworkYamuxAction::OutgoingData { - addr, - stream_id, - data: Data(out.into_boxed_slice()), - flags: Default::default(), - } - .into(), - )); - store.dispatch(Action::P2p( - P2pNetworkIdentifyStreamAction::Close { + P2pNetworkIdentifyStreamAction::SendIdentify { addr, peer_id, stream_id, + addresses, } .into(), )); diff --git a/poseidon/Cargo.toml b/poseidon/Cargo.toml new file mode 100644 index 0000000000..152e03f789 --- /dev/null +++ b/poseidon/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "poseidon" +version = "0.12.0" +edition = "2021" + +[lints] +workspace = true + +[dependencies] +mina-curves = { workspace = true } +once_cell = "1" +ark-ff = { workspace = true } diff --git a/poseidon/src/hash.rs b/poseidon/src/hash.rs new file mode 100644 index 0000000000..1f08ac5dd0 --- /dev/null +++ b/poseidon/src/hash.rs @@ -0,0 +1,569 @@ +use ark_ff::{BigInteger256, Field, FromBytes as _}; +use mina_curves::pasta::Fp; + +use crate::{PlonkSpongeConstantsKimchi, Sponge, SpongeParamsForField}; + +enum Item { + Bool(bool), + U2(u8), + U8(u8), + U32(u32), + U48([u8; 6]), + U64(u64), +} + +impl std::fmt::Debug for Item { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bool(arg0) => f.write_fmt(format_args!("{}_bool", i32::from(*arg0))), + Self::U2(arg0) => f.write_fmt(format_args!("{}_u2", arg0)), + Self::U8(arg0) => f.write_fmt(format_args!("{}_u8", arg0)), + Self::U32(arg0) => f.write_fmt(format_args!("{}_u32", arg0)), + Self::U48(arg0) => f.write_fmt(format_args!("{:?}_u48", arg0)), + Self::U64(arg0) => f.write_fmt(format_args!("{}_u64", arg0)), + } + } +} + +impl Item { + fn nbits(&self) -> u32 { + match self { + Item::Bool(_) => 1, + Item::U2(_) => 2, + Item::U8(_) => 8, + Item::U32(_) => 32, + Item::U48(_) => 48, + Item::U64(_) => 64, + } + } + + fn as_bigint(&self) -> u64 { + match self { + Item::Bool(v) => *v as u64, + Item::U2(v) => *v as u64, + Item::U8(v) => *v as u64, + Item::U32(v) => *v as u64, + Item::U48(v) => { + let mut bytes = <[u8; 32]>::default(); + bytes[..6].copy_from_slice(&v[..]); + BigInteger256::read(&bytes[..]).unwrap().to_64x4()[0] // Never fail with only 6 bytes + } + Item::U64(v) => *v, + } + } +} + +pub struct Inputs { + fields: Vec, + packeds: Vec, +} + +impl Default for Inputs { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Debug for Inputs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Inputs") + .field(&format!("fields[{:?}]", self.fields.len()), &self.fields) + .field(&format!("packeds[{:?}]", self.packeds.len()), &self.packeds) + .finish() + } +} + +#[allow(clippy::needless_range_loop)] +fn shl(bigint: &mut [u64; 4], mut n: u32) { + if n >= 64 * 4 { + *bigint = [0, 0, 0, 0]; + return; + } + while n >= 64 { + let mut t = 0; + for i in 0..4 { + core::mem::swap(&mut t, &mut bigint[i]); + } + n -= 64; + } + if n > 0 { + let mut t = 0; + for i in 0..4 { + let a = &mut bigint[i]; + let t2 = *a >> (64 - n); + *a <<= n; + *a |= t; + t = t2; + } + } +} + +impl Inputs { + pub fn new() -> Self { + Self { + fields: Vec::with_capacity(256), + packeds: Vec::with_capacity(256), + } + } + + pub fn append_bool(&mut self, value: bool) { + self.packeds.push(Item::Bool(value)); + } + + pub fn append_u2(&mut self, value: u8) { + self.packeds.push(Item::U2(value)); + } + + pub fn append_u8(&mut self, value: u8) { + self.packeds.push(Item::U8(value)); + } + + pub fn append_u32(&mut self, value: u32) { + self.packeds.push(Item::U32(value)); + } + + pub fn append_u64(&mut self, value: u64) { + self.packeds.push(Item::U64(value)); + } + + pub fn append_u48(&mut self, value: [u8; 6]) { + self.packeds.push(Item::U48(value)); + } + + pub fn append_field(&mut self, value: Fp) { + self.fields.push(value); + } + + pub fn append_bytes(&mut self, value: &[u8]) { + const BITS: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128]; + + self.packeds.reserve(value.len() * 8); + + for byte in value { + for bit in BITS { + self.append_bool(byte & bit != 0); + } + } + } + + // pub fn append(&mut self, value: &T) + // where + // T: ToInputs, + // { + // value.to_inputs(self); + // } + + #[allow(clippy::wrong_self_convention)] + pub fn to_fields(mut self) -> Vec { + let mut nbits = 0; + let mut current: [u64; 4] = [0, 0, 0, 0]; + + for (item, item_nbits) in self.packeds.iter().map(|i| (i.as_bigint(), i.nbits())) { + nbits += item_nbits; + + if nbits < 255 { + shl(&mut current, item_nbits); + + // Addition, but we use 'bitwise or' because we know bits of + // `current` are zero (we just shift-left them) + current[0] |= item; + } else { + self.fields + .push(BigInteger256::from_64x4(current).try_into().unwrap()); // Never fail + current = [item, 0, 0, 0]; + nbits = item_nbits; + } + } + + if nbits > 0 { + self.fields + .push(BigInteger256::from_64x4(current).try_into().unwrap()); // Never fail + } + + self.fields + } +} + +fn param_to_field_impl(param: &str, default: &[u8; 32]) -> Fp { + let param_bytes = param.as_bytes(); + let len = param_bytes.len(); + + let mut fp = *default; + fp[..len].copy_from_slice(param_bytes); + + Fp::read(&fp[..]).expect("fp read failed") +} + +pub fn param_to_field(param: &str) -> Fp { + const DEFAULT: &[u8; 32] = b"********************\0\0\0\0\0\0\0\0\0\0\0\0"; + + if param.len() > 20 { + panic!("must be 20 byte maximum"); + } + + param_to_field_impl(param, DEFAULT) +} + +fn param_to_field_noinputs(param: &str) -> Fp { + const DEFAULT: &[u8; 32] = &[0; 32]; + + if param.len() > 32 { + panic!("must be 32 byte maximum"); + } + + param_to_field_impl(param, DEFAULT) +} + +pub fn hash_with_kimchi(param: &LazyParam, fields: &[Fp]) -> Fp { + let LazyParam { + sponge_state, + state, + .. + } = param; + + let mut sponge = Sponge { + sponge_state: sponge_state.clone(), + state: *state, + ..Sponge::::default() + }; + + sponge.absorb(fields); + sponge.squeeze() +} + +pub fn hash_fields>(fields: &[F]) -> F { + let mut sponge = Sponge::::default(); + + sponge.absorb(fields); + sponge.squeeze() +} + +pub fn hash_noinputs(param: &LazyParam) -> Fp { + let LazyParam { last_squeezed, .. } = param; + + *last_squeezed +} + +#[derive(Debug)] +#[allow(dead_code)] // `string` is never read +pub struct LazyParam { + sponge_state: crate::SpongeState, + state: [Fp; 3], + last_squeezed: Fp, + string: &'static str, +} + +impl LazyParam { + pub fn state(&self) -> [Fp; 3] { + self.state + } +} + +pub mod params { + use once_cell::sync::Lazy; + + use super::*; + + macro_rules! impl_params { + ($({$name:tt, $string:tt}),*) => ($( + pub static $name: Lazy> = Lazy::new(|| { + let mut sponge = Sponge::::default(); + sponge.absorb(&[param_to_field($string)]); + let last_squeezed = sponge.squeeze(); + Box::new(LazyParam { + sponge_state: sponge.sponge_state, + state: sponge.state, + last_squeezed, + string: $string, + }) + }); + )*) + } + + impl_params!( + {MINA_ACCOUNT, "MinaAccount"}, + {MINA_PROTO_STATE, "MinaProtoState"}, + {MINA_PROTO_STATE_BODY, "MinaProtoStateBody"}, + {MINA_DERIVE_TOKEN_ID, "MinaDeriveTokenId"}, + {MINA_EPOCH_SEED, "MinaEpochSeed"}, + {MINA_SIDELOADED_VK, "MinaSideLoadedVk"}, + {MINA_VRF_MESSAGE, "MinaVrfMessage"}, + {MINA_VRF_OUTPUT, "MinaVrfOutput"}, + + {CODA_RECEIPT_UC, "CodaReceiptUC"}, + {COINBASE_STACK, "CoinbaseStack"}, + + {MINA_ACCOUNT_UPDATE_CONS, "MinaAcctUpdateCons"}, + {MINA_ACCOUNT_UPDATE_NODE, "MinaAcctUpdateNode"}, + {MINA_ACCOUNT_UPDATE_STACK_FRAME, "MinaAcctUpdStckFrm"}, + {MINA_ACCOUNT_UPDATE_STACK_FRAME_CONS, "MinaActUpStckFrmCons"}, + + {MINA_ZKAPP_ACCOUNT, "MinaZkappAccount"}, + {MINA_ZKAPP_MEMO, "MinaZkappMemo"}, + {MINA_ZKAPP_URI, "MinaZkappUri"}, + {MINA_ZKAPP_EVENT, "MinaZkappEvent"}, + {MINA_ZKAPP_EVENTS, "MinaZkappEvents"}, + {MINA_ZKAPP_SEQ_EVENTS, "MinaZkappSeqEvents"}, + + // devnet + {CODA_SIGNATURE, "CodaSignature"}, + {TESTNET_ZKAPP_BODY, "TestnetZkappBody"}, + // mainnet + {MINA_SIGNATURE_MAINNET, "MinaSignatureMainnet"}, + {MAINNET_ZKAPP_BODY, "MainnetZkappBody"}, + + {MINA_MERKLE_TREE_0, "MinaMklTree000"}, + {MINA_MERKLE_TREE_1, "MinaMklTree001"}, + {MINA_MERKLE_TREE_2, "MinaMklTree002"}, + {MINA_MERKLE_TREE_3, "MinaMklTree003"}, + {MINA_MERKLE_TREE_4, "MinaMklTree004"}, + {MINA_MERKLE_TREE_5, "MinaMklTree005"}, + {MINA_MERKLE_TREE_6, "MinaMklTree006"}, + {MINA_MERKLE_TREE_7, "MinaMklTree007"}, + {MINA_MERKLE_TREE_8, "MinaMklTree008"}, + {MINA_MERKLE_TREE_9, "MinaMklTree009"}, + {MINA_MERKLE_TREE_10, "MinaMklTree010"}, + {MINA_MERKLE_TREE_11, "MinaMklTree011"}, + {MINA_MERKLE_TREE_12, "MinaMklTree012"}, + {MINA_MERKLE_TREE_13, "MinaMklTree013"}, + {MINA_MERKLE_TREE_14, "MinaMklTree014"}, + {MINA_MERKLE_TREE_15, "MinaMklTree015"}, + {MINA_MERKLE_TREE_16, "MinaMklTree016"}, + {MINA_MERKLE_TREE_17, "MinaMklTree017"}, + {MINA_MERKLE_TREE_18, "MinaMklTree018"}, + {MINA_MERKLE_TREE_19, "MinaMklTree019"}, + {MINA_MERKLE_TREE_20, "MinaMklTree020"}, + {MINA_MERKLE_TREE_21, "MinaMklTree021"}, + {MINA_MERKLE_TREE_22, "MinaMklTree022"}, + {MINA_MERKLE_TREE_23, "MinaMklTree023"}, + {MINA_MERKLE_TREE_24, "MinaMklTree024"}, + {MINA_MERKLE_TREE_25, "MinaMklTree025"}, + {MINA_MERKLE_TREE_26, "MinaMklTree026"}, + {MINA_MERKLE_TREE_27, "MinaMklTree027"}, + {MINA_MERKLE_TREE_28, "MinaMklTree028"}, + {MINA_MERKLE_TREE_29, "MinaMklTree029"}, + {MINA_MERKLE_TREE_30, "MinaMklTree030"}, + {MINA_MERKLE_TREE_31, "MinaMklTree031"}, + {MINA_MERKLE_TREE_32, "MinaMklTree032"}, + {MINA_MERKLE_TREE_33, "MinaMklTree033"}, + {MINA_MERKLE_TREE_34, "MinaMklTree034"}, + {MINA_MERKLE_TREE_35, "MinaMklTree035"}, + + {MINA_CB_MERKLE_TREE_0, "MinaCbMklTree000"}, + {MINA_CB_MERKLE_TREE_1, "MinaCbMklTree001"}, + {MINA_CB_MERKLE_TREE_2, "MinaCbMklTree002"}, + {MINA_CB_MERKLE_TREE_3, "MinaCbMklTree003"}, + {MINA_CB_MERKLE_TREE_4, "MinaCbMklTree004"}, + {MINA_CB_MERKLE_TREE_5, "MinaCbMklTree005"} + ); + + pub fn get_coinbase_param_for_height(height: usize) -> &'static LazyParam { + static ARRAY: [&Lazy>; 6] = [ + &MINA_CB_MERKLE_TREE_0, + &MINA_CB_MERKLE_TREE_1, + &MINA_CB_MERKLE_TREE_2, + &MINA_CB_MERKLE_TREE_3, + &MINA_CB_MERKLE_TREE_4, + &MINA_CB_MERKLE_TREE_5, + ]; + + ARRAY[height] + } + + pub fn get_merkle_param_for_height(height: usize) -> &'static LazyParam { + static ARRAY: [&Lazy>; 36] = [ + &MINA_MERKLE_TREE_0, + &MINA_MERKLE_TREE_1, + &MINA_MERKLE_TREE_2, + &MINA_MERKLE_TREE_3, + &MINA_MERKLE_TREE_4, + &MINA_MERKLE_TREE_5, + &MINA_MERKLE_TREE_6, + &MINA_MERKLE_TREE_7, + &MINA_MERKLE_TREE_8, + &MINA_MERKLE_TREE_9, + &MINA_MERKLE_TREE_10, + &MINA_MERKLE_TREE_11, + &MINA_MERKLE_TREE_12, + &MINA_MERKLE_TREE_13, + &MINA_MERKLE_TREE_14, + &MINA_MERKLE_TREE_15, + &MINA_MERKLE_TREE_16, + &MINA_MERKLE_TREE_17, + &MINA_MERKLE_TREE_18, + &MINA_MERKLE_TREE_19, + &MINA_MERKLE_TREE_20, + &MINA_MERKLE_TREE_21, + &MINA_MERKLE_TREE_22, + &MINA_MERKLE_TREE_23, + &MINA_MERKLE_TREE_24, + &MINA_MERKLE_TREE_25, + &MINA_MERKLE_TREE_26, + &MINA_MERKLE_TREE_27, + &MINA_MERKLE_TREE_28, + &MINA_MERKLE_TREE_29, + &MINA_MERKLE_TREE_30, + &MINA_MERKLE_TREE_31, + &MINA_MERKLE_TREE_32, + &MINA_MERKLE_TREE_33, + &MINA_MERKLE_TREE_34, + &MINA_MERKLE_TREE_35, + ]; + + ARRAY[height] + } + + macro_rules! impl_params_noinput { + ($({$name:tt, $string:tt}),*) => ($( + pub static $name: Lazy> = Lazy::new(|| { + let mut sponge = Sponge::::default(); + sponge.absorb(&[param_to_field_noinputs($string)]); + let last_squeezed = sponge.squeeze(); + Box::new(LazyParam { + sponge_state: sponge.sponge_state, + state: sponge.state, + last_squeezed, + string: $string, + }) + }); + )*) + } + + impl_params_noinput!( + {NO_INPUT_ZKAPP_ACTION_STATE_EMPTY_ELT, "MinaZkappActionStateEmptyElt"}, + {NO_INPUT_COINBASE_STACK, "CoinbaseStack"}, + {NO_INPUT_MINA_ZKAPP_EVENTS_EMPTY, "MinaZkappEventsEmpty"}, + {NO_INPUT_MINA_ZKAPP_ACTIONS_EMPTY, "MinaZkappActionsEmpty"} + ); +} + +pub mod legacy { + use ark_ff::fields::arithmetic::InvalidBigInt; + + use super::*; + + #[derive(Clone, Debug)] + pub struct Inputs { + fields: Vec, + bits: Vec, + } + + impl Default for Inputs { + fn default() -> Self { + Self::new() + } + } + + impl Inputs { + pub fn new() -> Self { + Self { + fields: Vec::with_capacity(256), + bits: Vec::with_capacity(512), + } + } + + pub fn append_bit(&mut self, bit: bool) { + self.bits.push(bit); + } + + pub fn append_bool(&mut self, value: bool) { + self.append_bit(value); + } + + pub fn append_bits(&mut self, bits: &[bool]) { + self.bits.extend(bits); + } + + pub fn append_bytes(&mut self, bytes: &[u8]) { + const BITS: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128]; + + self.bits.reserve(bytes.len() * 8); + + for byte in bytes { + for bit in BITS { + self.append_bit(byte & bit != 0); + } + } + } + + pub fn append_u64(&mut self, value: u64) { + self.append_bytes(&value.to_le_bytes()); + } + + pub fn append_u32(&mut self, value: u32) { + self.append_bytes(&value.to_le_bytes()); + } + + pub fn append_field(&mut self, field: F) { + self.fields.push(field); + } + } + + impl> Inputs { + pub fn to_fields(mut self) -> Vec { + const NBITS: usize = 255 - 1; + + self.fields.reserve(self.bits.len() / NBITS); + self.fields.extend(self.bits.chunks(NBITS).map(|bits| { + let mut field = [0u64; 4]; + for (index, bit) in bits.iter().enumerate() { + let limb_index = index / 64; + let bit_index = index % 64; + field[limb_index] |= (*bit as u64) << bit_index; + } + F::try_from(BigInteger256::from_64x4(field)).unwrap() // Never fail + })); + self.fields + } + } + + pub fn hash_with_kimchi(param: &LazyParam, fields: &[Fp]) -> Fp { + let LazyParam { + sponge_state, + state, + .. + } = param; + + let mut sponge = Sponge { + sponge_state: sponge_state.clone(), + state: *state, + ..Sponge::new_legacy() + }; + + sponge.absorb(fields); + sponge.squeeze() + } + + pub mod params { + use once_cell::sync::Lazy; + + use super::*; + + macro_rules! impl_params { + ($({$name:tt, $string:tt}),*) => ($( + pub static $name: Lazy> = Lazy::new(|| { + let mut sponge = Sponge::new_legacy(); + sponge.absorb(&[param_to_field($string)]); + let last_squeezed = sponge.squeeze(); + Box::new(LazyParam { + sponge_state: sponge.sponge_state, + state: sponge.state, + last_squeezed, + string: $string, + }) + }); + )*) + } + + impl_params!( + {CODA_RECEIPT_UC, "CodaReceiptUC"}, + + // devnet + {CODA_SIGNATURE, "CodaSignature"}, + {TESTNET_ZKAPP_BODY, "TestnetZkappBody"}, + // mainnet + {MINA_SIGNATURE_MAINNET, "MinaSignatureMainnet"}, + {MAINNET_ZKAPP_BODY, "MainnetZkappBody"} + ); + } +} diff --git a/poseidon/src/lib.rs b/poseidon/src/lib.rs new file mode 100644 index 0000000000..eabfe11b95 --- /dev/null +++ b/poseidon/src/lib.rs @@ -0,0 +1,283 @@ +#![allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)] + +use std::marker::PhantomData; + +use ark_ff::{BigInteger256, Field}; +use mina_curves::pasta::{Fp, Fq}; + +pub mod hash; +mod params; + +pub use params::*; + +pub trait SpongeConstants { + const SPONGE_CAPACITY: usize = 1; + const SPONGE_WIDTH: usize = 3; + const SPONGE_RATE: usize = 2; + const PERM_ROUNDS_FULL: usize; + const PERM_ROUNDS_PARTIAL: usize; + const PERM_HALF_ROUNDS_FULL: usize; + const PERM_SBOX: u32; + const PERM_FULL_MDS: bool; + const PERM_INITIAL_ARK: bool; +} + +#[derive(Clone)] +pub struct PlonkSpongeConstantsKimchi {} + +impl SpongeConstants for PlonkSpongeConstantsKimchi { + const SPONGE_CAPACITY: usize = 1; + const SPONGE_WIDTH: usize = 3; + const SPONGE_RATE: usize = 2; + const PERM_ROUNDS_FULL: usize = 55; + const PERM_ROUNDS_PARTIAL: usize = 0; + const PERM_HALF_ROUNDS_FULL: usize = 0; + const PERM_SBOX: u32 = 7; + const PERM_FULL_MDS: bool = true; + const PERM_INITIAL_ARK: bool = false; +} + +#[derive(Clone)] +pub struct PlonkSpongeConstantsLegacy {} + +impl SpongeConstants for PlonkSpongeConstantsLegacy { + const SPONGE_CAPACITY: usize = 1; + const SPONGE_WIDTH: usize = 3; + const SPONGE_RATE: usize = 2; + const PERM_ROUNDS_FULL: usize = 63; + const PERM_ROUNDS_PARTIAL: usize = 0; + const PERM_HALF_ROUNDS_FULL: usize = 0; + const PERM_SBOX: u32 = 5; + const PERM_FULL_MDS: bool = true; + const PERM_INITIAL_ARK: bool = true; +} + +#[inline(always)] +fn apply_mds_matrix(params: &SpongeParams, state: &[F]) -> [F; 3] { + let mut new_state = [F::zero(); 3]; + + for (i, sub_params) in params.mds.iter().enumerate() { + for (state, param) in state.iter().zip(sub_params) { + new_state[i].add_assign(*param * state); + } + } + + new_state +} + +pub fn full_round( + params: &SpongeParams, + state: &mut [F; 3], + r: usize, +) { + for state_i in state.iter_mut() { + *state_i = sbox::(*state_i); + } + *state = apply_mds_matrix::(params, state); + for (i, x) in params.round_constants[r].iter().enumerate() { + state[i].add_assign(x); + } +} + +pub fn poseidon_block_cipher( + params: &SpongeParams, + state: &mut [F; 3], +) { + if SC::PERM_INITIAL_ARK { + for (i, x) in params.round_constants[0].iter().enumerate() { + state[i].add_assign(x); + } + for r in 0..SC::PERM_ROUNDS_FULL { + full_round::(params, state, r + 1); + } + } else { + for r in 0..SC::PERM_ROUNDS_FULL { + full_round::(params, state, r); + } + } +} + +pub fn sbox(mut x: F) -> F { + // Faster than calling x.pow(SC::PERM_SBOX) + + if SC::PERM_SBOX == 7 { + let mut res = x.square(); + res *= x; + let res = res.square(); + res * x + } else { + let a = x; + for _ in 0..SC::PERM_SBOX - 1 { + x.mul_assign(a); + } + x + } + // x.pow([SC::PERM_SBOX as u64]) +} + +#[derive(Clone, Debug)] +pub enum SpongeState { + Absorbed(usize), + Squeezed(usize), +} + +#[derive(Debug)] +pub struct SpongeParams { + pub round_constants: Box<[[F; 3]]>, + pub mds: [[F; 3]; 3], +} + +pub trait SpongeParamsForField { + fn get_params() -> &'static SpongeParams; +} + +impl SpongeParamsForField for Fp { + fn get_params() -> &'static SpongeParams { + fp::params() + } +} + +impl SpongeParamsForField for Fq { + fn get_params() -> &'static SpongeParams { + fq::params() + } +} + +#[derive(Clone)] +pub struct Sponge { + pub sponge_state: SpongeState, + rate: usize, + pub state: [F; 3], + params: &'static SpongeParams, + constants: PhantomData, +} + +impl, C: SpongeConstants> Default for Sponge { + fn default() -> Self { + Self::new_with_params(F::get_params()) + } +} + +impl, C: SpongeConstants> Sponge { + pub fn new_with_params(params: &'static SpongeParams) -> Sponge { + Sponge { + state: [F::zero(); 3], + rate: C::SPONGE_RATE, + sponge_state: SpongeState::Absorbed(0), + params, + constants: PhantomData, + } + } + + pub fn absorb(&mut self, x: &[F]) { + if x.is_empty() { + // Same as the loop below but doesn't add `x` + match self.sponge_state { + SpongeState::Absorbed(n) => { + if n == self.rate { + self.poseidon_block_cipher(); + self.sponge_state = SpongeState::Absorbed(1); + } else { + self.sponge_state = SpongeState::Absorbed(n + 1); + } + } + SpongeState::Squeezed(_n) => { + self.sponge_state = SpongeState::Absorbed(1); + } + } + return; + } + for x in x.iter() { + match self.sponge_state { + SpongeState::Absorbed(n) => { + if n == self.rate { + self.poseidon_block_cipher(); + self.sponge_state = SpongeState::Absorbed(1); + self.state[0].add_assign(x); + } else { + self.sponge_state = SpongeState::Absorbed(n + 1); + self.state[n].add_assign(x); + } + } + SpongeState::Squeezed(_n) => { + self.state[0].add_assign(x); + self.sponge_state = SpongeState::Absorbed(1); + } + } + } + } + + pub fn squeeze(&mut self) -> F { + match self.sponge_state { + SpongeState::Squeezed(n) => { + if n == self.rate { + self.poseidon_block_cipher(); + self.sponge_state = SpongeState::Squeezed(1); + self.state[0] + } else { + self.sponge_state = SpongeState::Squeezed(n + 1); + self.state[n] + } + } + SpongeState::Absorbed(_n) => { + self.poseidon_block_cipher(); + self.sponge_state = SpongeState::Squeezed(1); + self.state[0] + } + } + } + + fn poseidon_block_cipher(&mut self) { + poseidon_block_cipher::(self.params, &mut self.state); + } +} + +impl Sponge { + pub fn new_legacy() -> Self { + use params::fp_legacy::params; + Sponge::::new_with_params(params()) + } +} + +#[derive(Clone)] +pub struct FqSponge { + sponge: Sponge, + last_squeezed: Vec, +} + +impl + Into> Default for FqSponge { + fn default() -> Self { + Self { + sponge: Sponge::default(), + last_squeezed: Vec::with_capacity(8), + } + } +} + +impl + Into> FqSponge { + pub fn absorb_fq(&mut self, x: &[F]) { + self.last_squeezed.clear(); + for fe in x { + self.sponge.absorb(&[*fe]) + } + } + + pub fn squeeze_limbs(&mut self) -> [u64; NUM_LIMBS] { + const HIGH_ENTROPY_LIMBS: usize = 2; + + if let Some(nremains) = self.last_squeezed.len().checked_sub(NUM_LIMBS) { + let limbs = std::array::from_fn(|i| self.last_squeezed[i]); + + self.last_squeezed.copy_within(NUM_LIMBS.., 0); + self.last_squeezed.truncate(nremains); + + limbs + } else { + let x: BigInteger256 = self.sponge.squeeze().into(); + let x: [u64; 4] = x.to_64x4(); + self.last_squeezed + .extend(&x.as_ref()[0..HIGH_ENTROPY_LIMBS]); + self.squeeze_limbs::() + } + } +} diff --git a/poseidon/src/params.rs b/poseidon/src/params.rs new file mode 100644 index 0000000000..05d449cf4e --- /dev/null +++ b/poseidon/src/params.rs @@ -0,0 +1,3127 @@ +use std::str::FromStr; + +use mina_curves::pasta::{Fp, Fq}; +use once_cell::sync::Lazy; + +use crate::SpongeParams; + +pub mod fq { + use super::*; + + fn make_params() -> SpongeParams { + SpongeParams { + mds: [ + [ + Fq::from_str( + "28115781186772277486790024060542467295096710153315236019619365740021995624782", + ) + .unwrap(), + Fq::from_str( + "22098002279041163367053200604969603243328318626084412751290336872362628294144", + ) + .unwrap(), + Fq::from_str( + "10518156075882958317589806716220047551309200159506906232124952575033472931386", + ) + .unwrap(), + ], + [ + Fq::from_str( + "8515206633865386306014865142947895502833797732365705727001733785057042819852", + ) + .unwrap(), + Fq::from_str( + "19310731234716792175834594131802557577955166208124819468043130037927500684373", + ) + .unwrap(), + Fq::from_str( + "361439796332338311597104753147071943681730695313819021679602959964518909239", + ) + .unwrap(), + ], + [ + Fq::from_str( + "2193808570710678216879007026210418088296432071066284289131688133644970611483", + ) + .unwrap(), + Fq::from_str( + "1201496953174589855481629688627002262719699487577300614284420648015658009380", + ) + .unwrap(), + Fq::from_str( + "11619800255560837597192574795389782851917036920101027584480912719351481334717", + ) + .unwrap(), + ], + ], + round_constants: Box::new([ + [ + Fq::from_str( + "2517640872121921965298496967863234221143680281046699148760560696057284005606", + ) + .unwrap(), + Fq::from_str( + "3391756047431116221709518926936538303706203177575259437741546230828058541679", + ) + .unwrap(), + Fq::from_str( + "28193080211857729746868575888309975056941007202713113547154010421664334143056", + ) + .unwrap(), + ], + [ + Fq::from_str( + "25261619184426186938919514618416881383323154981235406731208902193655587998749", + ) + .unwrap(), + Fq::from_str( + "5438499261516835502981531641588657477212528137520578797088407969732830437134", + ) + .unwrap(), + Fq::from_str( + "1447697894671779324954748568939217281372628544919576009518449387265606369859", + ) + .unwrap(), + ], + [ + Fq::from_str( + "5035532530235542599906399941203951970682478985022204457211063504597080640029", + ) + .unwrap(), + Fq::from_str( + "18548939393800290417015907795270784249198528773378593112394621615021029911007", + ) + .unwrap(), + Fq::from_str( + "28314657632459005492203969796973258399484591559931227050853551342156833947891", + ) + .unwrap(), + ], + [ + Fq::from_str( + "10075465805557971120845970058070916255338843492716768289922460436606689369477", + ) + .unwrap(), + Fq::from_str( + "21985996556868691161386211003270106475915714625334030557267947035839814254081", + ) + .unwrap(), + Fq::from_str( + "9778523497398309788873186849997676949503189428912377745814036481347657299161", + ) + .unwrap(), + ], + [ + Fq::from_str( + "6085447467925843146276340167082679235758707259098174769103982431882228334038", + ) + .unwrap(), + Fq::from_str( + "11214803418623679719680560978819619149235769633101428825693192995405955507848", + ) + .unwrap(), + Fq::from_str( + "20585482519401972421539035665320299097144487427998598740316244173221216198246", + ) + .unwrap(), + ], + [ + Fq::from_str( + "18602266896623204184748247002001496873223612100325866696399863661914256384486", + ) + .unwrap(), + Fq::from_str( + "22165919841309962137671309308234475433816142848229812860682345190836583925843", + ) + .unwrap(), + Fq::from_str( + "22833505632200982123686653495190412951871851216487329681987951602744930627412", + ) + .unwrap(), + ], + [ + Fq::from_str( + "200996541962081036547810490655955282117589336000744078845964972887355639644", + ) + .unwrap(), + Fq::from_str( + "17159390488590225463405148524511348095493761844950655304775985535830170165304", + ) + .unwrap(), + Fq::from_str( + "7519689807382250126180254188667761476713509751388558140260305473388567529705", + ) + .unwrap(), + ], + [ + Fq::from_str( + "14159331841037307097148990917607709903712709092721125605507719995418592745663", + ) + .unwrap(), + Fq::from_str( + "10490695046555645615062072066940833278139280813429718770298136076375411280286", + ) + .unwrap(), + Fq::from_str( + "9996921069626538041923613626115903019578182147993504053879837245826104687293", + ) + .unwrap(), + ], + [ + Fq::from_str( + "28009241574980093348462093077828465154604666812509186537490618830383877236685", + ) + .unwrap(), + Fq::from_str( + "18925279443828804264179873719494108834579217607847079902207023181925588871175", + ) + .unwrap(), + Fq::from_str( + "13126164514615718686767880517156253918404905174962666942976286681458411835722", + ) + .unwrap(), + ], + [ + Fq::from_str( + "1125667389564136291825905670957082668987611691949011617627091942772124917554", + ) + .unwrap(), + Fq::from_str( + "12737072162917928935765906421286553437026542524142430058538254259863452556200", + ) + .unwrap(), + Fq::from_str( + "9855113244149548216327019561589719324434080884827484555441182992249251832158", + ) + .unwrap(), + ], + [ + Fq::from_str( + "6006604346195593001833550983798183088851044846011297061071167569148810544010", + ) + .unwrap(), + Fq::from_str( + "23783465709464699444911580329342599880163107932561352210466223087637763994288", + ) + .unwrap(), + Fq::from_str( + "1581060363083815351710754851350813999229829634252940169154424073664057276774", + ) + .unwrap(), + ], + [ + Fq::from_str( + "24121961545310887440574053281799796355427122479626872394472157625455666323022", + ) + .unwrap(), + Fq::from_str( + "23925781309638869606256007860000699567158045595326122474217734988331349678475", + ) + .unwrap(), + Fq::from_str( + "433512980570318160778040929743715681206456334448542248765142091911433454703", + ) + .unwrap(), + ], + [ + Fq::from_str( + "8080307140515367021419180108267113624095868360927897204642243727009503935719", + ) + .unwrap(), + Fq::from_str( + "13661807750191096117929173962837770733539092996971801228126331071941306856508", + ) + .unwrap(), + Fq::from_str( + "9268394414065063505331314418649987795374055416089324253185088859000252370756", + ) + .unwrap(), + ], + [ + Fq::from_str( + "22374115023493407761095751712373350824513305398485824175669182288521610150311", + ) + .unwrap(), + Fq::from_str( + "22951274634403942446739133926874770994604864227598567536319143390467218980824", + ) + .unwrap(), + Fq::from_str( + "21411532836345163980832919797897483979345524322135010935120723250070247464549", + ) + .unwrap(), + ], + [ + Fq::from_str( + "20688285497159372157224857370703211924056803904697620218749985029000049442943", + ) + .unwrap(), + Fq::from_str( + "8350087190167057556241775495760369408781696125331535735138679647687106863977", + ) + .unwrap(), + Fq::from_str( + "13485893160159637778707269611856683957779710980787754997470728774769162419576", + ) + .unwrap(), + ], + [ + Fq::from_str( + "4621792784192688819920303666439776744566536330750316034321950771579978771021", + ) + .unwrap(), + Fq::from_str( + "13900656491552343190424687336475573267660717627286734246676255663734655019912", + ) + .unwrap(), + Fq::from_str( + "16577037405341365304416318048187907895286388691199320947077947552959834207823", + ) + .unwrap(), + ], + [ + Fq::from_str( + "17453637937712580666297652202332273322112052411250919589546137386514183913993", + ) + .unwrap(), + Fq::from_str( + "9852736110707561006399582579453396957225552488023642073454517393228764176471", + ) + .unwrap(), + Fq::from_str( + "8053970357622019747109700798952789019805031210730923951116580579194625334710", + ) + .unwrap(), + ], + [ + Fq::from_str( + "14566849926060034944494603512439278530775668595134329897253012222562109882008", + ) + .unwrap(), + Fq::from_str( + "8863944349051942080060073891691580009950648437676309749771884964336231381737", + ) + .unwrap(), + Fq::from_str( + "16455762285584757654310476505019438984453107876908065440396394186006196612077", + ) + .unwrap(), + ], + [ + Fq::from_str( + "28098375311516838082882166381119795701982164671360574802728073046992978741339", + ) + .unwrap(), + Fq::from_str( + "13538346067341652694825445642847479918140731375902310280683284825070643960891", + ) + .unwrap(), + Fq::from_str( + "18313412784975078534612748781201087502203257054025866271209086293337241477805", + ) + .unwrap(), + ], + [ + Fq::from_str( + "24807061345703288899043018750567607387907450632666147403804744880717736838940", + ) + .unwrap(), + Fq::from_str( + "16638378638176552952794487891875614248110181610295183306789394461536640085108", + ) + .unwrap(), + Fq::from_str( + "2342874860138849081032934096750004917991517717553229739958552529472431319656", + ) + .unwrap(), + ], + [ + Fq::from_str( + "21631810094765090996871180483650934431972930909326270651252393395613356531282", + ) + .unwrap(), + Fq::from_str( + "2220759912186713489010197903069023809260408491503960321105305330086947471014", + ) + .unwrap(), + Fq::from_str( + "14815764944505758746761442212662459585220143243155504464852948007238083120696", + ) + .unwrap(), + ], + [ + Fq::from_str( + "23947619952183462858644581465494050309407721428302029371055887418452994318961", + ) + .unwrap(), + Fq::from_str( + "25035254658153233628169609451068923631269927394392748023889572264723092874720", + ) + .unwrap(), + Fq::from_str( + "17468020412163678868776493601957969748197290347006692843306595815987772942732", + ) + .unwrap(), + ], + [ + Fq::from_str( + "15262198027618900223004625662874755104828479630165814039838611768431063172994", + ) + .unwrap(), + Fq::from_str( + "25161066724266754383358798644805908588326959881061318668106454787543611445887", + ) + .unwrap(), + Fq::from_str( + "2454250001039770891411267760383268680504653332090622148533496270387793031332", + ) + .unwrap(), + ], + [ + Fq::from_str( + "9171946491887082474979985164918822959719377078284664312866368737511724712644", + ) + .unwrap(), + Fq::from_str( + "6672870238005411132577302023934139592378291207852994424857452575898007687159", + ) + .unwrap(), + Fq::from_str( + "2950400608762766076731526167833938554190979516192019010641815746350334547745", + ) + .unwrap(), + ], + [ + Fq::from_str( + "10653725154501691589476837895400001173933804810435931645261606197625601363132", + ) + .unwrap(), + Fq::from_str( + "12717400214508961810851553873706609743505640660238109459222577386574996883747", + ) + .unwrap(), + Fq::from_str( + "5871058785976817081042949511195036111847495052209270758342334312740290470200", + ) + .unwrap(), + ], + [ + Fq::from_str( + "18192562665205900830717234913238180302424621739145466326708104656354353538015", + ) + .unwrap(), + Fq::from_str( + "19946412409172091711185698839696950657650658896270607012902209489827790455314", + ) + .unwrap(), + Fq::from_str( + "21997416257528392077410699901606794827305154904508120972585193876767785262539", + ) + .unwrap(), + ], + [ + Fq::from_str( + "16525092684784199198745517563091041705366544303388462641935777835264970071331", + ) + .unwrap(), + Fq::from_str( + "27613372589672512522307803997948488817865025374001297632527692577079750053456", + ) + .unwrap(), + Fq::from_str( + "23369674747888778238616865774843237791546925005553032792584302158017141634655", + ) + .unwrap(), + ], + [ + Fq::from_str( + "11012136308159330675912474383855146192700147583104742924419195363346115019405", + ) + .unwrap(), + Fq::from_str( + "20632243971343595216801828590185617698839041744000918292113739726624680548813", + ) + .unwrap(), + Fq::from_str( + "10530371852841765918702282883445676639977895775479854136871270050807595649710", + ) + .unwrap(), + ], + [ + Fq::from_str( + "1610594053831245596683250788274018471388810111366046583216577135605955718023", + ) + .unwrap(), + Fq::from_str( + "452300846172044702598793611907955884294868639769163388132276731316720796255", + ) + .unwrap(), + Fq::from_str( + "22297945145153422883128810575530182077542612397826351322358420927950400316504", + ) + .unwrap(), + ], + [ + Fq::from_str( + "28212510899948152845929142163236606049756849316851154583029383581129293825706", + ) + .unwrap(), + Fq::from_str( + "28325924586146971645663587791728624896861517146549428987043066595915712075981", + ) + .unwrap(), + Fq::from_str( + "23489013325315178311518261165509151135555509351661386106070231815049642443022", + ) + .unwrap(), + ], + [ + Fq::from_str( + "10150108696154604591036176090028652090941375062280095655463112192524823306544", + ) + .unwrap(), + Fq::from_str( + "14935856239824547404885450872472169780177654619496758596151670953532153419587", + ) + .unwrap(), + Fq::from_str( + "4367251608666794961207658726914177158125339342277880902441218521648798930454", + ) + .unwrap(), + ], + [ + Fq::from_str( + "14278046449956534912766622635951826857049583276976844525135170835571509013020", + ) + .unwrap(), + Fq::from_str( + "11627801940273881243235293875277734806211947530882079339115454640100174268255", + ) + .unwrap(), + Fq::from_str( + "22853853581419894582873479603685652928885253184240650995805892818180355600894", + ) + .unwrap(), + ], + [ + Fq::from_str( + "4405193089432137585625363585733613667088817369599257533888439029942466720878", + ) + .unwrap(), + Fq::from_str( + "26434497741746827048559732407319982377645052620918789373329661707603241810667", + ) + .unwrap(), + Fq::from_str( + "23558650878002025381506445692526977061352711282820117441110868042756853707843", + ) + .unwrap(), + ], + [ + Fq::from_str( + "27427423077748345654234924309581695092179468167973406115643356520054395647078", + ) + .unwrap(), + Fq::from_str( + "17585801825757985265979208086560185342609289319992678737491966299829354657891", + ) + .unwrap(), + Fq::from_str( + "22079131836316223121286612953926945430480043835170303484162677394496378207190", + ) + .unwrap(), + ], + [ + Fq::from_str( + "20126865597655889981803452476686954944892814234259869552204215672627920656068", + ) + .unwrap(), + Fq::from_str( + "5591585339015997308682985123056479221565470335707041924016523106405300562835", + ) + .unwrap(), + Fq::from_str( + "9422316572086279209843572429137982927615080330725918371521370800874341571474", + ) + .unwrap(), + ], + [ + Fq::from_str( + "2735677349719528139570614238939713941030373684882307164259316901880218894412", + ) + .unwrap(), + Fq::from_str( + "16229147459127626384090303399894157248853232127961182470501666316464149067069", + ) + .unwrap(), + Fq::from_str( + "17151067888069760812629817914442472623785916486309268828873486698948911058517", + ) + .unwrap(), + ], + [ + Fq::from_str( + "13833972862865550568348750465964022581895521701070662509936215512761615491351", + ) + .unwrap(), + Fq::from_str( + "9624679817699048440664645568701817641311119158936258215534754849666144699339", + ) + .unwrap(), + Fq::from_str( + "10273179847163882031630140477902608240997857384703412878925192706057610103613", + ) + .unwrap(), + ], + [ + Fq::from_str( + "3172037826021850467928085880043492158321918352296515787555947245998877188849", + ) + .unwrap(), + Fq::from_str( + "28890802281119993101506497911757988639840653958256859430239635494708187190915", + ) + .unwrap(), + Fq::from_str( + "23496953773368274731821824281559682992786773767847557735733251263969009271239", + ) + .unwrap(), + ], + [ + Fq::from_str( + "1509044982655321910215442389040863370827049078919961070795919190828975736187", + ) + .unwrap(), + Fq::from_str( + "13927172650979098916742472053302036482743492746437467103459483008024082210879", + ) + .unwrap(), + Fq::from_str( + "17248379591027039069313293591621091031164062825086122980769287846951363066520", + ) + .unwrap(), + ], + [ + Fq::from_str( + "11350333545134487336540967650634077894516131586708748380417042089147896079201", + ) + .unwrap(), + Fq::from_str( + "639497848254405996993150855123515463224731962182127668267769103213580096582", + ) + .unwrap(), + Fq::from_str( + "24528361599642320451530127347946798949257664936307333999618279589325586618880", + ) + .unwrap(), + ], + [ + Fq::from_str( + "8217015496508457685301448884203977810298711070026260090660268003968421268717", + ) + .unwrap(), + Fq::from_str( + "6703444480721420507060701216472376128524677965704475494357937059812166295103", + ) + .unwrap(), + Fq::from_str( + "8051365375874262471960241848873604339195556527603956582828833313772444122472", + ) + .unwrap(), + ], + [ + Fq::from_str( + "10412735174026641936105532807659667596947675372330827493649954160029449767122", + ) + .unwrap(), + Fq::from_str( + "8447576362386697729021229138353952824970707645851763166490398451107606293885", + ) + .unwrap(), + Fq::from_str( + "4802965296970904162106502573136505305073730277702271660292532219583823320181", + ) + .unwrap(), + ], + [ + Fq::from_str( + "3244354881334856885788568976540712586633556478250043997221528214026130052269", + ) + .unwrap(), + Fq::from_str( + "817270901440592571623549787267103386561304980129799240746702119063425010300", + ) + .unwrap(), + Fq::from_str( + "6566338353152134577893356938981496347522747926131278635019050445923229718029", + ) + .unwrap(), + ], + [ + Fq::from_str( + "4854521709622003124815206874897232905514824969466266873443062691298769768277", + ) + .unwrap(), + Fq::from_str( + "12830134034124699064152980183243986699241944691238427861184919962819448276943", + ) + .unwrap(), + Fq::from_str( + "24309439157688106320977023683093060719537142150089588950480669629964661236785", + ) + .unwrap(), + ], + [ + Fq::from_str( + "1853791709949511636795588377016980571084333441972847324139062389997895453872", + ) + .unwrap(), + Fq::from_str( + "11399505004623970417786749745036397690793259153591025248188283534764565207306", + ) + .unwrap(), + Fq::from_str( + "6280235834578097246976697944083887557501831809932305676532914637669922657807", + ) + .unwrap(), + ], + [ + Fq::from_str( + "1516294190187225192808636261678393666537186816904214776860202535671714230097", + ) + .unwrap(), + Fq::from_str( + "5835813607391397757416951433662507638966861369364000865214031356023042341328", + ) + .unwrap(), + Fq::from_str( + "25777313996516799380163546628133415256678997511953860435781885414872422583905", + ) + .unwrap(), + ], + [ + Fq::from_str( + "9749298878960864917089442034293906589697892682402070689770627645324414273893", + ) + .unwrap(), + Fq::from_str( + "19986612197193695239708718365565978831607994386509967951279410162135133793419", + ) + .unwrap(), + Fq::from_str( + "5020585421647265067890838871263925730422335215511670656851726444447972642755", + ) + .unwrap(), + ], + [ + Fq::from_str( + "7256822974971238434100017358319972368738353570339258522235883585691301791128", + ) + .unwrap(), + Fq::from_str( + "9789139064283320903202623693175751994730652446378861671859478926598420184293", + ) + .unwrap(), + Fq::from_str( + "19283468246375057076525422714896652730563534118070235174488237489890270899533", + ) + .unwrap(), + ], + [ + Fq::from_str( + "11487321478704551489982188818171823402443882145686911658585221913500937481156", + ) + .unwrap(), + Fq::from_str( + "16513958012405406860890342996091255867910990589443610357743227675107758695101", + ) + .unwrap(), + Fq::from_str( + "24764429351173766080138047602436205744310671344674490826288279531917797263231", + ) + .unwrap(), + ], + [ + Fq::from_str( + "8256258316375000496541664568891934707113720493937218096466691600593595285909", + ) + .unwrap(), + Fq::from_str( + "26919625894863883593081175799908601863265420311251948374988589188905317081443", + ) + .unwrap(), + Fq::from_str( + "10135851848127171199130812615581006825969108287418884763125596866448544567342", + ) + .unwrap(), + ], + [ + Fq::from_str( + "17567146349912867622479843655652582453162587996421871126612027345809646551661", + ) + .unwrap(), + Fq::from_str( + "2524802431860351616270075327416865184018211992251290134350377936184047953453", + ) + .unwrap(), + Fq::from_str( + "3417609143162661859785838333493682460709943782149216513733553607075915176256", + ) + .unwrap(), + ], + [ + Fq::from_str( + "6906455011502599710165862205505812668908382042647994457156780865092846286493", + ) + .unwrap(), + Fq::from_str( + "21042097659487317081899343674473811663642293019125869396575405454328274948985", + ) + .unwrap(), + Fq::from_str( + "25222370053690749913129090298406788520061040938312366403907461864202905656238", + ) + .unwrap(), + ], + [ + Fq::from_str( + "18933201791079410639949505893100361911334261775545573219434897335758052335005", + ) + .unwrap(), + Fq::from_str( + "14503331557348715387048413780116585195932777696828173626366829282421027153184", + ) + .unwrap(), + Fq::from_str( + "3558781473325529402549318082942465709639711182863041375748599816583729962116", + ) + .unwrap(), + ], + [ + Fq::from_str( + "23932570601084008621895097434501731960424360312878373523779451810455362953625", + ) + .unwrap(), + Fq::from_str( + "13286131463754478912858022007443470896920464302917391606059553157137090717219", + ) + .unwrap(), + Fq::from_str( + "9969435194445819847988134248075866286921574284754991873902788928171429847506", + ) + .unwrap(), + ], + [ + Fq::from_str( + "10821551500865029673311799086099720530496516676117927814621168667836737594374", + ) + .unwrap(), + Fq::from_str( + "57689402905128519605376551862931564078571458212398163192591670282543962941", + ) + .unwrap(), + Fq::from_str( + "4484359679395800410695081358212522306960518636189521201445105538223906998486", + ) + .unwrap(), + ], + ]), + } + } + + pub fn params() -> &'static SpongeParams { + static PARAMS: Lazy> = Lazy::new(make_params); + &PARAMS + } +} + +pub mod fp { + use super::*; + + fn make_params() -> SpongeParams { + SpongeParams { + mds: [ + [ + Fp::from_str( + "12035446894107573964500871153637039653510326950134440362813193268448863222019", + ) + .unwrap(), + Fp::from_str( + "25461374787957152039031444204194007219326765802730624564074257060397341542093", + ) + .unwrap(), + Fp::from_str( + "27667907157110496066452777015908813333407980290333709698851344970789663080149", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4491931056866994439025447213644536587424785196363427220456343191847333476930", + ) + .unwrap(), + Fp::from_str( + "14743631939509747387607291926699970421064627808101543132147270746750887019919", + ) + .unwrap(), + Fp::from_str( + "9448400033389617131295304336481030167723486090288313334230651810071857784477", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10525578725509990281643336361904863911009900817790387635342941550657754064843", + ) + .unwrap(), + Fp::from_str( + "27437632000253211280915908546961303399777448677029255413769125486614773776695", + ) + .unwrap(), + Fp::from_str( + "27566319851776897085443681456689352477426926500749993803132851225169606086988", + ) + .unwrap(), + ], + ], + + round_constants: Box::new([ + [ + Fp::from_str( + "21155079691556475130150866428468322463125560312786319980770950159250751855431", + ) + .unwrap(), + Fp::from_str( + "16883442198399350202652499677723930673110172289234921799701652810789093522349", + ) + .unwrap(), + Fp::from_str( + "17030687036425314703519085065002231920937594822150793091243263847382891822670", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25216718237129482752721276445368692059997901880654047883630276346421457427360", + ) + .unwrap(), + Fp::from_str( + "9054264347380455706540423067244764093107767235485930776517975315876127782582", + ) + .unwrap(), + Fp::from_str( + "26439087121446593160953570192891907825526260324480347638727375735543609856888", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15251000790817261169639394496851831733819930596125214313084182526610855787494", + ) + .unwrap(), + Fp::from_str( + "10861916012597714684433535077722887124099023163589869801449218212493070551767", + ) + .unwrap(), + Fp::from_str( + "18597653523270601187312528478986388028263730767495975370566527202946430104139", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15831416454198644276563319006805490049460322229057756462580029181847589006611", + ) + .unwrap(), + Fp::from_str( + "15171856919255965617705854914448645702014039524159471542852132430360867202292", + ) + .unwrap(), + Fp::from_str( + "15488495958879593647482715143904752785889816789652405888927117106448507625751", + ) + .unwrap(), + ], + [ + Fp::from_str( + "19039802679983063488134304670998725949842655199289961967801223969839823940152", + ) + .unwrap(), + Fp::from_str( + "4720101937153217036737330058775388037616286510783561045464678919473230044408", + ) + .unwrap(), + Fp::from_str( + "10226318327254973427513859412126640040910264416718766418164893837597674300190", + ) + .unwrap(), + ], + [ + Fp::from_str( + "20878756131129218406920515859235137275859844638301967889441262030146031838819", + ) + .unwrap(), + Fp::from_str( + "7178475685651744631172532830973371642652029385893667810726019303466125436953", + ) + .unwrap(), + Fp::from_str( + "1996970955918516145107673266490486752153434673064635795711751450164177339618", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15205545916434157464929420145756897321482314798910153575340430817222504672630", + ) + .unwrap(), + Fp::from_str( + "25660296961552699573824264215804279051322332899472350724416657386062327210698", + ) + .unwrap(), + Fp::from_str( + "13842611741937412200312851417353455040950878279339067816479233688850376089318", + ) + .unwrap(), + ], + [ + Fp::from_str( + "1383799642177300432144836486981606294838630135265094078921115713566691160459", + ) + .unwrap(), + Fp::from_str( + "1135532281155277588005319334542025976079676424839948500020664227027300010929", + ) + .unwrap(), + Fp::from_str( + "4384117336930380014868572224801371377488688194169758696438185377724744869360", + ) + .unwrap(), + ], + [ + Fp::from_str( + "21725577575710270071808882335900370909424604447083353471892004026180492193649", + ) + .unwrap(), + Fp::from_str( + "676128913284806802699862508051022306366147359505124346651466289788974059668", + ) + .unwrap(), + Fp::from_str( + "25186611339598418732666781049829183886812651492845008333418424746493100589207", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10402240124664763733060094237696964473609580414190944671778761753887884341073", + ) + .unwrap(), + Fp::from_str( + "11918307118590866200687906627767559273324023585642003803337447146531313172441", + ) + .unwrap(), + Fp::from_str( + "16895677254395661024186292503536662354181715337630376909778003268311296637301", + ) + .unwrap(), + ], + [ + Fp::from_str( + "23818602699032741669874498456696325705498383130221297580399035778119213224810", + ) + .unwrap(), + Fp::from_str( + "4285193711150023248690088154344086684336247475445482883105661485741762600154", + ) + .unwrap(), + Fp::from_str( + "19133204443389422404056150665863951250222934590192266371578950735825153238612", + ) + .unwrap(), + ], + [ + Fp::from_str( + "5515589673266504033533906836494002702866463791762187140099560583198974233395", + ) + .unwrap(), + Fp::from_str( + "11830435563729472715615302060564876527985621376031612798386367965451821182352", + ) + .unwrap(), + Fp::from_str( + "7510711479224915247011074129666445216001563200717943545636462819681638560128", + ) + .unwrap(), + ], + [ + Fp::from_str( + "24694843201907722940091503626731830056550128225297370217610328578733387733444", + ) + .unwrap(), + Fp::from_str( + "27361655066973784653563425664091383058914302579694897188019422193564924110528", + ) + .unwrap(), + Fp::from_str( + "21606788186194534241166833954371013788633495786419718955480491478044413102713", + ) + .unwrap(), + ], + [ + Fp::from_str( + "19934060063390905409309407607814787335159021816537006003398035237707924006757", + ) + .unwrap(), + Fp::from_str( + "8495813630060004961768092461554180468161254914257386012937942498774724649553", + ) + .unwrap(), + Fp::from_str( + "27524960680529762202005330464726908693944660961000958842417927307941561848461", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15178481650950399259757805400615635703086255035073919114667254549690862896985", + ) + .unwrap(), + Fp::from_str( + "16164780354695672259791105197274509251141405713012804937107314962551600380870", + ) + .unwrap(), + Fp::from_str( + "10529167793600778056702353412758954281652843049850979705476598375597148191979", + ) + .unwrap(), + ], + [ + Fp::from_str( + "721141070179074082553302896292167103755384741083338957818644728290501449040", + ) + .unwrap(), + Fp::from_str( + "22044408985956234023934090378372374883099115753118261312473550998188148912041", + ) + .unwrap(), + Fp::from_str( + "27068254103241989852888872162525066148367014691482601147536314217249046186315", + ) + .unwrap(), + ], + [ + Fp::from_str( + "3880429241956357176819112098792744584376727450211873998699580893624868748961", + ) + .unwrap(), + Fp::from_str( + "17387097125522937623262508065966749501583017524609697127088211568136333655623", + ) + .unwrap(), + Fp::from_str( + "6256814421247770895467770393029354017922744712896100913895513234184920631289", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2942627347777337187690939671601251987500285937340386328746818861972711408579", + ) + .unwrap(), + Fp::from_str( + "24031654937764287280548628128490074801809101323243546313826173430897408945397", + ) + .unwrap(), + Fp::from_str( + "14401457902976567713827506689641442844921449636054278900045849050301331732143", + ) + .unwrap(), + ], + [ + Fp::from_str( + "20170632877385406450742199836933900257692624353889848352407590794211839130727", + ) + .unwrap(), + Fp::from_str( + "24056496193857444725324410428861722338174099794084586764867109123681727290181", + ) + .unwrap(), + Fp::from_str( + "11257913009612703357266904349759250619633397075667824800196659858304604714965", + ) + .unwrap(), + ], + [ + Fp::from_str( + "22228158921984425749199071461510152694025757871561406897041788037116931009246", + ) + .unwrap(), + Fp::from_str( + "9152163378317846541430311327336774331416267016980485920222768197583559318682", + ) + .unwrap(), + Fp::from_str( + "13906695403538884432896105059360907560653506400343268230130536740148070289175", + ) + .unwrap(), + ], + [ + Fp::from_str( + "7220714562509721437034241786731185291972496952091254931195414855962344025067", + ) + .unwrap(), + Fp::from_str( + "27608867305903811397208862801981345878179337369367554478205559689592889691927", + ) + .unwrap(), + Fp::from_str( + "13288465747219756218882697408422850918209170830515545272152965967042670763153", + ) + .unwrap(), + ], + [ + Fp::from_str( + "8251343892709140154567051772980662609566359215743613773155065627504813327653", + ) + .unwrap(), + Fp::from_str( + "22035238365102171608166944627493632660244312563934708756134297161332908879090", + ) + .unwrap(), + Fp::from_str( + "13560937766273321037807329177749403409731524715067067740487246745322577571823", + ) + .unwrap(), + ], + [ + Fp::from_str( + "21652518608959234550262559135285358020552897349934571164032339186996805408040", + ) + .unwrap(), + Fp::from_str( + "22479086963324173427634460342145551255011746993910136574926173581069603086891", + ) + .unwrap(), + Fp::from_str( + "13676501958531751140966255121288182631772843001727158043704693838707387130095", + ) + .unwrap(), + ], + [ + Fp::from_str( + "5680310394102577950568930199056707827608275306479994663197187031893244826674", + ) + .unwrap(), + Fp::from_str( + "25125360450906166639190392763071557410047335755341060350879819485506243289998", + ) + .unwrap(), + Fp::from_str( + "22659254028501616785029594492374243581602744364859762239504348429834224676676", + ) + .unwrap(), + ], + [ + Fp::from_str( + "23101411405087512171421838856759448177512679869882987631073569441496722536782", + ) + .unwrap(), + Fp::from_str( + "24149774013240355952057123660656464942409328637280437515964899830988178868108", + ) + .unwrap(), + Fp::from_str( + "5782097512368226173095183217893826020351125522160843964147125728530147423065", + ) + .unwrap(), + ], + [ + Fp::from_str( + "13540762114500083869920564649399977644344247485313990448129838910231204868111", + ) + .unwrap(), + Fp::from_str( + "20421637734328811337527547703833013277831804985438407401987624070721139913982", + ) + .unwrap(), + Fp::from_str( + "7742664118615900772129122541139124149525273579639574972380600206383923500701", + ) + .unwrap(), + ], + [ + Fp::from_str( + "1109643801053963021778418773196543643970146666329661268825691230294798976318", + ) + .unwrap(), + Fp::from_str( + "16580663920817053843121063692728699890952505074386761779275436996241901223840", + ) + .unwrap(), + Fp::from_str( + "14638514680222429058240285918830106208025229459346033470787111294847121792366", + ) + .unwrap(), + ], + [ + Fp::from_str( + "17080385857812672649489217965285727739557573467014392822992021264701563205891", + ) + .unwrap(), + Fp::from_str( + "26176268111736737558502775993925696791974738793095023824029827577569530708665", + ) + .unwrap(), + Fp::from_str( + "4382756253392449071896813428140986330161215829425086284611219278674857536001", + ) + .unwrap(), + ], + [ + Fp::from_str( + "13934033814940585315406666445960471293638427404971553891617533231178815348902", + ) + .unwrap(), + Fp::from_str( + "27054912732979753314774418228399230433963143177662848084045249524271046173121", + ) + .unwrap(), + Fp::from_str( + "28916070403698593376490976676534962592542013020010643734621202484860041243391", + ) + .unwrap(), + ], + [ + Fp::from_str( + "24820015636966360150164458094894587765384135259446295278101998130934963922381", + ) + .unwrap(), + Fp::from_str( + "7969535238488580655870884015145760954416088335296905520306227531221721881868", + ) + .unwrap(), + Fp::from_str( + "7690547696740080985104189563436871930607055124031711216224219523236060212249", + ) + .unwrap(), + ], + [ + Fp::from_str( + "9712576468091272384496248353414290908377825697488757134833205246106605867289", + ) + .unwrap(), + Fp::from_str( + "12148698031438398980683630141370402088785182722473169207262735228500190477924", + ) + .unwrap(), + Fp::from_str( + "14359657643133476969781351728574842164124292705609900285041476162075031948227", + ) + .unwrap(), + ], + [ + Fp::from_str( + "23563839965372067275137992801035780013422228997724286060975035719045352435470", + ) + .unwrap(), + Fp::from_str( + "4184634822776323233231956802962638484057536837393405750680645555481330909086", + ) + .unwrap(), + Fp::from_str( + "16249511905185772125762038789038193114431085603985079639889795722501216492487", + ) + .unwrap(), + ], + [ + Fp::from_str( + "11001863048692031559800673473526311616702863826063550559568315794438941516621", + ) + .unwrap(), + Fp::from_str( + "4702354107983530219070178410740869035350641284373933887080161024348425080464", + ) + .unwrap(), + Fp::from_str( + "23751680507533064238793742311430343910720206725883441625894258483004979501613", + ) + .unwrap(), + ], + [ + Fp::from_str( + "28670526516158451470169873496541739545860177757793329093045522432279094518766", + ) + .unwrap(), + Fp::from_str( + "3568312993091537758218792253361873752799472566055209125947589819564395417072", + ) + .unwrap(), + Fp::from_str( + "1819755756343439646550062754332039103654718693246396323207323333948654200950", + ) + .unwrap(), + ], + [ + Fp::from_str( + "5372129954699791301953948907349887257752247843844511069896766784624930478273", + ) + .unwrap(), + Fp::from_str( + "17512156688034945920605615850550150476471921176481039715733979181538491476080", + ) + .unwrap(), + Fp::from_str( + "25777105342317622165159064911913148785971147228777677435200128966844208883059", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25350392006158741749134238306326265756085455157012701586003300872637887157982", + ) + .unwrap(), + Fp::from_str( + "20096724945283767296886159120145376967480397366990493578897615204296873954844", + ) + .unwrap(), + Fp::from_str( + "8063283381910110762785892100479219642751540456251198202214433355775540036851", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4393613870462297385565277757207010824900723217720226130342463666351557475823", + ) + .unwrap(), + Fp::from_str( + "9874972555132910032057499689351411450892722671352476280351715757363137891038", + ) + .unwrap(), + Fp::from_str( + "23590926474329902351439438151596866311245682682435235170001347511997242904868", + ) + .unwrap(), + ], + [ + Fp::from_str( + "17723373371137275859467518615551278584842947963894791032296774955869958211070", + ) + .unwrap(), + Fp::from_str( + "2350345015303336966039836492267992193191479606566494799781846958620636621159", + ) + .unwrap(), + Fp::from_str( + "27755207882790211140683010581856487965587066971982625511152297537534623405016", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6584607987789185408123601849106260907671314994378225066806060862710814193906", + ) + .unwrap(), + Fp::from_str( + "609759108847171587253578490536519506369136135254150754300671591987320319770", + ) + .unwrap(), + Fp::from_str( + "28435187585965602110074342250910608316032945187476441868666714022529803033083", + ) + .unwrap(), + ], + [ + Fp::from_str( + "16016664911651770663938916450245705908287192964254704641717751103464322455303", + ) + .unwrap(), + Fp::from_str( + "17551273293154696089066968171579395800922204266630874071186322718903959339163", + ) + .unwrap(), + Fp::from_str( + "20414195497994754529479032467015716938594722029047207834858832838081413050198", + ) + .unwrap(), + ], + [ + Fp::from_str( + "19773307918850685463180290966774465805537520595602496529624568184993487593855", + ) + .unwrap(), + Fp::from_str( + "24598603838812162820757838364185126333280131847747737533989799467867231166980", + ) + .unwrap(), + Fp::from_str( + "11040972566103463398651864390163813377135738019556270484707889323659789290225", + ) + .unwrap(), + ], + [ + Fp::from_str( + "5189242080957784038860188184443287562488963023922086723850863987437818393811", + ) + .unwrap(), + Fp::from_str( + "1435203288979376557721239239445613396009633263160237764653161500252258220144", + ) + .unwrap(), + Fp::from_str( + "13066591163578079667911016543985168493088721636164837520689376346534152547210", + ) + .unwrap(), + ], + [ + Fp::from_str( + "17345901407013599418148210465150865782628422047458024807490502489711252831342", + ) + .unwrap(), + Fp::from_str( + "22139633362249671900128029132387275539363684188353969065288495002671733200348", + ) + .unwrap(), + Fp::from_str( + "1061056418502836172283188490483332922126033656372467737207927075184389487061", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10241738906190857416046229928455551829189196941239601756375665129874835232299", + ) + .unwrap(), + Fp::from_str( + "27808033332417845112292408673209999320983657696373938259351951416571545364415", + ) + .unwrap(), + Fp::from_str( + "18820154989873674261497645724903918046694142479240549687085662625471577737140", + ) + .unwrap(), + ], + [ + Fp::from_str( + "7983688435214640842673294735439196010654951226956101271763849527529940619307", + ) + .unwrap(), + Fp::from_str( + "17067928657801807648925755556866676899145460770352731818062909643149568271566", + ) + .unwrap(), + Fp::from_str( + "24472070825156236829515738091791182856425635433388202153358580534810244942762", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25752201169361795911258625731016717414310986450004737514595241038036936283227", + ) + .unwrap(), + Fp::from_str( + "26041505376284666160132119888949817249574689146924196064963008712979256107535", + ) + .unwrap(), + Fp::from_str( + "23977050489096115210391718599021827780049209314283111721864956071820102846008", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26678257097278788410676026718736087312816016749016738933942134600725962413805", + ) + .unwrap(), + Fp::from_str( + "10480026985951498884090911619636977502506079971893083605102044931823547311729", + ) + .unwrap(), + Fp::from_str( + "21126631300593007055117122830961273871167754554670317425822083333557535463396", + ) + .unwrap(), + ], + [ + Fp::from_str( + "1564862894215434177641156287699106659379648851457681469848362532131406827573", + ) + .unwrap(), + Fp::from_str( + "13247162472821152334486419054854847522301612781818744556576865965657773174584", + ) + .unwrap(), + Fp::from_str( + "8673615954922496961704442777870253767001276027366984739283715623634850885984", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2794525076937490807476666942602262298677291735723129868457629508555429470085", + ) + .unwrap(), + Fp::from_str( + "4656175953888995612264371467596648522808911819700660048695373348629527757049", + ) + .unwrap(), + Fp::from_str( + "23221574237857660318443567292601561932489621919104226163978909845174616477329", + ) + .unwrap(), + ], + [ + Fp::from_str( + "1878392460078272317716114458784636517603142716091316893054365153068227117145", + ) + .unwrap(), + Fp::from_str( + "2370412714505757731457251173604396662292063533194555369091306667486647634097", + ) + .unwrap(), + Fp::from_str( + "17409784861870189930766639925394191888667317762328427589153989811980152373276", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25869136641898166514111941708608048269584233242773814014385564101168774293194", + ) + .unwrap(), + Fp::from_str( + "11361209360311194794795494027949518465383235799633128250259863567683341091323", + ) + .unwrap(), + Fp::from_str( + "14913258820718821235077379851098720071902170702113538811112331615559409988569", + ) + .unwrap(), + ], + [ + Fp::from_str( + "12957012022018304419868287033513141736995211906682903915897515954290678373899", + ) + .unwrap(), + Fp::from_str( + "17128889547450684566010972445328859295804027707361763477802050112063630550300", + ) + .unwrap(), + Fp::from_str( + "23329219085372232771288306767242735245018143857623151155581182779769305489903", + ) + .unwrap(), + ], + [ + Fp::from_str( + "1607741027962933685476527275858938699728586794398382348454736018784568853937", + ) + .unwrap(), + Fp::from_str( + "2611953825405141009309433982109911976923326848135736099261873796908057448476", + ) + .unwrap(), + Fp::from_str( + "7372230383134982628913227482618052530364724821976589156840317933676130378411", + ) + .unwrap(), + ], + [ + Fp::from_str( + "20203606758501212620842735123770014952499754751430660463060696990317556818571", + ) + .unwrap(), + Fp::from_str( + "4678361398979174017885631008335559529633853759463947250620930343087749944307", + ) + .unwrap(), + Fp::from_str( + "27176462634198471376002287271754121925750749676999036165457559387195124025594", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6361981813552614697928697527332318530502852015189048838072565811230204474643", + ) + .unwrap(), + Fp::from_str( + "13815234633287489023151647353581705241145927054858922281829444557905946323248", + ) + .unwrap(), + Fp::from_str( + "10888828634279127981352133512429657747610298502219125571406085952954136470354", + ) + .unwrap(), + ], + ]), + } + } + + pub fn params() -> &'static SpongeParams { + static PARAMS: Lazy> = Lazy::new(make_params); + &PARAMS + } +} + +pub mod fp_legacy { + use super::*; + + fn make_params() -> SpongeParams { + SpongeParams { + mds: [ + [ + Fp::from_str( + "5328350144166205084223774245058198666309664348635459768305312917086056785354", + ) + .unwrap(), + Fp::from_str( + "15214731724107930304595906373487084110291887262136882623959435918484004667388", + ) + .unwrap(), + Fp::from_str( + "22399519358931858664262538157042328690232277435337286643350379269028878354609", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10086628405675314879458652402278736459294354590428582803795166650930540770072", + ) + .unwrap(), + Fp::from_str( + "17127968360683744052278857147989507037142007029142438136689352416106177192235", + ) + .unwrap(), + Fp::from_str( + "14207324749280135281015658576564097509614634975132487654324863824516044294735", + ) + .unwrap(), + ], + [ + Fp::from_str( + "3059104278162906687184746935153057867173086006783171716838577369156969739687", + ) + .unwrap(), + Fp::from_str( + "16755849208683706534025643823697988418063305979108082130624352443958404325985", + ) + .unwrap(), + Fp::from_str( + "16889774624482628108075965871448623911656600744832339664842346756371603433407", + ) + .unwrap(), + ], + ], + round_constants: Box::new([ + [ + Fp::from_str( + "1346081094044643970582493287085428191977688221215786919106342366360741041016", + ) + .unwrap(), + Fp::from_str( + "10635969173348128974923358283368657934408577270968219574411363948927109531877", + ) + .unwrap(), + Fp::from_str( + "18431955373344919956072236142080066866861234899777299873162413437379924987003", + ) + .unwrap(), + ], + [ + Fp::from_str( + "5797044060651575840084283729791357462720161727701814038830889113712361837236", + ) + .unwrap(), + Fp::from_str( + "931288489507796144596974766082847744938192694315568692730730202141894005205", + ) + .unwrap(), + Fp::from_str( + "13659894470945121760517769979107966886673294523737498361566285362771110125394", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6076231707445968054305995680347976771585015308155855387339303513025362636128", + ) + .unwrap(), + Fp::from_str( + "28822740034050339685362260108484262889265034407340240070058997651710236456303", + ) + .unwrap(), + Fp::from_str( + "23420266473857869790486107029614186913447272961845992963194006142267563993493", + ) + .unwrap(), + ], + [ + Fp::from_str( + "13753917374184785903125509246122783296344288469304898921025291716613575849357", + ) + .unwrap(), + Fp::from_str( + "22396739346703340038555577564698139382745239004673153148674304627904081092826", + ) + .unwrap(), + Fp::from_str( + "13064238335532551154986111986409392866270911640785653458047811526842088084911", + ) + .unwrap(), + ], + [ + Fp::from_str( + "23165923875642452719095776619341762858050322341374771345641255745672274104746", + ) + .unwrap(), + Fp::from_str( + "1876216571769482372914291210815859835162659440705283782713345335434924136736", + ) + .unwrap(), + Fp::from_str( + "25448252060136178247213604035267580231762596830634036926922217427938159849142", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2161875315509206970842862195937323600322108268401381254431163181777726747153", + ) + .unwrap(), + Fp::from_str( + "19159855698625842998331760283165907305622417625829203038229273729196960321630", + ) + .unwrap(), + Fp::from_str( + "24828563875172432296791053766778475681869974948122169083176331088266823626561", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15959479662608710141128458274961057999257961784282074767105536637788386907463", + ) + .unwrap(), + Fp::from_str( + "8006369581283017287449277389162056290714176164680299906116833200510117952858", + ) + .unwrap(), + Fp::from_str( + "18794336794618132129607701188430371953320538976527988886453665523008714542779", + ) + .unwrap(), + ], + [ + Fp::from_str( + "19408271715954593722501381885401160867835377473312521553027032015227895029571", + ) + .unwrap(), + Fp::from_str( + "13654747284005184272412579731446984220568337794941823533879059135026064413631", + ) + .unwrap(), + Fp::from_str( + "14094055032353750931629930778481002727722804310855727808905931659115939920989", + ) + .unwrap(), + ], + [ + Fp::from_str( + "13241818625838429282823260827177433104574315653706102174619924764342778921524", + ) + .unwrap(), + Fp::from_str( + "25709259239494174564705048436260891089407557689981668111890003079561388887725", + ) + .unwrap(), + Fp::from_str( + "26866626910239634723971078462134580196819809568632305020800296809092442642381", + ) + .unwrap(), + ], + [ + Fp::from_str( + "23886826350713085163238005260075062110062681905356997481925492650252417143049", + ) + .unwrap(), + Fp::from_str( + "16853602711255261520713463306790360324679500458440235992292027384928526778856", + ) + .unwrap(), + Fp::from_str( + "18444710386168488194610417945072711530390091945738595259171890487504771614189", + ) + .unwrap(), + ], + [ + Fp::from_str( + "16896789009769903615328691751424474161656500693270070895928499575572871141439", + ) + .unwrap(), + Fp::from_str( + "23842266984616972287898037872537536999393060934879414668030219493005225085992", + ) + .unwrap(), + Fp::from_str( + "24369698563802298585444760814856330583118549706483939267059237951238240608187", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25360195173713628054110426524260405937218170863260484655473435413697869858790", + ) + .unwrap(), + Fp::from_str( + "1486437708678506228822038923353468635394979165769861487132708983207562337116", + ) + .unwrap(), + Fp::from_str( + "18653498960429911228442559598959970807723487073275324556015861725806677047150", + ) + .unwrap(), + ], + [ + Fp::from_str( + "18878179044241268037057256060083772636369783391816038647949347814518015576522", + ) + .unwrap(), + Fp::from_str( + "178715779905629247116805974152863592571182389085419970371289655361443016848", + ) + .unwrap(), + Fp::from_str( + "8381006794425876451998903949255801618132578446062133243427381291481465852184", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4176946262813877719206528849579392120806054050640974718891398605746592169324", + ) + .unwrap(), + Fp::from_str( + "16376345520728802444699629729684297833862527190772376028981704525651968727081", + ) + .unwrap(), + Fp::from_str( + "8399065769082251057361366626601550736334213197703006866551331927128775757919", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15435308585611812393531506745122614542196708285088622615406141986333182280857", + ) + .unwrap(), + Fp::from_str( + "4082259282787276939431186930090898350392871145699460879678141552997816391817", + ) + .unwrap(), + Fp::from_str( + "26348742719959309014730178326877937464605873211235784184917342950648457078699", + ) + .unwrap(), + ], + [ + Fp::from_str( + "9707631711734344681918469569872517425107158187591261754498805460753455298868", + ) + .unwrap(), + Fp::from_str( + "27910768846011709391567916011595957279088224137468948238696800459136335473132", + ) + .unwrap(), + Fp::from_str( + "20407239095656434708569263842372155762970847207558227886302782130015730063802", + ) + .unwrap(), + ], + [ + Fp::from_str( + "22726225412881182965250630589245572283256255052470345984553083359461473893802", + ) + .unwrap(), + Fp::from_str( + "12443967854426795490638709950679156338200426963050610832781263082981525248175", + ) + .unwrap(), + Fp::from_str( + "27102543658848146076219989119639465430524061997280788166887046421706499775415", + ) + .unwrap(), + ], + [ + Fp::from_str( + "14427224233985680214097547669945064793149553513421479297921556194475574770861", + ) + .unwrap(), + Fp::from_str( + "22917454832925781549840198815703114840452733537799472739275668965081704937832", + ) + .unwrap(), + Fp::from_str( + "3455076056123630366063931123762198941796412458154689469887583689725886013901", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4513100023937785913596662867311227004762025658663076805918211014066645403017", + ) + .unwrap(), + Fp::from_str( + "18187619530784075723418065322038024507729605774832001333883311123910954334059", + ) + .unwrap(), + Fp::from_str( + "9447065431426150382325592560406989926365684509675374414068135115024495130938", + ) + .unwrap(), + ], + [ + Fp::from_str( + "3227816098015819796753427754968234889554095489076864339942014527747604603014", + ) + .unwrap(), + Fp::from_str( + "14798316759185072116520458171957899889489461918408669809912344751222514418582", + ) + .unwrap(), + Fp::from_str( + "23013904852315603905843158448056763116188801262838729536210355401378476650033", + ) + .unwrap(), + ], + [ + Fp::from_str( + "20979191509934291452182967564058656088941447895799901211038858159903580333267", + ) + .unwrap(), + Fp::from_str( + "20772973010251235271448378823573767262405703078344288856168565499702414379868", + ) + .unwrap(), + Fp::from_str( + "10105446427739226002497411811738001382334316505480517822035303561899927603685", + ) + .unwrap(), + ], + [ + Fp::from_str( + "11079074761356717003579108002319997196881121172538617046865136940931215263187", + ) + .unwrap(), + Fp::from_str( + "4693927775411489288330326150094711670434597808961717172753867514688725690438", + ) + .unwrap(), + Fp::from_str( + "18581720304902876944842830383273503265470859268712618325357902881821721540119", + ) + .unwrap(), + ], + [ + Fp::from_str( + "3065369948183164725765083504606321683481629263177690053939474679689088169185", + ) + .unwrap(), + Fp::from_str( + "18515622379147081456114962668688706121098539582467584736624699157043365677487", + ) + .unwrap(), + Fp::from_str( + "17563088600719312877716085528177751048248154461245613291986010180187238198006", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26199746176994924146211004840756471702409132230831594954444947705902602287290", + ) + .unwrap(), + Fp::from_str( + "7576136600627345523051497639367002272003104458453478964661395239732811642605", + ) + .unwrap(), + Fp::from_str( + "20058687874612168338994287374025378897088936171250328231848098497610185784281", + ) + .unwrap(), + ], + [ + Fp::from_str( + "16894722532414195606958290526999761110785277556463400588047573469106594850228", + ) + .unwrap(), + Fp::from_str( + "13961730805696859614283621225672002906734926278118993580398533742874863598733", + ) + .unwrap(), + Fp::from_str( + "25256842011135514243352951950573936602906198374305137963222382546140030647211", + ) + .unwrap(), + ], + [ + Fp::from_str( + "18530360047537856737482157200091774590035773602620205695980247565433703032532", + ) + .unwrap(), + Fp::from_str( + "23014819965938599260086897799541446473887833964178378497976832161473586995397", + ) + .unwrap(), + Fp::from_str( + "27911426213258307990762460361663504655967992659180759140364181941291843542489", + ) + .unwrap(), + ], + [ + Fp::from_str( + "1067338118323302017358103178057182291035336430305886255160210378977812067042", + ) + .unwrap(), + Fp::from_str( + "17219092885519007424608854460610388434712113621163885775309496940189894433620", + ) + .unwrap(), + Fp::from_str( + "16432921127615937542183846559291144733339643093361323334499888895135356545408", + ) + .unwrap(), + ], + [ + Fp::from_str( + "28608851042959977114787048070153637607786033079364369200270218128830983558707", + ) + .unwrap(), + Fp::from_str( + "10121629780013165888398831090128011045011860641816380162950736555305748332191", + ) + .unwrap(), + Fp::from_str( + "2348036340843128746981122630521268144839343500596932561106759754644596320722", + ) + .unwrap(), + ], + [ + Fp::from_str( + "16619881370356823200358060093334065394764987467483650323706184068451904156452", + ) + .unwrap(), + Fp::from_str( + "2302436627861989749837563733434625231689351276818486757748445924305258835336", + ) + .unwrap(), + Fp::from_str( + "27514536540953539473280001431110316405453388911725550380123851609652679788049", + ) + .unwrap(), + ], + [ + Fp::from_str( + "9459277727420672604737117687200019308525004979918488827092207438664125039815", + ) + .unwrap(), + Fp::from_str( + "23425670740358068509956137586663046763224562225383386726193078231034380596217", + ) + .unwrap(), + Fp::from_str( + "7641885067011661443791509688937280323563328029517832788240965464798835873658", + ) + .unwrap(), + ], + [ + Fp::from_str( + "9579420382351699601929202663836555665702024548386778299996961509578687980280", + ) + .unwrap(), + Fp::from_str( + "18513671386572584282611234979588379470994484682444053600751415262497237017703", + ) + .unwrap(), + Fp::from_str( + "24923151431234706142737221165378041700050312199585085101919834422744926421604", + ) + .unwrap(), + ], + [ + Fp::from_str( + "21131320841803068139502705966375283830095161079635803028011171241658723560073", + ) + .unwrap(), + Fp::from_str( + "19208476595309656066589572658712717685014329237892885950958199953675225096566", + ) + .unwrap(), + Fp::from_str( + "24023185216737416080949689106968568821656545490748664446389634158498624398204", + ) + .unwrap(), + ], + [ + Fp::from_str( + "7510552996848634969347937904645640209946785877619890235458182993413526028718", + ) + .unwrap(), + Fp::from_str( + "3694415017252995094553868781762548289196990492336482360084813900937464847638", + ) + .unwrap(), + Fp::from_str( + "9219021070107873028263141554048987416559034633883158827414043929220388719352", + ) + .unwrap(), + ], + [ + Fp::from_str( + "5058327241234443421111591959922712922949620710493120384930391763032694640881", + ) + .unwrap(), + Fp::from_str( + "13148252221647574076185511663661016015859769210867362839817254885265598775418", + ) + .unwrap(), + Fp::from_str( + "15186790492457240277904880519227706403545816456632095870015828239411033220638", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2775942914650502409705888572245750999561427024488403026572311267798009048466", + ) + .unwrap(), + Fp::from_str( + "6277965230841030155341171319927732572492215818164736949144854827643964384893", + ) + .unwrap(), + Fp::from_str( + "24144742149845235561087977558785057713814731737434473021812189457617252043745", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25789129719327437503403457598813971826156253950521984610569937361506914183550", + ) + .unwrap(), + Fp::from_str( + "21500534320778995945845999974779950304491968082325255355181901574840373597824", + ) + .unwrap(), + Fp::from_str( + "17185359848218837018503091932245529880546896465437232425673134558221638601375", + ) + .unwrap(), + ], + [ + Fp::from_str( + "12253896579078110143384981818031883112606762215016553811786428215758384195713", + ) + .unwrap(), + Fp::from_str( + "12956658260778456372481429232709881794962204180363200699121804724437678625542", + ) + .unwrap(), + Fp::from_str( + "3023603786717368708677300377055384474816569333060487675635618249403832078921", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4186492855716808019562789862833898284927736051002588766326482010810259565130", + ) + .unwrap(), + Fp::from_str( + "4263939782228419774639068267872291539552889472311225829898746091327730032923", + ) + .unwrap(), + Fp::from_str( + "24068843626280451423530509388397151179174104901782990365720205643492047328816", + ) + .unwrap(), + ], + [ + Fp::from_str( + "14564937827374621319716285527475223392664010281568256859627186463065876537730", + ) + .unwrap(), + Fp::from_str( + "28367596550218705971881480694115935470211319172596432472834880507822452927283", + ) + .unwrap(), + Fp::from_str( + "28712267437482356021504544448225827500268648754270274754623969882031853409874", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4542596163006916397403529184431773692747461300288194722982487051249951403191", + ) + .unwrap(), + Fp::from_str( + "2530461821259252672899452671728393208543894014761816288817584587718369998371", + ) + .unwrap(), + Fp::from_str( + "12886393063011539390567049190923398676964700147222878509238966758839020897414", + ) + .unwrap(), + ], + [ + Fp::from_str( + "21593897590707514492037699253654745501762191795293908682495110982956631870528", + ) + .unwrap(), + Fp::from_str( + "13233005790593128135480716846773978578237145313006994631606474472023504621256", + ) + .unwrap(), + Fp::from_str( + "21621863098292803642478350494794106282518362577273973885587684567452726939909", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26068620073001644720969640099644251616742620988609091568084348314770436291745", + ) + .unwrap(), + Fp::from_str( + "18248589586787935500122854210401321966459127818593446990365211078521058875685", + ) + .unwrap(), + Fp::from_str( + "21247134484403265289037859533347798468858819117600251067578809852124865474448", + ) + .unwrap(), + ], + [ + Fp::from_str( + "7947383127165915366383984718363902897504221803836013123394785749404572432524", + ) + .unwrap(), + Fp::from_str( + "22173041014621867335598230447618036223462011647696367239478182269973488867154", + ) + .unwrap(), + Fp::from_str( + "16773227734018849308448505860847939069870370055633571816925675705713088305139", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10708707957340055662073314227607620808612686977606082605219160019699644826999", + ) + .unwrap(), + Fp::from_str( + "21249897193797038261479589555720746994050836195265348846222835266344091683000", + ) + .unwrap(), + Fp::from_str( + "12581195059139097540117398803363514148192715293133623516709277290477633379593", + ) + .unwrap(), + ], + [ + Fp::from_str( + "19779599816866992123290302397082614570282926215253589712189610064229996603178", + ) + .unwrap(), + Fp::from_str( + "21749216503901548676985371189807470207364320167486559936962401093285243029177", + ) + .unwrap(), + Fp::from_str( + "17600045923623503357380202389718735904174992978547372448837488832457719009224", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2732872979548118117758016335601225525660858727422778256671975055129965858636", + ) + .unwrap(), + Fp::from_str( + "13703031005128062046175331918702218558750713240446179585947851411173844703597", + ) + .unwrap(), + Fp::from_str( + "28447710105386636841938034820015573492556750872924193415447818187228356409281", + ) + .unwrap(), + ], + [ + Fp::from_str( + "28539960355005748517007309210788803416171161412204526246799800716567376494244", + ) + .unwrap(), + Fp::from_str( + "21329318452221893900731030722137844458345358926323127858742388587761302609863", + ) + .unwrap(), + Fp::from_str( + "28135302149599894709369178097439582767613940517471323224020113411362601191873", + ) + .unwrap(), + ], + [ + Fp::from_str( + "24980774120400248734054527936006392540889095705961960837980443629260392758683", + ) + .unwrap(), + Fp::from_str( + "20339911045808632098936066397942175169549806052128535543540543556255197716643", + ) + .unwrap(), + Fp::from_str( + "7929293103930252545581851978492699598413941396422930641071359388697302362494", + ) + .unwrap(), + ], + [ + Fp::from_str( + "8911092207145893152276662096451247820054843777071569723455408545101628926203", + ) + .unwrap(), + Fp::from_str( + "19648860643145256523615441075182036100116634560394529500146405733687718224516", + ) + .unwrap(), + Fp::from_str( + "14635387208623683806428528837466762532853903031263830054986064902455379735903", + ) + .unwrap(), + ], + [ + Fp::from_str( + "11555212214346132926966321609673228184079851030522218543981385635403167028692", + ) + .unwrap(), + Fp::from_str( + "20896918157639814425520058178561910811657326967880217845710779511927814874973", + ) + .unwrap(), + Fp::from_str( + "4650158165912007049140499755153804318686705949436165235742106170124284287326", + ) + .unwrap(), + ], + [ + Fp::from_str( + "13880660273492757167295696447853232191657893303250187467329180558670697369810", + ) + .unwrap(), + Fp::from_str( + "8043529172463774320604378774840863923445982272478964686447801046272917236836", + ) + .unwrap(), + Fp::from_str( + "2134399296482715903442913099374581981696436050603410080564843555725771329441", + ) + .unwrap(), + ], + [ + Fp::from_str( + "27320952903412641133501507962185246982787769547770982814240701526492601978122", + ) + .unwrap(), + Fp::from_str( + "23417491374379751329394424924400186404791519133465537872457405970098902747611", + ) + .unwrap(), + Fp::from_str( + "17612427354278346772575179176139417348059847375297761006336024476146551185903", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10710998507064742997612080847223278109404482930427999113323732519626499166548", + ) + .unwrap(), + Fp::from_str( + "14958094513415797513745395709487730603918953350067504982704138489305723550923", + ) + .unwrap(), + Fp::from_str( + "24096319595904213497633343966229498735553590589105811393277073274927955202995", + ) + .unwrap(), + ], + [ + Fp::from_str( + "17983724131200292654039765185049138356840415443160477259330748730019147254309", + ) + .unwrap(), + Fp::from_str( + "17598096800487588874709548646068838880468456205252324677357706597166777506441", + ) + .unwrap(), + Fp::from_str( + "27420647821110229619898200875848631488422182349567475956209153112306555222281", + ) + .unwrap(), + ], + [ + Fp::from_str( + "448538544835457571662601142415301047108854812427100562339376187510452313026", + ) + .unwrap(), + Fp::from_str( + "23494184556634922103535803143214434479598067155171780264810485708203176455201", + ) + .unwrap(), + Fp::from_str( + "22626342941879801989161990529511235538216563009907378573817996229389756621777", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26128268137723417163973860961686381960826033145738852158792607959175787222856", + ) + .unwrap(), + Fp::from_str( + "20225791828042873305317281581105429726352058325970107209484198122707862156597", + ) + .unwrap(), + Fp::from_str( + "7538871133759632802857159609785118198934349221046986784429069814655215585732", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26184554861259642274153262777073624024579929401668865520166966302070394487366", + ) + .unwrap(), + Fp::from_str( + "28755259264665180745537307265993667261709206143628938749669440804401623257679", + ) + .unwrap(), + Fp::from_str( + "11896066093033549470312328497237649508068258723531931099214795928200015717321", + ) + .unwrap(), + ], + [ + Fp::from_str( + "21657721599978732693249012287058163532690942515202465984736373311077240614059", + ) + .unwrap(), + Fp::from_str( + "9214914097169852704753116653702415951907628005986883140609006971322091003693", + ) + .unwrap(), + Fp::from_str( + "18710111680849814325169297240208687402588261569152088592693815711857504371037", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6813635166770764528979084175325709935892248249948967889926276426090222296643", + ) + .unwrap(), + Fp::from_str( + "20546585456429436268067726231902751119458200511988152296570567167520382569278", + ) + .unwrap(), + Fp::from_str( + "20087466019194902429054761607398988292568594301671509779549344754172952693871", + ) + .unwrap(), + ], + [ + Fp::from_str( + "28185105286740691904534067831357491310995891986363455251895371651360605333143", + ) + .unwrap(), + Fp::from_str( + "10108348212894231193041286244259038275269464277821588425688314560368589986063", + ) + .unwrap(), + Fp::from_str( + "11433633215392393209829215018579238412423821563056156785641278458497271271546", + ) + .unwrap(), + ], + [ + Fp::from_str( + "27870881917195016999862550657996865268956893566432995492427618003637597051321", + ) + .unwrap(), + Fp::from_str( + "102309803677783876701097881491240456320211833502658383473112057006867019389", + ) + .unwrap(), + Fp::from_str( + "22844040227595875612525628393174357057929113317578127744718774517498324646590", + ) + .unwrap(), + ], + [ + Fp::from_str( + "18364790233947478619325319418813215212267974311771564959136180502266118026133", + ) + .unwrap(), + Fp::from_str( + "2480624341921718230432383518425561514824501138863702825916674641657321180841", + ) + .unwrap(), + Fp::from_str( + "16778939567530361665956758171503829349658551798564323167725356065198936433124", + ) + .unwrap(), + ], + [ + Fp::from_str( + "11947564511486966895926950599696532964589539443187518177489990556481125699966", + ) + .unwrap(), + Fp::from_str( + "3133187646540385483015602955087323554103587039123577645562801570574691666057", + ) + .unwrap(), + Fp::from_str( + "27704797101265438206569218421707753788081674727344603874614391656565567951541", + ) + .unwrap(), + ], + [ + Fp::from_str( + "13001484695584753475562184349533365512515447041450030471627087395341039487710", + ) + .unwrap(), + Fp::from_str( + "477322000667279478600757543806155989948171541982639893984064422067850617496", + ) + .unwrap(), + Fp::from_str( + "13913755821658634147813329813115566967428755223601185963529801459396673113438", + ) + .unwrap(), + ], + [ + Fp::from_str( + "16621869429023470107454028095846067937827722393398508604914831452950874033411", + ) + .unwrap(), + Fp::from_str( + "21755744236927410239079501831014076529931327263341620300431356747367343619046", + ) + .unwrap(), + Fp::from_str( + "26538666591151124505694487799121414506088199961481579132019627484065014831180", + ) + .unwrap(), + ], + [ + Fp::from_str( + "3066480818457008068617042549071052338581291837882909165666223566402713429090", + ) + .unwrap(), + Fp::from_str( + "16182268213934119294035309949459684472027705439038023775276926916166831108357", + ) + .unwrap(), + Fp::from_str( + "28907604876608422892474268478706783033050951245339691569015166507728369585190", + ) + .unwrap(), + ], + [ + Fp::from_str( + "27973960109508292680965426133498827831691369851701664449575719912259359998113", + ) + .unwrap(), + Fp::from_str( + "1456924360278399121996742356757866616312146358469991014696110099534285524446", + ) + .unwrap(), + Fp::from_str( + "8234248752911525485438611255163504976087091103090603316695312869292347668495", + ) + .unwrap(), + ], + [ + Fp::from_str( + "8716078950082339630026654067608811496722305720644485560320987802533380421009", + ) + .unwrap(), + Fp::from_str( + "19016744645809919602099479306503354923553336014593353020688463619133130053825", + ) + .unwrap(), + Fp::from_str( + "24379650661051444982012238084495990858827340608012118841005379796362233056432", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2245379544097631382062919677963998259142792890502492881341386639439507471783", + ) + .unwrap(), + Fp::from_str( + "28788137434161061988371619554419440748189388934884757179010092973102292086583", + ) + .unwrap(), + Fp::from_str( + "7187000185648741287953633167647835668543536354944774631102766873251849991238", + ) + .unwrap(), + ], + [ + Fp::from_str( + "18319349500538500800225762827448369057030532278398270164660609327776487168142", + ) + .unwrap(), + Fp::from_str( + "2622932985948021877314529887962683530522545893985767148345336304947201715671", + ) + .unwrap(), + Fp::from_str( + "13805188629797792210337544360632964855143280581052079479249966961215582531026", + ) + .unwrap(), + ], + [ + Fp::from_str( + "27457600993464082637917106210690168172469473943609357897393615707457194410878", + ) + .unwrap(), + Fp::from_str( + "15448646156961779103834447043970817898237835202826003934642165760908058355399", + ) + .unwrap(), + Fp::from_str( + "9396792545729486882231669677795667529746274932273033601723318032992363022062", + ) + .unwrap(), + ], + [ + Fp::from_str( + "9927877141952679457141759789181418464292082444806533413864151258248124544859", + ) + .unwrap(), + Fp::from_str( + "23827901395971835838179844085051957393677906360196119690926757794561937573142", + ) + .unwrap(), + Fp::from_str( + "3273544693673216914876067527455588276979859627093391584406340272737391174619", + ) + .unwrap(), + ], + [ + Fp::from_str( + "19571510438350300564152393820251652609646082150148656806391655428002614034315", + ) + .unwrap(), + Fp::from_str( + "4458840243585913642400750597703353770666314833058197517675446022682775625834", + ) + .unwrap(), + Fp::from_str( + "6452218213610300363069953741424106105609715382419342511693148495219793324457", + ) + .unwrap(), + ], + [ + Fp::from_str( + "14558167930891460678441266912176752652821641543245953113671886345167213541771", + ) + .unwrap(), + Fp::from_str( + "10650967986920075561478528461783351160938460620955779955379459848889204404950", + ) + .unwrap(), + Fp::from_str( + "19990009778942542934049216419052172134625404062770188357110708518621145688588", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26855242974447190235826233682457047761532515293146087151296725996543442567035", + ) + .unwrap(), + Fp::from_str( + "22785340043356532865086769889360674409753343398766563441587096485751538658065", + ) + .unwrap(), + Fp::from_str( + "28603049427449348335651629195385434188071937908693764500052489540779792538285", + ) + .unwrap(), + ], + [ + Fp::from_str( + "20545812864989828913452616721240947168977365844984763819184465128164378967167", + ) + .unwrap(), + Fp::from_str( + "23234068381345797209897730226956922073109641728569353961504167817770340037954", + ) + .unwrap(), + Fp::from_str( + "26031714567641615877877111172701145299483019910006153132858512509897185854695", + ) + .unwrap(), + ], + [ + Fp::from_str( + "9512221744061419790435674197238913998387834650389922233458121639503195504983", + ) + .unwrap(), + Fp::from_str( + "12587458000103271975978240683793268604398305885278203470492658961734100340536", + ) + .unwrap(), + Fp::from_str( + "9670291694005369437277651504604785512303147991710650505302465204429311229197", + ) + .unwrap(), + ], + [ + Fp::from_str( + "26995526763045548800439747262386290359229145489609341602564040676717570935439", + ) + .unwrap(), + Fp::from_str( + "23742712112104280264401317024221734961713400615669958343926511931219510484675", + ) + .unwrap(), + Fp::from_str( + "27931469778579449247589315744656633392873808631802461175539563849884447358271", + ) + .unwrap(), + ], + [ + Fp::from_str( + "20669006894143187877081688942720159738269397552445286314270368345994751825389", + ) + .unwrap(), + Fp::from_str( + "26891772301075275370472640177651637211280740381619976926886106618375467277414", + ) + .unwrap(), + Fp::from_str( + "28387986011980449959047232529988203397251084614417760995257355718700961696092", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6579105010484741592730389416372694666279917604793318157514380025250233913402", + ) + .unwrap(), + Fp::from_str( + "11007035767869292700964744408562802781669930023548892567535397874932420229930", + ) + .unwrap(), + Fp::from_str( + "981148366863906885900456473323410468923514528856216824044152942069412627408", + ) + .unwrap(), + ], + [ + Fp::from_str( + "22213671088722307302576907504985884923571642958053627659840326928319445671280", + ) + .unwrap(), + Fp::from_str( + "1318836216310789598614608105109389429335273432455224127576823891011367206122", + ) + .unwrap(), + Fp::from_str( + "25586582796990779718352441955439394949194222626688223867952982491529809559257", + ) + .unwrap(), + ], + [ + Fp::from_str( + "4923739488579452777913681531125585976446366144127161879759262506690369040090", + ) + .unwrap(), + Fp::from_str( + "23505612338866210737103599484620591026802005128655081877133994175016351514827", + ) + .unwrap(), + Fp::from_str( + "323887003859465324514901860965142186539600668250760639664361851354147799637", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10198923064967306784017949469108033682156920551672348936591491217255268794658", + ) + .unwrap(), + Fp::from_str( + "9593680688139131432883442351722730169325112619984238956948153423155998917175", + ) + .unwrap(), + Fp::from_str( + "27027988263960602112273050725720071355535922812577299127302015348825197871870", + ) + .unwrap(), + ], + [ + Fp::from_str( + "14419883951157390867695097127684346981136020111885301573583640959136319507752", + ) + .unwrap(), + Fp::from_str( + "5104414988075833278683649298543440897371415916271358703850262680431809374355", + ) + .unwrap(), + Fp::from_str( + "24739655595299332818980677669648719986462429574612913501586844601377825836782", + ) + .unwrap(), + ], + [ + Fp::from_str( + "28522818684103966731129743408029731246564480741348128436668680764518115102581", + ) + .unwrap(), + Fp::from_str( + "21520350704208288978690888796633940487888044365108767319141211249242880355961", + ) + .unwrap(), + Fp::from_str( + "17391005598311948834360476853940353239444383292422171321575043660157438608537", + ) + .unwrap(), + ], + [ + Fp::from_str( + "15367833944125677011173327826570204350687925236257190051755087781855930646142", + ) + .unwrap(), + Fp::from_str( + "21715073802090413714601069529558707101797361591183718695054701329871284436172", + ) + .unwrap(), + Fp::from_str( + "8994093285353831008525761670339342200997965950202092028313103110478252647618", + ) + .unwrap(), + ], + [ + Fp::from_str( + "8370824693889782161629525898408725452177580012023459750897244954935682978671", + ) + .unwrap(), + Fp::from_str( + "16123253540853556024347150096993154278773652905830608614979368087152152043083", + ) + .unwrap(), + Fp::from_str( + "3535380953353495025888433493640531836449699255364366295870140701379497967423", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6954518484798178646508803478426114267143074508396663899281411171704702743829", + ) + .unwrap(), + Fp::from_str( + "28903134801897070276701950388422104654018369750191967384271618837091859516942", + ) + .unwrap(), + Fp::from_str( + "20872505363530172448468374920196608937030884647150175861507911076568784054834", + ) + .unwrap(), + ], + [ + Fp::from_str( + "6902861581703501105786795670676641959401710346423594578401934671029571262513", + ) + .unwrap(), + Fp::from_str( + "10124161387604183369443890585742198433184078889862870469507328332805848271064", + ) + .unwrap(), + Fp::from_str( + "10488004060799269337071647841224034919633445750252076195310163972966405029030", + ) + .unwrap(), + ], + [ + Fp::from_str( + "507704911991278613147490289466075160618843900088471236546244459176211783848", + ) + .unwrap(), + Fp::from_str( + "7252739745607302667257774481690407709040936359589867974787811552896597703097", + ) + .unwrap(), + Fp::from_str( + "23278073497974004442836030100920157527910770509761505828038443336325476654930", + ) + .unwrap(), + ], + [ + Fp::from_str( + "22766285055433137793164317120096790621982728188995759745859222009100808389090", + ) + .unwrap(), + Fp::from_str( + "23129058299483468195787339200845749049960038336751758017949899311636830205152", + ) + .unwrap(), + Fp::from_str( + "16665333681978951552434356320651834889869437822496200946959897681307959400425", + ) + .unwrap(), + ], + [ + Fp::from_str( + "12145699202182574939376505075528461451757079041659894988784442097333218352048", + ) + .unwrap(), + Fp::from_str( + "26340666275844437932755852805027863696219004039301187587209926587657008948704", + ) + .unwrap(), + Fp::from_str( + "19208771804191839410002226941825269105677187954811130189835856228258013753206", + ) + .unwrap(), + ], + [ + Fp::from_str( + "21957102494792377508237608216278079874536155315851198461024084071231867104453", + ) + .unwrap(), + Fp::from_str( + "6933367436450995525851693784691226222726503560893470094614235356287049091852", + ) + .unwrap(), + Fp::from_str( + "15707767379191450768747057313641112321773921923533732633534831270357733757271", + ) + .unwrap(), + ], + [ + Fp::from_str( + "27661963645951389261638591385668507557739541354225916772550248746235106571003", + ) + .unwrap(), + Fp::from_str( + "19699458096897937575096494582288688995241392471402204995195057374756282223421", + ) + .unwrap(), + Fp::from_str( + "902873385171181344315871113842580653512118892800584003934454469411716098791", + ) + .unwrap(), + ], + [ + Fp::from_str( + "17184835876565576154014372215369798779520343573944211203710896053325717110660", + ) + .unwrap(), + Fp::from_str( + "664657295519303589036289440053175741110032988007278988577620229144220576240", + ) + .unwrap(), + Fp::from_str( + "10803972669668998371638869508774217165881281885838503958226056357738500321396", + ) + .unwrap(), + ], + [ + Fp::from_str( + "2329846733754251453632375727999372856194157027336139087170310553870624325301", + ) + .unwrap(), + Fp::from_str( + "14139944357035048486675740400655356660678187875721949218090128899571575479791", + ) + .unwrap(), + Fp::from_str( + "18368148273419807418427674359327442879484531833435081951870369910704734685351", + ) + .unwrap(), + ], + [ + Fp::from_str( + "10480273665080572189328459165704340191901489646067580012574464138528963201459", + ) + .unwrap(), + Fp::from_str( + "21773636700078124500346009061678153597323236568110076029811348966753228682835", + ) + .unwrap(), + Fp::from_str( + "18184268307211429260956076021417309535471438696101133218049142374847151474905", + ) + .unwrap(), + ], + [ + Fp::from_str( + "25957533025669311312382992376854735734491934602484112256289764602447226406852", + ) + .unwrap(), + Fp::from_str( + "22223261506176684934865714490719116745135417403915426392159449667435294570739", + ) + .unwrap(), + Fp::from_str( + "22937309162832499167063076416585504361695925730111272512450449042837586253575", + ) + .unwrap(), + ], + [ + Fp::from_str( + "16956181785481598286719868503945127919581091625126206673934113115358441284347", + ) + .unwrap(), + Fp::from_str( + "8497782777197814773596870810881707148695901557289856910220737358078100998191", + ) + .unwrap(), + Fp::from_str( + "21135503731586600979470064722475007625236017670426339278983640892218291297054", + ) + .unwrap(), + ], + [ + Fp::from_str( + "17809297343844488723046665739910571149089769215421130894378638450427880983923", + ) + .unwrap(), + Fp::from_str( + "72435395972188389387093550708873189001876361107443937983754878061522372356", + ) + .unwrap(), + Fp::from_str( + "7511239878692099209014947248389283109997289411550315391143819429585903287870", + ) + .unwrap(), + ], + ]), + } + } + + pub fn params() -> &'static SpongeParams { + static PARAMS: Lazy> = Lazy::new(make_params); + &PARAMS + } +} diff --git a/producer-dashboard/Cargo.toml b/producer-dashboard/Cargo.toml index 8870214126..fa4906d800 100644 --- a/producer-dashboard/Cargo.toml +++ b/producer-dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-producer-dashboard" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" diff --git a/run.yaml b/run.yaml index b1792a7e54..bc69691964 100644 --- a/run.yaml +++ b/run.yaml @@ -72,11 +72,11 @@ spec: - | apt-get update && apt-get -y install git curl gcc libssl-dev pkg-config curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - PATH=$PATH:~/.cargo/bin && rustup update 1.80 + PATH=$PATH:~/.cargo/bin && rustup update 1.83 git clone https://github.com/openmina/openmina cd openmina git fetch && git checkout feat/tweak-for-debugger - PATH=$PATH:~/.cargo/bin && cargo +1.80 build --release --bin openmina -p cli --no-default-features + PATH=$PATH:~/.cargo/bin && cargo +1.83 build --release --bin openmina -p cli --no-default-features cp target/release/openmina /usr/local/bin/openmina openmina node -p 10000 --libp2p-port 8302 ports: diff --git a/snark/Cargo.toml b/snark/Cargo.toml index 240ba97771..b6e26fcc1f 100644 --- a/snark/Cargo.toml +++ b/snark/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snark" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -12,12 +12,12 @@ o1-utils = { workspace = true } kimchi = { workspace = true } mina-poseidon = { workspace = true } poly-commitment = { workspace = true } +ark-ff = { workspace = true } strum_macros = "0.26" derive_more = "0.99.17" serde = { version = "1.0", features = ["rc"] } serde_json = { version = "1.0", features = ["float_roundtrip"] } -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } ark-ec = { version = "0.3.0", features = [ "std" ] } ark-poly = { version = "0.3.0", features = [ "std" ] } once_cell = "1" @@ -25,6 +25,7 @@ hex = "0.4" redux = { workspace = true } ledger = { workspace = true } mina-p2p-messages = { workspace = true } +poseidon = { workspace = true } sha2 = "0.10" num-bigint = "0.4" thiserror = "1.0.50" diff --git a/snark/src/block_verify_effectful/snark_block_verify_effects.rs b/snark/src/block_verify_effectful/snark_block_verify_effects.rs index dcdefcb6b1..754119d3c9 100644 --- a/snark/src/block_verify_effectful/snark_block_verify_effects.rs +++ b/snark/src/block_verify_effectful/snark_block_verify_effects.rs @@ -14,7 +14,6 @@ impl SnarkBlockVerifyEffectfulAction { block, verifier_index, verifier_srs, - .. } => { store .service() diff --git a/snark/src/merkle_path/mod.rs b/snark/src/merkle_path/mod.rs index 71c748b44d..837d8e7302 100644 --- a/snark/src/merkle_path/mod.rs +++ b/snark/src/merkle_path/mod.rs @@ -1,7 +1,6 @@ -use std::fmt::Write; - use ark_ff::fields::arithmetic::InvalidBigInt; use mina_p2p_messages::{bigint::BigInt, v2::MerkleTreeNode}; +use poseidon::hash::params::get_merkle_param_for_height; /// Computes the root hash of the merkle tree with an account and its merkle path /// @@ -13,18 +12,15 @@ pub fn calc_merkle_root_hash( ) -> Result { let account: ledger::Account = account.try_into()?; let mut child_hash = account.hash(); - let mut param = String::with_capacity(16); - for (depth, path) in merkle_path.iter().enumerate() { + for (height, path) in merkle_path.iter().enumerate() { let hashes = match path { MerkleTreeNode::Left(right) => [child_hash, right.to_field()?], MerkleTreeNode::Right(left) => [left.to_field()?, child_hash], }; - param.clear(); - write!(&mut param, "MinaMklTree{:03}", depth).unwrap(); - - child_hash = ledger::hash_with_kimchi(param.as_str(), &hashes) + let param = get_merkle_param_for_height(height); + child_hash = poseidon::hash::hash_with_kimchi(param, &hashes) } Ok(child_hash.into()) diff --git a/snark/src/user_command_verify_effectful/snark_user_command_verify_effects.rs b/snark/src/user_command_verify_effectful/snark_user_command_verify_effects.rs index 53f5b9d697..aa066481f8 100644 --- a/snark/src/user_command_verify_effectful/snark_user_command_verify_effects.rs +++ b/snark/src/user_command_verify_effectful/snark_user_command_verify_effects.rs @@ -9,11 +9,12 @@ impl SnarkUserCommandVerifyEffectfulAction { Store::Service: SnarkUserCommandVerifyService, { match self { - Self::Init { - req_id, commands, .. + SnarkUserCommandVerifyEffectfulAction::Init { + req_id, + commands, + verifier_index, + verifier_srs, } => { - let verifier_index = store.state().work_verify.verifier_index.clone(); - let verifier_srs = store.state().work_verify.verifier_srs.clone(); store .service() .verify_init(req_id, verifier_index, verifier_srs, commands); diff --git a/snark/src/work_verify/snark_work_verify_actions.rs b/snark/src/work_verify/snark_work_verify_actions.rs index d50054acd4..fc4b5f3d8c 100644 --- a/snark/src/work_verify/snark_work_verify_actions.rs +++ b/snark/src/work_verify/snark_work_verify_actions.rs @@ -46,12 +46,8 @@ impl redux::EnablingCondition for SnarkWorkVerifyAction { .jobs .get(*req_id) .map_or(false, |v| v.is_init()), - SnarkWorkVerifyAction::Error { req_id, .. } => state - .work_verify - .jobs - .get(*req_id) - .map_or(false, |v| v.is_pending()), - SnarkWorkVerifyAction::Success { req_id } => state + SnarkWorkVerifyAction::Error { req_id, .. } + | SnarkWorkVerifyAction::Success { req_id } => state .work_verify .jobs .get(*req_id) diff --git a/snark/src/work_verify/snark_work_verify_reducer.rs b/snark/src/work_verify/snark_work_verify_reducer.rs index 858b3fa2f5..e55d78acd6 100644 --- a/snark/src/work_verify/snark_work_verify_reducer.rs +++ b/snark/src/work_verify/snark_work_verify_reducer.rs @@ -1,4 +1,4 @@ -use openmina_core::{Substate, SubstateAccess}; +use openmina_core::{bug_condition, Substate, SubstateAccess}; use redux::EnablingCondition; use crate::work_verify_effectful::SnarkWorkVerifyEffectfulAction; @@ -29,14 +29,15 @@ pub fn reducer( batch, sender, req_id, - // TODO(tizoc): store the callbacks on the state - on_error: _, - on_success: _, + on_error, + on_success, } => { state.jobs.add(SnarkWorkVerifyStatus::Init { time: meta.time(), batch: batch.clone(), sender: sender.clone(), + on_error: on_error.clone(), + on_success: on_success.clone(), }); // Dispatch @@ -45,7 +46,6 @@ pub fn reducer( let dispatcher = state_context.into_dispatcher(); dispatcher.push(SnarkWorkVerifyEffectfulAction::Init { req_id: *req_id, - sender: sender.clone(), batch: batch.clone(), verifier_index, verifier_srs, @@ -55,46 +55,91 @@ pub fn reducer( SnarkWorkVerifyAction::Pending { req_id } => { if let Some(req) = state.jobs.get_mut(*req_id) { *req = match req { - SnarkWorkVerifyStatus::Init { batch, sender, .. } => { - SnarkWorkVerifyStatus::Pending { - time: meta.time(), - batch: std::mem::take(batch), - sender: std::mem::take(sender), - } - } + SnarkWorkVerifyStatus::Init { + batch, + sender, + on_error, + on_success, + .. + } => SnarkWorkVerifyStatus::Pending { + time: meta.time(), + batch: std::mem::take(batch), + sender: std::mem::take(sender), + on_error: on_error.clone(), + on_success: on_success.clone(), + }, _ => return, }; } } SnarkWorkVerifyAction::Error { req_id, error } => { - if let Some(req) = state.jobs.get_mut(*req_id) { - if let SnarkWorkVerifyStatus::Pending { batch, sender, .. } = req { - *req = SnarkWorkVerifyStatus::Error { - time: meta.time(), - batch: std::mem::take(batch), - sender: std::mem::take(sender), - error: error.clone(), - } - } - } + let Some(req) = state.jobs.get_mut(*req_id) else { + bug_condition!( + "Invalid state for `SnarkWorkVerifyAction::Error` job not found with id: {}", + req_id + ); + return; + }; + let SnarkWorkVerifyStatus::Pending { + batch, + sender, + on_error, + .. + } = req + else { + bug_condition!( + "Invalid state of `SnarkWorkVerifyStatus` for `SnarkWorkVerifyAction::Error`" + ); + return; + }; + let callback = on_error.clone(); + let sender = std::mem::take(sender); + *req = SnarkWorkVerifyStatus::Error { + time: meta.time(), + batch: std::mem::take(batch), + sender: sender.clone(), + error: error.clone(), + }; // Dispatch let dispatcher = state_context.into_dispatcher(); + dispatcher.push_callback(callback, (*req_id, sender)); dispatcher.push(SnarkWorkVerifyAction::Finish { req_id: *req_id }); } SnarkWorkVerifyAction::Success { req_id } => { - if let Some(req) = state.jobs.get_mut(*req_id) { - if let SnarkWorkVerifyStatus::Pending { batch, sender, .. } = req { - *req = SnarkWorkVerifyStatus::Success { - time: meta.time(), - batch: std::mem::take(batch), - sender: std::mem::take(sender), - }; - } - } + let Some(req) = state.jobs.get_mut(*req_id) else { + bug_condition!( + "Invalid state for `SnarkWorkVerifyAction::Success` job not found with id: {}", + req_id + ); + return; + }; + let SnarkWorkVerifyStatus::Pending { + batch, + sender, + on_success, + .. + } = req + else { + bug_condition!( + "Invalid state of `SnarkWorkVerifyStatus` for `SnarkWorkVerifyAction::Error`" + ); + return; + }; + + let callback = on_success.clone(); + let sender = std::mem::take(sender); + let batch = std::mem::take(batch); + + *req = SnarkWorkVerifyStatus::Success { + time: meta.time(), + batch: batch.clone(), + sender: sender.clone(), + }; // Dispatch let dispatcher = state_context.into_dispatcher(); + dispatcher.push_callback(callback, (*req_id, sender, batch)); dispatcher.push(SnarkWorkVerifyAction::Finish { req_id: *req_id }); } SnarkWorkVerifyAction::Finish { req_id } => { diff --git a/snark/src/work_verify/snark_work_verify_state.rs b/snark/src/work_verify/snark_work_verify_state.rs index 7e3c9bf64b..048ca41da9 100644 --- a/snark/src/work_verify/snark_work_verify_state.rs +++ b/snark/src/work_verify/snark_work_verify_state.rs @@ -49,11 +49,15 @@ pub enum SnarkWorkVerifyStatus { // TODO(binier): move p2p/src/identity to shared crate and use // `PeerId` here. sender: String, + on_success: redux::Callback<(SnarkWorkVerifyId, String, Vec)>, + on_error: redux::Callback<(SnarkWorkVerifyId, String)>, }, Pending { time: redux::Timestamp, batch: Vec, sender: String, + on_success: redux::Callback<(SnarkWorkVerifyId, String, Vec)>, + on_error: redux::Callback<(SnarkWorkVerifyId, String)>, }, Error { time: redux::Timestamp, diff --git a/snark/src/work_verify_effectful/snark_work_verify_effectful_actions.rs b/snark/src/work_verify_effectful/snark_work_verify_effectful_actions.rs index cab27a4efe..6234b3e715 100644 --- a/snark/src/work_verify_effectful/snark_work_verify_effectful_actions.rs +++ b/snark/src/work_verify_effectful/snark_work_verify_effectful_actions.rs @@ -12,7 +12,6 @@ pub enum SnarkWorkVerifyEffectfulAction { Init { req_id: SnarkWorkVerifyId, batch: Vec, - sender: String, verifier_index: TransactionVerifier, verifier_srs: Arc, }, diff --git a/snark/src/work_verify_effectful/snark_work_verify_effects.rs b/snark/src/work_verify_effectful/snark_work_verify_effects.rs index b5b9fe15f9..1a7400db24 100644 --- a/snark/src/work_verify_effectful/snark_work_verify_effects.rs +++ b/snark/src/work_verify_effectful/snark_work_verify_effects.rs @@ -9,9 +9,12 @@ impl SnarkWorkVerifyEffectfulAction { Store::Service: SnarkWorkVerifyService, { match self { - Self::Init { req_id, batch, .. } => { - let verifier_index = store.state().work_verify.verifier_index.clone(); - let verifier_srs = store.state().work_verify.verifier_srs.clone(); + SnarkWorkVerifyEffectfulAction::Init { + req_id, + batch, + verifier_srs, + verifier_index, + } => { store .service() .verify_init(req_id, verifier_index, verifier_srs, batch); diff --git a/tests/files/accounts/libp2p-key b/tests/files/accounts/libp2p-key new file mode 100644 index 0000000000..e5778a57b3 --- /dev/null +++ b/tests/files/accounts/libp2p-key @@ -0,0 +1 @@ +{"box_primitive":"xsalsa20poly1305","pw_primitive":"argon2i","nonce":"7xjB1MvPCE9jBFFn5Y87GzHQaJVXBWBBtRUiCfi","pwsalt":"AhVEKpj8G6MP7gzveuRx85pJJDro","pwdiff":[134217728,6],"ciphertext":"7qt8X7ekepe4iGNpUhsLuT8j4aZNSf4jvUUPknhVbAV5Achndcw8pG333TU1ZsfC3LPz2ZWjtpGZgqEW3dGWSWDyzj4MBrwmqqAntze8L17pZJrLVmgYwdtCHqSc27FTCjcwLfaxAtDUWPNQX8S93B4DDf3TUn79BFerCfRfeSdqfNYrV9KhrC5wRF23BRnXh6A84PAgzp9bh8fnQQHJNDboQDmJ6aCdUKUbHYYYMQkkWSa7NXs9UjY7TV1szDc4vnfxiXpYmtaC85Q2H55dF3LybG12hJnzVBBcF"} \ No newline at end of file diff --git a/tools/bootstrap-sandbox/Cargo.toml b/tools/bootstrap-sandbox/Cargo.toml index f94115a70e..16d2f59ee1 100644 --- a/tools/bootstrap-sandbox/Cargo.toml +++ b/tools/bootstrap-sandbox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-bootstrap-sandbox" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dependencies] diff --git a/tools/bootstrap-sandbox/Dockerfile b/tools/bootstrap-sandbox/Dockerfile index 3f07f0be8c..7ab38d3a52 100644 --- a/tools/bootstrap-sandbox/Dockerfile +++ b/tools/bootstrap-sandbox/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.80.0-bullseye AS builder +FROM rust:1.83.0-bullseye AS builder RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts diff --git a/tools/fuzzing/Cargo.toml b/tools/fuzzing/Cargo.toml index e2f3abe3f5..937931df41 100644 --- a/tools/fuzzing/Cargo.toml +++ b/tools/fuzzing/Cargo.toml @@ -1,15 +1,17 @@ [package] name = "transaction_fuzzer" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dependencies] ark-serialize = "0.4.2" +poseidon = { workspace = true } mina-hasher = { workspace = true } mina-signer = { workspace = true } mina-curves = { workspace = true } mina-p2p-messages = { workspace = true } +ark-ff = { workspace = true } openmina-core = { path = "../../core" } ledger = { path = "../../ledger", package = "mina-tree", features = ["fuzzing"] } serde = { version = "1.0", features = ["derive"] } @@ -23,7 +25,6 @@ rsprocmaps = "0.3.2" leb128 = "0.2.1" rand = { version = "0.8.5", features = ["small_rng"] } ring_buffer = "2.0.2" -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } ark-ec = { version = "0.3.0", features = [ "std" ] } #ark-ff = { git = "https://github.com/openmina/algebra", branch = "openmina", features = [ "parallel", "asm", "std" ] } #ark-ec = { git = "https://github.com/openmina/algebra", branch = "openmina", features = [ "std" ] } diff --git a/tools/fuzzing/src/transaction_fuzzer/mutator.rs b/tools/fuzzing/src/transaction_fuzzer/mutator.rs index ab938d6e0d..19b2e72deb 100644 --- a/tools/fuzzing/src/transaction_fuzzer/mutator.rs +++ b/tools/fuzzing/src/transaction_fuzzer/mutator.rs @@ -9,7 +9,6 @@ use crate::transaction_fuzzer::generator::gen_curve_point; use ark_ff::Zero; use ledger::{ generators::zkapp_command_builder::get_transaction_commitments, - hash_with_kimchi, scan_state::{ currency::{Amount, Balance, Fee, MinMax, Nonce, Signed, Slot}, transaction_logic::{ @@ -40,6 +39,7 @@ use mina_p2p_messages::{ }, }; use mina_signer::{CompressedPubKey, NetworkId, Signature, Signer}; +use poseidon::hash::{hash_with_kimchi, params::MINA_ACCOUNT_UPDATE_CONS}; use rand::{seq::SliceRandom, Rng}; #[coverage(off)] @@ -321,15 +321,13 @@ impl MutatorFromAccount for FuzzerCtx { _ => unimplemented!(), } } - } else { - if self.gen.rng.gen_bool(0.5) { - *t = Timing::Timed { - initial_minimum_balance: self.gen(), - cliff_time: self.gen(), - cliff_amount: self.gen(), - vesting_period: self.gen(), - vesting_increment: self.gen(), - } + } else if self.gen.rng.gen_bool(0.5) { + *t = Timing::Timed { + initial_minimum_balance: self.gen(), + cliff_time: self.gen(), + cliff_amount: self.gen(), + vesting_period: self.gen(), + vesting_increment: self.gen(), } } } @@ -708,8 +706,10 @@ impl Mutator> for FuzzerCtx { Fp::zero() }; - t.0[i].stack_hash = - MutableFp::new(hash_with_kimchi("MinaAcctUpdateCons", &[tree_digest, h_tl])); + t.0[i].stack_hash = MutableFp::new(hash_with_kimchi( + &MINA_ACCOUNT_UPDATE_CONS, + &[tree_digest, h_tl], + )); } } } diff --git a/tools/gossipsub-sandbox/Cargo.toml b/tools/gossipsub-sandbox/Cargo.toml index 92878eed19..efb698ba14 100644 --- a/tools/gossipsub-sandbox/Cargo.toml +++ b/tools/gossipsub-sandbox/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openmina-gossipsub-sandbox" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dependencies] diff --git a/tools/hash-tool/Cargo.toml b/tools/hash-tool/Cargo.toml index 41145275b3..7a8a438bc8 100644 --- a/tools/hash-tool/Cargo.toml +++ b/tools/hash-tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hash-tool" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dependencies] diff --git a/tools/ledger-tool/Cargo.toml b/tools/ledger-tool/Cargo.toml index ad5f8376cb..fa4de99c0e 100644 --- a/tools/ledger-tool/Cargo.toml +++ b/tools/ledger-tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ledger-tool" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dependencies] diff --git a/tools/salsa-simple/Cargo.toml b/tools/salsa-simple/Cargo.toml index be6f76562a..22c2458b43 100644 --- a/tools/salsa-simple/Cargo.toml +++ b/tools/salsa-simple/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "salsa-simple" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dev-dependencies] diff --git a/tools/salsa-simple/src/lib.rs b/tools/salsa-simple/src/lib.rs index 46c25fab5f..39a30ffa8b 100644 --- a/tools/salsa-simple/src/lib.rs +++ b/tools/salsa-simple/src/lib.rs @@ -343,7 +343,7 @@ mod helpers { struct V; - impl<'de, const N: usize> de::Visitor<'de> for V { + impl de::Visitor<'_> for V { type Value = [u8; N]; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/tools/transport/Cargo.toml b/tools/transport/Cargo.toml index 357cbde214..6918f62d38 100644 --- a/tools/transport/Cargo.toml +++ b/tools/transport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mina-transport" -version = "0.11.0" +version = "0.12.0" edition = "2021" [dependencies] diff --git a/versions.sh b/versions.sh index 16754b0d88..d8de6fcd54 100755 --- a/versions.sh +++ b/versions.sh @@ -9,24 +9,26 @@ fi VERSION=$1 version_prefix="version = " -echo "Settting version to: '$VERSION'" +echo "Setting version to: '$VERSION'" for file in "${FILES[@]}" do # This version is handled manually if [ "$file" = "./mina-p2p-messages/Cargo.toml" ]; then continue fi - old_version=`cat $file | grep ^"$version_prefix"` + old_version=$(grep -m 1 ^"$version_prefix" "$file") if [ -z "$old_version" ]; then continue fi new_version="version = \"$VERSION\"" + sed -i '' "s/^$old_version/$new_version/g" "$file" + + version_after=$(grep ^version "$file" | tr -d "$version_prefix") + version_before="${old_version/#$version_prefix}" - # replace version - sed -i "s/^$old_version/$new_version/g" $file - version_after=`cat $file | grep ^version | tr --delete "$version_prefix"` + version_before="${version_before//\"}" echo "$file $version_before -> $version_after" done diff --git a/vrf/Cargo.toml b/vrf/Cargo.toml index cf4f4c9d6f..c07f06fe54 100644 --- a/vrf/Cargo.toml +++ b/vrf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vrf" -version = "0.11.0" +version = "0.12.0" edition = "2021" license = "Apache-2.0" @@ -18,7 +18,8 @@ mina-curves = { workspace = true } o1-utils = { workspace = true } ledger = { workspace = true } openmina-node-account = { workspace = true } -ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +poseidon = { workspace = true } +ark-ff = { workspace = true } ark-ec = { version = "0.3.0", features = [ "std" ] } ark-serialize = { version = "0.3.0", features = [ "std" ] } num = "0.4" diff --git a/vrf/src/message.rs b/vrf/src/message.rs index e46f645c64..10f67bbb62 100644 --- a/vrf/src/message.rs +++ b/vrf/src/message.rs @@ -1,9 +1,10 @@ use ark_ff::{One, SquareRootField, Zero}; +use ledger::{proofs::transaction::legacy_input::to_bits, ToInputs}; use mina_curves::pasta::curves::pallas::Pallas as CurvePoint; -use mina_hasher::{create_kimchi, Hashable, Hasher, ROInput}; use mina_p2p_messages::v2::EpochSeed; use o1_utils::FieldHelpers; +use poseidon::hash::{params::MINA_VRF_MESSAGE, Inputs}; use serde::{Deserialize, Serialize}; use super::{BaseField, VrfError, VrfResult}; @@ -27,8 +28,7 @@ impl VrfMessage { } pub fn hash(&self) -> BaseField { - let mut hasher = create_kimchi::(()); - hasher.update(self).digest() + self.hash_with_param(&MINA_VRF_MESSAGE) } pub fn to_group(&self) -> VrfResult { @@ -88,10 +88,8 @@ impl VrfMessage { } } -impl Hashable for VrfMessage { - type D = (); - - fn to_roinput(&self) -> ROInput { +impl ToInputs for VrfMessage { + fn to_inputs(&self, inputs: &mut Inputs) { let epoch_seed = match self.epoch_seed.to_field() { Ok(epoch_seed) => epoch_seed, Err(_) => { @@ -99,21 +97,10 @@ impl Hashable for VrfMessage { mina_hasher::Fp::zero() } }; - let mut roi = ROInput::new().append_field(epoch_seed); - - for i in (0..LEDGER_DEPTH).rev() { - roi = if self.delegator_index >> i & 1u64 == 1 { - roi.append_bool(true) - } else { - roi.append_bool(false) - }; + inputs.append_field(epoch_seed); + inputs.append_u32(self.global_slot); + for bit in to_bits::<_, LEDGER_DEPTH>(self.delegator_index) { + inputs.append_bool(bit); } - - roi = roi.append_u32(self.global_slot); - roi - } - - fn domain_string(_: Self::D) -> Option { - "MinaVrfMessage".to_string().into() } } diff --git a/vrf/src/output.rs b/vrf/src/output.rs index 4226d27161..40b7410c70 100644 --- a/vrf/src/output.rs +++ b/vrf/src/output.rs @@ -1,8 +1,10 @@ +use ark_ec::short_weierstrass_jacobian::GroupAffine; use ark_ff::{BigInteger, BigInteger256, PrimeField}; -use mina_hasher::{create_kimchi, Hashable, Hasher, ROInput}; +use ledger::{AppendToInputs, ToInputs}; use mina_p2p_messages::v2::ConsensusVrfOutputTruncatedStableV1; use num::{BigInt, BigRational, One, ToPrimitive}; use o1_utils::FieldHelpers; +use poseidon::hash::params::MINA_VRF_OUTPUT; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -25,18 +27,16 @@ impl VrfOutputHashInput { } } -impl Hashable for VrfOutputHashInput { - type D = (); +impl ToInputs for VrfOutputHashInput { + fn to_inputs(&self, inputs: &mut poseidon::hash::Inputs) { + let Self { + message, + g: GroupAffine { x, y, .. }, + } = self; - fn to_roinput(&self) -> ROInput { - ROInput::new() - .append_roinput(self.message.to_roinput()) - .append_field(self.g.x) - .append_field(self.g.y) - } - - fn domain_string(_: Self::D) -> Option { - "MinaVrfOutput".to_string().into() + inputs.append(message); + inputs.append(x); + inputs.append(y); } } @@ -57,9 +57,8 @@ impl VrfOutput { } pub fn hash(&self) -> BaseField { - let vrf_output_hash_input = VrfOutputHashInput::new(self.message.clone(), self.output); - let mut hasher = create_kimchi::(()); - hasher.update(&vrf_output_hash_input).digest() + let hash_input = VrfOutputHashInput::new(self.message.clone(), self.output); + hash_input.hash_with_param(&MINA_VRF_OUTPUT) } pub fn truncated(&self) -> ScalarField { @@ -158,7 +157,7 @@ mod test { let converted = ConsensusVrfOutputTruncatedStableV1::from(vrf_output); let converted_string = serde_json::to_string_pretty(&converted).unwrap(); let converted_string_deser: String = serde_json::from_str(&converted_string).unwrap(); - let expected = String::from("48H9Qk4D6RzS9kAJQX9HCDjiJ5qLiopxgxaS6xbDCWNaKQMQ9Y4C"); + let expected = String::from("39cyg4ZmMtnb_aFUIerNAoAJV8qtkfOpq0zFzPspjgM="); assert_eq!(expected, converted_string_deser); }