diff --git a/.github/workflows/build_push_workload.yml b/.github/workflows/build_push_workload.yml index 758cb29f..7bcdfd34 100644 --- a/.github/workflows/build_push_workload.yml +++ b/.github/workflows/build_push_workload.yml @@ -44,7 +44,7 @@ jobs: - name: Build and push workload uses: docker/build-push-action@v5 with: - context: ./workload + context: . file: ./workload/Dockerfile push: true tags: ${{ steps.meta-workload.outputs.tags }}, ${{ env.GITHUB_REF_NAME }} diff --git a/.github/workflows/pr_antithesis_test.yml b/.github/workflows/pr_antithesis_test.yml index 0272a600..fc66022e 100644 --- a/.github/workflows/pr_antithesis_test.yml +++ b/.github/workflows/pr_antithesis_test.yml @@ -206,7 +206,7 @@ jobs: - name: Build and push workload uses: docker/build-push-action@v5 with: - context: ./workload + context: . file: ./workload/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} diff --git a/Makefile b/Makefile index cdddcef7..d955a174 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,12 @@ # ========================================== -include versions.env -# Git tags/commits for each image -drand_tag = $(shell git ls-remote --tags https://github.com/drand/drand.git | grep -E 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$$' | tail -n1 | sed 's/.*refs\/tags\///') -lotus_tag = $(shell git ls-remote https://github.com/filecoin-project/lotus.git HEAD | cut -f1) -curio_tag = $(shell git ls-remote https://github.com/filecoin-project/curio.git refs/heads/pdpv0 | cut -f1) -forest_commit = $(shell git ls-remote https://github.com/ChainSafe/forest.git HEAD | cut -f1) +# Pinned versions — read from each Dockerfile so builds are reproducible. +# To update: change the pinned value in the Dockerfile, then run `make check-versions`. +drand_tag = $(shell grep -oP 'ARG GIT_BRANCH="\K[^"]+' drand/Dockerfile) +lotus_tag = $(shell grep -oP 'ARG REF="\K[^"]+' lotus/Dockerfile) +curio_tag = $(shell grep -oP 'ARG GIT_COMMIT="\K[^"]+' curio/Dockerfile) +forest_commit = $(shell grep -oP 'ARG GIT_COMMIT="\K[^"]+' forest/Dockerfile) # Docker configuration builder = docker @@ -16,8 +17,8 @@ BUILD_CMD = docker build TARGET_ARCH ?= $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') DOCKER_PLATFORM = linux/$(TARGET_ARCH) -# Build mode: "local" uses local images, "remote" uses Antithesis registry -BUILD_MODE ?= local +# Build mode: empty for local images, "remote" uses Antithesis registry +BUILD_MODE ?= # ========================================== # Show version info @@ -52,6 +53,36 @@ show-arch: @echo "Current target architecture: $(TARGET_ARCH)" @echo "Docker platform: $(DOCKER_PLATFORM)" +# ========================================== +# Version drift check +# ========================================== +.PHONY: check-versions +check-versions: + @echo "Checking pinned versions against upstream..." + @UP_DRAND=$$(git ls-remote --tags https://github.com/drand/drand.git \ + | grep -E 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$$' | tail -n1 | sed 's/.*refs\/tags\///'); \ + UP_LOTUS=$$(git ls-remote https://github.com/filecoin-project/lotus.git HEAD | cut -f1); \ + UP_CURIO=$$(git ls-remote https://github.com/filecoin-project/curio.git refs/heads/pdpv0 | cut -f1); \ + UP_FOREST=$$(git ls-remote https://github.com/ChainSafe/forest.git HEAD | cut -f1); \ + WARN=0; \ + if [ "$$UP_DRAND" != "$(drand_tag)" ]; then \ + echo " WARNING: drand pinned=$(drand_tag) upstream=$$UP_DRAND"; WARN=1; \ + else echo " drand: up to date ($(drand_tag))"; fi; \ + if [ "$$UP_LOTUS" != "$(lotus_tag)" ]; then \ + echo " WARNING: lotus pinned=$(lotus_tag) upstream=$$UP_LOTUS"; WARN=1; \ + else echo " lotus: up to date ($(lotus_tag))"; fi; \ + if [ "$$UP_CURIO" != "$(curio_tag)" ]; then \ + echo " WARNING: curio pinned=$(curio_tag) upstream=$$UP_CURIO"; WARN=1; \ + else echo " curio: up to date ($(curio_tag))"; fi; \ + if [ "$$UP_FOREST" != "$(forest_commit)" ]; then \ + echo " WARNING: forest pinned=$(forest_commit) upstream=$$UP_FOREST"; WARN=1; \ + else echo " forest: up to date ($(forest_commit))"; fi; \ + if [ "$$WARN" -eq 1 ]; then \ + echo ""; \ + echo "Some pinned versions are behind upstream."; \ + echo "To update: change the pinned value in the relevant Dockerfile, re-test patches, and commit."; \ + fi + # ========================================== # Build individual images # ========================================== @@ -65,7 +96,7 @@ build-drand: build-lotus: @echo "Building lotus for $(TARGET_ARCH)..." @echo " Git commit: $(lotus_tag)" - $(BUILD_CMD) --build-arg=GIT_BRANCH=$(lotus_tag) -t lotus:latest -f lotus/Dockerfile lotus + $(BUILD_CMD) --build-arg=REF=$(lotus_tag) -t lotus:latest -f lotus/Dockerfile lotus .PHONY: build-forest build-forest: @@ -78,7 +109,7 @@ build-curio: @echo "Building curio for $(TARGET_ARCH)..." @echo " Git commit: $(curio_tag)" @echo " Build mode: $(BUILD_MODE)" - $(BUILD_CMD) --build-arg=GIT_BRANCH=$(curio_tag) --build-arg=LOTUS_TAG=$(lotus_tag) --build-arg=BUILD_MODE=$(BUILD_MODE) -t curio:latest -f curio/Dockerfile curio + $(BUILD_CMD) --build-arg=GIT_COMMIT=$(curio_tag) --build-arg=LOTUS_TAG=latest --build-arg=BUILD_MODE=$(BUILD_MODE) -t curio:latest -f curio/Dockerfile curio .PHONY: build-workload build-workload: @@ -195,6 +226,7 @@ help: @echo "" @echo "Info:" @echo " make show-versions Show all image version tags" + @echo " make check-versions Check pinned versions against upstream" @echo " make show-arch Show target architecture" @echo "" @echo "Current arch: $(TARGET_ARCH)" \ No newline at end of file diff --git a/curio/Dockerfile b/curio/Dockerfile index bba3d5a9..858bbbeb 100644 --- a/curio/Dockerfile +++ b/curio/Dockerfile @@ -1,7 +1,7 @@ # STAGE 1: Use the pre-built lotus image as a source for its binaries # This avoids rebuilding lotus and uses the same version as the rest of your environment. -# BUILD_MODE: "local" uses local lotus image, "remote" uses Antithesis registry -ARG BUILD_MODE=local +# BUILD_MODE: empty (default) uses local lotus image, any non-empty value uses Antithesis registry +ARG BUILD_MODE= ARG LOTUS_TAG=latest ARG ANTITHESIS_REGISTRY=us-central1-docker.pkg.dev/molten-verve-216720/filecoin-repository FROM ${BUILD_MODE:+${ANTITHESIS_REGISTRY}/}lotus:${LOTUS_TAG} as lotus-builder @@ -47,7 +47,8 @@ RUN set -eux; \ RUN git clone https://github.com/filecoin-project/curio.git /opt/curio WORKDIR /opt/curio -RUN git checkout pdpv0 +ARG GIT_COMMIT="2bfe87dfe72d6face2c5e11d4b714bc636778ff7" +RUN git checkout ${GIT_COMMIT} RUN git config --global user.email "richard.yao@antithesis.com" RUN git config --global user.name "Richard" COPY ./increase-cpu-avail-antithesis.patch ./increase-cpu-avail-antithesis.patch diff --git a/docker-compose.yaml b/docker-compose.yaml index 99dc8a18..b4ff8f3d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -225,6 +225,27 @@ services: - ./data/forest0:/root/devgen/forest0 - ./data/curio:/root/devgen/curio + hegel-workload: + <<: [ *filecoin_service ] + image: workload:latest + container_name: hegel-workload + entrypoint: ["/opt/antithesis/entrypoint/entrypoint-hegel.sh"] + environment: + - STRESS_NODES=lotus0,lotus1,lotus2,lotus3 + - STRESS_RPC_PORT=1234 + - STRESS_KEYSTORE_PATH=/shared/configs/stress_keystore.json + - STRESS_WAIT_HEIGHT=10 + - RUST_LOG=info + volumes: + - ./shared/configs:/shared/configs + - ./shared:/shared + - ./data/lotus0:/root/devgen/lotus0 + - ./data/lotus1:/root/devgen/lotus1 + - ./data/lotus2:/root/devgen/lotus2 + - ./data/lotus3:/root/devgen/lotus3 + - ./data/forest0:/root/devgen/forest0 + - ./data/curio:/root/devgen/curio + lotus-miner0: <<: [ *filecoin_service, *needs_lotus0_healthy ] image: lotus:${LOTUS_MINER_0_TAG:-${LOTUS_TAG:-latest}} diff --git a/drand/Dockerfile b/drand/Dockerfile index d590f575..7f4a0a73 100644 --- a/drand/Dockerfile +++ b/drand/Dockerfile @@ -3,7 +3,7 @@ FROM docker.io/golang:1.25.1 RUN apt-get update && \ apt-get install -y ca-certificates build-essential clang ocl-icd-opencl-dev ocl-icd-libopencl1 jq libhwloc-dev git -ARG GIT_BRANCH="v2.1.3" +ARG GIT_BRANCH="v2.1.5" RUN git clone --depth=1 --branch="${GIT_BRANCH}" https://github.com/drand/drand /drand diff --git a/forest/Dockerfile b/forest/Dockerfile index 682ad78a..b764aa80 100644 --- a/forest/Dockerfile +++ b/forest/Dockerfile @@ -1,7 +1,7 @@ FROM docker.io/rust:1.81-bookworm AS builder # Pin forest to a specific branch -ARG GIT_COMMIT="" +ARG GIT_COMMIT="d0dc5b83e776a9df14371dd4e26179526b8696b2" ARG LOCAL_BUILD=1 # Step 1: Install dependencies RUN apt-get update && \ @@ -21,12 +21,7 @@ RUN git clone https://github.com/chainsafe/forest /forest WORKDIR /forest -# Use provided commit or default to latest main -RUN if [ -n "${GIT_COMMIT}" ]; then \ - git checkout ${GIT_COMMIT}; \ - else \ - echo "No GIT_COMMIT specified, using latest main"; \ - fi +RUN git checkout ${GIT_COMMIT} COPY ./patches/forest.patch ./forest.patch RUN git apply forest.patch @@ -37,27 +32,35 @@ WORKDIR /forest COPY ./forest_config.toml.tpl . COPY libvoidstar.so /usr/local/lib/libvoidstar.so ENV LD_LIBRARY_PATH="/usr/local/lib:${LD_LIBRARY_PATH}" -# Build forest binaries + Rust instrumentation on main forest binary for code coverage +# Build forest binaries + optional Rust instrumentation on main forest binary for code coverage +# LOCAL_BUILD=1 (default): skip instrumentation for fast local builds +# LOCAL_BUILD=0 (CI): enable Antithesis instrumentation with libvoidstar RUN cargo build --release --bin forest-cli && \ cargo build --release --bin forest-tool && \ cargo build --release --bin forest-wallet && \ cp /forest/target/release/forest-cli /usr/local/cargo/bin/forest-cli && \ cp /forest/target/release/forest-tool /usr/local/cargo/bin/forest-tool && \ cp /forest/target/release/forest-wallet /usr/local/cargo/bin/forest-wallet && \ - # Setup Antithesis instrumentation - export LD_LIBRARY_PATH=/usr/local/lib && \ - export RUSTFLAGS="-Ccodegen-units=1 -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 \ - -Cllvm-args=-sanitizer-coverage-trace-pc-guard -Clink-arg=-Wl,-z,undefs \ - -Clink-args=-Wl,--build-id -L/usr/local/lib -lvoidstar" && \ - cd /forest && \ - sed -i 's/strip = true/strip = false/' Cargo.toml && \ - cargo build --release --bin forest && \ - cp /forest/target/release/forest /usr/local/cargo/bin/forest && \ - cd / && \ - mkdir -p /symbols && \ - ln -s /usr/local/cargo/bin/forest /symbols/forest && \ - ldd /symbols/forest | grep "libvoidstar" && \ - nm /symbols/forest | grep "sanitizer_cov_trace_pc_guard"; + if [ "$LOCAL_BUILD" = "0" ]; then \ + echo "Building forest with Antithesis instrumentation..." && \ + export LD_LIBRARY_PATH=/usr/local/lib && \ + export RUSTFLAGS="-Ccodegen-units=1 -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 \ + -Cllvm-args=-sanitizer-coverage-trace-pc-guard -Clink-arg=-Wl,-z,undefs \ + -Clink-args=-Wl,--build-id -L/usr/local/lib -lvoidstar" && \ + sed -i 's/strip = true/strip = false/' Cargo.toml && \ + cargo build --release --bin forest && \ + cp /forest/target/release/forest /usr/local/cargo/bin/forest && \ + mkdir -p /symbols && \ + ln -s /usr/local/cargo/bin/forest /symbols/forest && \ + ldd /symbols/forest | grep "libvoidstar" && \ + nm /symbols/forest | grep "sanitizer_cov_trace_pc_guard"; \ + else \ + echo "Building forest without instrumentation (local build)..." && \ + cargo build --release --bin forest && \ + cp /forest/target/release/forest /usr/local/cargo/bin/forest && \ + mkdir -p /symbols && \ + ln -s /usr/local/cargo/bin/forest /symbols/forest; \ + fi WORKDIR /forest diff --git a/forest/patches/forest.patch b/forest/patches/forest.patch index bd5c6ab3..8294713d 100644 --- a/forest/patches/forest.patch +++ b/forest/patches/forest.patch @@ -33,7 +33,7 @@ index 32e34106c..dc8e01860 100644 OpportunisticGraftScoreThreshold = 3.5 ) diff --git a/src/libp2p/gossip_params.rs b/src/libp2p/gossip_params.rs -index 26298991e..5873e3cd1 100644 +index 38822868c..55aa20dd6 100644 --- a/src/libp2p/gossip_params.rs +++ b/src/libp2p/gossip_params.rs @@ -45,7 +45,7 @@ fn build_msg_topic_config() -> TopicScoreParams { @@ -84,21 +84,15 @@ index 26298991e..5873e3cd1 100644 opportunistic_graft_threshold: 3.5, } diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs -index 35b9dfa..5fade36 100644 +index 620ab0086..8177746ba 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs -@@ -1076,12 +1076,12 @@ impl ChainGetTipSetV2 { - // Latest F3 finalized tipset is older than EC finality, falling back to EC finality -- if head.epoch() > f3_finalized_head.epoch() + ctx.chain_config().policy.chain_finality { -+ if head.epoch() > f3_finalized_head.epoch() + 20 { - Self::get_ec_finalized_tipset(ctx) - } else { - Ok(f3_finalized_head) - } - } - +@@ -1082,7 +1082,7 @@ impl ChainGetTipSetV2 { + pub fn get_ec_finalized_tipset(ctx: &Ctx) -> anyhow::Result { let head = ctx.chain_store().heaviest_tipset(); - let ec_finality_epoch = (head.epoch() - ctx.chain_config().policy.chain_finality).max(0); + let ec_finality_epoch = (head.epoch() - 20).max(0); Ok(ctx.chain_index().tipset_by_height( + ec_finality_epoch, + head, diff --git a/hegel-workload/.gitignore b/hegel-workload/.gitignore new file mode 100644 index 00000000..dda6af7b --- /dev/null +++ b/hegel-workload/.gitignore @@ -0,0 +1,2 @@ +.hegel/ +target/ \ No newline at end of file diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock new file mode 100644 index 00000000..9767cd78 --- /dev/null +++ b/hegel-workload/Cargo.lock @@ -0,0 +1,4631 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "antithesis_sdk" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" +dependencies = [ + "libc", + "libloading", + "linkme", + "once_cell", + "rand 0.8.5", + "rustc_version_runtime", + "serde", + "serde_json", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" +dependencies = [ + "http 0.2.12", + "log", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cbor4ii" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cid" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" +dependencies = [ + "core2", + "multibase", + "multihash", + "serde", + "serde_bytes", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn 2.0.117", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fvm_ipld_blockstore" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b8b31e022f71b73440054f7e5171231a1ebc745adf075014d5aa8ea78ea283" +dependencies = [ + "anyhow", + "cid", + "multihash-codetable", +] + +[[package]] +name = "fvm_ipld_encoding" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fd0c7d16be0076920acd5bf13e705a80dfe6540d4722b19745daa9ea93722a" +dependencies = [ + "anyhow", + "cid", + "fvm_ipld_blockstore", + "multihash-codetable", + "serde", + "serde_ipld_dagcbor", + "serde_repr", + "serde_tuple", + "thiserror 2.0.18", +] + +[[package]] +name = "fvm_shared" +version = "4.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d3a23bf6d2a96ca280745afea1a79c5b701d5ac07814ec50ea34380762d47a" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "blake2b_simd", + "cid", + "data-encoding", + "data-encoding-macro", + "fvm_ipld_encoding", + "num-bigint", + "num-derive", + "num-integer", + "num-traits", + "serde", + "thiserror 2.0.18", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hegel-workload" +version = "0.1.0" +dependencies = [ + "antithesis_sdk", + "base64", + "blake2b_simd", + "cid", + "env_logger", + "futures", + "fvm_ipld_encoding", + "fvm_shared", + "getrandom 0.2.17", + "hegeltest", + "hex", + "k256", + "libp2p", + "log", + "multihash", + "multihash-codetable", + "reqwest", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "tokio", +] + +[[package]] +name = "hegeltest" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ed973cb20e511f82953288d815cb08983695a26519c3e9f59941d163bf9d19" +dependencies = [ + "ciborium", + "crc32fast", + "hegeltest-macros", + "paste", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "hegeltest-macros" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f02fefc14c7e6bf4999b549649e463276b3cf342cdfc1e25935f14fa48c2164" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hickory-proto" +version = "0.25.0-alpha.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d00147af6310f4392a31680db52a3ed45a2e0f68eb18e8c3fe5537ecc96d9e2" +dependencies = [ + "async-recursion", + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.2", + "socket2 0.5.10", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.0-alpha.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.2", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http 1.4.0", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "if-watch" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows", +] + +[[package]] +name = "igd-next" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b0d7d4541def58a37bf8efc559683f21edce7c82f0d866c93ac21f7e098f93" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http 1.4.0", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.8.5", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.3", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipld-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" +dependencies = [ + "cid", + "serde", + "serde_bytes", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "js-sys" +version = "0.3.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libp2p" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72dc443ddd0254cb49a794ed6b6728400ee446a0f7ab4a07d0209ee98de20e9" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.17", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-quic", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 2.0.18", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38944b7cb981cc93f2f0fb411ff82d0e983bd226fbcc8d559639a3a73236568b" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe9323175a17caa8a2ed4feaf8a548eeef5e0b72d03840a0eab4bcb0210ce1c" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-core" +version = "0.43.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "thiserror 2.0.18", + "tracing", + "unsigned-varint 0.8.0", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b780a1150214155b0ed1cdf09fbd2e1b0442604f9146a431d1b21d23eef7bd7" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver", + "libp2p-core", + "libp2p-identity", + "parking_lot", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d558548fa3b5a8e9b66392f785921e363c57c05dcadfda4db0d41ae82d313e4a" +dependencies = [ + "async-channel", + "asynchronous-codec", + "base64", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "getrandom 0.2.17", + "hashlink", + "hex_fmt", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "sha2", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-identify" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c06862544f02d05d62780ff590cc25a75f5c2b9df38ec7a370dcae8bb873cf" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "bs58", + "ed25519-dalek", + "hkdf", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "sha2", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-mdns" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d0ba095e1175d797540e16b62e7576846b883cb5046d4159086837b36846cc" +dependencies = [ + "futures", + "hickory-proto", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-metrics" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce58c64292e87af624fcb86465e7dd8342e46a388d71e8fec0ab37ee789630a" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-swarm", + "pin-project", + "prometheus-client", + "web-time", +] + +[[package]] +name = "libp2p-noise" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "snow", + "static_assertions", + "thiserror 2.0.18", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-quic" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41432a159b00424a0abaa2c80d786cddff81055ac24aa127e0cf375f7858d880" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "quinn", + "rand 0.8.5", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803399b4b6f68adb85e63ab573ac568154b193e9a640f03e0f2890eabbcb37f8" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "lru", + "multistream-select", + "once_cell", + "rand 0.8.5", + "smallvec", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "libp2p-tcp" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65346fb4d36035b23fec4e7be4c320436ba53537ce9b6be1d1db1f70c905cad0" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls", + "rustls-webpki", + "thiserror 2.0.18", + "x509-parser", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d457b9ecceb66e7199f049926fad447f1f17f040e8d29d690c086b4cab8ed14a" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-yamux" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 2.0.18", + "tracing", + "yamux 0.12.1", + "yamux 0.13.10", +] + +[[package]] +name = "linkme" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e3283ed2d0e50c06dd8602e0ab319bb048b6325d0bba739db64ed8205179898" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "serde", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multihash-codetable" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67996849749d25f1da9f238e8ace2ece8f9d6bdf3f9750aaf2ae7de3a5cad8ea" +dependencies = [ + "blake2b_simd", + "blake2s_simd", + "blake3", + "core2", + "digest", + "multihash-derive", + "ripemd", + "sha1", + "sha2", + "sha3", + "strobe-rs", +] + +[[package]] +name = "multihash-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1b7edab35d920890b88643a765fc9bd295cf0201f4154dda231bef9b8404eb" +dependencies = [ + "core2", + "multihash", + "multihash-derive-impl", +] + +[[package]] +name = "multihash-derive-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3dc7141bd06405929948754f0628d247f5ca1865be745099205e5086da957cb" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-route" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +dependencies = [ + "bitflags 2.11.0", + "libc", + "log", + "netlink-packet-core", +] + +[[package]] +name = "netlink-proto" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus-client" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" +dependencies = [ + "dtoa", + "itoa", + "parking_lot", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rtnetlink" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" +dependencies = [ + "futures-channel", + "futures-util", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_ipld_dagcbor" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" +dependencies = [ + "cbor4ii", + "ipld-core", + "scopeguard", + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_tuple" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f025b91216f15a2a32aa39669329a475733590a015835d1783549a56d09427" +dependencies = [ + "serde", + "serde_tuple_macros", +] + +[[package]] +name = "serde_tuple_macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4076151d1a2b688e25aaf236997933c66e18b870d0369f8b248b8ab2be630d7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring", + "rustc_version", + "sha2", + "subtle", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strobe-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98fe17535ea31344936cc58d29fec9b500b0452ddc4cc24c429c8a921a0e84e5" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "keccak", + "subtle", + "zeroize", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19280959e2844181895ef62f065c63e0ca07ece4771b53d89bfdb967d97cbf05" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749466a37ee189057f54748b200186b59a03417a117267baf3fd89cecc9fb837" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "yamux" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.2", + "static_assertions", + "web-time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml new file mode 100644 index 00000000..a0be1b24 --- /dev/null +++ b/hegel-workload/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "hegel-workload" +version = "0.1.0" +edition = "2021" + +[features] +antithesis = ["hegeltest/antithesis", "antithesis_sdk/full"] + +[dependencies] +hegeltest = "0.3" +antithesis_sdk = "0.2" +libp2p = { version = "0.55", features = ["gossipsub", "identify", "tcp", "noise", "yamux", "tokio", "macros"] } +tokio = { version = "1", features = ["full"] } +log = "0.4" +env_logger = "0.11" +sha2 = "0.10" +getrandom = "0.2" +futures = "0.3" +fvm_shared = { version = "4", features = [] } +fvm_ipld_encoding = "0.5" +serde_bytes = "0.11" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +cid = { version = "0.11", features = ["serde"] } +multihash-codetable = { version = "0.1", features = ["sha2"] } +multihash = "0.19" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +k256 = { version = "0.13", features = ["ecdsa"] } +blake2b_simd = "1" +hex = "0.4" +base64 = "0.22" diff --git a/hegel-workload/src/assertions.rs b/hegel-workload/src/assertions.rs new file mode 100644 index 00000000..847494fb --- /dev/null +++ b/hegel-workload/src/assertions.rs @@ -0,0 +1,162 @@ +use crate::rpc::{check_mpool_consistency, LotusRpc}; +use log::info; +use serde_json::json; +use std::sync::atomic::{AtomicI64, AtomicBool, Ordering}; +use std::time::Duration; + +/// Tracks the highest chain height we've observed, for progress assertions. +static HIGHEST_HEIGHT: AtomicI64 = AtomicI64::new(0); + +/// Set once when both P2P and RPC traffic have been active in the same window. +static MIXED_TRAFFIC_OBSERVED: AtomicBool = AtomicBool::new(false); + +/// Record that P2P publishing happened in this monitoring window. +static P2P_ACTIVE: AtomicBool = AtomicBool::new(false); + +/// Record that RPC push happened in this monitoring window. +static RPC_ACTIVE: AtomicBool = AtomicBool::new(false); + +/// Signal that P2P traffic was active (called from the generator loop). +pub fn mark_p2p_active() { + P2P_ACTIVE.store(true, Ordering::Relaxed); +} + +/// Signal that RPC traffic was active (called from the RPC traffic task). +pub fn mark_rpc_active() { + RPC_ACTIVE.store(true, Ordering::Relaxed); +} + +/// Run periodic assertion checks against Lotus nodes via RPC. +/// This is designed to be spawned as a long-lived tokio task. +/// +/// Fault tolerance: every RPC call may fail (node killed, partitioned, etc). +/// We only fire assertions when we get successful responses. +pub async fn run_rpc_monitor(clients: Vec<(String, LotusRpc)>, interval: Duration) { + info!("rpc_monitor: starting with {} clients, interval {:?}", clients.len(), interval); + + loop { + tokio::time::sleep(interval).await; + + for (name, client) in &clients { + // --- ChainHead liveness --- + if let Some(head) = client.chain_head().await { + let height = head.height; + + // Sometimes: node responds to ChainHead (liveness) + antithesis_sdk::assert_sometimes!( + true, + "Node responds to ChainHead RPC", + &json!({"node": name, "height": height}) + ); + + // Sometimes: chain height advances (progress) + let prev = HIGHEST_HEIGHT.fetch_max(height, Ordering::Relaxed); + if height > prev { + antithesis_sdk::assert_sometimes!( + true, + "Chain height advances", + &json!({"node": name, "previous": prev, "current": height}) + ); + } + } + + // --- MpoolPending consistency --- + if let Some(pending) = client.mpool_pending().await { + let (consistent, duplicates) = check_mpool_consistency(&pending); + + // AlwaysOrUnreachable: when we can read the mempool, no sender + // should have duplicate nonces. This catches corruption from + // data races like the curTs race we found. + antithesis_sdk::assert_always_or_unreachable!( + consistent, + "MpoolPending has no duplicate nonces per sender", + &json!({ + "node": name, + "pending_count": pending.len(), + "duplicates": format!("{:?}", duplicates), + }) + ); + } + + // --- MpoolSelect exercising allPending() --- + // This calls the code path with the second unprotected curTs read. + // We don't assert on the result content, just that it doesn't + // crash or hang (the node staying responsive is the assertion). + if client.mpool_select(0.8).await.is_some() { + antithesis_sdk::assert_sometimes!( + true, + "MpoolSelect returns successfully", + &json!({"node": name}) + ); + } + } + + // --- Mixed traffic assertion --- + let p2p = P2P_ACTIVE.swap(false, Ordering::Relaxed); + let rpc = RPC_ACTIVE.swap(false, Ordering::Relaxed); + if p2p && rpc && !MIXED_TRAFFIC_OBSERVED.load(Ordering::Relaxed) { + MIXED_TRAFFIC_OBSERVED.store(true, Ordering::Relaxed); + antithesis_sdk::assert_reachable!( + "Mixed P2P and RPC traffic executed concurrently", + &json!({}) + ); + } + } +} + +/// Run RPC-based message traffic alongside P2P GossipSub traffic. +/// Periodically pushes messages through MpoolPush to create contention +/// with the P2P Add() path. +/// +/// We generate minimal valid-looking JSON messages. They'll be rejected +/// by signature validation, but the point is to exercise the Add() -> checkMessage() +/// -> curTsLk contention path before rejection. +pub async fn run_rpc_traffic(clients: Vec<(String, LotusRpc)>, interval: Duration) { + info!("rpc_traffic: starting with {} clients, interval {:?}", clients.len(), interval); + + let mut push_count: u64 = 0; + + loop { + tokio::time::sleep(interval).await; + + for (name, client) in &clients { + // Build a minimal signed message that will enter the Add() path + // before being rejected by signature verification. The structure + // must be valid enough to reach checkMessage(). + let msg_json = json!({ + "Message": { + "Version": 0, + "To": "f01000", + "From": "f01001", + "Nonce": push_count, + "Value": "0", + "GasLimit": 1000000, + "GasFeeCap": "100000", + "GasPremium": "1000", + "Method": 0, + "Params": null, + }, + "Signature": { + "Type": 1, + "Data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + } + }); + + let accepted = client.mpool_push_raw(&msg_json).await; + + if accepted { + antithesis_sdk::assert_sometimes!( + true, + "MpoolPush accepted a message via RPC", + &json!({"node": name, "push_count": push_count}) + ); + } + + // Regardless of acceptance, if we got any response (not a network + // error), mark RPC as active for the mixed-traffic assertion. + // The push_raw call already logged any network errors. + mark_rpc_active(); + push_count += 1; + } + } +} diff --git a/hegel-workload/src/cbor.rs b/hegel-workload/src/cbor.rs new file mode 100644 index 00000000..479c729a --- /dev/null +++ b/hegel-workload/src/cbor.rs @@ -0,0 +1,240 @@ +/// Write a CBOR major type header into a Vec. +fn write_major_type(buf: &mut Vec, major: u8, value: u64) { + let mt = major << 5; + if value < 24 { + buf.push(mt | value as u8); + } else if value < 256 { + buf.push(mt | 24); + buf.push(value as u8); + } else if value < 65536 { + buf.push(mt | 25); + buf.extend_from_slice(&(value as u16).to_be_bytes()); + } else if value < 4_294_967_296 { + buf.push(mt | 26); + buf.extend_from_slice(&(value as u32).to_be_bytes()); + } else { + buf.push(mt | 27); + buf.extend_from_slice(&value.to_be_bytes()); + } +} + +pub fn cbor_uint64(v: u64) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 0, v); + buf +} + +pub fn cbor_int64(v: i64) -> Vec { + if v >= 0 { + cbor_uint64(v as u64) + } else { + let mut buf = Vec::new(); + write_major_type(&mut buf, 1, (-v - 1) as u64); + buf + } +} + +pub fn cbor_bytes(b: &[u8]) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 2, b.len() as u64); + buf.extend_from_slice(b); + buf +} + +pub fn cbor_text(s: &str) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 3, s.len() as u64); + buf.extend_from_slice(s.as_bytes()); + buf +} + +pub fn cbor_nil() -> Vec { + vec![0xf6] +} + +pub fn cbor_bool(v: bool) -> Vec { + if v { + vec![0xf5] + } else { + vec![0xf4] + } +} + +pub fn cbor_array(elements: &[&[u8]]) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 4, elements.len() as u64); + for e in elements { + buf.extend_from_slice(e); + } + buf +} + +pub fn cbor_cid(cid_bytes: &[u8]) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 6, 42); // CBOR tag 42 + let tagged_len = cid_bytes.len() + 1; + write_major_type(&mut buf, 2, tagged_len as u64); + buf.push(0x00); // multibase identity prefix + buf.extend_from_slice(cid_bytes); + buf +} + +pub fn big_int_bytes(v: u64) -> Vec { + if v == 0 { + return vec![]; + } + let raw = v.to_be_bytes(); + let start = raw.iter().position(|&b| b != 0).unwrap_or(7); + let mut result = Vec::with_capacity(1 + raw.len() - start); + result.push(0x00); // positive sign + result.extend_from_slice(&raw[start..]); + result +} + +pub fn random_cid() -> Vec { + use sha2::{Digest, Sha256}; + let data = random_bytes(32); + let hash = Sha256::digest(&data); + let mut cid = Vec::new(); + cid.push(0x01); // CIDv1 + cid.push(0x71); // dag-cbor codec + cid.push(0x12); // sha2-256 hash function + cid.push(0x20); // 32-byte digest + cid.extend_from_slice(&hash); + cid +} + +/// Generate random bytes using getrandom. Inside Antithesis, getrandom is +/// intercepted so this is deterministic per-run. +pub fn random_bytes(len: usize) -> Vec { + let mut buf = vec![0u8; len]; + getrandom::getrandom(&mut buf).expect("getrandom failed"); + buf +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cbor_uint64_zero() { + assert_eq!(cbor_uint64(0), vec![0x00]); + } + + #[test] + fn test_cbor_uint64_small() { + assert_eq!(cbor_uint64(23), vec![0x17]); + } + + #[test] + fn test_cbor_uint64_one_byte() { + assert_eq!(cbor_uint64(24), vec![0x18, 0x18]); + } + + #[test] + fn test_cbor_nil() { + assert_eq!(cbor_nil(), vec![0xf6]); + } + + #[test] + fn test_cbor_bool() { + assert_eq!(cbor_bool(true), vec![0xf5]); + assert_eq!(cbor_bool(false), vec![0xf4]); + } + + #[test] + fn test_cbor_bytes_empty() { + assert_eq!(cbor_bytes(&[]), vec![0x40]); + } + + #[test] + fn test_cbor_bytes_data() { + assert_eq!(cbor_bytes(&[1, 2, 3]), vec![0x43, 1, 2, 3]); + } + + #[test] + fn test_cbor_array_empty() { + assert_eq!(cbor_array(&[]), vec![0x80]); + } + + #[test] + fn test_cbor_array_nested() { + let elements: Vec> = vec![cbor_uint64(1), cbor_nil()]; + let refs: Vec<&[u8]> = elements.iter().map(|e| e.as_slice()).collect(); + assert_eq!(cbor_array(&refs), vec![0x82, 0x01, 0xf6]); + } + + #[test] + fn test_cbor_int64_positive() { + assert_eq!(cbor_int64(0), cbor_uint64(0)); + assert_eq!(cbor_int64(10), cbor_uint64(10)); + } + + #[test] + fn test_cbor_int64_negative() { + assert_eq!(cbor_int64(-1), vec![0x20]); + assert_eq!(cbor_int64(-10), vec![0x29]); + } + + #[test] + fn test_big_int_bytes_zero() { + assert_eq!(big_int_bytes(0), Vec::::new()); + } + + #[test] + fn test_big_int_bytes_positive() { + assert_eq!(big_int_bytes(1), vec![0x00, 0x01]); + assert_eq!(big_int_bytes(256), vec![0x00, 0x01, 0x00]); + } + + #[test] + fn test_cbor_text() { + assert_eq!(cbor_text(""), vec![0x60]); + assert_eq!(cbor_text("hi"), vec![0x62, b'h', b'i']); + } + + #[test] + fn test_cbor_cid() { + let cid_bytes = vec![0x01, 0x71, 0x12, 0x20]; + let encoded = cbor_cid(&cid_bytes); + // Tag 42: 0xd8 0x2a, then byte string of len 5 (4 + 1 prefix): 0x45, then 0x00 prefix + assert_eq!(encoded[0], 0xd8); + assert_eq!(encoded[1], 0x2a); + assert_eq!(encoded[2], 0x45); + assert_eq!(encoded[3], 0x00); + assert_eq!(&encoded[4..], &cid_bytes[..]); + } + + #[test] + fn test_random_cid_length() { + let cid = random_cid(); + // CIDv1 header (4 bytes) + 32-byte SHA-256 digest = 36 bytes + assert_eq!(cid.len(), 36); + assert_eq!(cid[0], 0x01); // CIDv1 + assert_eq!(cid[1], 0x71); // dag-cbor + assert_eq!(cid[2], 0x12); // sha2-256 + assert_eq!(cid[3], 0x20); // 32 bytes + } + + #[test] + fn test_cbor_uint64_two_byte() { + // 256 = 0x100, needs 2-byte encoding + assert_eq!(cbor_uint64(256), vec![0x19, 0x01, 0x00]); + } + + #[test] + fn test_cbor_uint64_four_byte() { + // 65536 = 0x10000, needs 4-byte encoding + assert_eq!(cbor_uint64(65536), vec![0x1a, 0x00, 0x01, 0x00, 0x00]); + } + + #[test] + fn test_cbor_uint64_eight_byte() { + let v: u64 = 4_294_967_296; // 0x100000000 + let encoded = cbor_uint64(v); + assert_eq!( + encoded, + vec![0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00] + ); + } +} diff --git a/hegel-workload/src/discovery.rs b/hegel-workload/src/discovery.rs new file mode 100644 index 00000000..afce7ee8 --- /dev/null +++ b/hegel-workload/src/discovery.rs @@ -0,0 +1,109 @@ +use libp2p::{Multiaddr, PeerId}; +use log::{info, warn}; +use std::time::Duration; + +/// A discovered target node with its libp2p address and peer ID. +#[derive(Debug, Clone)] +pub struct TargetNode { + pub name: String, + pub addr: Multiaddr, + pub peer_id: PeerId, +} + +/// Parse a multiaddr string like "/ip4/.../tcp/.../p2p/" into (dial_addr, peer_id). +/// Returns the multiaddr without the /p2p/ suffix (for dialing) and the extracted PeerId. +pub fn parse_multiaddr(s: &str) -> Option<(Multiaddr, PeerId)> { + let full: Multiaddr = s.parse().ok()?; + let mut addr = Multiaddr::empty(); + let mut peer_id = None; + for proto in full.iter() { + match proto { + libp2p::multiaddr::Protocol::P2p(id) => { + peer_id = Some(id); + } + other => { + addr.push(other); + } + } + } + Some((addr, peer_id?)) +} + +/// Discover nodes by reading multiaddr files from devgen directory. +/// Retries every 5 seconds for up to 5 minutes per node. +pub fn discover_nodes(names: &[String], devgen_dir: &str) -> Vec { + let mut nodes = Vec::new(); + for name in names { + let path = format!("{}/{}/{}-ipv4addr", devgen_dir, name, name); + info!("waiting for multiaddr file: {}", path); + + let mut content = None; + for attempt in 0..60 { + match std::fs::read_to_string(&path) { + Ok(s) if !s.trim().is_empty() => { + content = Some(s); + break; + } + _ => { + if attempt % 12 == 0 { + info!("still waiting for {} (attempt {})", path, attempt); + } + std::thread::sleep(Duration::from_secs(5)); + } + } + } + + let Some(data) = content else { + warn!("timed out waiting for {}, skipping", path); + continue; + }; + + let line = data.trim(); + match parse_multiaddr(line) { + Some((addr, peer_id)) => { + info!("discovered {} at {} (peer {})", name, addr, peer_id); + nodes.push(TargetNode { + name: name.clone(), + addr, + peer_id, + }); + } + None => { + warn!("failed to parse multiaddr for {}: {:?}", name, line); + } + } + } + nodes +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_multiaddr_line() { + let line = "/ip4/172.18.0.6/tcp/37081/p2p/12D3KooWKDdkB19Qi5S5Kq69dPfczv1Zdx1dj4ivsgLNKkKtWx5a"; + let (addr, peer_id) = parse_multiaddr(line).unwrap(); + assert!(addr.to_string().contains("172.18.0.6")); + assert!(!peer_id.to_string().is_empty()); + } + + #[test] + fn test_parse_multiaddr_with_whitespace() { + let line = "/ip4/10.0.0.1/tcp/1234/p2p/12D3KooWKDdkB19Qi5S5Kq69dPfczv1Zdx1dj4ivsgLNKkKtWx5a"; + let result = parse_multiaddr(line.trim()); + assert!(result.is_some()); + } + + #[test] + fn test_parse_multiaddr_invalid() { + assert!(parse_multiaddr("not-a-multiaddr").is_none()); + assert!(parse_multiaddr("").is_none()); + } + + #[test] + fn test_parse_multiaddr_no_peer_id() { + // Multiaddr without /p2p/ component should return None + assert!(parse_multiaddr("/ip4/10.0.0.1/tcp/1234").is_none()); + } +} diff --git a/hegel-workload/src/generators/address.rs b/hegel-workload/src/generators/address.rs new file mode 100644 index 00000000..5552e602 --- /dev/null +++ b/hegel-workload/src/generators/address.rs @@ -0,0 +1,107 @@ +use fvm_shared::address::Address; +use hegel::generators as gs; + +/// Generate a structurally valid Filecoin Address. +/// Uses fvm_shared constructors to guarantee correct encoding. +/// Values are fuzzed (random keys, arbitrary actor IDs) so that +/// semantic validation fails deeper in the pipeline. +#[hegel::composite] +pub fn filecoin_address(tc: hegel::TestCase) -> Address { + let protocol: u8 = tc.draw(gs::sampled_from(vec![0u8, 1, 2, 3, 4])); + match protocol { + 0 => tc.draw(id_address()), + 1 => tc.draw(secp256k1_address()), + 2 => tc.draw(actor_address()), + 3 => tc.draw(bls_address()), + _ => tc.draw(delegated_address()), + } +} + +/// ID address with fuzzed actor IDs (system actors, miners, nonexistent, boundary). +#[hegel::composite] +pub fn id_address(tc: hegel::TestCase) -> Address { + let actor_id: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, // system actor + 1, // init actor + 2, // reward actor + 3, // cron actor + 4, // storage power actor + 5, // storage market actor + 6, // verified registry actor + 7, // datacap actor + 10, // EAM actor + 99, // burnt funds actor + 1000, // first miner + 1001, // second miner + 2000, // some account + 999_999, // high actor ID + ])); + Address::new_id(actor_id) +} + +/// Secp256k1 address with random 65-byte public key (won't match any real actor). +#[hegel::composite] +pub fn secp256k1_address(tc: hegel::TestCase) -> Address { + // Secp256k1 public keys are 65 bytes (uncompressed) + let pubkey: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(65).max_size(65)); + Address::new_secp256k1(&pubkey).expect("65-byte pubkey should produce valid secp256k1 address") +} + +/// Actor address with random 32-byte data. +#[hegel::composite] +pub fn actor_address(tc: hegel::TestCase) -> Address { + let data: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + Address::new_actor(&data) +} + +/// BLS address with random 48-byte public key. +#[hegel::composite] +pub fn bls_address(tc: hegel::TestCase) -> Address { + let pubkey: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(48).max_size(48)); + Address::new_bls(&pubkey).expect("48-byte pubkey should produce valid BLS address") +} + +/// Delegated address with fuzzed namespace and sub-address. +#[hegel::composite] +pub fn delegated_address(tc: hegel::TestCase) -> Address { + let namespace: u64 = tc.draw(gs::sampled_from(vec![ + 10u64, // EAM (Ethereum Address Manager) + 1, // init actor namespace + 99, // arbitrary + 1000, // high namespace + ])); + let sub_len: usize = tc.draw(gs::sampled_from(vec![20usize, 1, 32, 54])); + let subaddr: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(sub_len).max_size(sub_len)); + Address::new_delegated(namespace, &subaddr) + .expect("delegated address construction should not fail") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[hegel::test(test_cases = 50)] + fn test_filecoin_address_roundtrips(tc: hegel::TestCase) { + let addr: Address = tc.draw(filecoin_address()); + // Verify the address can be serialized to bytes and back + let bytes = addr.to_bytes(); + let recovered = Address::from_bytes(&bytes).expect("address should roundtrip"); + assert_eq!(addr, recovered); + } + + #[hegel::test(test_cases = 50)] + fn test_id_address_valid(tc: hegel::TestCase) { + let addr: Address = tc.draw(id_address()); + assert_eq!(addr.protocol(), fvm_shared::address::Protocol::ID); + } + + #[hegel::test(test_cases = 50)] + fn test_bls_address_valid(tc: hegel::TestCase) { + let addr: Address = tc.draw(bls_address()); + assert_eq!(addr.protocol(), fvm_shared::address::Protocol::BLS); + } +} diff --git a/hegel-workload/src/generators/blocks.rs b/hegel-workload/src/generators/blocks.rs new file mode 100644 index 00000000..fc39b2e4 --- /dev/null +++ b/hegel-workload/src/generators/blocks.rs @@ -0,0 +1,258 @@ +use cid::Cid; +use fvm_ipld_encoding::to_vec; +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::econ::TokenAmount; +use hegel::generators as gs; +use multihash_codetable::{Code, MultihashDigest}; +use serde::ser::{SerializeSeq, Serializer}; +use serde::Serialize; + +use crate::cbor::{random_bytes}; +use crate::generators::address::filecoin_address; + +// --------------------------------------------------------------------------- +// Wire types — mirror Lotus's CBOR-generated encoding exactly. +// All are serialized as fixed-length CBOR arrays via custom Serialize impls. +// --------------------------------------------------------------------------- + +/// Ticket: CBOR array(1) [VRFProof bytes] or CBOR null. +#[derive(Debug)] +struct Ticket { + vrf_proof: Vec, +} + +impl Serialize for Ticket { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(1))?; + seq.serialize_element(&serde_bytes::Bytes::new(&self.vrf_proof))?; + seq.end() + } +} + +/// ElectionProof: CBOR array(2) [WinCount int64, VRFProof bytes] or CBOR null. +#[derive(Debug)] +struct ElectionProof { + win_count: i64, + vrf_proof: Vec, +} + +impl Serialize for ElectionProof { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(2))?; + seq.serialize_element(&self.win_count)?; + seq.serialize_element(&serde_bytes::Bytes::new(&self.vrf_proof))?; + seq.end() + } +} + +/// Generate a random CID (CIDv1, dag-cbor codec, sha2-256 hash). +fn gen_random_cid() -> Cid { + let data = random_bytes(32); + let mh = Code::Sha2_256.digest(&data); + Cid::new_v1(0x71, mh) // 0x71 = dag-cbor codec +} + +/// BlockHeader: CBOR array(16) matching Lotus's field order exactly. +/// Pointer fields (Ticket, ElectionProof, BLSAggregate, BlockSig) are +/// Option — None serializes as CBOR null, exercising nil-pointer paths. +#[derive(Debug)] +struct BlockHeader { + miner: Address, + ticket: Option, + election_proof: Option, + beacon_entries: Vec<()>, // empty slice → CBOR array(0) + win_post_proof: Vec<()>, // empty slice → CBOR array(0) + parents: Vec, + parent_weight: TokenAmount, + height: i64, + parent_state_root: Cid, + parent_message_receipts: Cid, + messages: Cid, + bls_aggregate: Option, + timestamp: u64, + block_sig: Option, + fork_signaling: u64, + parent_base_fee: TokenAmount, +} + +impl Serialize for BlockHeader { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(16))?; + seq.serialize_element(&self.miner)?; + seq.serialize_element(&self.ticket)?; + seq.serialize_element(&self.election_proof)?; + seq.serialize_element(&self.beacon_entries)?; + seq.serialize_element(&self.win_post_proof)?; + seq.serialize_element(&self.parents)?; + seq.serialize_element(&self.parent_weight)?; + seq.serialize_element(&self.height)?; + seq.serialize_element(&self.parent_state_root)?; + seq.serialize_element(&self.parent_message_receipts)?; + seq.serialize_element(&self.messages)?; + seq.serialize_element(&self.bls_aggregate)?; + seq.serialize_element(&self.timestamp)?; + seq.serialize_element(&self.block_sig)?; + seq.serialize_element(&self.fork_signaling)?; + seq.serialize_element(&self.parent_base_fee)?; + seq.end() + } +} + +/// BlockMsg: CBOR array(3) [Header, BlsMessages []CID, SecpkMessages []CID]. +#[derive(Debug)] +struct BlockMsg { + header: BlockHeader, + bls_messages: Vec, + secpk_messages: Vec, +} + +impl Serialize for BlockMsg { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(3))?; + seq.serialize_element(&self.header)?; + seq.serialize_element(&self.bls_messages)?; + seq.serialize_element(&self.secpk_messages)?; + seq.end() + } +} + +// --------------------------------------------------------------------------- +// Generators +// --------------------------------------------------------------------------- + +/// Generate a `BlockMsg` as DAG-CBOR bytes. +#[hegel::composite] +pub fn block_msg(tc: hegel::TestCase) -> Vec { + let header = tc.draw(gen_block_header()); + let bls_messages = tc.draw(gen_cid_list()); + let secpk_messages = tc.draw(gen_cid_list()); + let msg = BlockMsg { + header, + bls_messages, + secpk_messages, + }; + to_vec(&msg).expect("DAG-CBOR serialization of BlockMsg should not fail") +} + +/// Generate a BlockHeader with fuzzed fields. +/// Pointer fields randomly alternate between Some/None to exercise nil-pointer +/// code paths — this is where many Filecoin bugs have been found. +#[hegel::composite] +fn gen_block_header(tc: hegel::TestCase) -> BlockHeader { + let miner: Address = tc.draw(filecoin_address()); + + // Ticket — nullable pointer field + let has_ticket: bool = tc.draw(gs::booleans()); + let ticket = if has_ticket { + let vrf_proof: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + Some(Ticket { vrf_proof }) + } else { + None + }; + + // ElectionProof — nullable pointer field + let has_election: bool = tc.draw(gs::booleans()); + let election_proof = if has_election { + let win_count: i64 = tc.draw(gs::sampled_from(vec![ + 0i64, 1, -1, 5, 100, i64::MAX, i64::MIN + 1, + ])); + let vrf_proof: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + Some(ElectionProof { + win_count, + vrf_proof, + }) + } else { + None + }; + + // Parents: 0-2 CIDs + let num_parents: usize = tc.draw(gs::integers::().min_value(0).max_value(2)); + let parents: Vec = (0..num_parents).map(|_| gen_random_cid()).collect(); + + // Parent weight + let pw_val: u64 = tc.draw(gs::sampled_from(vec![0u64, 1, 100, 999_999_999, u64::MAX])); + let parent_weight = TokenAmount::from_atto(pw_val); + + // Height + let height: i64 = tc.draw(gs::sampled_from(vec![0i64, 1, 10, 100, 1000, i64::MAX])); + + // CID fields + let parent_state_root = gen_random_cid(); + let parent_message_receipts = gen_random_cid(); + let messages = gen_random_cid(); + + // BLSAggregate — nullable pointer field + let has_bls_agg: bool = tc.draw(gs::booleans()); + let bls_aggregate = if has_bls_agg { + let bytes: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(96).max_size(96)); + Some(Signature::new_bls(bytes)) + } else { + None + }; + + let timestamp: u64 = tc.draw(gs::sampled_from(vec![0u64, 1_700_000_000, u64::MAX])); + + // BlockSig — nullable pointer field + let has_block_sig: bool = tc.draw(gs::booleans()); + let block_sig = if has_block_sig { + let bytes: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(96).max_size(96)); + Some(Signature::new_bls(bytes)) + } else { + None + }; + + let fork_signaling: u64 = tc.draw(gs::sampled_from(vec![0u64, 1, u64::MAX])); + let parent_base_fee = TokenAmount::from_atto(100u64); + + BlockHeader { + miner, + ticket, + election_proof, + beacon_entries: vec![], + win_post_proof: vec![], + parents, + parent_weight, + height, + parent_state_root, + parent_message_receipts, + messages, + bls_aggregate, + timestamp, + block_sig, + fork_signaling, + parent_base_fee, + } +} + +/// Generate 0-5 random CIDs. +#[hegel::composite] +fn gen_cid_list(tc: hegel::TestCase) -> Vec { + let count: usize = tc.draw(gs::integers::().min_value(0).max_value(5)); + (0..count).map(|_| gen_random_cid()).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[hegel::test(test_cases = 50)] + fn test_block_msg_is_cbor_array_3(tc: hegel::TestCase) { + let block_bytes: Vec = tc.draw(block_msg()); + assert_eq!(block_bytes[0], 0x83, "BlockMsg must be CBOR array(3)"); + } + + #[hegel::test(test_cases = 50)] + fn test_block_msg_nonempty(tc: hegel::TestCase) { + let block_bytes: Vec = tc.draw(block_msg()); + assert!( + block_bytes.len() > 30, + "block too short: {} bytes", + block_bytes.len() + ); + } +} diff --git a/hegel-workload/src/generators/messages.rs b/hegel-workload/src/generators/messages.rs new file mode 100644 index 00000000..6f51d124 --- /dev/null +++ b/hegel-workload/src/generators/messages.rs @@ -0,0 +1,129 @@ +use fvm_ipld_encoding::{RawBytes, to_vec}; +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::econ::TokenAmount; +use fvm_shared::message::Message; +use hegel::generators as gs; + +use crate::generators::address::filecoin_address; + +/// Generate a complete SignedMessage as DAG-CBOR bytes. +/// Serialized as a CBOR tuple (Message, Signature) matching Lotus wire format. +#[hegel::composite] +pub fn signed_message(tc: hegel::TestCase) -> Vec { + let msg = tc.draw(filecoin_message()); + let sig = tc.draw(fuzz_signature()); + // Lotus encodes SignedMessage as CBOR array [Message, Signature] + to_vec(&(&msg, &sig)).expect("DAG-CBOR serialization of SignedMessage should not fail") +} + +/// Generate a Filecoin Message with structurally valid fields but fuzzed values. +/// Addresses are valid format, values are edge-case, nonces/methods are fuzzed. +#[hegel::composite] +fn filecoin_message(tc: hegel::TestCase) -> Message { + let to: Address = tc.draw(filecoin_address()); + let from: Address = tc.draw(filecoin_address()); + + let sequence: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 42, 1000, 1_000_000, u64::MAX, + ])); + + let value = tc.draw(fuzz_token_amount()); + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 10_000_000, 100_000, u64::MAX, + ])); + let gas_fee_cap = tc.draw(fuzz_token_amount()); + let gas_premium = tc.draw(fuzz_token_amount()); + + let method_num: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 20, 24, u64::MAX, 999999, + ])); + + let params = tc.draw(fuzz_params()); + + Message { + version: 0, + to, + from, + sequence, + value, + method_num, + params, + gas_limit, + gas_fee_cap, + gas_premium, + } +} + +/// Generate fuzzed TokenAmount values (BigInt). +#[hegel::composite] +fn fuzz_token_amount(tc: hegel::TestCase) -> TokenAmount { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(4)); + match variant { + 0 => TokenAmount::from_atto(0), + 1 => TokenAmount::from_atto(1), + 2 => TokenAmount::from_atto(u64::MAX), + 3 => TokenAmount::from_atto(1_000_000_000_000_000_000u64), // 1 FIL + _ => { + let v: u64 = tc.draw(gs::integers::()); + TokenAmount::from_atto(v) + } + } +} + +/// Generate a signature with valid type but random (incorrect) bytes. +/// This passes decoding but fails signature verification. +#[hegel::composite] +fn fuzz_signature(tc: hegel::TestCase) -> Signature { + let use_bls: bool = tc.draw(gs::booleans()); + if use_bls { + // BLS signatures are 96 bytes + let bytes: Vec = tc.draw(gs::vecs(gs::integers::()).min_size(96).max_size(96)); + Signature::new_bls(bytes) + } else { + // Secp256k1 signatures are 65 bytes + let bytes: Vec = tc.draw(gs::vecs(gs::integers::()).min_size(65).max_size(65)); + Signature::new_secp256k1(bytes) + } +} + +/// Generate message params: empty or random CBOR-compatible bytes. +#[hegel::composite] +fn fuzz_params(tc: hegel::TestCase) -> RawBytes { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); + match variant { + 0 => RawBytes::new(vec![]), + 1 => { + // Valid CBOR empty array as params + RawBytes::new(vec![0x80]) + } + _ => { + let len: usize = tc.draw(gs::integers::().min_value(1).max_value(64)); + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + RawBytes::new(payload) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[hegel::test(test_cases = 50)] + fn test_signed_message_is_valid_cbor(tc: hegel::TestCase) { + let msg_bytes: Vec = tc.draw(signed_message()); + // Should start with CBOR array(2) header + assert_eq!(msg_bytes[0], 0x82, "SignedMessage must be CBOR array(2)"); + } + + #[hegel::test(test_cases = 50)] + fn test_signed_message_nonempty(tc: hegel::TestCase) { + let msg_bytes: Vec = tc.draw(signed_message()); + assert!( + msg_bytes.len() > 10, + "message too short: {} bytes", + msg_bytes.len() + ); + } +} diff --git a/hegel-workload/src/generators/mod.rs b/hegel-workload/src/generators/mod.rs new file mode 100644 index 00000000..80665be8 --- /dev/null +++ b/hegel-workload/src/generators/mod.rs @@ -0,0 +1,3 @@ +pub mod address; +pub mod blocks; +pub mod messages; diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs new file mode 100644 index 00000000..aff3d3b7 --- /dev/null +++ b/hegel-workload/src/main.rs @@ -0,0 +1,211 @@ +mod assertions; +mod cbor; +mod discovery; +mod generators; +mod network; +mod properties; +mod rpc; +mod scenario; +mod wallet; + +use assertions::{mark_p2p_active, run_rpc_monitor, run_rpc_traffic}; +use discovery::discover_nodes; +use generators::blocks::block_msg; +use generators::messages::signed_message; +use network::{build_swarm, run_network, PublishRequest}; +use rpc::discover_rpc_clients; +use scenario::types::ScenarioIO; +use scenario::{run_scenario, ScenarioContext}; +use wallet::load_keystore; + +use hegel::generators as gs; +use log::{error, info, warn}; +use std::time::Duration; +use tokio::sync::mpsc; + +fn main() { + env_logger::init(); + info!("hegel-workload starting"); + + // Parse configuration from environment + let stress_nodes = std::env::var("STRESS_NODES").unwrap_or_else(|_| "lotus0".to_string()); + let node_names: Vec = stress_nodes + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + let devgen_dir = std::env::var("DEVGEN_DIR").unwrap_or_else(|_| "/root/devgen".to_string()); + let network_name = read_network_name(&devgen_dir); + let batch_size: u64 = std::env::var("HEGEL_BATCH_SIZE") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(100); + let publish_delay: Duration = Duration::from_millis( + std::env::var("PUBLISH_DELAY_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(500), + ); + let rpc_port: u16 = std::env::var("RPC_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1234); + let monitor_interval: Duration = Duration::from_millis( + std::env::var("MONITOR_INTERVAL_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(5000), + ); + let rpc_traffic_interval: Duration = Duration::from_millis( + std::env::var("RPC_TRAFFIC_INTERVAL_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1000), + ); + + info!( + "config: nodes={:?}, network={}, batch_size={}, rpc_port={}", + node_names, network_name, batch_size, rpc_port + ); + + // Discover peers for P2P + let nodes = discover_nodes(&node_names, &devgen_dir); + if nodes.is_empty() { + error!("no nodes discovered, exiting"); + std::process::exit(1); + } + info!("discovered {} nodes for P2P", nodes.len()); + + // Discover RPC clients + let rpc_clients = discover_rpc_clients(&node_names, &devgen_dir, rpc_port); + info!("created {} RPC clients", rpc_clients.len()); + + // Build topics + let msgs_topic = format!("/fil/msgs/{}", network_name); + let blocks_topic = format!("/fil/blocks/{}", network_name); + let topics = vec![msgs_topic.clone(), blocks_topic.clone()]; + + // Build swarm and channel + let swarm = build_swarm().expect("failed to build libp2p swarm"); + let (tx, rx) = mpsc::channel::(256); + let (ready_tx, ready_rx) = tokio::sync::oneshot::channel(); + + // Prepare peer info for the network task + let peers: Vec<_> = nodes.iter().map(|n| (n.addr.clone(), n.peer_id)).collect(); + + // Spawn tokio runtime for network + RPC tasks + let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); + + // Spawn network task (existing P2P layer) + rt.spawn(run_network(swarm, peers, topics, rx, ready_tx)); + + // Spawn RPC monitor (assertions + consistency checks) + // Clone the clients since we need separate ownership for each task. + let monitor_clients = discover_rpc_clients(&node_names, &devgen_dir, rpc_port); + rt.spawn(run_rpc_monitor(monitor_clients, monitor_interval)); + + // Spawn RPC traffic (mixed P2P + RPC contention) + let traffic_clients = discover_rpc_clients(&node_names, &devgen_dir, rpc_port); + rt.spawn(run_rpc_traffic(traffic_clients, rpc_traffic_interval)); + + // Wait for mesh to be ready before generating + info!("waiting for mesh to be ready..."); + let _ = rt.block_on(ready_rx); + + // Fire a GossipSub connectivity assertion + antithesis_sdk::assert_sometimes!( + true, + "GossipSub peer connected", + &serde_json::json!({}) + ); + + info!("starting Hegel generation loop"); + + let keystore_path = std::env::var("STRESS_KEYSTORE_PATH") + .unwrap_or_else(|_| "/shared/configs/stress_keystore.json".to_string()); + let wallets = load_keystore(&keystore_path); + + let scenario_io = ScenarioIO { + p2p_tx: tx.clone(), + rpc_clients: discover_rpc_clients(&node_names, &devgen_dir, rpc_port), + rt_handle: rt.handle().clone(), + msgs_topic: msgs_topic.clone(), + blocks_topic: blocks_topic.clone(), + }; + + if wallets.is_empty() { + warn!("no wallets loaded, falling back to flat generation only"); + } else { + info!( + "loaded {} wallets, running composable scenario loop", + wallets.len() + ); + } + + // Main Hegel loop — composable scenarios if wallets are available, flat generation otherwise + loop { + let tx_ref = &tx; + let msgs_topic_ref = &msgs_topic; + let blocks_topic_ref = &blocks_topic; + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if !wallets.is_empty() { + // Composable scenario mode + let mut ctx = ScenarioContext::new(wallets.clone()); + hegel::Hegel::new(|tc| { + run_scenario(tc, &mut ctx, &scenario_io); + }) + .settings(hegel::Settings::new().test_cases(batch_size)) + .run(); + } else { + // Flat generation fallback (original behavior) + hegel::Hegel::new(|tc| { + let use_blocks: bool = tc.draw(gs::booleans()); + + if use_blocks { + let data: Vec = tc.draw(block_msg()); + properties::log_generation(blocks_topic_ref, data.len()); + let _ = tx_ref.blocking_send(PublishRequest { + topic: blocks_topic_ref.to_string(), + data, + }); + } else { + let data: Vec = tc.draw(signed_message()); + properties::log_generation(msgs_topic_ref, data.len()); + let _ = tx_ref.blocking_send(PublishRequest { + topic: msgs_topic_ref.to_string(), + data, + }); + } + + mark_p2p_active(); + std::thread::sleep(publish_delay); + }) + .settings(hegel::Settings::new().test_cases(batch_size)) + .run(); + } + })); + + if let Err(e) = result { + warn!("Hegel batch failure (expected in fuzzing): {:?}", e); + } + } +} + +/// Read the network name from lotus0's devgen directory. +fn read_network_name(devgen_dir: &str) -> String { + let path = format!("{}/lotus0/network_name", devgen_dir); + for _ in 0..60 { + if let Ok(name) = std::fs::read_to_string(&path) { + let name = name.trim().to_string(); + if !name.is_empty() { + return name; + } + } + std::thread::sleep(std::time::Duration::from_secs(5)); + } + warn!( + "could not read network name from {}, defaulting to '2k'", + path + ); + "2k".to_string() +} diff --git a/hegel-workload/src/network.rs b/hegel-workload/src/network.rs new file mode 100644 index 00000000..35a7cb9c --- /dev/null +++ b/hegel-workload/src/network.rs @@ -0,0 +1,168 @@ +use libp2p::{ + gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode}, + identify, + identity, + noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, PeerId, Swarm, +}; +use log::{info, warn}; +use sha2::{Sha256, Digest}; +use std::time::Duration; +use tokio::sync::mpsc; +use futures::StreamExt; + +/// Messages sent from the generator thread to the network task. +pub struct PublishRequest { + pub topic: String, + pub data: Vec, +} + +/// Combined behaviour: GossipSub for pubsub + Identify for protocol negotiation. +/// Lotus requires the identify protocol before accepting GossipSub traffic. +#[derive(NetworkBehaviour)] +pub struct Behaviour { + gossipsub: gossipsub::Behaviour, + identify: identify::Behaviour, +} + +/// Build a libp2p Swarm with GossipSub + Identify configured for Filecoin interop. +pub fn build_swarm() -> Result, Box> { + let local_key = identity::Keypair::generate_ed25519(); + + // Content-hash message ID function — critical for Lotus interop. + let message_id_fn = |message: &gossipsub::Message| -> MessageId { + let mut hasher = Sha256::new(); + hasher.update(&message.data); + MessageId::from(hasher.finalize().to_vec()) + }; + + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(1)) + .validation_mode(ValidationMode::Permissive) + .message_id_fn(message_id_fn) + .build() + .map_err(|e| format!("gossipsub config error: {}", e))?; + + let gossipsub = gossipsub::Behaviour::new( + MessageAuthenticity::Signed(local_key.clone()), + gossipsub_config, + ) + .map_err(|e| format!("gossipsub behaviour error: {}", e))?; + + let identify = identify::Behaviour::new(identify::Config::new( + "/fil/hegel/0.1.0".to_string(), + local_key.public(), + )); + + let swarm = libp2p::SwarmBuilder::with_existing_identity(local_key) + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|_| Behaviour { gossipsub, identify })? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) + .build(); + + Ok(swarm) +} + +/// Run the network event loop. Connects to peers, subscribes to topics, and +/// publishes messages received on the `rx` channel. +/// +/// Sends a signal on `ready_tx` once at least one peer has joined the mesh +/// for any subscribed topic, so the generator knows it can start publishing. +pub async fn run_network( + mut swarm: Swarm, + peers: Vec<(Multiaddr, PeerId)>, + topics: Vec, + mut rx: mpsc::Receiver, + ready_tx: tokio::sync::oneshot::Sender<()>, +) { + // Listen on random port + swarm + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .expect("failed to listen"); + + // Register peers as explicit GossipSub peers and dial them. + // Explicit peers always receive published messages regardless of mesh state, + // which avoids InsufficientPeers errors when Go/Rust GossipSub protocol + // negotiation doesn't fully establish a mesh. + for (addr, peer_id) in &peers { + swarm.behaviour_mut().gossipsub.add_explicit_peer(peer_id); + let dial_addr = addr.clone().with(libp2p::multiaddr::Protocol::P2p(*peer_id)); + match swarm.dial(dial_addr.clone()) { + Ok(_) => info!("dialing {} (explicit peer)", dial_addr), + Err(e) => warn!("failed to dial {}: {}", dial_addr, e), + } + } + + // Subscribe to topics + for t in &topics { + let topic = IdentTopic::new(t); + if let Err(e) = swarm.behaviour_mut().gossipsub.subscribe(&topic) { + warn!("failed to subscribe to {}: {}", t, e); + } else { + info!("subscribed to {}", t); + } + } + + // Wait for at least one connection to be established (up to 60s). + // With explicit peers, we don't need mesh formation — just a live connection. + info!("waiting for peer connections..."); + let deadline = tokio::time::Instant::now() + Duration::from_secs(60); + let mut connected = false; + let mut ready_tx = Some(ready_tx); + while tokio::time::Instant::now() < deadline && !connected { + tokio::select! { + _ = tokio::time::sleep_until(deadline) => { break; } + event = swarm.select_next_some() => { + if let SwarmEvent::ConnectionEstablished { peer_id, .. } = event { + info!("connected to {} (explicit peer)", peer_id); + connected = true; + } + } + } + } + if connected { + // Brief pause for identify exchange + gossipsub subscription propagation + tokio::time::sleep(Duration::from_secs(3)).await; + info!("peer connected, signalling ready"); + } else { + warn!("no peer connections after 60s, proceeding anyway"); + } + if let Some(tx) = ready_tx.take() { + let _ = tx.send(()); + } + info!("entering event loop"); + + // Main event loop + loop { + tokio::select! { + event = swarm.select_next_some() => { + match event { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + info!("connected to {}", peer_id); + } + SwarmEvent::ConnectionClosed { peer_id, .. } => { + warn!("disconnected from {}", peer_id); + } + _ => {} + } + } + Some(req) = rx.recv() => { + let topic = IdentTopic::new(&req.topic); + match swarm.behaviour_mut().gossipsub.publish(topic, req.data) { + Ok(msg_id) => { + log::debug!("published to {}: {:?}", req.topic, msg_id); + } + Err(e) => { + warn!("publish to {} failed: {}", req.topic, e); + } + } + } + } + } +} diff --git a/hegel-workload/src/properties.rs b/hegel-workload/src/properties.rs new file mode 100644 index 00000000..95fee1e0 --- /dev/null +++ b/hegel-workload/src/properties.rs @@ -0,0 +1,7 @@ +use log::info; + +/// Log a generation event. In Antithesis mode, the hegeltest crate automatically +/// emits assertions to sdk.jsonl — this function provides additional logging. +pub fn log_generation(topic: &str, data_len: usize) { + info!("generated {} bytes for topic {}", data_len, topic); +} diff --git a/hegel-workload/src/rpc.rs b/hegel-workload/src/rpc.rs new file mode 100644 index 00000000..5e665542 --- /dev/null +++ b/hegel-workload/src/rpc.rs @@ -0,0 +1,184 @@ +use log::{debug, warn}; +use reqwest::Client; +use serde::Deserialize; +use serde_json::{json, Value}; +use std::collections::HashMap; +use std::time::Duration; + +const RPC_TIMEOUT: Duration = Duration::from_secs(10); + +/// A fault-tolerant JSON-RPC client for Lotus nodes. +/// All methods return Option — None means the node was unreachable or returned +/// an error, which is expected under Antithesis fault injection. +pub struct LotusRpc { + client: Client, + url: String, + token: String, +} + +#[derive(Debug, Deserialize)] +struct JsonRpcResponse { + result: Option, + error: Option, +} + +#[derive(Debug, Deserialize)] +struct JsonRpcError { + code: i64, + message: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ChainHead { + #[serde(rename = "Height")] + pub height: i64, +} + +/// A pending signed message from MpoolPending. +#[derive(Debug, Clone, Deserialize)] +pub struct PendingMessage { + #[serde(rename = "Message")] + pub message: PendingInner, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PendingInner { + #[serde(rename = "From")] + pub from: String, + #[serde(rename = "Nonce")] + pub nonce: u64, +} + +impl LotusRpc { + pub fn new(host: &str, port: u16, token: &str) -> Self { + let client = Client::builder() + .timeout(RPC_TIMEOUT) + .build() + .expect("failed to build reqwest client"); + Self { + client, + url: format!("http://{}:{}/rpc/v1", host, port), + token: token.to_string(), + } + } + + async fn call Deserialize<'de>>(&self, method: &str, params: Value) -> Option { + let body = json!({ + "jsonrpc": "2.0", + "method": format!("Filecoin.{}", method), + "params": params, + "id": 1, + }); + + let resp = self + .client + .post(&self.url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await; + + let resp = match resp { + Ok(r) => r, + Err(e) => { + debug!("RPC {} failed (network): {}", method, e); + return None; + } + }; + + let parsed: JsonRpcResponse = match resp.json().await { + Ok(p) => p, + Err(e) => { + debug!("RPC {} failed (parse): {}", method, e); + return None; + } + }; + + if let Some(err) = parsed.error { + debug!("RPC {} returned error {}: {}", method, err.code, err.message); + return None; + } + + parsed.result + } + + pub async fn chain_head(&self) -> Option { + self.call("ChainHead", json!([])).await + } + + pub async fn mpool_pending(&self) -> Option> { + // MpoolPending takes a TipSetKey argument; empty array means "current head" + self.call("MpoolPending", json!([[]])).await + } + + /// Push a raw signed message (CBOR bytes) to the mempool via RPC. + /// Returns true if the node accepted the push (regardless of validation outcome). + pub async fn mpool_push_raw(&self, signed_msg_json: &Value) -> bool { + let result: Option = self.call("MpoolPush", json!([signed_msg_json])).await; + result.is_some() + } + + /// Call MpoolSelect to exercise the allPending() code path. + /// The quality parameter controls how aggressively to select messages. + pub async fn mpool_select(&self, quality: f64) -> Option> { + // MpoolSelect takes (TipSetKey, quality) + self.call("MpoolSelect", json!([[], quality])).await + } + + /// Get the next expected nonce for an address from the mempool. + /// This accounts for pending messages, not just on-chain state. + pub async fn mpool_get_nonce(&self, address: &str) -> Option { + self.call("MpoolGetNonce", serde_json::json!([address])) + .await + } +} + +/// Discover RPC endpoints for nodes, reading JWT tokens from devgen. +/// Returns a Vec of (node_name, LotusRpc) for all reachable nodes. +pub fn discover_rpc_clients( + node_names: &[String], + devgen_dir: &str, + rpc_port: u16, +) -> Vec<(String, LotusRpc)> { + let mut clients = Vec::new(); + for name in node_names { + let token_path = format!("{}/{}/{}-jwt", devgen_dir, name, name); + let token = match std::fs::read_to_string(&token_path) { + Ok(t) => t.trim().to_string(), + Err(e) => { + warn!("no JWT for {} at {}: {}, trying without auth", name, token_path, e); + String::new() + } + }; + clients.push((name.clone(), LotusRpc::new(name, rpc_port, &token))); + } + clients +} + +/// Check MpoolPending for duplicate nonces per sender. +/// Returns (is_consistent, details) where details contains any duplicates found. +pub fn check_mpool_consistency(pending: &[PendingMessage]) -> (bool, HashMap>) { + let mut nonces_by_sender: HashMap> = HashMap::new(); + for msg in pending { + nonces_by_sender + .entry(msg.message.from.clone()) + .or_default() + .push(msg.message.nonce); + } + + let mut duplicates: HashMap> = HashMap::new(); + let mut consistent = true; + + for (sender, nonces) in &nonces_by_sender { + let mut seen = std::collections::HashSet::new(); + for &n in nonces { + if !seen.insert(n) { + consistent = false; + duplicates.entry(sender.clone()).or_default().push(n); + } + } + } + + (consistent, duplicates) +} diff --git a/hegel-workload/src/scenario/actions.rs b/hegel-workload/src/scenario/actions.rs new file mode 100644 index 00000000..f8fb3fa6 --- /dev/null +++ b/hegel-workload/src/scenario/actions.rs @@ -0,0 +1,487 @@ +use crate::generators::blocks::block_msg; +use crate::generators::messages::signed_message; +use crate::network::PublishRequest; +use crate::scenario::types::*; +use crate::wallet::sign_message; +use fvm_ipld_encoding::{to_vec as cbor_serialize, RawBytes}; +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::econ::TokenAmount; +use fvm_shared::message::Message; +use hegel::generators as gs; + +/// Draw a random wallet from the loaded keystore. +pub fn pick_wallet(tc: hegel::TestCase, wallets: &[Wallet]) -> Wallet { + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(wallets.len() - 1)); + wallets[idx].clone() +} + +/// Sign a Message and package it into a SignedMsg. +pub fn build_signed_msg(msg: Message, private_key: &[u8]) -> SignedMsg { + let signature = sign_message(&msg, private_key).expect("signing should not fail"); + let cbor_bytes = + cbor_serialize(&(&msg, &signature)).expect("CBOR serialization should not fail"); + SignedMsg { + message: msg, + signature, + cbor_bytes, + sender_key: private_key.to_vec(), + } +} + +/// Build a validly-signed transfer message. +/// Uses the correct nonce from sender state, method_num=0 (plain transfer). +/// Fuzzes: value amount, gas_limit, gas_premium. +pub fn create_valid_transfer( + tc: hegel::TestCase, + sender: &WalletState, + recipient: &Wallet, +) -> SignedMsg { + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, + 1, + 1_000, + 1_000_000, + 1_000_000_000_000_000_000, // 1 FIL + ])); + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 1_000_000u64, + 10_000_000, + 50_000_000, + 100_000_000, + ])); + let gas_premium_atto: u64 = tc.draw(gs::sampled_from(vec![ + 1_000u64, + 10_000, + 100_000, + 1_000_000, + ])); + + let msg = Message { + version: 0, + to: recipient.address, + from: sender.wallet.address, + sequence: sender.nonce, + value: TokenAmount::from_atto(value_atto), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit, + gas_fee_cap: TokenAmount::from_atto(gas_premium_atto * 2), + gas_premium: TokenAmount::from_atto(gas_premium_atto), + }; + + build_signed_msg(msg, &sender.wallet.private_key) +} + +/// Same sender+nonce as original, different recipient. Valid signature. +pub fn create_nonce_reuse( + tc: hegel::TestCase, + original: &SignedMsg, + other_wallets: &[Wallet], +) -> SignedMsg { + let idx: usize = + tc.draw(gs::integers::().min_value(0).max_value(other_wallets.len() - 1)); + let new_recipient = &other_wallets[idx]; + + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, + 1, + 1_000, + 1_000_000, + ])); + + let msg = Message { + version: 0, + to: new_recipient.address, + from: original.message.from, + sequence: original.message.sequence, + value: TokenAmount::from_atto(value_atto), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: original.message.gas_limit, + gas_fee_cap: original.message.gas_fee_cap.clone(), + gas_premium: original.message.gas_premium.clone(), + }; + + build_signed_msg(msg, &original.sender_key) +} + +/// Same sender+nonce, higher gas_premium. Valid signature. +pub fn create_gas_bump(tc: hegel::TestCase, original: &SignedMsg) -> SignedMsg { + let multiplier: u64 = tc.draw(gs::sampled_from(vec![2u64, 5, 10, 50])); + + // Extract a base premium value. Since TokenAmount is BigInt, we use a pragmatic + // approach: serialize the original premium and if it's small enough, multiply it. + // Otherwise use a fixed base. + let base_premium: u64 = 1_000_000; // fallback base + let new_premium_atto = base_premium * multiplier; + + // Ensure the new premium is strictly greater than the original by also adding + // a bump on top of whatever the original was. + // We construct a new premium that's guaranteed larger. + let msg = Message { + version: 0, + to: original.message.to, + from: original.message.from, + sequence: original.message.sequence, + value: original.message.value.clone(), + method_num: original.message.method_num, + params: original.message.params.clone(), + gas_limit: original.message.gas_limit, + gas_fee_cap: TokenAmount::from_atto(new_premium_atto * 2), + gas_premium: &original.message.gas_premium + TokenAmount::from_atto(new_premium_atto), + }; + + build_signed_msg(msg, &original.sender_key) +} + +/// Valid transfer but with nonce = current + gap (2..10). Skips ahead. +pub fn create_nonce_gap( + tc: hegel::TestCase, + sender: &WalletState, + recipient: &Wallet, +) -> SignedMsg { + let gap: u64 = tc.draw(gs::integers::().min_value(2).max_value(10)); + + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 1_000, 1_000_000, + ])); + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 1_000_000u64, 10_000_000, 50_000_000, + ])); + let gas_premium_atto: u64 = tc.draw(gs::sampled_from(vec![ + 1_000u64, 10_000, 100_000, + ])); + + let msg = Message { + version: 0, + to: recipient.address, + from: sender.wallet.address, + sequence: sender.nonce + gap, + value: TokenAmount::from_atto(value_atto), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit, + gas_fee_cap: TokenAmount::from_atto(gas_premium_atto * 2), + gas_premium: TokenAmount::from_atto(gas_premium_atto), + }; + + build_signed_msg(msg, &sender.wallet.private_key) +} + +/// Valid sender/nonce/signature but fuzzed method_num, gas values, params. +pub fn create_semi_valid_msg( + tc: hegel::TestCase, + sender: &WalletState, + recipient: &Wallet, +) -> SignedMsg { + let method_num: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 999999, u64::MAX, + ])); + + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 10_000_000, u64::MAX, + ])); + + let gas_premium_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, u64::MAX, + ])); + + let gas_fee_cap_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, u64::MAX, + ])); + + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, u64::MAX, + ])); + + // Fuzz params: empty, CBOR empty array, or random bytes + let params_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); + let params = match params_variant { + 0 => RawBytes::new(vec![]), + 1 => RawBytes::new(vec![0x80]), // CBOR empty array + _ => { + let len: usize = tc.draw(gs::integers::().min_value(1).max_value(64)); + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + RawBytes::new(payload) + } + }; + + let msg = Message { + version: 0, + to: recipient.address, + from: sender.wallet.address, + sequence: sender.nonce, + value: TokenAmount::from_atto(value_atto), + method_num, + params, + gas_limit, + gas_fee_cap: TokenAmount::from_atto(gas_fee_cap_atto), + gas_premium: TokenAmount::from_atto(gas_premium_atto), + }; + + build_signed_msg(msg, &sender.wallet.private_key) +} + +/// Fully fuzzed message using the existing signed_message() generator. +/// Stores placeholder values in the message field since we can't decompose the CBOR. +/// The cbor_bytes field is what matters for publishing. +pub fn create_fuzzed_msg(tc: hegel::TestCase) -> SignedMsg { + let cbor_bytes: Vec = tc.draw(signed_message()); + + SignedMsg { + message: Message { + version: 0, + to: Address::new_id(0), + from: Address::new_id(0), + sequence: 0, + value: TokenAmount::from_atto(0u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 0, + gas_fee_cap: TokenAmount::from_atto(0u64), + gas_premium: TokenAmount::from_atto(0u64), + }, + signature: Signature::new_secp256k1(vec![0u8; 65]), + cbor_bytes, + sender_key: vec![], + } +} + +/// Fully fuzzed block using the existing block_msg() generator. +pub fn create_fuzzed_block(tc: hegel::TestCase) -> FuzzedBlock { + let cbor_bytes: Vec = tc.draw(block_msg()); + FuzzedBlock { cbor_bytes } +} + +// --------------------------------------------------------------------------- +// Delivery actions +// --------------------------------------------------------------------------- + +/// Publish a signed message over GossipSub P2P. +pub fn publish_msg_p2p(msg: &SignedMsg, io: &ScenarioIO) { + let _ = io.p2p_tx.blocking_send(PublishRequest { + topic: io.msgs_topic.clone(), + data: msg.cbor_bytes.clone(), + }); + crate::assertions::mark_p2p_active(); + log::debug!("scenario: published msg via P2P ({} bytes)", msg.cbor_bytes.len()); +} + +/// Publish a signed message via RPC MpoolPush to a random node. +pub fn publish_msg_rpc(tc: hegel::TestCase, msg: &SignedMsg, io: &ScenarioIO) { + if io.rpc_clients.is_empty() { + return; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (name, client) = &io.rpc_clients[idx]; + + // Build JSON representation for MpoolPush + let sig_bytes = msg.signature.bytes(); + let sig_b64 = base64_encode(sig_bytes); + + let msg_json = serde_json::json!({ + "Message": { + "Version": msg.message.version, + "To": msg.message.to.to_string(), + "From": msg.message.from.to_string(), + "Nonce": msg.message.sequence, + "Value": msg.message.value.atto().to_string(), + "GasLimit": msg.message.gas_limit, + "GasFeeCap": msg.message.gas_fee_cap.atto().to_string(), + "GasPremium": msg.message.gas_premium.atto().to_string(), + "Method": msg.message.method_num, + "Params": if msg.message.params.bytes().is_empty() { + serde_json::Value::Null + } else { + serde_json::Value::String(base64_encode(msg.message.params.bytes())) + }, + }, + "Signature": { + "Type": 1, + "Data": sig_b64, + } + }); + + let accepted = io.rt_handle.block_on(client.mpool_push_raw(&msg_json)); + log::debug!("scenario: published msg via RPC to {} (accepted={})", name, accepted); + if accepted { + antithesis_sdk::assert_sometimes!( + true, + "Scenario: valid signed message accepted via RPC", + &serde_json::json!({"from": msg.message.from.to_string(), "nonce": msg.message.sequence}) + ); + } + crate::assertions::mark_rpc_active(); +} + +/// Publish a fuzzed block over GossipSub P2P. +pub fn publish_block_p2p(block: &FuzzedBlock, io: &ScenarioIO) { + let _ = io.p2p_tx.blocking_send(PublishRequest { + topic: io.blocks_topic.clone(), + data: block.cbor_bytes.clone(), + }); + crate::assertions::mark_p2p_active(); + log::debug!("scenario: published block via P2P ({} bytes)", block.cbor_bytes.len()); +} + +fn base64_encode(bytes: &[u8]) -> String { + use base64::Engine; + base64::engine::general_purpose::STANDARD.encode(bytes) +} + +// --------------------------------------------------------------------------- +// Observation actions +// --------------------------------------------------------------------------- + +/// Observe chain head from a random node. +pub fn observe_chain_head(tc: hegel::TestCase, io: &ScenarioIO) -> Option { + if io.rpc_clients.is_empty() { + return None; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (name, client) = &io.rpc_clients[idx]; + + let head = io.rt_handle.block_on(client.chain_head())?; + log::debug!("scenario: observed chain head height={} from {}", head.height, name); + Some(ChainTip { height: head.height }) +} + +/// Observe a wallet's current nonce via MpoolGetNonce. +pub fn observe_nonce(tc: hegel::TestCase, wallet: &Wallet, io: &ScenarioIO) -> Option { + if io.rpc_clients.is_empty() { + return None; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (name, client) = &io.rpc_clients[idx]; + + let addr_str = wallet.address.to_string(); + let nonce = io.rt_handle.block_on(client.mpool_get_nonce(&addr_str))?; + log::debug!("scenario: observed nonce={} for {} from {}", nonce, addr_str, name); + antithesis_sdk::assert_sometimes!( + true, + "Scenario: wallet nonce observed", + &serde_json::json!({"address": addr_str, "nonce": nonce}) + ); + Some(WalletState { + wallet: wallet.clone(), + nonce, + }) +} + +/// Observe current mempool state. +pub fn observe_mempool(tc: hegel::TestCase, io: &ScenarioIO) -> Option { + if io.rpc_clients.is_empty() { + return None; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (_, client) = &io.rpc_clients[idx]; + + let pending = io.rt_handle.block_on(client.mpool_pending())?; + Some(MempoolSnapshot { + pending: pending.iter().map(|m| (m.message.from.clone(), m.message.nonce)).collect(), + }) +} + +/// Wait for a message to leave the mempool (included in a tipset) or timeout. +pub fn wait_for_inclusion(msg: &SignedMsg, io: &ScenarioIO) -> Option { + let addr_str = msg.message.from.to_string(); + let nonce = msg.message.sequence; + + // Poll every 2s for up to 20s (keep scenarios moving) + for _ in 0..10 { + std::thread::sleep(std::time::Duration::from_secs(2)); + + for (_, client) in &io.rpc_clients { + if let Some(pending) = io.rt_handle.block_on(client.mpool_pending()) { + let still_pending = pending + .iter() + .any(|m| m.message.from == addr_str && m.message.nonce == nonce); + if !still_pending { + log::debug!("scenario: message from {} nonce {} included", addr_str, nonce); + antithesis_sdk::assert_sometimes!( + true, + "Scenario: message left mempool", + &serde_json::json!({"from": addr_str, "nonce": nonce}) + ); + return Some(IncludedMsg { original: msg.clone() }); + } + } + } + } + log::warn!("scenario: timed out waiting for inclusion of {} nonce {}", addr_str, nonce); + None +} + +/// Random pause between actions. +pub fn pause(tc: hegel::TestCase) { + let ms: u64 = tc.draw(gs::sampled_from(vec![100u64, 500, 1000, 2000, 5000])); + std::thread::sleep(std::time::Duration::from_millis(ms)); +} + +#[cfg(test)] +mod tests { + use super::*; + use fvm_shared::address::Address; + + fn test_wallet(id: u64) -> Wallet { + Wallet { + address: Address::new_id(id), + private_key: vec![id as u8; 32], + } + } + + fn test_wallet_state(id: u64, nonce: u64) -> WalletState { + WalletState { + wallet: test_wallet(id), + nonce, + } + } + + #[hegel::test(test_cases = 20)] + fn test_create_valid_transfer_has_correct_nonce(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 42); + let recipient = test_wallet(2); + let msg = create_valid_transfer(tc, &sender_state, &recipient); + assert_eq!(msg.message.sequence, 42); + assert_eq!(msg.message.from, sender_state.wallet.address); + assert_eq!(msg.message.to, recipient.address); + assert_eq!(msg.signature.bytes().len(), 65); + } + + #[hegel::test(test_cases = 20)] + fn test_create_nonce_reuse_keeps_nonce(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 10); + let recipient = test_wallet(2); + let original = create_valid_transfer(tc.clone(), &sender_state, &recipient); + let other_wallets = vec![test_wallet(3), test_wallet(4)]; + let reused = create_nonce_reuse(tc, &original, &other_wallets); + assert_eq!(reused.message.sequence, original.message.sequence); + assert_eq!(reused.message.from, original.message.from); + } + + #[hegel::test(test_cases = 20)] + fn test_create_gas_bump_higher_premium(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 5); + let recipient = test_wallet(2); + let original = create_valid_transfer(tc.clone(), &sender_state, &recipient); + let bumped = create_gas_bump(tc, &original); + assert_eq!(bumped.message.sequence, original.message.sequence); + assert!(bumped.message.gas_premium > original.message.gas_premium); + } + + #[hegel::test(test_cases = 20)] + fn test_create_nonce_gap_skips_ahead(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 10); + let recipient = test_wallet(2); + let msg = create_nonce_gap(tc, &sender_state, &recipient); + assert!(msg.message.sequence > sender_state.nonce); + } + + #[hegel::test(test_cases = 20)] + fn test_create_fuzzed_msg_is_valid_cbor(tc: hegel::TestCase) { + let msg = create_fuzzed_msg(tc); + assert_eq!(msg.cbor_bytes[0], 0x82, "SignedMessage must be CBOR array(2)"); + } +} diff --git a/hegel-workload/src/scenario/mod.rs b/hegel-workload/src/scenario/mod.rs new file mode 100644 index 00000000..5590c0d2 --- /dev/null +++ b/hegel-workload/src/scenario/mod.rs @@ -0,0 +1,419 @@ +pub mod actions; +pub mod types; + +use types::*; + +/// All possible action kinds the scenario stepper can draw. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ActionKind { + // Always available (no inputs required) + PickWallet, + ObserveChainHead, + ObserveMempool, + CreateFuzzedMsg, + CreateFuzzedBlock, + Pause, + + // Requires wallet_states (observed nonce) + ObserveNonce, + CreateValidTransfer, + CreateNonceGap, + CreateSemiValidMsg, + CreateDrain, + + // Requires signed_msgs + PublishMsgP2p, + PublishMsgRpc, + CreateNonceReuse, + CreateGasBump, + WaitForInclusion, + + // Requires fuzzed_blocks + PublishBlockP2p, + + // Requires chain_tips + CreateBlockAtHeight, +} + +/// Holds the typed bag of intermediate values produced by actions. +pub struct ScenarioContext { + pub wallets: Vec, + pub wallet_states: Vec, + pub signed_msgs: Vec, + pub fuzzed_blocks: Vec, + pub chain_tips: Vec, + pub mempool_snapshots: Vec, +} + +impl ScenarioContext { + pub fn new(wallets: Vec) -> Self { + Self { + wallets, + wallet_states: vec![], + signed_msgs: vec![], + fuzzed_blocks: vec![], + chain_tips: vec![], + mempool_snapshots: vec![], + } + } + + /// Return the list of actions whose input requirements are satisfied. + pub fn available_actions(&self) -> Vec { + let mut actions = vec![ + ActionKind::PickWallet, + ActionKind::ObserveChainHead, + ActionKind::ObserveMempool, + ActionKind::CreateFuzzedMsg, + ActionKind::CreateFuzzedBlock, + ActionKind::Pause, + ]; + + if !self.wallet_states.is_empty() { + actions.extend_from_slice(&[ + ActionKind::ObserveNonce, + ActionKind::CreateValidTransfer, + ActionKind::CreateNonceGap, + ActionKind::CreateSemiValidMsg, + ActionKind::CreateDrain, + ]); + } + + if !self.signed_msgs.is_empty() { + actions.extend_from_slice(&[ + ActionKind::PublishMsgP2p, + ActionKind::PublishMsgRpc, + ActionKind::CreateNonceReuse, + ActionKind::CreateGasBump, + ActionKind::WaitForInclusion, + ]); + } + + if !self.fuzzed_blocks.is_empty() { + actions.push(ActionKind::PublishBlockP2p); + } + + if !self.chain_tips.is_empty() { + actions.push(ActionKind::CreateBlockAtHeight); + } + + actions + } +} + +/// Execute a single randomly-chosen action, updating the scenario context. +pub fn execute_step(tc: hegel::TestCase, ctx: &mut ScenarioContext, io: &types::ScenarioIO) { + let available = ctx.available_actions(); + if available.is_empty() { + return; + } + + let action = tc.clone().draw(hegel::generators::sampled_from(available)); + + match action { + ActionKind::PickWallet => { + let wallet = actions::pick_wallet(tc.clone(), &ctx.wallets); + if let Some(state) = actions::observe_nonce(tc.clone(), &wallet, io) { + ctx.wallet_states.push(state); + } + } + ActionKind::ObserveChainHead => { + if let Some(tip) = actions::observe_chain_head(tc.clone(), io) { + ctx.chain_tips.push(tip); + } + } + ActionKind::ObserveMempool => { + if let Some(snapshot) = actions::observe_mempool(tc.clone(), io) { + ctx.mempool_snapshots.push(snapshot); + } + } + ActionKind::ObserveNonce => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let wallet = ctx.wallet_states[idx].wallet.clone(); + if let Some(state) = actions::observe_nonce(tc.clone(), &wallet, io) { + ctx.wallet_states[idx] = state; + } + } + ActionKind::CreateValidTransfer => { + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_valid_transfer( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateNonceReuse => { + let msg_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let original = ctx.signed_msgs[msg_idx].clone(); + let msg = actions::create_nonce_reuse(tc.clone(), &original, &ctx.wallets); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateGasBump => { + let msg_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let original = ctx.signed_msgs[msg_idx].clone(); + let msg = actions::create_gas_bump(tc.clone(), &original); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateNonceGap => { + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_nonce_gap( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateSemiValidMsg => { + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_semi_valid_msg( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateDrain => { + // Same as CreateValidTransfer for now + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_valid_transfer( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateFuzzedMsg => { + let msg = actions::create_fuzzed_msg(tc.clone()); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateFuzzedBlock => { + let block = actions::create_fuzzed_block(tc.clone()); + ctx.fuzzed_blocks.push(block); + } + ActionKind::CreateBlockAtHeight => { + // Use existing fuzzed block generator (height parameterization is future work) + let block = actions::create_fuzzed_block(tc.clone()); + ctx.fuzzed_blocks.push(block); + } + ActionKind::PublishMsgP2p => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + actions::publish_msg_p2p(&ctx.signed_msgs[idx], io); + } + ActionKind::PublishMsgRpc => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let msg = ctx.signed_msgs[idx].clone(); + actions::publish_msg_rpc(tc.clone(), &msg, io); + } + ActionKind::PublishBlockP2p => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.fuzzed_blocks.len() - 1), + ); + actions::publish_block_p2p(&ctx.fuzzed_blocks[idx], io); + } + ActionKind::WaitForInclusion => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let msg = ctx.signed_msgs[idx].clone(); + if actions::wait_for_inclusion(&msg, io).is_some() { + // Re-observe nonce for the sender + let sender_wallet = ctx.wallets.iter().find(|w| w.address == msg.message.from); + if let Some(wallet) = sender_wallet { + if let Some(state) = actions::observe_nonce(tc.clone(), wallet, io) { + if let Some(existing) = ctx + .wallet_states + .iter_mut() + .find(|s| s.wallet.address == msg.message.from) + { + *existing = state; + } + } + } + } + } + ActionKind::Pause => { + actions::pause(tc.clone()); + } + } +} + +/// Run a complete scenario: draw N steps and execute them sequentially. +pub fn run_scenario(tc: hegel::TestCase, ctx: &mut ScenarioContext, io: &types::ScenarioIO) { + let num_steps: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(1) + .max_value(10), + ); + log::info!("scenario: starting with {} steps", num_steps); + + for step in 0..num_steps { + log::info!( + "scenario: step {}/{}, context: {} wallet_states, {} msgs, {} blocks", + step + 1, + num_steps, + ctx.wallet_states.len(), + ctx.signed_msgs.len(), + ctx.fuzzed_blocks.len(), + ); + execute_step(tc.clone(), ctx, io); + } + + antithesis_sdk::assert_sometimes!( + true, + "Scenario: multi-step scenario completed", + &serde_json::json!({"steps": num_steps, "msgs_created": ctx.signed_msgs.len()}) + ); + log::info!("scenario: complete"); +} + +#[cfg(test)] +mod tests { + use super::*; + use fvm_shared::address::Address; + + fn empty_context() -> ScenarioContext { + ScenarioContext::new(vec![Wallet { + address: Address::new_id(1000), + private_key: vec![1u8; 32], + }]) + } + + fn dummy_signed_msg() -> SignedMsg { + use fvm_shared::econ::TokenAmount; + use fvm_ipld_encoding::RawBytes; + SignedMsg { + message: fvm_shared::message::Message { + version: 0, + to: Address::new_id(1000), + from: Address::new_id(1001), + sequence: 0, + value: TokenAmount::from_atto(0u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 0, + gas_fee_cap: TokenAmount::from_atto(0u64), + gas_premium: TokenAmount::from_atto(0u64), + }, + signature: fvm_shared::crypto::signature::Signature::new_secp256k1(vec![0u8; 65]), + cbor_bytes: vec![], + sender_key: vec![1u8; 32], + } + } + + #[test] + fn test_initial_actions_available() { + let ctx = empty_context(); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::PickWallet)); + assert!(actions.contains(&ActionKind::ObserveChainHead)); + assert!(actions.contains(&ActionKind::ObserveMempool)); + assert!(actions.contains(&ActionKind::CreateFuzzedMsg)); + assert!(actions.contains(&ActionKind::CreateFuzzedBlock)); + assert!(actions.contains(&ActionKind::Pause)); + // These require inputs we don't have yet + assert!(!actions.contains(&ActionKind::CreateValidTransfer)); + assert!(!actions.contains(&ActionKind::PublishMsgP2p)); + } + + #[test] + fn test_transfer_available_after_observe() { + let mut ctx = empty_context(); + ctx.wallet_states.push(WalletState { + wallet: ctx.wallets[0].clone(), + nonce: 0, + }); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::CreateValidTransfer)); + assert!(actions.contains(&ActionKind::CreateNonceGap)); + assert!(actions.contains(&ActionKind::CreateSemiValidMsg)); + } + + #[test] + fn test_publish_available_after_create() { + let mut ctx = empty_context(); + ctx.signed_msgs.push(dummy_signed_msg()); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::PublishMsgP2p)); + assert!(actions.contains(&ActionKind::PublishMsgRpc)); + assert!(actions.contains(&ActionKind::CreateNonceReuse)); + assert!(actions.contains(&ActionKind::CreateGasBump)); + } + + #[test] + fn test_block_publish_available() { + let mut ctx = empty_context(); + ctx.fuzzed_blocks.push(FuzzedBlock { + cbor_bytes: vec![0x83], + }); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::PublishBlockP2p)); + } + + #[test] + fn test_block_at_height_requires_chain_tip() { + let mut ctx = empty_context(); + assert!(!ctx.available_actions().contains(&ActionKind::CreateBlockAtHeight)); + ctx.chain_tips.push(ChainTip { height: 100 }); + assert!(ctx.available_actions().contains(&ActionKind::CreateBlockAtHeight)); + } +} diff --git a/hegel-workload/src/scenario/types.rs b/hegel-workload/src/scenario/types.rs new file mode 100644 index 00000000..9db36a79 --- /dev/null +++ b/hegel-workload/src/scenario/types.rs @@ -0,0 +1,64 @@ +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::message::Message; + +use crate::network::PublishRequest; +use crate::rpc::LotusRpc; +use tokio::sync::mpsc; + +/// A wallet loaded from the stress keystore. +#[derive(Debug, Clone)] +pub struct Wallet { + pub address: Address, + pub private_key: Vec, +} + +/// Observed on-chain state for a wallet. +#[derive(Debug, Clone)] +pub struct WalletState { + pub wallet: Wallet, + pub nonce: u64, +} + +/// A signed message ready for delivery (not yet published). +#[derive(Debug, Clone)] +pub struct SignedMsg { + pub message: Message, + pub signature: Signature, + pub cbor_bytes: Vec, + pub sender_key: Vec, +} + +/// A fuzzed block ready for delivery. +#[derive(Debug, Clone)] +pub struct FuzzedBlock { + pub cbor_bytes: Vec, +} + +/// Observed chain head. +#[derive(Debug, Clone)] +pub struct ChainTip { + pub height: i64, +} + +/// Snapshot of mempool state. +#[derive(Debug, Clone)] +pub struct MempoolSnapshot { + pub pending: Vec<(String, u64)>, +} + +/// A message confirmed as included on-chain. +#[derive(Debug, Clone)] +pub struct IncludedMsg { + pub original: SignedMsg, +} + +/// I/O handles passed to actions that need network or RPC access. +/// Separate from ScenarioContext so context remains pure data. +pub struct ScenarioIO { + pub p2p_tx: mpsc::Sender, + pub rpc_clients: Vec<(String, LotusRpc)>, + pub rt_handle: tokio::runtime::Handle, + pub msgs_topic: String, + pub blocks_topic: String, +} diff --git a/hegel-workload/src/wallet.rs b/hegel-workload/src/wallet.rs new file mode 100644 index 00000000..bc81269c --- /dev/null +++ b/hegel-workload/src/wallet.rs @@ -0,0 +1,238 @@ +use crate::scenario::types::Wallet; +use cid::Cid; +use fvm_ipld_encoding::to_vec as cbor_serialize; +use fvm_shared::address::Network; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::message::Message; +use k256::ecdsa::{SigningKey, signature::hazmat::PrehashSigner}; +use log::{info, warn}; +use multihash_codetable::{Code, MultihashDigest}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct KeystoreEntry { + #[serde(rename = "Address")] + address: String, + #[serde(rename = "PrivateKey")] + private_key: String, +} + +/// Parse a JSON keystore string into a list of Wallet structs. +/// The JSON format is an array of objects with "Address" and "PrivateKey" fields, +/// where PrivateKey is a hex-encoded 32-byte secp256k1 private key. +pub fn parse_keystore(json_str: &str) -> Result, String> { + let entries: Vec = + serde_json::from_str(json_str).map_err(|e| format!("JSON parse error: {}", e))?; + + let mut wallets = Vec::with_capacity(entries.len()); + for entry in entries { + // Try mainnet first (f-prefix), fall back to testnet (t-prefix) + let address = Network::Mainnet + .parse_address(&entry.address) + .or_else(|_| Network::Testnet.parse_address(&entry.address)) + .map_err(|e| format!("invalid address '{}': {}", entry.address, e))?; + + let private_key = hex::decode(&entry.private_key) + .map_err(|e| format!("invalid hex private key for {}: {}", entry.address, e))?; + + if private_key.len() != 32 { + return Err(format!( + "private key for {} is {} bytes, expected 32", + entry.address, + private_key.len() + )); + } + + wallets.push(Wallet { + address, + private_key, + }); + } + + Ok(wallets) +} + +/// Load wallets from the stress keystore JSON file at the given path. +/// Retries every 5 seconds for up to 5 minutes if the file does not yet exist. +/// Returns an empty vec on timeout. +pub fn load_keystore(path: &str) -> Vec { + let max_attempts = 60; // 5 minutes / 5 seconds + for attempt in 1..=max_attempts { + match std::fs::read_to_string(path) { + Ok(contents) => match parse_keystore(&contents) { + Ok(wallets) => { + info!( + "loaded {} wallets from keystore '{}'", + wallets.len(), + path + ); + return wallets; + } + Err(e) => { + warn!("failed to parse keystore '{}': {}", path, e); + return Vec::new(); + } + }, + Err(_) => { + if attempt == 1 { + info!( + "keystore '{}' not yet available, waiting (attempt {}/{})", + path, attempt, max_attempts + ); + } else { + info!( + "still waiting for keystore '{}' (attempt {}/{})", + path, attempt, max_attempts + ); + } + std::thread::sleep(std::time::Duration::from_secs(5)); + } + } + } + + warn!( + "timed out waiting for keystore '{}' after {} seconds", + path, + max_attempts * 5 + ); + Vec::new() +} + +pub fn sign_message(msg: &Message, private_key: &[u8]) -> Result { + // Step 1: CBOR-serialize the message + let cbor_bytes = cbor_serialize(msg).map_err(|e| format!("cbor serialize: {}", e))?; + + // Step 2: Compute CID (CIDv1, dag-cbor codec 0x71, SHA2-256) + let mh = Code::Sha2_256.digest(&cbor_bytes); + let cid = Cid::new_v1(0x71, mh); + let cid_bytes = cid.to_bytes(); + + // Step 3: Blake2b-256 hash the CID bytes (Filecoin signing convention) + let hash = blake2b_simd::Params::new() + .hash_length(32) + .hash(&cid_bytes); + + // Step 4: ECDSA sign with recovery + let signing_key = + SigningKey::from_bytes(private_key.into()).map_err(|e| format!("bad key: {}", e))?; + let (sig, recovery_id) = signing_key + .sign_prehash(hash.as_bytes()) + .map_err(|e| format!("sign failed: {}", e))?; + + // Encode as 65 bytes: r (32) || s (32) || v (1) + let mut sig_bytes = Vec::with_capacity(65); + sig_bytes.extend_from_slice(&sig.to_bytes()); + sig_bytes.push(recovery_id.to_byte()); + + Ok(Signature::new_secp256k1(sig_bytes)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_keystore_json() { + // Use a valid 32-byte secp256k1 private key (hex = 64 chars). + // Use testnet address format (t-prefix) matching the production keystore. + let key_hex = "0101010101010101010101010101010101010101010101010101010101010101"; + let json = format!( + r#"[{{"Address": "t1d2xrzcslx7xlbbylc5c3d5lvandqw4iwl6epxba", "PrivateKey": "{}"}}]"#, + key_hex + ); + + let wallets = parse_keystore(&json).expect("should parse successfully"); + assert_eq!(wallets.len(), 1); + assert_eq!(wallets[0].private_key.len(), 32); + assert_eq!(wallets[0].private_key, vec![0x01u8; 32]); + } + + #[test] + fn test_parse_keystore_empty() { + let wallets = parse_keystore("[]").expect("should parse empty array"); + assert!(wallets.is_empty()); + } + + #[test] + fn test_parse_keystore_bad_hex() { + // Use testnet ID address (t01000) — parse_address(Testnet) handles it. + let json = r#"[{"Address": "t01000", "PrivateKey": "not_valid_hex!"}]"#; + let result = parse_keystore(json); + assert!(result.is_err(), "expected error on invalid hex"); + let err = result.unwrap_err(); + assert!( + err.contains("invalid hex"), + "error message should mention 'invalid hex', got: {}", + err + ); + } + + #[test] + fn test_parse_keystore_wrong_key_length() { + // Only 16 bytes (32 hex chars) instead of 32. + let short_key = "01020304050607080910111213141516"; + let json = format!( + r#"[{{"Address": "t01000", "PrivateKey": "{}"}}]"#, + short_key + ); + let result = parse_keystore(&json); + assert!(result.is_err(), "expected error on wrong key length"); + let err = result.unwrap_err(); + assert!( + err.contains("16 bytes") && err.contains("expected 32"), + "error message should mention byte length, got: {}", + err + ); + } + + #[test] + fn test_sign_message_produces_65_byte_signature() { + use fvm_ipld_encoding::RawBytes; + use fvm_shared::address::Address; + use fvm_shared::econ::TokenAmount; + use fvm_shared::message::Message; + + let private_key = vec![1u8; 32]; + let msg = Message { + version: 0, + to: Address::new_id(1000), + from: Address::new_id(1001), + sequence: 0, + value: TokenAmount::from_atto(1000u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 1_000_000, + gas_fee_cap: TokenAmount::from_atto(100_000u64), + gas_premium: TokenAmount::from_atto(1_000u64), + }; + + let sig = sign_message(&msg, &private_key).unwrap(); + assert_eq!(sig.bytes().len(), 65, "secp256k1 recoverable sig must be 65 bytes"); + } + + #[test] + fn test_sign_message_deterministic() { + use fvm_ipld_encoding::RawBytes; + use fvm_shared::address::Address; + use fvm_shared::econ::TokenAmount; + use fvm_shared::message::Message; + + let private_key = vec![42u8; 32]; + let msg = Message { + version: 0, + to: Address::new_id(1000), + from: Address::new_id(1001), + sequence: 5, + value: TokenAmount::from_atto(0u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 1_000_000, + gas_fee_cap: TokenAmount::from_atto(100_000u64), + gas_premium: TokenAmount::from_atto(1_000u64), + }; + + let sig1 = sign_message(&msg, &private_key).unwrap(); + let sig2 = sign_message(&msg, &private_key).unwrap(); + assert_eq!(sig1.bytes(), sig2.bytes(), "signing must be deterministic"); + } +} diff --git a/lotus/Dockerfile b/lotus/Dockerfile index 71dc6bd2..b64e5e80 100644 --- a/lotus/Dockerfile +++ b/lotus/Dockerfile @@ -6,9 +6,10 @@ FROM golang:$GO_VERSION RUN apt-get update && apt-get install -y jq libhwloc-dev ocl-icd-opencl-dev && rm -rf /var/lib/apt/lists/* # cloning lotus -ARG REF="master" -RUN git clone --depth=1 https://github.com/filecoin-project/lotus.git --branch=${REF} /lotus +ARG REF="006da4c7e1c1c29ac02b32112c0d205e4085ba35" +RUN git clone https://github.com/filecoin-project/lotus.git /lotus WORKDIR /lotus +RUN git checkout ${REF} # copy & apply lotus patch (local drand; setting bootstrap and finality epoch) COPY patches/lotus.patch lotus.patch @@ -37,6 +38,10 @@ WORKDIR /lotus_instrumented/customer/ # deleting original source code RUN rm -rf /lotus +# install Rust toolchain (required by filecoin-ffi to build filcrypto from source) +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + # install runtime binaries (lotus-shed used in curio) (not using: 2k-lotus-worker) RUN make 2k-lotus 2k-lotus-miner 2k-lotus-seed 2k-lotus-shed diff --git a/lotus/scripts/start-lotus.sh b/lotus/scripts/start-lotus.sh index 17692749..580a0f07 100755 --- a/lotus/scripts/start-lotus.sh +++ b/lotus/scripts/start-lotus.sh @@ -124,6 +124,20 @@ fi connect_with_retries() { local max_retries=10 local addr_file="$1" + local node_name="$2" + local wait_retries=3 + + # Wait briefly for the addr file to appear; skip if the node isn't running + for (( j=1; j<=wait_retries; j++ )); do + if [ -f "$addr_file" ] && [ -s "$addr_file" ]; then + break + fi + if [ "$j" -eq "$wait_retries" ]; then + echo "SKIP: $node_name addr file not found at $addr_file (node probably not running)" + return 0 + fi + sleep 2 + done for (( j=1; j<=max_retries; j++ )); do echo "attempt $j/$max_retries..." @@ -137,7 +151,7 @@ connect_with_retries() { fi done - echo "ERROR: reached $max_retries attempts." + echo "ERROR: reached $max_retries attempts for $node_name." return 1 } @@ -152,7 +166,7 @@ for (( i=0; i<$NUM_LOTUS_CLIENTS; i++ )); do addr_file="${OTHER_LOTUS_DATA_DIR}/lotus${i}-ipv4addr" echo "Connecting to lotus$i at $addr_file" - connect_with_retries "$addr_file" + connect_with_retries "$addr_file" "lotus$i" done echo "connecting to forest nodes..." @@ -162,7 +176,7 @@ for (( i=0; i<$NUM_FOREST_CLIENTS; i++ )); do addr_file="${FOREST_DATA_DIR}/forest${i}-ipv4addr" echo "Connecting to forest$i at $addr_file" - connect_with_retries "$addr_file" + connect_with_retries "$addr_file" "forest$i" done touch "${SHARED_CONFIGS}/lotus-${node_number}-ready" diff --git a/workload/Dockerfile b/workload/Dockerfile index fb05aa32..0163fc71 100644 --- a/workload/Dockerfile +++ b/workload/Dockerfile @@ -1,8 +1,15 @@ +# ── Stage 1: Build hegel-workload (Rust) ── +FROM rust:1.86 AS rust-builder +WORKDIR /build +COPY hegel-workload/Cargo.toml hegel-workload/Cargo.lock ./ +COPY hegel-workload/src/ ./src/ +RUN cargo build --release --features antithesis + FROM ubuntu:latest # Core dependencies only — no Foundry, no Rust, no pnpm, no Hardhat RUN apt-get update -y && \ - apt-get install -y curl jq gcc ntpdate git && \ + apt-get install -y curl jq gcc ntpdate git python3 python3-venv ca-certificates && \ ARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') && \ curl -OL "https://golang.org/dl/go1.23.2.linux-${ARCH}.tar.gz" && \ tar -C /usr/local -xzf "go1.23.2.linux-${ARCH}.tar.gz" && \ @@ -12,8 +19,11 @@ ENV PATH="/usr/local/go/bin:${PATH}" ENV GOPATH="/go" ENV PATH="$GOPATH/bin:$PATH" +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/root/.local/bin:${PATH}" + WORKDIR /opt/antithesis -COPY . . +COPY workload/ . # Build Go binaries RUN go mod download @@ -29,6 +39,13 @@ RUN GOARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') \ RUN GOARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') \ go build -o ./protocol-fuzzer ./cmd/protocol-fuzzer -RUN chmod +x ./entrypoint/entrypoint.sh +COPY --from=rust-builder /build/target/release/hegel-workload /usr/local/bin/hegel-workload + +# Pre-install hegel-core so the workload doesn't need internet access at runtime. +# HEGEL_SERVER_COMMAND tells hegeltest to use this binary instead of downloading. +RUN uv venv .hegel/venv && uv pip install --python .hegel/venv/bin/python hegel-core==0.2.3 +ENV HEGEL_SERVER_COMMAND="/opt/antithesis/.hegel/venv/bin/hegel" + +RUN chmod +x ./entrypoint/entrypoint.sh ./entrypoint/entrypoint-hegel.sh ENTRYPOINT ["/opt/antithesis/entrypoint/entrypoint.sh"] diff --git a/workload/entrypoint/entrypoint-hegel.sh b/workload/entrypoint/entrypoint-hegel.sh new file mode 100644 index 00000000..094f4297 --- /dev/null +++ b/workload/entrypoint/entrypoint-hegel.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { echo -e "${GREEN}[HEGEL]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[HEGEL]${NC} $1"; } + +# ── 1. Ensure Antithesis output directory exists ── +mkdir -p "${ANTITHESIS_OUTPUT_DIR:-/tmp/antithesis}" + +# ── 2. Wait for genesis keystore (produced by the main workload container) ── +KEYSTORE="${STRESS_KEYSTORE_PATH:-/shared/configs/stress_keystore.json}" +log_info "Waiting for keystore at ${KEYSTORE}..." +while [ ! -f "$KEYSTORE" ] || [ ! -s "$KEYSTORE" ]; do sleep 2; done +log_info "Keystore ready." + +# ── 3. Wait for node multiaddr files ── +DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" +IFS=',' read -ra NODES <<< "${STRESS_NODES:-lotus0}" +log_info "Waiting for node multiaddr files..." +MAX_WAIT=300 +WAITED=0 +FOUND=false +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + for node in "${NODES[@]}"; do + node=$(echo "$node" | tr -d ' ') + ADDR_FILE="${DEVGEN_DIR}/${node}/${node}-ipv4addr" + if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then + log_info "Found multiaddr for ${node}" + FOUND=true + break + fi + done + if [ "$FOUND" = true ]; then + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done +if [ "$FOUND" = false ]; then + log_warn "No multiaddr files found after ${MAX_WAIT}s, continuing anyway..." +fi + +# ── 4. Wait for blockchain to reach minimum epoch ── +WAIT_HEIGHT="${STRESS_WAIT_HEIGHT:-10}" +RPC_URL="http://lotus0:${STRESS_RPC_PORT:-1234}/rpc/v1" +log_info "Waiting for block height to reach ${WAIT_HEIGHT}..." +while true; do + height=$(curl -sf -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"Filecoin.ChainHead","params":[],"id":1}' \ + "$RPC_URL" 2>/dev/null | jq -r '.result.Height // empty' 2>/dev/null) + if [ -n "$height" ] && [ "$height" -ge "$WAIT_HEIGHT" ] 2>/dev/null; then + log_info "Blockchain ready at height ${height}" + break + fi + log_info "Current height: ${height:-unknown}, waiting..." + sleep 5 +done + +# ── 5. Launch hegel workload ── +log_info "Launching hegel workload..." +exec env RUST_LOG="${RUST_LOG:-info}" /usr/local/bin/hegel-workload diff --git a/workload/entrypoint/entrypoint.sh b/workload/entrypoint/entrypoint.sh index ae3c2fa5..bf7e8054 100755 --- a/workload/entrypoint/entrypoint.sh +++ b/workload/entrypoint/entrypoint.sh @@ -13,6 +13,52 @@ log_info "Generating pre-funded genesis wallets..." /opt/antithesis/genesis-prep --count 100 --out /shared/configs log_info "Genesis wallet generation complete." +# ── 1b. Ensure Antithesis output directory exists ── +mkdir -p "${ANTITHESIS_OUTPUT_DIR:-/tmp/antithesis}" + +# ── 1c. Wait for node multiaddr files ── +DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" +IFS=',' read -ra NODES <<< "${STRESS_NODES:-lotus0}" +log_info "Waiting for node multiaddr files..." +MAX_WAIT=300 +WAITED=0 +FOUND=false +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + for node in "${NODES[@]}"; do + node=$(echo "$node" | tr -d ' ') + ADDR_FILE="${DEVGEN_DIR}/${node}/${node}-ipv4addr" + if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then + log_info "Found multiaddr for ${node}" + FOUND=true + break + fi + done + if [ "$FOUND" = true ]; then + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done +if [ "$FOUND" = false ]; then + log_warn "No multiaddr files found after ${MAX_WAIT}s, continuing anyway..." +fi + +# ── 1d. Wait for network name ── +NETWORK_FILE="${DEVGEN_DIR}/lotus0/network_name" +log_info "Waiting for network name at ${NETWORK_FILE}..." +WAITED=0 +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + if [ -f "$NETWORK_FILE" ] && [ -s "$NETWORK_FILE" ]; then + log_info "Network name: $(cat "$NETWORK_FILE")" + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done + +# ── 1e. Allow nodes to finish connecting ── +sleep 10 + # ── 2. Time sync ── log_info "Synchronizing system time..." if ntpdate -q pool.ntp.org &>/dev/null; then