diff --git a/Cargo.lock b/Cargo.lock
index f584133e4..86f0fa464 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -303,6 +303,16 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247"
 
+[[package]]
+name = "async-attributes"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "async-channel"
 version = "1.9.0"
@@ -334,11 +344,23 @@ checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
 dependencies = [
  "async-task",
  "concurrent-queue",
- "fastrand",
- "futures-lite",
+ "fastrand 2.2.0",
+ "futures-lite 2.5.0",
  "slab",
 ]
 
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
 [[package]]
 name = "async-global-executor"
 version = "2.4.1"
@@ -347,32 +369,61 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
 dependencies = [
  "async-channel 2.3.1",
  "async-executor",
- "async-io",
- "async-lock",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
  "blocking",
- "futures-lite",
+ "futures-lite 2.5.0",
  "once_cell",
 ]
 
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite 1.13.0",
+ "log",
+ "parking",
+ "polling 2.8.0",
+ "rustix 0.37.28",
+ "slab",
+ "socket2 0.4.10",
+ "waker-fn",
+]
+
 [[package]]
 name = "async-io"
 version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
 dependencies = [
- "async-lock",
+ "async-lock 3.4.0",
  "cfg-if",
  "concurrent-queue",
  "futures-io",
- "futures-lite",
+ "futures-lite 2.5.0",
  "parking",
- "polling",
- "rustix",
+ "polling 3.7.4",
+ "rustix 0.38.40",
  "slab",
  "tracing",
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
 [[package]]
 name = "async-lock"
 version = "3.4.0"
@@ -384,6 +435,53 @@ dependencies = [
  "pin-project-lite",
 ]
 
+[[package]]
+name = "async-net"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f"
+dependencies = [
+ "async-io 1.13.0",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
+[[package]]
+name = "async-process"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
+dependencies = [
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 3.1.0",
+ "futures-lite 1.13.0",
+ "rustix 0.38.40",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-process"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
+dependencies = [
+ "async-channel 2.3.1",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener 5.3.1",
+ "futures-lite 2.5.0",
+ "rustix 0.38.40",
+ "tracing",
+]
+
 [[package]]
 name = "async-recursion"
 version = "1.1.1"
@@ -395,21 +493,41 @@ dependencies = [
  "syn 2.0.87",
 ]
 
+[[package]]
+name = "async-signal"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
+dependencies = [
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.40",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "async-std"
 version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615"
 dependencies = [
+ "async-attributes",
  "async-channel 1.9.0",
  "async-global-executor",
- "async-io",
- "async-lock",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "async-process 2.3.0",
  "crossbeam-utils",
  "futures-channel",
  "futures-core",
  "futures-io",
- "futures-lite",
+ "futures-lite 2.5.0",
  "gloo-timers 0.3.0",
  "kv-log-macro",
  "log",
@@ -421,6 +539,21 @@ dependencies = [
  "wasm-bindgen-futures",
 ]
 
+[[package]]
+name = "async-std-resolver"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa5ee46ec0c518414838d2fdc7dd18f6ba7d934b6e728005c958621da450682d"
+dependencies = [
+ "async-std",
+ "async-trait",
+ "futures-io",
+ "futures-util",
+ "hickory-resolver",
+ "pin-utils",
+ "socket2 0.5.7",
+]
+
 [[package]]
 name = "async-stm"
 version = "0.4.0"
@@ -843,6 +976,19 @@ dependencies = [
  "constant_time_eq 0.3.1",
 ]
 
+[[package]]
+name = "blake3"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
+dependencies = [
+ "arrayref",
+ "arrayvec 0.7.6",
+ "cc",
+ "cfg-if",
+ "constant_time_eq 0.3.1",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.9.0"
@@ -879,7 +1025,7 @@ dependencies = [
  "async-channel 2.3.1",
  "async-task",
  "futures-io",
- "futures-lite",
+ "futures-lite 2.5.0",
  "piper",
 ]
 
@@ -1067,6 +1213,16 @@ dependencies = [
  "pkg-config",
 ]
 
+[[package]]
+name = "cached"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af4dfac631a8e77b2f327f7852bb6172771f5279c4512efe79fad6067b37be3d"
+dependencies = [
+ "hashbrown 0.11.2",
+ "once_cell",
+]
+
 [[package]]
 name = "camino"
 version = "1.1.9"
@@ -1190,8 +1346,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
+ "js-sys",
  "num-traits",
  "serde",
+ "wasm-bindgen",
  "windows-targets 0.52.6",
 ]
 
@@ -1201,6 +1359,19 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
 
+[[package]]
+name = "cid"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2"
+dependencies = [
+ "core2",
+ "multibase",
+ "multihash 0.16.3",
+ "serde",
+ "unsigned-varint 0.7.2",
+]
+
 [[package]]
 name = "cid"
 version = "0.10.1"
@@ -2279,6 +2450,19 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "env_logger"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
+dependencies = [
+ "atty",
+ "humantime 2.1.0",
+ "log",
+ "regex",
+ "termcolor",
+]
+
 [[package]]
 name = "env_logger"
 version = "0.10.2"
@@ -2642,6 +2826,17 @@ version = "2.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 
+[[package]]
+name = "event-listener"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "event-listener"
 version = "5.3.1"
@@ -2731,6 +2926,15 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
 
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
 [[package]]
 name = "fastrand"
 version = "2.2.0"
@@ -2768,7 +2972,7 @@ name = "fendermint_actor_activity_tracker"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fil_actors_evm_shared",
  "fil_actors_runtime",
  "frc42_dispatch",
@@ -2789,7 +2993,7 @@ name = "fendermint_actor_chainmetadata"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fil_actors_runtime",
  "frc42_dispatch",
  "fvm_ipld_amt",
@@ -2807,7 +3011,7 @@ name = "fendermint_actor_eam"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fil_actor_eam",
  "fil_actors_evm_shared",
  "fil_actors_runtime",
@@ -2828,7 +3032,7 @@ name = "fendermint_actor_gas_market_eip1559"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fendermint_actors_api",
  "fil_actors_evm_shared",
  "fil_actors_runtime",
@@ -2849,7 +3053,7 @@ name = "fendermint_actors"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fendermint_actor_activity_tracker",
  "fendermint_actor_chainmetadata",
  "fendermint_actor_eam",
@@ -2867,7 +3071,7 @@ name = "fendermint_actors_api"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fil_actors_evm_shared",
  "fil_actors_runtime",
  "frc42_dispatch",
@@ -2890,7 +3094,7 @@ dependencies = [
  "async-stm",
  "async-trait",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "fendermint_abci",
  "fendermint_actor_gas_market_eip1559",
  "fendermint_actors_api",
@@ -2925,7 +3129,7 @@ dependencies = [
  "ipc_ipld_resolver",
  "k256 0.11.6",
  "lazy_static",
- "libipld",
+ "libipld 0.16.0",
  "libp2p",
  "libp2p-bitswap",
  "literally",
@@ -2935,7 +3139,7 @@ dependencies = [
  "paste",
  "prometheus",
  "prometheus_exporter",
- "prost",
+ "prost 0.11.9",
  "quickcheck",
  "quickcheck_macros",
  "rand_chacha",
@@ -2961,7 +3165,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "clap 4.5.21",
  "fendermint_materializer",
  "fendermint_vm_actor_interface",
@@ -3016,7 +3220,7 @@ dependencies = [
  "async-trait",
  "byteorder",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "ethers",
  "fendermint_actor_gas_market_eip1559",
  "fendermint_actors_api",
@@ -3060,7 +3264,7 @@ dependencies = [
  "anyhow",
  "async-trait",
  "axum",
- "cid",
+ "cid 0.10.1",
  "clap 4.5.21",
  "erased-serde",
  "ethers",
@@ -3162,7 +3366,7 @@ name = "fendermint_rocksdb"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fendermint_storage",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
@@ -3182,7 +3386,7 @@ dependencies = [
  "async-trait",
  "base64 0.21.7",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "clap 4.5.21",
  "ethers",
  "fendermint_crypto",
@@ -3193,7 +3397,7 @@ dependencies = [
  "fvm_shared",
  "hex",
  "lazy_static",
- "prost",
+ "prost 0.11.9",
  "serde",
  "serde_json",
  "tendermint 0.31.1",
@@ -3230,7 +3434,7 @@ version = "0.1.0"
 dependencies = [
  "arbitrary",
  "arbtest",
- "cid",
+ "cid 0.10.1",
  "ethers",
  "fendermint_testing",
  "fvm_ipld_encoding",
@@ -3259,7 +3463,7 @@ name = "fendermint_vm_actor_interface"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "ethers",
  "ethers-core",
  "fendermint_crypto",
@@ -3289,7 +3493,7 @@ name = "fendermint_vm_core"
 version = "0.1.0"
 dependencies = [
  "arbitrary",
- "cid",
+ "cid 0.10.1",
  "fnv",
  "fvm_shared",
  "lazy_static",
@@ -3304,7 +3508,7 @@ dependencies = [
 name = "fendermint_vm_encoding"
 version = "0.1.0"
 dependencies = [
- "cid",
+ "cid 0.10.1",
  "fvm_shared",
  "ipc-api",
  "num-traits",
@@ -3325,7 +3529,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "arbitrary",
- "cid",
+ "cid 0.10.1",
  "fendermint_actor_eam",
  "fendermint_crypto",
  "fendermint_testing",
@@ -3357,7 +3561,7 @@ dependencies = [
  "async-stm",
  "async-trait",
  "base64 0.21.7",
- "cid",
+ "cid 0.10.1",
  "ethers",
  "fendermint_actor_activity_tracker",
  "fendermint_actor_chainmetadata",
@@ -3390,7 +3594,7 @@ dependencies = [
  "ipc-api",
  "ipc-observability",
  "ipc_actors_abis",
- "libipld",
+ "libipld 0.16.0",
  "merkle-tree-rs",
  "multihash 0.18.1",
  "num-traits",
@@ -3421,7 +3625,7 @@ dependencies = [
  "anyhow",
  "arbitrary",
  "blake2b_simd",
- "cid",
+ "cid 0.10.1",
  "ethers",
  "ethers-core",
  "fendermint_crypto",
@@ -3449,7 +3653,7 @@ name = "fendermint_vm_resolver"
 version = "0.1.0"
 dependencies = [
  "async-stm",
- "cid",
+ "cid 0.10.1",
  "im",
  "ipc-api",
  "ipc_ipld_resolver",
@@ -3465,7 +3669,7 @@ dependencies = [
  "anyhow",
  "arbitrary",
  "async-stm",
- "cid",
+ "cid 0.10.1",
  "dircpy",
  "fendermint_testing",
  "fendermint_vm_core",
@@ -3502,7 +3706,7 @@ dependencies = [
  "async-stm",
  "async-trait",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "clap 4.5.21",
  "ethers",
  "fendermint_crypto",
@@ -3568,7 +3772,7 @@ checksum = "e0b1448c65c9a054c640fc086e03b730919ca4feca697c34ed3bda9f16aa982f"
 dependencies = [
  "anyhow",
  "async-std",
- "cid",
+ "cid 0.10.1",
  "clap 4.5.21",
  "futures",
  "fvm_ipld_blockstore",
@@ -3585,7 +3789,7 @@ version = "15.0.0-rc1"
 source = "git+https://github.com/filecoin-project/builtin-actors?tag=v15.0.0#831bf1b9fe281835409c39a8c454a68dd3200bbc"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fil_actors_evm_shared",
  "fil_actors_runtime",
  "fvm_ipld_blockstore",
@@ -3623,7 +3827,7 @@ dependencies = [
  "blake2b_simd",
  "byteorder",
  "castaway",
- "cid",
+ "cid 0.10.1",
  "fvm_ipld_amt",
  "fvm_ipld_bitfield",
  "fvm_ipld_blockstore",
@@ -3833,7 +4037,6 @@ dependencies = [
 [[package]]
 name = "frc42_dispatch"
 version = "7.0.0"
-source = "git+https://github.com/filecoin-project/actors-utils?rev=0f8365151f44785f7bead4e5500258fd5c8944e6#0f8365151f44785f7bead4e5500258fd5c8944e6"
 dependencies = [
  "frc42_hasher",
  "frc42_macros",
@@ -3846,7 +4049,6 @@ dependencies = [
 [[package]]
 name = "frc42_hasher"
 version = "5.0.0"
-source = "git+https://github.com/filecoin-project/actors-utils?rev=0f8365151f44785f7bead4e5500258fd5c8944e6#0f8365151f44785f7bead4e5500258fd5c8944e6"
 dependencies = [
  "fvm_sdk",
  "fvm_shared",
@@ -3856,13 +4058,13 @@ dependencies = [
 [[package]]
 name = "frc42_macros"
 version = "5.0.0"
-source = "git+https://github.com/filecoin-project/actors-utils?rev=0f8365151f44785f7bead4e5500258fd5c8944e6#0f8365151f44785f7bead4e5500258fd5c8944e6"
 dependencies = [
  "blake2b_simd",
  "frc42_hasher",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
+ "trybuild",
 ]
 
 [[package]]
@@ -3940,13 +4142,28 @@ version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
 
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
 [[package]]
 name = "futures-lite"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
 dependencies = [
- "fastrand",
+ "fastrand 2.2.0",
  "futures-core",
  "futures-io",
  "parking",
@@ -4045,7 +4262,7 @@ dependencies = [
  "ambassador",
  "anyhow",
  "arbitrary",
- "cid",
+ "cid 0.10.1",
  "derive_more",
  "filecoin-proofs-api",
  "fvm-wasm-instrument",
@@ -4091,7 +4308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5fea333475130094f27ce67809aae3f69eb5247541d835950b7c5da733dbbb34"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
  "itertools 0.11.0",
@@ -4119,7 +4336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d064b957420f5ecc137a153baaa6c32e2eb19b674135317200b6f2537eabdbfd"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "multihash 0.18.1",
 ]
 
@@ -4129,7 +4346,7 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6190f03442b67b21a3d4e115c4d4dd3468aed24e27ebb074218822c1b3df41ba"
 dependencies = [
- "cid",
+ "cid 0.10.1",
  "futures",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
@@ -4145,7 +4362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90608092e31d9a06236268c58f7c36668ab4b2a48afafe3a97e08f094ad7ae50"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fvm_ipld_blockstore",
  "multihash 0.18.1",
  "serde",
@@ -4163,11 +4380,11 @@ checksum = "48c900736087ff87cc51f669eee2f8e000c80717472242eb3f712aaa059ac3b3"
 dependencies = [
  "anyhow",
  "byteorder",
- "cid",
+ "cid 0.10.1",
  "forest_hash_utils",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
- "libipld-core",
+ "libipld-core 0.16.0",
  "multihash 0.18.1",
  "once_cell",
  "serde",
@@ -4181,7 +4398,7 @@ version = "4.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b2df521d41de41c34ac712720abdd6e29e68e3ed922554db70dbf8bc49a82b7"
 dependencies = [
- "cid",
+ "cid 0.10.1",
  "fvm_ipld_encoding",
  "fvm_shared",
  "lazy_static",
@@ -4201,7 +4418,7 @@ dependencies = [
  "bitflags 2.6.0",
  "blake2b_simd",
  "bls-signatures 0.15.0",
- "cid",
+ "cid 0.10.1",
  "data-encoding",
  "data-encoding-macro",
  "filecoin-proofs-api",
@@ -4232,11 +4449,12 @@ dependencies = [
 [[package]]
 name = "gcra"
 version = "0.4.0"
-source = "git+https://github.com/consensus-shipyard/gcra-rs.git?branch=main#621a45559a1107778dfcb6ebdf00a7ee848a8d8c"
 dependencies = [
+ "chrono",
  "dashmap",
  "rustc-hash 1.1.0",
  "thiserror 1.0.69",
+ "tokio",
 ]
 
 [[package]]
@@ -4374,6 +4592,12 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
@@ -4533,7 +4757,7 @@ dependencies = [
  "ipnet",
  "once_cell",
  "rand",
- "socket2",
+ "socket2 0.5.7",
  "thiserror 1.0.69",
  "tinyvec",
  "tokio",
@@ -4693,7 +4917,7 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "socket2",
+ "socket2 0.5.7",
  "tokio",
  "tower-service",
  "tracing",
@@ -4977,7 +5201,7 @@ version = "3.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e"
 dependencies = [
- "async-io",
+ "async-io 2.4.0",
  "core-foundation",
  "fnv",
  "futures",
@@ -4985,6 +5209,7 @@ dependencies = [
  "ipnet",
  "log",
  "rtnetlink",
+ "smol",
  "system-configuration",
  "tokio",
  "windows",
@@ -5120,6 +5345,17 @@ version = "3.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
 
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "iowrap"
 version = "0.2.1"
@@ -5134,7 +5370,7 @@ name = "ipc-api"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "ethers",
  "fil_actors_runtime",
  "fnv",
@@ -5168,7 +5404,7 @@ dependencies = [
  "async-trait",
  "base64 0.21.7",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "clap 4.5.21",
  "clap_complete",
  "env_logger 0.10.2",
@@ -5231,7 +5467,7 @@ dependencies = [
  "async-trait",
  "base64 0.21.7",
  "bytes",
- "cid",
+ "cid 0.10.1",
  "dirs",
  "ethers",
  "ethers-contract",
@@ -5276,7 +5512,7 @@ name = "ipc-types"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fvm_ipld_amt",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
@@ -5342,7 +5578,7 @@ dependencies = [
  "base64 0.21.7",
  "blake2b_simd",
  "bloom",
- "cid",
+ "cid 0.10.1",
  "env_logger 0.10.2",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
@@ -5353,7 +5589,7 @@ dependencies = [
  "ipc-observability",
  "ipc_ipld_resolver",
  "lazy_static",
- "libipld",
+ "libipld 0.16.0",
  "libp2p",
  "libp2p-bitswap",
  "libp2p-mplex",
@@ -5377,7 +5613,7 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
 dependencies = [
- "socket2",
+ "socket2 0.5.7",
  "widestring",
  "windows-sys 0.48.0",
  "winreg",
@@ -5626,6 +5862,24 @@ version = "0.2.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
 
+[[package]]
+name = "libipld"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9c3aa309c260aa2f174bac968901eddc546e9d85950c28eae6a7bec402f926"
+dependencies = [
+ "async-trait",
+ "cached",
+ "fnv",
+ "libipld-cbor 0.14.0",
+ "libipld-core 0.14.0",
+ "libipld-macro 0.14.0",
+ "log",
+ "multihash 0.16.3",
+ "parking_lot",
+ "thiserror 1.0.69",
+]
+
 [[package]]
 name = "libipld"
 version = "0.16.0"
@@ -5633,14 +5887,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1ccd6b8ffb3afee7081fcaec00e1b099fd1c7ccf35ba5729d88538fcc3b4599"
 dependencies = [
  "fnv",
- "libipld-cbor",
- "libipld-core",
- "libipld-macro",
+ "libipld-cbor 0.16.0",
+ "libipld-core 0.16.0",
+ "libipld-macro 0.16.0",
  "log",
  "multihash 0.18.1",
  "thiserror 1.0.69",
 ]
 
+[[package]]
+name = "libipld-cbor"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd1ab68c9d26f20c7d0dfea6eecbae8c00359875210001b33ca27d4a02f3d09"
+dependencies = [
+ "byteorder",
+ "libipld-core 0.14.0",
+ "thiserror 1.0.69",
+]
+
 [[package]]
 name = "libipld-cbor"
 version = "0.16.0"
@@ -5648,7 +5913,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77d98c9d1747aa5eef1cf099cd648c3fd2d235249f5fed07522aaebc348e423b"
 dependencies = [
  "byteorder",
- "libipld-core",
+ "libipld-core 0.16.0",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "libipld-core"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d44790246ec6b7314cba745992c23d479d018073e66d49ae40ae1b64e5dd8eb5"
+dependencies = [
+ "anyhow",
+ "cid 0.8.6",
+ "core2",
+ "multibase",
+ "multihash 0.16.3",
  "thiserror 1.0.69",
 ]
 
@@ -5659,7 +5938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5acd707e8d8b092e967b2af978ed84709eaded82b75effe6cb6f6cc797ef8158"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "core2",
  "multibase",
  "multihash 0.18.1",
@@ -5667,13 +5946,22 @@ dependencies = [
  "thiserror 1.0.69",
 ]
 
+[[package]]
+name = "libipld-macro"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852c011562ae5059b67c3a917f9f5945af5a68df8e39ede4444fff33274d25e2"
+dependencies = [
+ "libipld-core 0.14.0",
+]
+
 [[package]]
 name = "libipld-macro"
 version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71171c54214f866ae6722f3027f81dff0931e600e5a61e6b1b6a49ca0b5ed4ae"
 dependencies = [
- "libipld-core",
+ "libipld-core 0.16.0",
 ]
 
 [[package]]
@@ -5744,17 +6032,23 @@ dependencies = [
 [[package]]
 name = "libp2p-bitswap"
 version = "0.25.1"
-source = "git+https://github.com/consensus-shipyard/libp2p-bitswap.git?branch=chore-upgrade-libipld#d5da20fd791ee700c9a6c9f32f3df0031598938d"
 dependencies = [
+ "async-std",
  "async-trait",
+ "env_logger 0.9.3",
  "fnv",
  "futures",
  "lazy_static",
- "libipld",
+ "libipld 0.14.0",
+ "libipld 0.16.0",
  "libp2p",
+ "multihash 0.16.3",
  "prometheus",
+ "prost 0.9.0",
+ "prost-build",
  "thiserror 1.0.69",
  "tracing",
+ "tracing-subscriber",
  "unsigned-varint 0.7.2",
 ]
 
@@ -5805,6 +6099,7 @@ version = "0.41.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d17cbcf7160ff35c3e8e560de4a068fe9d6cb777ea72840e48eb76ff9576c4b6"
 dependencies = [
+ "async-std-resolver",
  "async-trait",
  "futures",
  "hickory-resolver",
@@ -5884,6 +6179,7 @@ dependencies = [
  "multihash 0.19.2",
  "quick-protobuf",
  "rand",
+ "ring 0.17.8",
  "serde",
  "sha2 0.10.8",
  "thiserror 1.0.69",
@@ -5927,6 +6223,8 @@ version = "0.45.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49007d9a339b3e1d7eeebc4d67c05dbf23d300b7d091193ec2d3f26802d7faf2"
 dependencies = [
+ "async-io 2.4.0",
+ "async-std",
  "data-encoding",
  "futures",
  "hickory-proto",
@@ -5936,7 +6234,7 @@ dependencies = [
  "libp2p-swarm",
  "rand",
  "smallvec",
- "socket2",
+ "socket2 0.5.7",
  "tokio",
  "tracing",
  "void",
@@ -6046,6 +6344,7 @@ version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c67296ad4e092e23f92aea3d2bdb6f24eab79c0929ed816dfb460ea2f4567d2b"
 dependencies = [
+ "async-std",
  "bytes",
  "futures",
  "futures-timer",
@@ -6058,7 +6357,7 @@ dependencies = [
  "rand",
  "ring 0.17.8",
  "rustls 0.23.16",
- "socket2",
+ "socket2 0.5.7",
  "thiserror 1.0.69",
  "tokio",
  "tracing",
@@ -6090,6 +6389,7 @@ version = "0.44.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "80cae6cb75f89dbca53862f9ebe0b9f463aa7b302762fcfaafb9e51dcc9b0f7e"
 dependencies = [
+ "async-std",
  "either",
  "fnv",
  "futures",
@@ -6126,13 +6426,14 @@ version = "0.41.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b2460fc2748919adff99ecbc1aab296e4579e41f374fb164149bd2c9e529d4c"
 dependencies = [
+ "async-io 1.13.0",
  "futures",
  "futures-timer",
  "if-watch",
  "libc",
  "libp2p-core",
  "libp2p-identity",
- "socket2",
+ "socket2 0.5.7",
  "tokio",
  "tracing",
 ]
@@ -6278,6 +6579,12 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.14"
@@ -6401,7 +6708,7 @@ version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64"
 dependencies = [
- "rustix",
+ "rustix 0.38.40",
 ]
 
 [[package]]
@@ -6515,6 +6822,20 @@ dependencies = [
  "data-encoding-macro",
 ]
 
+[[package]]
+name = "multihash"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc"
+dependencies = [
+ "blake3",
+ "core2",
+ "digest 0.10.7",
+ "multihash-derive",
+ "sha2 0.10.8",
+ "unsigned-varint 0.7.2",
+]
+
 [[package]]
 name = "multihash"
 version = "0.18.1"
@@ -6561,6 +6882,12 @@ dependencies = [
  "synstructure 0.12.6",
 ]
 
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
 [[package]]
 name = "multistream-select"
 version = "0.13.0"
@@ -6671,6 +6998,7 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307"
 dependencies = [
+ "async-io 1.13.0",
  "bytes",
  "futures",
  "libc",
@@ -7358,7 +7686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
 dependencies = [
  "atomic-waker",
- "fastrand",
+ "fastrand 2.2.0",
  "futures-io",
 ]
 
@@ -7388,6 +7716,22 @@ version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
 
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "polling"
 version = "3.7.4"
@@ -7398,7 +7742,7 @@ dependencies = [
  "concurrent-queue",
  "hermit-abi 0.4.0",
  "pin-project-lite",
- "rustix",
+ "rustix 0.38.40",
  "tracing",
  "windows-sys 0.59.0",
 ]
@@ -7576,7 +7920,7 @@ dependencies = [
  "hex",
  "lazy_static",
  "procfs-core",
- "rustix",
+ "rustix 0.38.40",
 ]
 
 [[package]]
@@ -7659,6 +8003,16 @@ dependencies = [
  "unarray",
 ]
 
+[[package]]
+name = "prost"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001"
+dependencies = [
+ "bytes",
+ "prost-derive 0.9.0",
+]
+
 [[package]]
 name = "prost"
 version = "0.11.9"
@@ -7666,7 +8020,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
 dependencies = [
  "bytes",
- "prost-derive",
+ "prost-derive 0.11.9",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"
+dependencies = [
+ "bytes",
+ "heck 0.3.3",
+ "itertools 0.10.5",
+ "lazy_static",
+ "log",
+ "multimap",
+ "petgraph",
+ "prost 0.9.0",
+ "prost-types 0.9.0",
+ "regex",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
+dependencies = [
+ "anyhow",
+ "itertools 0.10.5",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -7682,13 +8069,23 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "prost-types"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
+dependencies = [
+ "bytes",
+ "prost 0.9.0",
+]
+
 [[package]]
 name = "prost-types"
 version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
 dependencies = [
- "prost",
+ "prost 0.11.9",
 ]
 
 [[package]]
@@ -7785,6 +8182,8 @@ version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
 dependencies = [
+ "async-io 2.4.0",
+ "async-std",
  "bytes",
  "futures-io",
  "pin-project-lite",
@@ -7792,7 +8191,7 @@ dependencies = [
  "quinn-udp",
  "rustc-hash 2.0.0",
  "rustls 0.23.16",
- "socket2",
+ "socket2 0.5.7",
  "thiserror 2.0.3",
  "tokio",
  "tracing",
@@ -7827,7 +8226,7 @@ dependencies = [
  "cfg_aliases",
  "libc",
  "once_cell",
- "socket2",
+ "socket2 0.5.7",
  "tracing",
  "windows-sys 0.59.0",
 ]
@@ -8184,6 +8583,7 @@ version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0"
 dependencies = [
+ "async-global-executor",
  "futures",
  "log",
  "netlink-packet-route",
@@ -8245,6 +8645,20 @@ dependencies = [
  "nom",
 ]
 
+[[package]]
+name = "rustix"
+version = "0.37.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "rustix"
 version = "0.38.40"
@@ -8254,7 +8668,7 @@ dependencies = [
  "bitflags 2.6.0",
  "errno",
  "libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.4.14",
  "windows-sys 0.52.0",
 ]
 
@@ -8616,7 +9030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e880e0b1f9c7a8db874642c1217f7e19b29e325f24ab9f0fcb11818adec7f01"
 dependencies = [
  "cbor4ii",
- "cid",
+ "cid 0.10.1",
  "scopeguard",
  "serde",
 ]
@@ -8945,6 +9359,23 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "smol"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
+dependencies = [
+ "async-channel 1.9.0",
+ "async-executor",
+ "async-fs",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-net",
+ "async-process 1.8.1",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
 [[package]]
 name = "snap"
 version = "1.1.1"
@@ -8968,6 +9399,16 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "socket2"
 version = "0.5.7"
@@ -9362,6 +9803,12 @@ version = "0.12.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
 
+[[package]]
+name = "target-triple"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078"
+
 [[package]]
 name = "tempfile"
 version = "3.14.0"
@@ -9369,9 +9816,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
 dependencies = [
  "cfg-if",
- "fastrand",
+ "fastrand 2.2.0",
  "once_cell",
- "rustix",
+ "rustix 0.38.40",
  "windows-sys 0.59.0",
 ]
 
@@ -9390,8 +9837,8 @@ dependencies = [
  "k256 0.13.4",
  "num-traits",
  "once_cell",
- "prost",
- "prost-types",
+ "prost 0.11.9",
+ "prost-types 0.11.9",
  "ripemd",
  "serde",
  "serde_bytes",
@@ -9420,8 +9867,8 @@ dependencies = [
  "futures",
  "num-traits",
  "once_cell",
- "prost",
- "prost-types",
+ "prost 0.11.9",
+ "prost-types 0.11.9",
  "serde",
  "serde_bytes",
  "serde_json",
@@ -9473,8 +9920,8 @@ dependencies = [
  "flex-error",
  "num-derive 0.3.3",
  "num-traits",
- "prost",
- "prost-types",
+ "prost 0.11.9",
+ "prost-types 0.11.9",
  "serde",
  "serde_bytes",
  "subtle-encoding",
@@ -9491,8 +9938,8 @@ dependencies = [
  "flex-error",
  "num-derive 0.3.3",
  "num-traits",
- "prost",
- "prost-types",
+ "prost 0.11.9",
+ "prost-types 0.11.9",
  "serde",
  "serde_bytes",
  "subtle-encoding",
@@ -9713,7 +10160,7 @@ dependencies = [
  "parking_lot",
  "pin-project-lite",
  "signal-hook-registry",
- "socket2",
+ "socket2 0.5.7",
  "tokio-macros",
  "windows-sys 0.52.0",
 ]
@@ -9937,7 +10384,7 @@ dependencies = [
  "bytes",
  "futures",
  "pin-project",
- "prost",
+ "prost 0.11.9",
  "tendermint 0.31.1",
  "tendermint-proto 0.31.1",
  "tokio",
@@ -10091,6 +10538,21 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
+[[package]]
+name = "trybuild"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4"
+dependencies = [
+ "glob",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "target-triple",
+ "termcolor",
+ "toml 0.8.19",
+]
+
 [[package]]
 name = "tungstenite"
 version = "0.18.0"
@@ -10330,7 +10792,7 @@ version = "1.0.0"
 source = "git+https://github.com/filecoin-project/builtin-actors?tag=v15.0.0#831bf1b9fe281835409c39a8c454a68dd3200bbc"
 dependencies = [
  "anyhow",
- "cid",
+ "cid 0.10.1",
  "fvm_ipld_blockstore",
  "fvm_ipld_encoding",
  "fvm_ipld_hamt",
@@ -10348,6 +10810,12 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 
+[[package]]
+name = "waker-fn"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
+
 [[package]]
 name = "walkdir"
 version = "2.5.0"
@@ -10538,7 +11006,7 @@ dependencies = [
  "postcard",
  "psm",
  "rayon",
- "rustix",
+ "rustix 0.38.40",
  "serde",
  "serde_derive",
  "smallvec",
@@ -10743,6 +11211,18 @@ version = "0.25.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
 
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix 0.38.40",
+]
+
 [[package]]
 name = "widestring"
 version = "1.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 30c5cc7d1..4232eb781 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,9 @@ members = [
 
   # merkle
   "ext/merkle-tree-rs",
+  "ext/gcra-rs",
+  "ext/libp2p-bitswap",
+  "ext/frc42_dispatch",
 
   # ipc
   "ipc/cli",
@@ -81,7 +84,6 @@ fnv = "1.0"
 futures = "0.3"
 futures-core = "0.3"
 futures-util = "0.3"
-gcra = "0.4"
 hex = "0.4"
 hex-literal = "0.4.1"
 http = "0.2.12"
@@ -113,8 +115,7 @@ libp2p = { version = "0.53", default-features = false, features = [
   "plaintext",
 ] }
 libp2p-mplex = { version = "0.41" }
-# libp2p-bitswap = "0.25.1"
-libp2p-bitswap = { git = "https://github.com/consensus-shipyard/libp2p-bitswap.git", branch = "chore-upgrade-libipld" } # Updated to libipld 0.16
+libp2p-bitswap = { path = "ext/libp2p-bitswap" }
 libsecp256k1 = "0.7"
 literally = "0.1.3"
 log = "0.4"
@@ -230,7 +231,7 @@ cid = { version = "0.10.1", default-features = false, features = [
   "std",
 ] }
 
-frc42_dispatch = { git = "https://github.com/filecoin-project/actors-utils", rev = "0f8365151f44785f7bead4e5500258fd5c8944e6" }
+frc42_dispatch = { path = "./ext/frc42_dispatch" }
 
 # Using the same tendermint-rs dependency as tower-abci. From both we are interested in v037 modules.
 tower-abci = { version = "0.7" }
@@ -244,10 +245,6 @@ tendermint-rpc = { version = "0.31", features = [
 ] }
 tendermint-proto = { version = "0.31" }
 
-[patch.crates-io]
-# Use stable-only features.
-gcra = { git = "https://github.com/consensus-shipyard/gcra-rs.git", branch = "main" }
-
 [profile.wasm]
 inherits = "release"
 panic = "abort"
diff --git a/ext/frc42_dispatch/Cargo.toml b/ext/frc42_dispatch/Cargo.toml
new file mode 100644
index 000000000..1e7d391e0
--- /dev/null
+++ b/ext/frc42_dispatch/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "frc42_dispatch"
+description = "Filecoin FRC-0042 calling convention/dispatch support library"
+version = "7.0.0"
+license = "MIT OR Apache-2.0"
+keywords = ["filecoin", "dispatch", "frc-0042"]
+repository = "https://github.com/helix-onchain/filecoin/"
+edition = "2021"
+
+
+[dependencies]
+fvm_ipld_encoding = { workspace = true }
+fvm_sdk = { workspace = true, optional = true }
+fvm_shared = { workspace = true }
+frc42_hasher = { version = "5.0.0", path = "hasher" }
+frc42_macros = { version = "5.0.0", path = "macros" }
+thiserror = { version = "1.0.31" }
+
+[features]
+# disable default features to avoid dependence on fvm_sdk (for proc macro and similar purposes)
+default = ["use_sdk"]
+use_sdk = ["dep:fvm_sdk"]
diff --git a/ext/frc42_dispatch/README.md b/ext/frc42_dispatch/README.md
new file mode 100644
index 000000000..b8ef38f84
--- /dev/null
+++ b/ext/frc42_dispatch/README.md
@@ -0,0 +1,5 @@
+# frc42_dispatch
+
+Helper library to work with [FRC-0042](https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0042.md) method hashing
+
+There's an example of it in use [here](https://github.com/helix-onchain/filecoin/tree/main/dispatch_examples/greeter)
\ No newline at end of file
diff --git a/ext/frc42_dispatch/generate_hashes.py b/ext/frc42_dispatch/generate_hashes.py
new file mode 100644
index 000000000..77b94af98
--- /dev/null
+++ b/ext/frc42_dispatch/generate_hashes.py
@@ -0,0 +1,27 @@
+from hashlib import blake2b
+
+# See https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0042.md#method-number-computation
+def method_number(name):
+    name = '1|' + name
+    hash = blake2b(name.encode('ascii'), digest_size=64)
+    #print('digest: ' + hash.hexdigest())
+    #print(f'{len(hash.digest())} bytes long')
+
+    digest = hash.digest()
+    while digest:
+        chunk = digest[:4]
+        num = int.from_bytes(chunk, byteorder='big')
+        if num >= 1<<24:
+            return num
+        digest = digest[4:]
+    raise Exception("Method ID could not be determined, please change it") 
+
+
+# these are all the method names used in the example token actor
+methods = ['Name', 'Symbol', 'TotalSupply', 'BalanceOf', 'Allowance', 'IncreaseAllowance',
+           'DecreaseAllowance', 'RevokeAllowance', 'Burn', 'TransferFrom', 'Transfer', 'Mint']
+for method in methods:
+    num = method_number(method)
+    #print(f'{num:08x}\t{method}')
+    # print out Rust code for use in a test
+    print(f'assert_eq!(method_hash!("{method}"), 0x{num:08x});')
\ No newline at end of file
diff --git a/ext/frc42_dispatch/hasher/Cargo.toml b/ext/frc42_dispatch/hasher/Cargo.toml
new file mode 100644
index 000000000..4b1bd0372
--- /dev/null
+++ b/ext/frc42_dispatch/hasher/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "frc42_hasher"
+version = "5.0.0"
+license = "MIT OR Apache-2.0"
+description = "Filecoin FRC-0042 calling convention method hashing"
+repository = "https://github.com/helix-onchain/filecoin/"
+edition = "2021"
+
+[dependencies]
+fvm_sdk = { workspace = true, optional = true }
+fvm_shared = { workspace = true, optional = true }
+thiserror = { version = "1.0.31" }
+
+[features]
+# The fvm dependencies are optional. Useful for proc macro and similar purposes.
+default = ["use_sdk"]
+use_sdk = ["dep:fvm_sdk", "dep:fvm_shared"]
diff --git a/ext/frc42_dispatch/hasher/src/hash.rs b/ext/frc42_dispatch/hasher/src/hash.rs
new file mode 100644
index 000000000..f40e980fb
--- /dev/null
+++ b/ext/frc42_dispatch/hasher/src/hash.rs
@@ -0,0 +1,230 @@
+use thiserror::Error;
+
+/// Minimal interface for a hashing function.
+///
+/// [`Hasher::hash()`] must return a digest that is at least 4 bytes long so that it can be cast to
+/// a [`u32`].
+pub trait Hasher {
+    /// For an input of bytes return a digest that is at least 4 bytes long.
+    fn hash(&self, bytes: &[u8]) -> Vec<u8>;
+}
+
+/// Hasher that uses the blake2b hash syscall provided by the FVM.
+#[cfg(feature = "use_sdk")]
+#[derive(Default)]
+pub struct Blake2bSyscall {}
+
+#[cfg(feature = "use_sdk")]
+impl Hasher for Blake2bSyscall {
+    // fvm_sdk dependence can be removed by setting default-features to false
+    fn hash(&self, bytes: &[u8]) -> Vec<u8> {
+        use fvm_shared::crypto::hash::SupportedHashes;
+        fvm_sdk::crypto::hash_owned(SupportedHashes::Blake2b512, bytes)
+    }
+}
+
+/// Uses an underlying hashing function (blake2b by convention) to generate method numbers from
+/// method names.
+#[derive(Default)]
+pub struct MethodResolver<T: Hasher> {
+    hasher: T,
+}
+
+#[derive(Error, PartialEq, Eq, Debug)]
+pub enum MethodNameErr {
+    #[error("empty method name provided")]
+    EmptyString,
+    #[error("method name does not conform to the FRC-0042 convention {0}")]
+    IllegalName(#[from] IllegalNameErr),
+    #[error("unable to calculate method id, choose a another method name")]
+    IndeterminableId,
+}
+
+#[derive(Error, PartialEq, Eq, Debug)]
+pub enum IllegalNameErr {
+    #[error("method name doesn't start with capital letter or _")]
+    NotValidStart,
+    #[error("method name contains letters outside [a-zA-Z0-9_]")]
+    IllegalCharacters,
+}
+
+impl<T: Hasher> MethodResolver<T> {
+    const CONSTRUCTOR_METHOD_NAME: &'static str = "Constructor";
+    const CONSTRUCTOR_METHOD_NUMBER: u64 = 1_u64;
+    const FIRST_METHOD_NUMBER: u64 = 1 << 24;
+    const DIGEST_CHUNK_LENGTH: usize = 4;
+
+    /// Creates a [`MethodResolver`] with an instance of a hasher (blake2b by convention).
+    pub fn new(hasher: T) -> Self {
+        Self { hasher }
+    }
+
+    /// Generates a standard FRC-0042 compliant method number.
+    ///
+    /// The method number is calculated as the first four bytes of `hash(method-name)`.
+    /// The name `Constructor` is always hashed to 1 and other method names that hash to
+    /// 0 or 1 are avoided via rejection sampling.
+    pub fn method_number(&self, method_name: &str) -> Result<u64, MethodNameErr> {
+        check_method_name(method_name)?;
+
+        if method_name == Self::CONSTRUCTOR_METHOD_NAME {
+            return Ok(Self::CONSTRUCTOR_METHOD_NUMBER);
+        }
+
+        let method_name = format!("1|{method_name}");
+        let digest = self.hasher.hash(method_name.as_bytes());
+
+        for chunk in digest.chunks(Self::DIGEST_CHUNK_LENGTH) {
+            if chunk.len() < Self::DIGEST_CHUNK_LENGTH {
+                // last chunk may be smaller than 4 bytes
+                break;
+            }
+
+            let method_id = as_u32(chunk) as u64;
+            // Method numbers below FIRST_METHOD_NUMBER are reserved for other use
+            if method_id >= Self::FIRST_METHOD_NUMBER {
+                return Ok(method_id);
+            }
+        }
+
+        Err(MethodNameErr::IndeterminableId)
+    }
+}
+
+/// Checks that a method name is valid and compliant with the FRC-0042 standard recommendations.
+///
+/// - Only ASCII characters in `[a-zA-Z0-9_]` are allowed.
+/// - Starts with a character in `[A-Z_]`.
+fn check_method_name(method_name: &str) -> Result<(), MethodNameErr> {
+    if method_name.is_empty() {
+        return Err(MethodNameErr::EmptyString);
+    }
+
+    // Check starts with capital letter
+    let first_letter = method_name.chars().next().unwrap(); // safe because we checked for empty string
+    if !(first_letter.is_ascii_uppercase() || first_letter == '_') {
+        return Err(IllegalNameErr::NotValidStart.into());
+    }
+
+    // Check that all characters are legal
+    if !method_name
+        .chars()
+        .all(|c| c.is_ascii_alphanumeric() || c == '_')
+    {
+        return Err(IllegalNameErr::IllegalCharacters.into());
+    }
+
+    Ok(())
+}
+
+/// Takes a byte array and interprets it as a u32 number.
+///
+/// Using big-endian order interperets the first four bytes to an int.
+///
+/// The slice passed to this must be at least length 4.
+fn as_u32(bytes: &[u8]) -> u32 {
+    u32::from_be_bytes(
+        bytes[0..4]
+            .try_into()
+            .expect("bytes was not at least length 4"),
+    )
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::{Hasher, IllegalNameErr, MethodNameErr, MethodResolver};
+
+    #[derive(Clone, Copy)]
+    struct FakeHasher {}
+    impl Hasher for FakeHasher {
+        fn hash(&self, bytes: &[u8]) -> Vec<u8> {
+            bytes.to_vec()
+        }
+    }
+
+    #[test]
+    fn constructor_is_1() {
+        let method_hasher = MethodResolver::new(FakeHasher {});
+        assert_eq!(method_hasher.method_number("Constructor").unwrap(), 1);
+    }
+
+    #[test]
+    fn normal_method_is_hashed() {
+        let fake_hasher = FakeHasher {};
+        let method_hasher = MethodResolver::new(fake_hasher);
+        // note that the method hashing prepends each name with "1|" as a domain separator
+        assert_eq!(
+            method_hasher.method_number("NormalMethod").unwrap(),
+            super::as_u32(&fake_hasher.hash(b"1|NormalMethod")) as u64
+        );
+
+        assert_eq!(
+            method_hasher.method_number("NormalMethod2").unwrap(),
+            super::as_u32(&fake_hasher.hash(b"1|NormalMethod2")) as u64
+        );
+    }
+
+    #[test]
+    fn disallows_invalid_method_names() {
+        let method_hasher = MethodResolver::new(FakeHasher {});
+        assert_eq!(
+            method_hasher.method_number("Invalid|Method").unwrap_err(),
+            MethodNameErr::IllegalName(IllegalNameErr::IllegalCharacters)
+        );
+        assert_eq!(
+            method_hasher.method_number("").unwrap_err(),
+            MethodNameErr::EmptyString
+        );
+        assert_eq!(
+            method_hasher.method_number("invalidMethod").unwrap_err(),
+            MethodNameErr::IllegalName(IllegalNameErr::NotValidStart)
+        );
+    }
+
+    /// Fake hasher that always returns a digest beginning with b"\0\0\0\0".
+    #[derive(Clone, Copy)]
+    struct FakeHasher0 {}
+    impl Hasher for FakeHasher0 {
+        fn hash(&self, bytes: &[u8]) -> Vec<u8> {
+            let mut hash: Vec<u8> = vec![0, 0, 0, 0];
+            let mut suffix = bytes.to_vec();
+            hash.append(suffix.as_mut());
+            hash
+        }
+    }
+
+    /// Fake hasher that always returns a digest beginning with b"\0\0\0\1".
+    #[derive(Clone, Copy)]
+    struct FakeHasher1 {}
+    impl Hasher for FakeHasher1 {
+        fn hash(&self, bytes: &[u8]) -> Vec<u8> {
+            let mut hash: Vec<u8> = vec![0, 0, 0, 1];
+            let mut suffix = bytes.to_vec();
+            hash.append(suffix.as_mut());
+            hash
+        }
+    }
+
+    #[test]
+    fn avoids_disallowed_method_numbers() {
+        let hasher_0 = FakeHasher0 {};
+        let method_hasher_0 = MethodResolver::new(hasher_0);
+
+        // This simulates a method name that would hash to 0
+        let contrived_0 = "MethodName";
+        let contrived_0_digest = hasher_0.hash(contrived_0.as_bytes());
+        assert_eq!(super::as_u32(&contrived_0_digest), 0);
+        // But the method number is not a collision
+        assert_ne!(method_hasher_0.method_number(contrived_0).unwrap(), 0);
+
+        let hasher_1 = FakeHasher1 {};
+        let method_hasher_1 = MethodResolver::new(hasher_1);
+        // This simulates a method name that would hash to 1
+        let contrived_1 = "MethodName";
+        let contrived_1_digest = hasher_1.hash(contrived_1.as_bytes());
+        assert_eq!(super::as_u32(&contrived_1_digest), 1);
+        // But the method number is not a collision
+        assert_ne!(method_hasher_1.method_number(contrived_1).unwrap(), 1);
+    }
+}
diff --git a/ext/frc42_dispatch/hasher/src/lib.rs b/ext/frc42_dispatch/hasher/src/lib.rs
new file mode 100644
index 000000000..ec5d33c10
--- /dev/null
+++ b/ext/frc42_dispatch/hasher/src/lib.rs
@@ -0,0 +1 @@
+pub mod hash;
diff --git a/ext/frc42_dispatch/macros/Cargo.toml b/ext/frc42_dispatch/macros/Cargo.toml
new file mode 100644
index 000000000..9a056184b
--- /dev/null
+++ b/ext/frc42_dispatch/macros/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "frc42_macros"
+version = "5.0.0"
+license = "MIT OR Apache-2.0"
+description = "Filecoin FRC-0042 calling convention procedural macros"
+repository = "https://github.com/helix-onchain/filecoin/"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+blake2b_simd = { version = "1.0.0" }
+frc42_hasher = { version = "5.0.0", path = "../hasher", default-features = false }
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0", features = ["full"] }
+
+[dev-dependencies]
+trybuild = "1.0"
diff --git a/ext/frc42_dispatch/macros/example/Cargo.toml b/ext/frc42_dispatch/macros/example/Cargo.toml
new file mode 100644
index 000000000..d3ffbb713
--- /dev/null
+++ b/ext/frc42_dispatch/macros/example/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "example"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+frc42_macros = { version = "5.0.0", path = ".." }
diff --git a/ext/frc42_dispatch/macros/example/src/main.rs b/ext/frc42_dispatch/macros/example/src/main.rs
new file mode 100644
index 000000000..7f3ed17e9
--- /dev/null
+++ b/ext/frc42_dispatch/macros/example/src/main.rs
@@ -0,0 +1,9 @@
+use frc42_macros::method_hash;
+
+fn main() {
+    let str_hash = method_hash!("Method");
+    println!("String hash: {str_hash:x}");
+
+    // this one breaks naming rules and will fail to compile
+    //println!("error hash: {}", method_hash!("some_function"));
+}
diff --git a/ext/frc42_dispatch/macros/src/hash.rs b/ext/frc42_dispatch/macros/src/hash.rs
new file mode 100644
index 000000000..118201e84
--- /dev/null
+++ b/ext/frc42_dispatch/macros/src/hash.rs
@@ -0,0 +1,9 @@
+use blake2b_simd::blake2b;
+use frc42_hasher::hash::Hasher;
+
+pub struct Blake2bHasher {}
+impl Hasher for Blake2bHasher {
+    fn hash(&self, bytes: &[u8]) -> Vec<u8> {
+        blake2b(bytes).as_bytes().to_vec()
+    }
+}
diff --git a/ext/frc42_dispatch/macros/src/lib.rs b/ext/frc42_dispatch/macros/src/lib.rs
new file mode 100644
index 000000000..40c3eb1b2
--- /dev/null
+++ b/ext/frc42_dispatch/macros/src/lib.rs
@@ -0,0 +1,62 @@
+use frc42_hasher::hash::MethodResolver;
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{parse_macro_input, LitStr, Result};
+
+mod hash;
+use crate::hash::Blake2bHasher;
+
+struct MethodName(LitStr);
+
+impl MethodName {
+    /// Hash the method name.
+    fn hash(&self) -> u64 {
+        let resolver = MethodResolver::new(Blake2bHasher {});
+        resolver.method_number(&self.0.value()).unwrap()
+    }
+}
+
+impl Parse for MethodName {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let lookahead = input.lookahead1();
+
+        if lookahead.peek(LitStr) {
+            input.parse().map(MethodName)
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+#[proc_macro]
+pub fn method_hash(input: TokenStream) -> TokenStream {
+    let name: MethodName = parse_macro_input!(input);
+    let hash = name.hash();
+    quote!(#hash).into()
+}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        let t = trybuild::TestCases::new();
+        t.pass("tests/build-success.rs");
+    }
+
+    #[test]
+    fn empty_names() {
+        let t = trybuild::TestCases::new();
+        // NOTE: these need to live in a separate directory under `tests`
+        // otherwise cargo tries to build them every time and everything breaks
+        t.compile_fail("tests/naming/empty-name-string.rs");
+        t.compile_fail("tests/naming/missing-name.rs");
+    }
+
+    #[test]
+    fn bad_names() {
+        let t = trybuild::TestCases::new();
+        t.compile_fail("tests/naming/illegal-chars.rs");
+        t.compile_fail("tests/naming/non-capital-start.rs");
+    }
+}
diff --git a/ext/frc42_dispatch/macros/tests/build-success.rs b/ext/frc42_dispatch/macros/tests/build-success.rs
new file mode 100644
index 000000000..4c180543e
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/build-success.rs
@@ -0,0 +1,21 @@
+use frc42_macros::method_hash;
+
+fn main() {
+    assert_eq!(method_hash!("Method"), 0xa20642fc);
+    assert_eq!(method_hash!("_Method"), 0xeb9575aa);
+
+    // method names from the example token actor
+    // numbers are hashed by the python script included in the main dispatch crate
+    assert_eq!(method_hash!("Name"), 0x02ea015c);
+    assert_eq!(method_hash!("Symbol"), 0x7adab63e);
+    assert_eq!(method_hash!("TotalSupply"), 0x06da7a35);
+    assert_eq!(method_hash!("BalanceOf"), 0x8710e1ac);
+    assert_eq!(method_hash!("Allowance"), 0xfaa45236);
+    assert_eq!(method_hash!("IncreaseAllowance"), 0x69ecb918);
+    assert_eq!(method_hash!("DecreaseAllowance"), 0x5b286f21);
+    assert_eq!(method_hash!("RevokeAllowance"), 0xa4d840b1);
+    assert_eq!(method_hash!("Burn"), 0x5584159a);
+    assert_eq!(method_hash!("TransferFrom"), 0xd7d4deed);
+    assert_eq!(method_hash!("Transfer"), 0x04cbf732);
+    assert_eq!(method_hash!("Mint"), 0x06f84ab2);
+}
diff --git a/ext/frc42_dispatch/macros/tests/naming/empty-name-string.rs b/ext/frc42_dispatch/macros/tests/naming/empty-name-string.rs
new file mode 100644
index 000000000..33d56a4a0
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/empty-name-string.rs
@@ -0,0 +1,6 @@
+use frc42_macros::method_hash;
+
+fn main() {
+	// this should panic due to empty string
+	let _str_hash = method_hash!("");
+}
diff --git a/ext/frc42_dispatch/macros/tests/naming/empty-name-string.stderr b/ext/frc42_dispatch/macros/tests/naming/empty-name-string.stderr
new file mode 100644
index 000000000..64a1c1ec5
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/empty-name-string.stderr
@@ -0,0 +1,7 @@
+error: proc macro panicked
+ --> tests/naming/empty-name-string.rs:5:18
+  |
+5 |     let _str_hash = method_hash!("");
+  |                     ^^^^^^^^^^^^^^^^
+  |
+  = help: message: called `Result::unwrap()` on an `Err` value: EmptyString
diff --git a/ext/frc42_dispatch/macros/tests/naming/illegal-chars.rs b/ext/frc42_dispatch/macros/tests/naming/illegal-chars.rs
new file mode 100644
index 000000000..6c0558793
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/illegal-chars.rs
@@ -0,0 +1,6 @@
+use frc42_macros::method_hash;
+
+fn main() {
+	// should panic because the name contains illegal chars
+	let _str_hash = method_hash!("Bad!Method!Name!");
+}
diff --git a/ext/frc42_dispatch/macros/tests/naming/illegal-chars.stderr b/ext/frc42_dispatch/macros/tests/naming/illegal-chars.stderr
new file mode 100644
index 000000000..766c459b2
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/illegal-chars.stderr
@@ -0,0 +1,7 @@
+error: proc macro panicked
+ --> tests/naming/illegal-chars.rs:5:18
+  |
+5 |     let _str_hash = method_hash!("Bad!Method!Name!");
+  |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  |
+  = help: message: called `Result::unwrap()` on an `Err` value: IllegalName(IllegalCharacters)
diff --git a/ext/frc42_dispatch/macros/tests/naming/missing-name.rs b/ext/frc42_dispatch/macros/tests/naming/missing-name.rs
new file mode 100644
index 000000000..dfc3da6e2
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/missing-name.rs
@@ -0,0 +1,6 @@
+use frc42_macros::method_hash;
+
+fn main() {
+	// should panic because no string or identifier provided
+    let _ident_hash = method_hash!();
+}
diff --git a/ext/frc42_dispatch/macros/tests/naming/missing-name.stderr b/ext/frc42_dispatch/macros/tests/naming/missing-name.stderr
new file mode 100644
index 000000000..348376580
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/missing-name.stderr
@@ -0,0 +1,7 @@
+error: unexpected end of input, expected string literal
+ --> tests/naming/missing-name.rs:5:23
+  |
+5 |     let _ident_hash = method_hash!();
+  |                       ^^^^^^^^^^^^^^
+  |
+  = note: this error originates in the macro `method_hash` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/ext/frc42_dispatch/macros/tests/naming/non-capital-start.rs b/ext/frc42_dispatch/macros/tests/naming/non-capital-start.rs
new file mode 100644
index 000000000..8ea57d182
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/non-capital-start.rs
@@ -0,0 +1,6 @@
+use frc42_macros::method_hash;
+
+fn main() {
+	// should panic because the name starts with non-capital letter
+	let _str_hash = method_hash!("noPlaceForCamelCase");
+}
diff --git a/ext/frc42_dispatch/macros/tests/naming/non-capital-start.stderr b/ext/frc42_dispatch/macros/tests/naming/non-capital-start.stderr
new file mode 100644
index 000000000..30ead070d
--- /dev/null
+++ b/ext/frc42_dispatch/macros/tests/naming/non-capital-start.stderr
@@ -0,0 +1,7 @@
+error: proc macro panicked
+ --> tests/naming/non-capital-start.rs:5:18
+  |
+5 |     let _str_hash = method_hash!("noPlaceForCamelCase");
+  |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  |
+  = help: message: called `Result::unwrap()` on an `Err` value: IllegalName(NotValidStart)
diff --git a/ext/frc42_dispatch/src/lib.rs b/ext/frc42_dispatch/src/lib.rs
new file mode 100644
index 000000000..ad64c36db
--- /dev/null
+++ b/ext/frc42_dispatch/src/lib.rs
@@ -0,0 +1,9 @@
+pub use frc42_hasher as hasher;
+pub use frc42_hasher::hash;
+pub use frc42_macros::method_hash;
+
+pub mod match_method;
+pub mod message;
+
+#[cfg(test)]
+mod tests {}
diff --git a/ext/frc42_dispatch/src/match_method.rs b/ext/frc42_dispatch/src/match_method.rs
new file mode 100644
index 000000000..5406d3818
--- /dev/null
+++ b/ext/frc42_dispatch/src/match_method.rs
@@ -0,0 +1,111 @@
+#[macro_export]
+macro_rules! match_method {
+    ($method:expr, {$($body:tt)*}) => {
+        match_method!{@match $method, {}, $($body)*}
+    };
+    (@match $method:expr, {$($body:tt)*}, $(,)*) => {
+        match $method {
+            $($body)*
+        }
+    };
+    // matches block with comma
+    (@match $method:expr, {$($body:tt)*}, $p:literal => $e:expr, $($tail:tt)*) => {
+        match_method! {
+            @match
+            $method,
+            {
+                $($body)*
+                $crate::method_hash!($p) => $e,
+            },
+            $($tail)*
+        }
+    };
+    // matches block without comma
+    (@match $method:expr, {$($body:tt)*}, $p:literal => $e:block $($tail:tt)*) => {
+        match_method! {
+            @match
+            $method,
+            {
+                $($body)*
+                $crate::method_hash!($p) => $e,
+            },
+            $($tail)*
+        }
+    };
+    // matches _ with a trailing comma
+    (@match $method:expr, {$($body:tt)*}, _ => $e:expr, $($tail:tt)*) => {
+        match_method! {
+            @match
+            $method,
+            {
+                $($body)*
+                _ => $e,
+            },
+            $($tail)*
+        }
+    };
+    // matches _ without a trailing comma (common if it's the last item)
+    (@match $method:expr, {$($body:tt)*}, _ => $e:expr) => {
+        match_method! {
+            @match
+            $method,
+            {
+                $($body)*
+                _ => $e,
+            },
+        }
+    };
+}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn handle_constructor() {
+        let method_num = 1u64; // constructor should always hash to 1
+        let ret = match_method!(method_num, {
+            "Constructor" => Some(1),
+            _ => None,
+        });
+
+        assert_eq!(ret, Some(1));
+    }
+
+    #[test]
+    fn handle_unknown_method() {
+        let method_num = 12345u64; // not a method we know about
+        let ret = match_method!(method_num, {
+            "Constructor" => Some(1),
+            _ => None,
+        });
+
+        assert_eq!(ret, None);
+    }
+
+    #[test]
+    fn handle_user_method() {
+        let method_num = crate::method_hash!("TokensReceived");
+        let ret = match_method!(method_num, {
+            "Constructor" => Some(1),
+            "TokensReceived" => Some(2),
+            _ => None,
+        });
+
+        assert_eq!(ret, Some(2));
+    }
+
+    #[test]
+    fn handle_optional_commas() {
+        let method_num = crate::method_hash!("TokensReceived");
+        let ret = match_method!(method_num, {
+            "Constructor" => Some(1),
+            "TokensReceived" => {
+                Some(2)
+            }
+            _ => {
+                None
+            }
+        });
+
+        assert_eq!(ret, Some(2));
+    }
+}
diff --git a/ext/frc42_dispatch/src/message.rs b/ext/frc42_dispatch/src/message.rs
new file mode 100644
index 000000000..fe274e191
--- /dev/null
+++ b/ext/frc42_dispatch/src/message.rs
@@ -0,0 +1,65 @@
+use fvm_ipld_encoding::ipld_block::IpldBlock;
+#[cfg(feature = "use_sdk")]
+use fvm_sdk::send;
+use fvm_shared::{address::Address, econ::TokenAmount, error::ErrorNumber, Response};
+use thiserror::Error;
+
+use crate::hash::{Hasher, MethodNameErr, MethodResolver};
+
+/// Utility to invoke standard methods on deployed actors.
+#[derive(Default)]
+pub struct MethodMessenger<T: Hasher> {
+    method_resolver: MethodResolver<T>,
+}
+
+#[derive(Error, PartialEq, Eq, Debug)]
+pub enum MethodMessengerError {
+    #[error("error when calculating method name: `{0}`")]
+    MethodName(#[from] MethodNameErr),
+    #[error("error sending message: `{0}`")]
+    Syscall(#[from] ErrorNumber),
+}
+
+impl<T: Hasher> MethodMessenger<T> {
+    /// Creates a new method messenger using a specified hashing function (blake2b by default).
+    pub fn new(hasher: T) -> Self {
+        Self {
+            method_resolver: MethodResolver::new(hasher),
+        }
+    }
+
+    /// Calls a method (by name) on a specified actor by constructing and publishing the underlying
+    /// on-chain message.
+    #[cfg(feature = "use_sdk")]
+    pub fn call_method(
+        &self,
+        to: &Address,
+        method: &str,
+        params: Option<IpldBlock>,
+        value: TokenAmount,
+    ) -> Result<Response, MethodMessengerError> {
+        let method = self.method_resolver.method_number(method)?;
+        send::send(
+            to,
+            method,
+            params,
+            value,
+            None,
+            fvm_shared::sys::SendFlags::empty(),
+        )
+        .map_err(MethodMessengerError::from)
+    }
+
+    #[cfg(not(feature = "use_sdk"))]
+    #[allow(unused_variables)]
+    pub fn call_method(
+        &self,
+        to: &Address,
+        method: &str,
+        params: Option<IpldBlock>,
+        value: TokenAmount,
+    ) -> Result<Response, MethodMessengerError> {
+        let _method = self.method_resolver.method_number(method)?;
+        unimplemented!()
+    }
+}
diff --git a/ext/gcra-rs/.gitignore b/ext/gcra-rs/.gitignore
new file mode 100644
index 000000000..ea8c4bf7f
--- /dev/null
+++ b/ext/gcra-rs/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/ext/gcra-rs/Cargo.lock b/ext/gcra-rs/Cargo.lock
new file mode 100644
index 000000000..802afe887
--- /dev/null
+++ b/ext/gcra-rs/Cargo.lock
@@ -0,0 +1,638 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "cxx"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
+dependencies = [
+ "cfg-if",
+ "hashbrown",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "gcra"
+version = "0.4.0"
+dependencies = [
+ "chrono",
+ "dashmap",
+ "rustc-hash",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "mio"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
+dependencies = [
+ "libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "scratch"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tokio"
+version = "1.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/ext/gcra-rs/Cargo.toml b/ext/gcra-rs/Cargo.toml
new file mode 100644
index 000000000..1f2c71a14
--- /dev/null
+++ b/ext/gcra-rs/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "gcra"
+version = "0.4.0"
+edition = "2021"
+authors = ["Sam Shih <lytefast@github.com>"]
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/lytefast/gcra-rs"
+homepage = "https://github.com/lytefast/gcra-rs"
+description = "A basic implementation of GCRA algorithm for rate limiting"
+keywords = ["rate-limit", "rate", "limit", "gcra"]
+
+[features]
+default = ["rate-limiter"]
+rate-limiter = ["dashmap", "rustc-hash"]
+
+[dependencies]
+dashmap = { version = "5.4.0", optional = true }
+rustc-hash = { version = "1.1.0", optional = true }
+thiserror = "1.0.39"
+
+[dev-dependencies]
+chrono = "0.4.23"
+tokio = { version = "1.26.0", features = ["full"] }
+
+[[example]]
+name = "rate_limiter"
+required-features = ["rate-limiter"]
diff --git a/ext/gcra-rs/LICENSE.md b/ext/gcra-rs/LICENSE.md
new file mode 100644
index 000000000..9f5fe052e
--- /dev/null
+++ b/ext/gcra-rs/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Sam Shih
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ext/gcra-rs/README.md b/ext/gcra-rs/README.md
new file mode 100644
index 000000000..6105c7c97
--- /dev/null
+++ b/ext/gcra-rs/README.md
@@ -0,0 +1,55 @@
+[![Build Status](https://travis-ci.com/lytefast/gcra.svg?branch=master)](https://travis-ci.com/lytefast/gcra)
+[![License](https://img.shields.io/github/license/lytefast/gcra.svg)](LICENSE)
+[![Documentation](https://docs.rs/gcra/badge.svg)](https://docs.rs/gcra/)
+[![crates.io](https://img.shields.io/crates/v/gcra.svg)](https://crates.io/crates/gcra)
+
+# GCRA: A basic implementation
+
+Library which implements the core
+[GCRA](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm) functionality in rust.
+
+## Features
+
+- `rate-limiter` a LRU + expiring rate limiter. Implements `Send + Sync` so can be used asynchronously.
+
+## Usage
+
+```rust
+use gcra::{GcraState, RateLimit};
+
+fn check_rate_limit() {
+  const LIMIT: u32 = 1;
+  // Create a rate limit that allows `1/1s`
+  let rate_limit = RateLimit::per_sec(LIMIT);
+
+  let mut user_state = GcraState::default();
+  assert!(user_state.check_and_modify(&rate_limit, 1).is_ok());
+  assert!(
+      user_state.check_and_modify(&rate_limit, 1).is_err(),
+      "We should be over the limit now"
+  );
+}
+```
+
+### With `rate-limiter`
+
+```rust
+use gcra::{GcraError, RateLimit, RateLimiter};
+
+#[tokio::main]
+async fn main() -> Result<(), GcraError> {
+    let rate_limit = RateLimit::per_sec(2);
+    let mut rl = RateLimiter::new(4);
+
+    rl.check("key", rate_limit.clone(), 1).await?;
+    rl.check("key", rate_limit.clone(), 1).await?;
+
+    match rl.check("key", rate_limit.clone(), 1).await {
+        Err(GcraError::DeniedUntil { next_allowed_at }) => {
+            print!("Denied: Request next at {:?}", next_allowed_at);
+            Ok(())
+        }
+        unexpected => panic!("Opps something went wrong! {:?}", unexpected),
+    }
+}
+```
diff --git a/ext/gcra-rs/examples/gcra_check.rs b/ext/gcra-rs/examples/gcra_check.rs
new file mode 100644
index 000000000..58108a684
--- /dev/null
+++ b/ext/gcra-rs/examples/gcra_check.rs
@@ -0,0 +1,47 @@
+use std::time::Instant;
+
+use chrono::{DateTime, Duration, Utc};
+use gcra::{GcraError, GcraState, RateLimit};
+
+fn check_rate_limit(rate_limit: &RateLimit, gcra_state: &mut GcraState) -> bool {
+    const COST: u32 = 1;
+    match gcra_state.check_and_modify(rate_limit, COST) {
+        Ok(_) => {
+            println!("allowed");
+            true
+        }
+        Err(GcraError::DeniedUntil { next_allowed_at }) => {
+            println!("denied. Try again at {:?}", to_date_time(next_allowed_at));
+            false
+        }
+
+        Err(error) => {
+            println!("denied: {:?}", error);
+            false
+        }
+    }
+}
+
+fn to_date_time(instant: Instant) -> DateTime<Utc> {
+    let diff = instant - Instant::now();
+    Utc::now() + Duration::from_std(diff).unwrap()
+}
+
+fn main() {
+    const LIMIT: u32 = 3;
+    // Create a rate limit that allows `3/1s`
+    let rate_limit = RateLimit::per_sec(LIMIT);
+
+    let mut user_state = GcraState::default();
+    for i in 0..LIMIT {
+        assert!(
+            check_rate_limit(&rate_limit, &mut user_state),
+            "Attempt #{} should be allowed",
+            i + 1
+        );
+    }
+    assert!(
+        !check_rate_limit(&rate_limit, &mut user_state),
+        "We should be over the limit now"
+    );
+}
diff --git a/ext/gcra-rs/examples/rate_limiter.rs b/ext/gcra-rs/examples/rate_limiter.rs
new file mode 100644
index 000000000..c92268a8d
--- /dev/null
+++ b/ext/gcra-rs/examples/rate_limiter.rs
@@ -0,0 +1,21 @@
+use gcra::{GcraError, RateLimit, RateLimiter};
+
+const CACHE_CAPACITY: usize = 4;
+const WORKER_SHARD_COUNT: usize = 2;
+
+#[tokio::main]
+async fn main() -> Result<(), GcraError> {
+    let rate_limit = RateLimit::per_sec(2);
+    let mut rl = RateLimiter::with_shards(CACHE_CAPACITY, WORKER_SHARD_COUNT);
+
+    rl.check("key", rate_limit.clone(), 1).await?;
+    rl.check("key", rate_limit.clone(), 1).await?;
+
+    match rl.check("key", rate_limit.clone(), 1).await {
+        Err(GcraError::DeniedUntil { next_allowed_at }) => {
+            print!("Denied: Request next at {:?}", next_allowed_at);
+            Ok(())
+        }
+        unexpected => panic!("Opps something went wrong! {:?}", unexpected),
+    }
+}
diff --git a/ext/gcra-rs/src/gcra.rs b/ext/gcra-rs/src/gcra.rs
new file mode 100644
index 000000000..8e362130e
--- /dev/null
+++ b/ext/gcra-rs/src/gcra.rs
@@ -0,0 +1,489 @@
+use std::time::Instant;
+use thiserror::Error;
+
+use crate::rate_limit::RateLimit;
+
+#[derive(Error, Debug, PartialEq, Eq)]
+pub enum GcraError {
+    /// Cost of the increment exceeds the rate limit and  will never succeed
+    #[error("Cost of the increment ({cost}) exceeds the rate limit ({rate_limit:?}) and will never succeed)")]
+    DeniedIndefinitely { cost: u32, rate_limit: RateLimit },
+    /// Limited request until after the [Instant]
+    #[error("Denied until {next_allowed_at:?}")]
+    DeniedUntil { next_allowed_at: Instant },
+}
+
+/// Holds the minmum amount of state necessary to implement a GRCA leaky buckets.
+/// Refer to: [understanding GCRA](https://blog.ian.stapletoncordas.co/2018/12/understanding-generic-cell-rate-limiting.html)
+#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Copy)]
+pub struct GcraState {
+    /// GCRA's Theoretical Arrival Time (**TAT**)
+    /// An unset value signals a new state
+    pub tat: Option<Instant>,
+}
+
+impl GcraState {
+    /// Check if we are allowed to proceed. If so updated our internal state and return true.
+    ///
+    /// Simply passes the current Instant to [`check_and_modify_at()`]
+    #[inline]
+    pub fn check_and_modify(&mut self, rate_limit: &RateLimit, cost: u32) -> Result<(), GcraError> {
+        let arrived_at = Instant::now();
+        self.check_and_modify_at(rate_limit, arrived_at, cost)
+    }
+
+    /// Check if we are allowed to proceed at the given arrival time.
+    /// If so updated our internal state and return true.
+    /// Explaination of GCRA can be found [here](https://blog.ian.stapletoncordas.co/2018/12/understanding-generic-cell-rate-limiting.html)
+    ///
+    /// # Returns
+    /// If denied, will return an [Result::Err] where the value is the next allowed timestamp.
+    pub fn check_and_modify_at(
+        &mut self,
+        rate_limit: &RateLimit,
+        arrived_at: Instant,
+        cost: u32,
+    ) -> Result<(), GcraError> {
+        let increment_interval = rate_limit.increment_interval(cost);
+
+        let compute_tat = |new_tat: Instant| {
+            if increment_interval > rate_limit.period {
+                return Err(GcraError::DeniedIndefinitely {
+                    cost,
+                    rate_limit: rate_limit.clone(),
+                });
+            }
+
+            Ok(new_tat + increment_interval)
+        };
+
+        let tat = match self.tat {
+            Some(tat) => tat,
+            None => {
+                // First ever request. Allow passage and update self.
+                self.tat = Some(compute_tat(arrived_at)?);
+                return Ok(());
+            }
+        };
+
+        // We had a previous request
+        if tat < arrived_at {
+            // prev request was really old
+            let new_tat = std::cmp::max(tat, arrived_at);
+            self.tat = Some(compute_tat(new_tat)?);
+            Ok(())
+        } else {
+            // prev request was recent and there's a possibility that we've reached the limit
+            let delay_variation_tolerance = rate_limit.period;
+            let new_tat = compute_tat(tat)?;
+
+            let next_allowed_at = new_tat - delay_variation_tolerance;
+            if next_allowed_at <= arrived_at {
+                self.tat = Some(new_tat);
+                Ok(())
+            } else {
+                // Denied, must wait until next_allowed_at
+                Err(GcraError::DeniedUntil { next_allowed_at })
+            }
+        }
+    }
+
+    /// Reverts rate_limit by cost, and updated our internal state.
+    ///
+    /// Simply passes the current Instant to [`revert_at()`]
+    #[inline]
+    pub fn revert(&mut self, rate_limit: &RateLimit, cost: u32) -> Result<(), GcraError> {
+        let arrived_at = Instant::now();
+        self.revert_at(rate_limit, arrived_at, cost)
+    }
+
+    /// Reverts rate_limit by cost, and updated our internal state.
+    ///
+    /// This is a hack that substracts the incremental cost from the TAT.
+    pub fn revert_at(
+        &mut self,
+        rate_limit: &RateLimit,
+        arrived_at: Instant,
+        cost: u32,
+    ) -> Result<(), GcraError> {
+        let increment_interval = rate_limit.increment_interval(cost);
+
+        let compute_revert_tat = |new_tat: Instant| new_tat - increment_interval;
+
+        let tat = match self.tat {
+            Some(tat) => tat,
+            None => {
+                // First ever request. Nothing to do.
+                return Ok(());
+            }
+        };
+
+        // We had a previous request
+        if tat < arrived_at {
+            // Reset state: prev request was really old
+            self.tat = None;
+        } else {
+            // prev request was recent
+            self.tat = Some(compute_revert_tat(tat));
+        }
+        Ok(())
+    }
+
+    pub fn remaining_resources(&self, rate_limit: &RateLimit, now: Instant) -> u32 {
+        if rate_limit.period.is_zero() {
+            return 0;
+        }
+
+        let time_to_tat = match self.tat.and_then(|tat| tat.checked_duration_since(now)) {
+            Some(duration_until) => duration_until,
+            None => return rate_limit.resource_limit,
+        };
+
+        // Logically this makes more sense as:
+        //   consumed_resources = time_to_tat * (resource_limit/period)
+        // but we run it this way because of Duration's arithmetic functions
+
+        // Requires nightly
+        // let consumed_resources =
+        //     (time_to_tat * rate_limit.resource_limit).div_duration_f32(rate_limit.period);
+
+        let consumed_resources = time_to_tat.as_nanos() as f64
+            / rate_limit.period.as_nanos() as f64
+            * rate_limit.resource_limit as f64;
+
+        rate_limit.resource_limit - consumed_resources.ceil() as u32
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::time::Duration;
+
+    use super::*;
+
+    #[test]
+    fn test_rate_limit_unused_counts() {
+        let base_tat = Instant::now();
+        let rate_limit = RateLimit::new(10, Duration::from_secs(1));
+
+        assert_eq!(
+            4,
+            GcraState {
+                tat: Some(base_tat + Duration::from_millis(550))
+            }
+            .remaining_resources(&rate_limit, base_tat),
+            "Remaining count should ceiled"
+        );
+        assert_eq!(
+            0,
+            GcraState {
+                tat: Some(base_tat + Duration::from_millis(950))
+            }
+            .remaining_resources(&rate_limit, base_tat),
+            "Remaining count should ceiled, thus preventing any additional requests"
+        );
+
+        assert_eq!(
+            9,
+            GcraState {
+                tat: Some(base_tat + Duration::from_millis(100))
+            }
+            .remaining_resources(&rate_limit, base_tat),
+            "Remaining count is based on max_period timeout"
+        );
+    }
+
+    #[test]
+    fn gcra_basics() {
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(1, Duration::from_secs(1));
+
+        let first_req_ts = Instant::now();
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify(&rate_limit, 1),
+            "request #1 should pass"
+        );
+        let after_first_tat = gcra.tat;
+        assert!(
+            after_first_tat.is_some(),
+            "state should be modified and have a TAT in the future"
+        );
+
+        let next_allowed_ts = match gcra.check_and_modify(&rate_limit, 1) {
+            Err(GcraError::DeniedUntil { next_allowed_at }) => next_allowed_at,
+            _ => panic!("request #2 should be denied temporarily"),
+        };
+        assert!(
+            next_allowed_ts >= first_req_ts + Duration::from_secs(1),
+            "we should only be allowed after the burst period"
+        );
+        assert_eq!(after_first_tat, gcra.tat, "State should be unchanged.")
+    }
+
+    #[test]
+    fn gcra_limited() {
+        const LIMIT: u32 = 5;
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(LIMIT, Duration::from_secs(1));
+
+        let req_ts = Instant::now();
+        for i in 0..LIMIT {
+            assert_eq!(
+                Ok(()),
+                gcra.check_and_modify_at(&rate_limit, req_ts, 1),
+                "request #{} should pass",
+                i + 1
+            );
+        }
+
+        assert_eq!(
+            Some(req_ts + rate_limit.period),
+            gcra.tat,
+            "state should be modified and have a TAT for the full period",
+        );
+
+        // Trigger another event
+        let denied_result = gcra.check_and_modify_at(&rate_limit, req_ts, 1);
+
+        assert_eq!(
+            Some(req_ts + rate_limit.period),
+            gcra.tat,
+            "state should not have changed when at limit",
+        );
+
+        assert_eq!(
+            Err(GcraError::DeniedUntil {
+                next_allowed_at: req_ts + rate_limit.emission_interval
+            }),
+            denied_result,
+            "next request should be denied",
+        );
+    }
+
+    #[test]
+    fn gcra_revert_new() {
+        const LIMIT: u32 = 5;
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(LIMIT, Duration::from_secs(1));
+
+        let req_ts = Instant::now();
+        // Revert before any calls
+        assert!(
+            gcra.revert_at(&rate_limit, req_ts, 1).is_ok(),
+            "revert should have released resources"
+        );
+        assert_eq!(None, gcra.tat, "state should not have changed at all",);
+    }
+
+    #[test]
+    fn gcra_revert_existing() {
+        const LIMIT: u32 = 5;
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(LIMIT, Duration::from_secs(1));
+
+        let req_ts = Instant::now();
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify_at(&rate_limit, req_ts, 5),
+            "use up all resources",
+        );
+
+        assert_eq!(
+            Some(req_ts + rate_limit.period),
+            gcra.tat,
+            "state should be modified and have a TAT for the full period",
+        );
+
+        // Revert
+        assert!(
+            gcra.revert_at(&rate_limit, req_ts, 1).is_ok(),
+            "revert should have released resources"
+        );
+        assert_eq!(
+            Some(req_ts + rate_limit.period - rate_limit.increment_interval(1)),
+            gcra.tat,
+            "state should not have changed when at limit",
+        );
+
+        // Confirm revert re-enables
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify_at(&rate_limit, req_ts, 1),
+            "additional resources should have been freed",
+        );
+    }
+
+    #[test]
+    fn gcra_revert_existing_ancient() {
+        const LIMIT: u32 = 5;
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(LIMIT, Duration::from_secs(1));
+
+        let past_req_ts = Instant::now() - Duration::from_secs(100);
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify_at(&rate_limit, past_req_ts, 5),
+            "use up all resources, but in distant past",
+        );
+        assert_eq!(
+            Some(past_req_ts + rate_limit.period),
+            gcra.tat,
+            "state should be modified and have a TAT for the past",
+        );
+
+        // Revert using current time
+        let req_ts = Instant::now();
+        assert!(
+            gcra.revert_at(&rate_limit, req_ts, 1).is_ok(),
+            "revert should have released resources"
+        );
+        assert_eq!(
+            None, gcra.tat,
+            "state should have reset since it was so old",
+        );
+
+        // Confirm revert had 0 effect
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify_at(&rate_limit, req_ts, 1),
+            "additional resources should have been freed",
+        );
+        assert_eq!(
+            gcra.tat,
+            Some(req_ts + rate_limit.increment_interval(1)),
+            "new TAT state should have been moved forward according to cost like normal"
+        );
+    }
+
+    #[test]
+    fn gcra_leaky() {
+        // const INCREMENT_INTERVAL: u64 = 500;
+        const INCREMENT_INTERVAL: Duration = Duration::from_millis(500);
+
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(10, 10 * INCREMENT_INTERVAL);
+        assert_eq!(INCREMENT_INTERVAL, rate_limit.emission_interval);
+
+        let arrived_at = Instant::now();
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify_at(&rate_limit, arrived_at, 1),
+            "request #1 should pass"
+        );
+        assert_eq!(
+            gcra.tat,
+            Some(arrived_at + INCREMENT_INTERVAL),
+            "new TAT state should have been moved forward according to cost"
+        );
+
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify(&rate_limit, 9),
+            "request #2 should consume all remaining resources and pass"
+        );
+        assert!(
+            matches!(gcra.check_and_modify(&rate_limit, 1), Err(_allowed_at)),
+            "request #3 should fail since all resources consumed"
+        );
+
+        let current_tat = gcra.tat.expect("should have a tat state after use");
+        assert!(current_tat > Instant::now(), "tat in the future");
+
+        assert!(
+            matches!(
+                // manually force time check that we know will fail
+                gcra.check_and_modify_at(
+                    &rate_limit,
+                    current_tat - rate_limit.period - Duration::from_millis(1),
+                    1
+                ),
+                Err(_allowed_at)
+            ),
+            "request #4 before leak period should fail. INCREMENT_INTERVAL has not passed yet."
+        );
+
+        assert!(
+            matches!(
+                gcra.check_and_modify_at(&rate_limit, current_tat - rate_limit.period, 1),
+                Err(_allowed_at)
+            ),
+            "request #5 after leak period should pass. INCREMENT_INTERVAL has passed"
+        );
+    }
+
+    #[test]
+    fn gcra_cost_indefinitely_denied() {
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(5, Duration::from_secs(1));
+
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify(&rate_limit, 1),
+            "request #1 should pass"
+        );
+
+        let over_limit_cost = rate_limit.resource_limit + 1;
+        match gcra.check_and_modify(&rate_limit, over_limit_cost) {
+            Err(GcraError::DeniedIndefinitely {
+                cost,
+                rate_limit: rl,
+            }) => {
+                assert_eq!(over_limit_cost, cost);
+                assert_eq!(rate_limit, rl);
+            }
+            e => panic!("request #2 would never succeed {:?}", e),
+        };
+    }
+
+    #[test]
+    fn gcra_cost_temporarily_denied() {
+        let mut gcra = GcraState::default();
+        let rate_limit = RateLimit::new(5, Duration::from_secs(1));
+
+        let first_req_ts = Instant::now();
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify(&rate_limit, 1),
+            "request #1 should pass"
+        );
+
+        let after_first_tat = gcra.tat;
+        assert!(
+            after_first_tat.is_some(),
+            "state should be modified and have a TAT in the future"
+        );
+
+        let next_allowed_ts = match gcra.check_and_modify(&rate_limit, rate_limit.resource_limit) {
+            Err(GcraError::DeniedUntil { next_allowed_at }) => next_allowed_at,
+            _ => panic!("request #2 is only temporarily denied"),
+        };
+
+        assert!(
+            next_allowed_ts >= first_req_ts + rate_limit.increment_interval(1),
+            "we should only be allowed after the burst period {:?} >= {:?}",
+            next_allowed_ts,
+            first_req_ts + rate_limit.period
+        );
+        assert_eq!(after_first_tat, gcra.tat, "State should be unchanged.")
+    }
+
+    #[test]
+    fn gcra_refreshed_after_period() {
+        let past_time = Instant::now() - Duration::from_millis(1001);
+        let mut gcra = GcraState {
+            tat: Some(past_time),
+        };
+        let rate_limit = RateLimit::new(1, Duration::from_secs(1));
+        assert_eq!(
+            Ok(()),
+            gcra.check_and_modify(&rate_limit, 1),
+            "request #1 should pass"
+        );
+
+        assert!(
+            matches!(gcra.check_and_modify(&rate_limit, 1), Err(_allowed_at)),
+            "request #2 should fail"
+        );
+    }
+}
diff --git a/ext/gcra-rs/src/lib.rs b/ext/gcra-rs/src/lib.rs
new file mode 100644
index 000000000..8c644db99
--- /dev/null
+++ b/ext/gcra-rs/src/lib.rs
@@ -0,0 +1,58 @@
+//! Library which implements the core
+//! [GCRA](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm) functionality in rust.
+//!
+//! # Features
+//! - `rate-limiter` a LRU + expiring rate limiter. Implements `Send + Sync` so
+//!   can be used asynchronously.
+//!
+//! # Usage
+//!
+//! ```rust
+//! use gcra::{GcraState, RateLimit};
+//!
+//! fn check_rate_limit() {
+//!   const LIMIT: u32 = 1;
+//!   // Create a rate limit that allows `1/1s`
+//!   let rate_limit = RateLimit::per_sec(LIMIT);
+//!
+//!   let mut user_state = GcraState::default();
+//!   assert!(user_state.check_and_modify(&rate_limit, 1).is_ok());
+//!   assert!(
+//!       user_state.check_and_modify(&rate_limit, 1).is_err(),
+//!       "We should be over the limit now"
+//!   );
+//! }
+//! ```
+//!
+//! ## With `rate-limiter`
+//!
+//! ```rust
+//! use gcra::{GcraError, RateLimit, RateLimiter};
+//!
+//! #[tokio::main]
+//! async fn main() -> Result<(), GcraError> {
+//!     let rate_limit = RateLimit::per_sec(2);
+//!     let mut rl = RateLimiter::new(4);
+//!
+//!     rl.check("key", rate_limit.clone(), 1).await?;
+//!     rl.check("key", rate_limit.clone(), 1).await?;
+//!
+//!     match rl.check("key", rate_limit.clone(), 1).await {
+//!         Err(GcraError::DeniedUntil { next_allowed_at }) => {
+//!             print!("Denied: Request next at {:?}", next_allowed_at);
+//!             Ok(())
+//!         }
+//!         unexpected => panic!("Opps something went wrong! {:?}", unexpected),
+//!     }
+//! }
+//! ```
+
+mod gcra;
+mod rate_limit;
+#[cfg(feature = "rate-limiter")]
+mod rate_limiter;
+
+pub use crate::gcra::{GcraError, GcraState};
+pub use crate::rate_limit::RateLimit;
+#[cfg(feature = "rate-limiter")]
+pub use crate::rate_limiter::{RateLimitEntry, RateLimitRequest, RateLimiter};
diff --git a/ext/gcra-rs/src/rate_limit.rs b/ext/gcra-rs/src/rate_limit.rs
new file mode 100644
index 000000000..77189ad15
--- /dev/null
+++ b/ext/gcra-rs/src/rate_limit.rs
@@ -0,0 +1,45 @@
+use std::time::Duration;
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+/// Defines the configuration for a GCRA rate limit.
+pub struct RateLimit {
+    // Amount of resources that are allowed in a given period.
+    pub resource_limit: u32,
+    // The length of which to allow access to the resource.
+    pub period: Duration,
+
+    /// Incremental duration cost of a single resource check
+    pub emission_interval: Duration,
+}
+
+impl RateLimit {
+    pub fn new(resource_limit: u32, period: Duration) -> Self {
+        let emission_interval = period / resource_limit;
+        Self {
+            resource_limit,
+            period,
+            emission_interval,
+        }
+    }
+
+    #[inline]
+    pub fn per_sec(resource_limit: u32) -> Self {
+        Self::new(resource_limit, Duration::from_secs(1))
+    }
+
+    /// Given a `cost`, calculates the increment interval.
+    pub fn increment_interval(&self, cost: u32) -> Duration {
+        self.emission_interval * cost
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn rate_limit_emission_interal() {
+        let rate_limit = RateLimit::new(10, Duration::from_secs(20));
+        assert_eq!(Duration::from_secs(2), rate_limit.emission_interval)
+    }
+}
diff --git a/ext/gcra-rs/src/rate_limiter/clock.rs b/ext/gcra-rs/src/rate_limiter/clock.rs
new file mode 100644
index 000000000..c3491a8be
--- /dev/null
+++ b/ext/gcra-rs/src/rate_limiter/clock.rs
@@ -0,0 +1,48 @@
+use std::time::Instant;
+
+/// Abstraction for getting time.
+pub trait Clock {
+    fn now(&self) -> Instant {
+        Instant::now()
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct InstantClock;
+impl Clock for InstantClock {}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+
+    use std::{
+        sync::{Arc, Mutex},
+        time::{Duration, Instant},
+    };
+
+    #[derive(Debug, Clone)]
+    pub struct FakeClock {
+        now: Instant,
+        delta: Arc<Mutex<Duration>>,
+    }
+
+    impl Clock for FakeClock {
+        fn now(&self) -> Instant {
+            self.now + *self.delta.lock().unwrap()
+        }
+    }
+
+    impl FakeClock {
+        pub fn new() -> Self {
+            Self {
+                now: Instant::now(),
+                delta: Arc::new(Mutex::new(Duration::default())),
+            }
+        }
+
+        pub fn advance_by(&self, duration: Duration) {
+            let mut delta = self.delta.lock().unwrap();
+            *delta += duration;
+        }
+    }
+}
diff --git a/ext/gcra-rs/src/rate_limiter/entry.rs b/ext/gcra-rs/src/rate_limiter/entry.rs
new file mode 100644
index 000000000..c10732798
--- /dev/null
+++ b/ext/gcra-rs/src/rate_limiter/entry.rs
@@ -0,0 +1,33 @@
+use std::{
+    ops::{Deref, DerefMut},
+    time::Instant,
+};
+
+use crate::{GcraState, RateLimit};
+
+#[derive(Default, Debug, Clone)]
+pub struct RateLimitEntry {
+    pub gcra_state: GcraState,
+    pub expires_at: Option<Instant>,
+}
+
+impl Deref for RateLimitEntry {
+    type Target = GcraState;
+
+    fn deref(&self) -> &Self::Target {
+        &self.gcra_state
+    }
+}
+
+impl DerefMut for RateLimitEntry {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.gcra_state
+    }
+}
+
+impl RateLimitEntry {
+    pub(super) fn update_expiration(&mut self, rate_limit: &RateLimit) {
+        let expires_at = self.tat.unwrap_or_else(Instant::now) + rate_limit.period;
+        self.expires_at = Some(expires_at);
+    }
+}
diff --git a/ext/gcra-rs/src/rate_limiter/mod.rs b/ext/gcra-rs/src/rate_limiter/mod.rs
new file mode 100644
index 000000000..c089639f4
--- /dev/null
+++ b/ext/gcra-rs/src/rate_limiter/mod.rs
@@ -0,0 +1,7 @@
+mod clock;
+mod entry;
+#[allow(clippy::module_inception)]
+mod rate_limiter;
+
+pub use entry::*;
+pub use rate_limiter::*;
diff --git a/ext/gcra-rs/src/rate_limiter/rate_limiter.rs b/ext/gcra-rs/src/rate_limiter/rate_limiter.rs
new file mode 100644
index 000000000..ecbafadd5
--- /dev/null
+++ b/ext/gcra-rs/src/rate_limiter/rate_limiter.rs
@@ -0,0 +1,265 @@
+use dashmap::DashMap;
+use rustc_hash::FxHasher;
+use std::{
+    fmt::Display,
+    hash::{BuildHasher, BuildHasherDefault, Hash},
+    time::Instant,
+};
+
+use super::{
+    clock::{Clock, InstantClock},
+    entry::RateLimitEntry,
+};
+use crate::{GcraError, RateLimit};
+
+pub type FxBuildHasher = BuildHasherDefault<FxHasher>;
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug)]
+pub struct RateLimitRequest<T: Eq + Hash> {
+    key: T,
+}
+
+impl<T> Display for RateLimitRequest<T>
+where
+    T: Display + Eq + Hash,
+{
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "RateLimitRequest={}", self.key)
+    }
+}
+
+/// A sharded rate limiter implementation using an internal [GcraState] per entry.
+/// It is `Send + Sync + Clone` and manages an internal LRU with expiration.
+#[derive(Clone)]
+pub struct RateLimiter<T: Eq + Hash, C = InstantClock, S = FxBuildHasher> {
+    clock: C,
+    map: DashMap<RateLimitRequest<T>, RateLimitEntry, S>,
+}
+
+impl<Key> RateLimiter<Key, InstantClock, FxBuildHasher>
+where
+    Key: Send + Clone + Hash + Eq + Display + 'static,
+{
+    /// Constructs an sharded instance of a rate limiter.
+    pub fn new(max_data_capacity: usize) -> Self {
+        Self {
+            clock: InstantClock,
+            map: DashMap::with_capacity_and_hasher(max_data_capacity, FxBuildHasher::default()),
+        }
+    }
+
+    /// Constructs an sharded instance of a rate limiter with a specific amount of shards.
+    pub fn with_shards(max_data_capacity: usize, num_shards: usize) -> Self {
+        Self {
+            clock: InstantClock,
+            map: DashMap::with_capacity_and_hasher_and_shard_amount(
+                max_data_capacity,
+                FxBuildHasher::default(),
+                num_shards,
+            ),
+        }
+    }
+}
+
+impl<Key, C, S> RateLimiter<Key, C, S>
+where
+    Key: Send + Clone + Hash + Eq + Display + 'static,
+    C: Clock,
+    S: Default + BuildHasher + Clone,
+{
+    pub fn with_clock(clock: C) -> Self {
+        Self {
+            clock,
+            map: DashMap::default(),
+        }
+    }
+
+    /// Check to see if [key] is rate limited.
+    /// # Errors
+    /// - [GcraError::DeniedUntil] if the request can succeed after the [Instant] returned.
+    /// - [GcraError::DeniedIndefinitely] if the request can never succeed
+    #[inline]
+    pub async fn check(
+        &mut self,
+        key: Key,
+        rate_limit: RateLimit,
+        cost: u32,
+    ) -> Result<Instant, GcraError> {
+        self.check_at(key, rate_limit, cost, self.clock.now()).await
+    }
+
+    /// Check to see if [key] is rate limited.
+    ///
+    /// # Errors
+    /// - [GcraError::DeniedUntil] if the request can succeed after the [Instant] returned.
+    /// - [GcraError::DeniedIndefinitely] if the request can never succeed
+    pub async fn check_at(
+        &mut self,
+        key: Key,
+        rate_limit: RateLimit,
+        cost: u32,
+        arrived_at: Instant,
+    ) -> Result<Instant, GcraError> {
+        let request_key = RateLimitRequest { key };
+
+        let mut entry = self.map.entry(request_key.clone()).or_default();
+        match entry.check_and_modify_at(&rate_limit, arrived_at, cost) {
+            Ok(_) => {
+                entry.update_expiration(&rate_limit);
+                // Guaranteed to be set from update_expiration
+                let expires_at = entry.expires_at.unwrap();
+                Ok(expires_at)
+            }
+            Err(e @ GcraError::DeniedUntil { .. }) => Err(e),
+            Err(e @ GcraError::DeniedIndefinitely { .. }) => {
+                // Free the lock so we can remove the entry
+                drop(entry);
+                // No need to keep this in the map
+                self.map.remove(&request_key);
+                Err(e)
+            }
+        }
+    }
+
+    /// Removes entries that have expired
+    pub fn prune_expired(&mut self) {
+        let now = self.clock.now();
+
+        self.map.retain(|_key, entry| match entry.expires_at {
+            Some(expires_at) => expires_at > now,
+            None => true,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::rate_limiter::clock::tests::FakeClock;
+    use core::panic;
+    use std::time::{Duration, Instant};
+
+    use super::*;
+
+    #[tokio::test]
+    async fn rate_limiter_run_until_denied() {
+        let rate_limit = RateLimit::new(3, Duration::from_secs(3));
+        let mut rl = RateLimiter::with_shards(4, 2);
+
+        for _ in 0..rate_limit.resource_limit {
+            assert!(
+                rl.check("key", rate_limit.clone(), 1).await.is_ok(),
+                "Shouldn't be rate limited yet"
+            );
+        }
+
+        match rl.check("key", rate_limit, 1).await {
+            Ok(_) => panic!("We should be rate limited"),
+            Err(GcraError::DeniedUntil { next_allowed_at }) => {
+                assert!(next_allowed_at > Instant::now())
+            }
+            Err(_) => panic!("Unexpected error"),
+        }
+    }
+
+    #[tokio::test]
+    async fn rate_limiter_indefinitly_denied() {
+        let rate_limit = RateLimit::new(3, Duration::from_secs(3));
+        let mut rl = RateLimiter::with_shards(4, 2);
+
+        match rl.check("key", rate_limit.clone(), 9).await {
+            Ok(_) => panic!("We should be rate limited"),
+            Err(GcraError::DeniedIndefinitely {
+                cost,
+                rate_limit: err_rate_limit,
+            }) => {
+                assert_eq!(cost, 9);
+                assert_eq!(err_rate_limit, rate_limit);
+            }
+            Err(_) => panic!("Unexpected error"),
+        }
+    }
+
+    #[tokio::test]
+    async fn rate_limiter_leaks() {
+        let rate_limit = RateLimit::per_sec(2);
+        let mut rl = RateLimiter::with_shards(4, 2);
+
+        let now = Instant::now();
+        assert!(rl.check_at("key", rate_limit.clone(), 1, now).await.is_ok());
+        assert!(
+            rl.check_at(
+                "key",
+                rate_limit.clone(),
+                1,
+                now + Duration::from_millis(250)
+            )
+            .await
+            .is_ok(),
+            "delay the 2nd check"
+        );
+        assert!(
+            rl.check_at(
+                "key",
+                rate_limit.clone(),
+                1,
+                now + Duration::from_millis(251)
+            )
+            .await
+            .is_err(),
+            "check we are denied start"
+        );
+        assert!(
+            rl.check_at(
+                "key",
+                rate_limit.clone(),
+                1,
+                now + Duration::from_millis(499)
+            )
+            .await
+            .is_err(),
+            "check we are denied end"
+        );
+        assert!(
+            rl.check_at(
+                "key",
+                rate_limit.clone(),
+                1,
+                now + Duration::from_millis(501)
+            )
+            .await
+            .is_ok(),
+            "1st use should be released"
+        )
+    }
+
+    #[tokio::test]
+    async fn rate_limiter_prune_expired() {
+        let clock = FakeClock::new();
+
+        let rate_limit = RateLimit::per_sec(3);
+        let mut rl: RateLimiter<_, _, FxBuildHasher> = RateLimiter::with_clock(clock.clone());
+
+        for index in 0..rate_limit.resource_limit {
+            assert!(
+                rl.check(index, rate_limit.clone(), 1).await.is_ok(),
+                "Shouldn't be rate limited yet"
+            );
+        }
+
+        let before_len = rl.map.len();
+        rl.prune_expired();
+        let after_len = rl.map.len();
+        assert_eq!(
+            before_len, after_len,
+            "Nothing has expired, no elements should be removed"
+        );
+
+        clock.advance_by(Duration::from_secs(10));
+        rl.prune_expired();
+        let after_len = rl.map.len();
+        assert_eq!(
+            0, after_len,
+            "All entries have expired, no elements expected"
+        );
+    }
+}
diff --git a/ext/libp2p-bitswap/.gitignore b/ext/libp2p-bitswap/.gitignore
new file mode 100644
index 000000000..53eaa2196
--- /dev/null
+++ b/ext/libp2p-bitswap/.gitignore
@@ -0,0 +1,2 @@
+/target
+**/*.rs.bk
diff --git a/ext/libp2p-bitswap/Cargo.lock b/ext/libp2p-bitswap/Cargo.lock
new file mode 100644
index 000000000..37c651fcb
--- /dev/null
+++ b/ext/libp2p-bitswap/Cargo.lock
@@ -0,0 +1,3707 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[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 = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "asn1-rs"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "asn1_der"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247"
+
+[[package]]
+name = "async-attributes"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 4.0.3",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
+dependencies = [
+ "async-lock 3.3.0",
+ "async-task",
+ "concurrent-queue",
+ "fastrand 2.0.1",
+ "futures-lite 2.2.0",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
+dependencies = [
+ "async-channel 2.1.1",
+ "async-executor",
+ "async-io 2.2.2",
+ "async-lock 3.3.0",
+ "blocking",
+ "futures-lite 2.2.0",
+ "once_cell",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite 1.13.0",
+ "log",
+ "parking",
+ "polling 2.8.0",
+ "rustix 0.37.27",
+ "slab",
+ "socket2 0.4.10",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-io"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7"
+dependencies = [
+ "async-lock 3.3.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.2.0",
+ "parking",
+ "polling 3.3.1",
+ "rustix 0.38.28",
+ "slab",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
+dependencies = [
+ "event-listener 4.0.3",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-net"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f"
+dependencies = [
+ "async-io 1.13.0",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
+[[package]]
+name = "async-process"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
+dependencies = [
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 3.1.0",
+ "futures-lite 1.13.0",
+ "rustix 0.38.28",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
+dependencies = [
+ "async-io 2.2.2",
+ "async-lock 2.8.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.28",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-std"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
+dependencies = [
+ "async-attributes",
+ "async-channel 1.9.0",
+ "async-global-executor",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-process",
+ "crossbeam-utils",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite 1.13.0",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "once_cell",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-std-resolver"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c0ed2b6671c13d2c28756c5a64e04759c1e0b5d3d7ac031f521c3561e21fbcb"
+dependencies = [
+ "async-std",
+ "async-trait",
+ "futures-io",
+ "futures-util",
+ "hickory-resolver",
+ "pin-utils",
+ "socket2 0.5.5",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
+
+[[package]]
+name = "async-trait"
+version = "0.1.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[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 = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base-x"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
+
+[[package]]
+name = "base64"
+version = "0.21.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "blake3"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[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 = "blocking"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
+dependencies = [
+ "async-channel 2.1.1",
+ "async-lock 3.3.0",
+ "async-task",
+ "fastrand 2.0.1",
+ "futures-io",
+ "futures-lite 2.2.0",
+ "piper",
+ "tracing",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "cached"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af4dfac631a8e77b2f327f7852bb6172771f5279c4512efe79fad6067b37be3d"
+dependencies = [
+ "hashbrown 0.11.2",
+ "once_cell",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chacha20"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[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 = "cid"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2"
+dependencies = [
+ "core2",
+ "multibase",
+ "multihash 0.16.3",
+ "serde",
+ "unsigned-varint 0.7.2",
+]
+
+[[package]]
+name = "cid"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd94671561e36e4e7de75f753f577edafb0e7c05d6e4547229fdf7938fbcd2c3"
+dependencies = [
+ "core2",
+ "multibase",
+ "multihash 0.18.1",
+ "serde",
+ "unsigned-varint 0.7.2",
+]
+
+[[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 = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+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 = "constant_time_eq"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+
+[[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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[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.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "platforms",
+ "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.48",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "data-encoding-macro"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e"
+dependencies = [
+ "data-encoding",
+ "data-encoding-macro-internal",
+]
+
+[[package]]
+name = "data-encoding-macro-internal"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3"
+dependencies = [
+ "data-encoding",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "der"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "der-parser"
+version = "8.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[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.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "rand_core",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "enum-as-inner"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.3",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-bounded"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1e2774cc104e198ef3d3e1ff4ab40f86fa3245d6cb6a3a46174f21463cee173"
+dependencies = [
+ "futures-timer",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+ "num_cpus",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-lite"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
+dependencies = [
+ "fastrand 2.0.1",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "futures-rustls"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28"
+dependencies = [
+ "futures-io",
+ "rustls",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
+name = "hickory-proto"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "enum-as-inner",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "idna 0.4.0",
+ "ipnet",
+ "once_cell",
+ "rand",
+ "socket2 0.5.5",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "hickory-resolver"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-proto",
+ "ipconfig",
+ "lru-cache",
+ "once_cell",
+ "parking_lot",
+ "rand",
+ "resolv-conf",
+ "smallvec",
+ "thiserror",
+ "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 = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "if-addrs"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "if-watch"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e"
+dependencies = [
+ "async-io 2.2.2",
+ "core-foundation",
+ "fnv",
+ "futures",
+ "if-addrs",
+ "ipnet",
+ "log",
+ "rtnetlink",
+ "smol",
+ "system-configuration",
+ "windows",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ipconfig"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
+dependencies = [
+ "socket2 0.5.5",
+ "widestring",
+ "windows-sys 0.48.0",
+ "winreg",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "libipld"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9c3aa309c260aa2f174bac968901eddc546e9d85950c28eae6a7bec402f926"
+dependencies = [
+ "async-trait",
+ "cached",
+ "fnv",
+ "libipld-cbor",
+ "libipld-core 0.14.0",
+ "libipld-macro 0.14.0",
+ "log",
+ "multihash 0.16.3",
+ "parking_lot",
+ "thiserror",
+]
+
+[[package]]
+name = "libipld"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1ccd6b8ffb3afee7081fcaec00e1b099fd1c7ccf35ba5729d88538fcc3b4599"
+dependencies = [
+ "fnv",
+ "libipld-core 0.16.0",
+ "libipld-macro 0.16.0",
+ "log",
+ "multihash 0.18.1",
+ "thiserror",
+]
+
+[[package]]
+name = "libipld-cbor"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd1ab68c9d26f20c7d0dfea6eecbae8c00359875210001b33ca27d4a02f3d09"
+dependencies = [
+ "byteorder",
+ "libipld-core 0.14.0",
+ "thiserror",
+]
+
+[[package]]
+name = "libipld-core"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d44790246ec6b7314cba745992c23d479d018073e66d49ae40ae1b64e5dd8eb5"
+dependencies = [
+ "anyhow",
+ "cid 0.8.6",
+ "core2",
+ "multibase",
+ "multihash 0.16.3",
+ "thiserror",
+]
+
+[[package]]
+name = "libipld-core"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5acd707e8d8b092e967b2af978ed84709eaded82b75effe6cb6f6cc797ef8158"
+dependencies = [
+ "anyhow",
+ "cid 0.10.1",
+ "core2",
+ "multibase",
+ "multihash 0.18.1",
+ "thiserror",
+]
+
+[[package]]
+name = "libipld-macro"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852c011562ae5059b67c3a917f9f5945af5a68df8e39ede4444fff33274d25e2"
+dependencies = [
+ "libipld-core 0.14.0",
+]
+
+[[package]]
+name = "libipld-macro"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71171c54214f866ae6722f3027f81dff0931e600e5a61e6b1b6a49ca0b5ed4ae"
+dependencies = [
+ "libipld-core 0.16.0",
+]
+
+[[package]]
+name = "libp2p"
+version = "0.53.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "681fb3f183edfbedd7a57d32ebe5dcdc0b9f94061185acf3c30249349cc6fc99"
+dependencies = [
+ "bytes",
+ "either",
+ "futures",
+ "futures-timer",
+ "getrandom",
+ "instant",
+ "libp2p-allow-block-list",
+ "libp2p-connection-limits",
+ "libp2p-core",
+ "libp2p-dns",
+ "libp2p-identity",
+ "libp2p-mdns",
+ "libp2p-noise",
+ "libp2p-quic",
+ "libp2p-request-response",
+ "libp2p-swarm",
+ "libp2p-tcp",
+ "libp2p-yamux",
+ "multiaddr",
+ "pin-project",
+ "rw-stream-sink",
+ "thiserror",
+]
+
+[[package]]
+name = "libp2p-allow-block-list"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "107b238b794cb83ab53b74ad5dcf7cca3200899b72fe662840cfb52f5b0a32e6"
+dependencies = [
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "void",
+]
+
+[[package]]
+name = "libp2p-bitswap"
+version = "0.25.1"
+dependencies = [
+ "async-std",
+ "async-trait",
+ "env_logger",
+ "fnv",
+ "futures",
+ "lazy_static",
+ "libipld 0.14.0",
+ "libipld 0.16.0",
+ "libp2p",
+ "multihash 0.16.3",
+ "prometheus",
+ "prost",
+ "prost-build",
+ "thiserror",
+ "tracing",
+ "tracing-subscriber",
+ "unsigned-varint 0.7.2",
+]
+
+[[package]]
+name = "libp2p-connection-limits"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7cd50a78ccfada14de94cbacd3ce4b0138157f376870f13d3a8422cd075b4fd"
+dependencies = [
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "void",
+]
+
+[[package]]
+name = "libp2p-core"
+version = "0.41.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8130a8269e65a2554d55131c770bdf4bcd94d2b8d4efb24ca23699be65066c05"
+dependencies = [
+ "either",
+ "fnv",
+ "futures",
+ "futures-timer",
+ "instant",
+ "libp2p-identity",
+ "multiaddr",
+ "multihash 0.19.1",
+ "multistream-select",
+ "once_cell",
+ "parking_lot",
+ "pin-project",
+ "quick-protobuf",
+ "rand",
+ "rw-stream-sink",
+ "smallvec",
+ "thiserror",
+ "tracing",
+ "unsigned-varint 0.8.0",
+ "void",
+]
+
+[[package]]
+name = "libp2p-dns"
+version = "0.41.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d17cbcf7160ff35c3e8e560de4a068fe9d6cb777ea72840e48eb76ff9576c4b6"
+dependencies = [
+ "async-std-resolver",
+ "async-trait",
+ "futures",
+ "hickory-resolver",
+ "libp2p-core",
+ "libp2p-identity",
+ "parking_lot",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-identity"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0"
+dependencies = [
+ "asn1_der",
+ "bs58",
+ "ed25519-dalek",
+ "hkdf",
+ "multihash 0.19.1",
+ "quick-protobuf",
+ "rand",
+ "ring 0.17.7",
+ "sha2",
+ "thiserror",
+ "tracing",
+ "zeroize",
+]
+
+[[package]]
+name = "libp2p-mdns"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49007d9a339b3e1d7eeebc4d67c05dbf23d300b7d091193ec2d3f26802d7faf2"
+dependencies = [
+ "async-io 2.2.2",
+ "async-std",
+ "data-encoding",
+ "futures",
+ "hickory-proto",
+ "if-watch",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "rand",
+ "smallvec",
+ "socket2 0.5.5",
+ "tracing",
+ "void",
+]
+
+[[package]]
+name = "libp2p-noise"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecd0545ce077f6ea5434bcb76e8d0fe942693b4380aaad0d34a358c2bd05793"
+dependencies = [
+ "asynchronous-codec",
+ "bytes",
+ "curve25519-dalek",
+ "futures",
+ "libp2p-core",
+ "libp2p-identity",
+ "multiaddr",
+ "multihash 0.19.1",
+ "once_cell",
+ "quick-protobuf",
+ "rand",
+ "sha2",
+ "snow",
+ "static_assertions",
+ "thiserror",
+ "tracing",
+ "x25519-dalek",
+ "zeroize",
+]
+
+[[package]]
+name = "libp2p-quic"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0375cdfee57b47b313ef1f0fdb625b78aed770d33a40cf1c294a371ff5e6666"
+dependencies = [
+ "async-std",
+ "bytes",
+ "futures",
+ "futures-timer",
+ "if-watch",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-tls",
+ "parking_lot",
+ "quinn",
+ "rand",
+ "ring 0.16.20",
+ "rustls",
+ "socket2 0.5.5",
+ "thiserror",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-request-response"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12823250fe0c45bdddea6eefa2be9a609aff1283ff4e1d8a294fdbb89572f6f"
+dependencies = [
+ "async-trait",
+ "futures",
+ "futures-bounded",
+ "futures-timer",
+ "instant",
+ "libp2p-core",
+ "libp2p-identity",
+ "libp2p-swarm",
+ "rand",
+ "smallvec",
+ "tracing",
+ "void",
+]
+
+[[package]]
+name = "libp2p-swarm"
+version = "0.44.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e92532fc3c4fb292ae30c371815c9b10103718777726ea5497abc268a4761866"
+dependencies = [
+ "async-std",
+ "either",
+ "fnv",
+ "futures",
+ "futures-timer",
+ "instant",
+ "libp2p-core",
+ "libp2p-identity",
+ "multistream-select",
+ "once_cell",
+ "rand",
+ "smallvec",
+ "tracing",
+ "void",
+]
+
+[[package]]
+name = "libp2p-tcp"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2460fc2748919adff99ecbc1aab296e4579e41f374fb164149bd2c9e529d4c"
+dependencies = [
+ "async-io 1.13.0",
+ "futures",
+ "futures-timer",
+ "if-watch",
+ "libc",
+ "libp2p-core",
+ "libp2p-identity",
+ "socket2 0.5.5",
+ "tracing",
+]
+
+[[package]]
+name = "libp2p-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93ce7e3c2e7569d685d08ec795157981722ff96e9e9f9eae75df3c29d02b07a5"
+dependencies = [
+ "futures",
+ "futures-rustls",
+ "libp2p-core",
+ "libp2p-identity",
+ "rcgen",
+ "ring 0.16.20",
+ "rustls",
+ "rustls-webpki",
+ "thiserror",
+ "x509-parser",
+ "yasna",
+]
+
+[[package]]
+name = "libp2p-yamux"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200cbe50349a44760927d50b431d77bed79b9c0a3959de1af8d24a63434b71e5"
+dependencies = [
+ "either",
+ "futures",
+ "libp2p-core",
+ "thiserror",
+ "tracing",
+ "yamux 0.12.1",
+ "yamux 0.13.1",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+dependencies = [
+ "value-bag",
+]
+
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "multiaddr"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070"
+dependencies = [
+ "arrayref",
+ "byteorder",
+ "data-encoding",
+ "libp2p-identity",
+ "multibase",
+ "multihash 0.19.1",
+ "percent-encoding",
+ "serde",
+ "static_assertions",
+ "unsigned-varint 0.7.2",
+ "url",
+]
+
+[[package]]
+name = "multibase"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404"
+dependencies = [
+ "base-x",
+ "data-encoding",
+ "data-encoding-macro",
+]
+
+[[package]]
+name = "multihash"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc"
+dependencies = [
+ "blake3",
+ "core2",
+ "digest",
+ "multihash-derive",
+ "sha2",
+ "unsigned-varint 0.7.2",
+]
+
+[[package]]
+name = "multihash"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfd8a792c1694c6da4f68db0a9d707c72bd260994da179e6030a5dcee00bb815"
+dependencies = [
+ "core2",
+ "multihash-derive",
+ "unsigned-varint 0.7.2",
+]
+
+[[package]]
+name = "multihash"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492"
+dependencies = [
+ "core2",
+ "unsigned-varint 0.7.2",
+]
+
+[[package]]
+name = "multihash-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "synstructure",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
+
+[[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.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "libc",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab"
+dependencies = [
+ "anyhow",
+ "bitflags 1.3.2",
+ "byteorder",
+ "libc",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-utils"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "paste",
+ "thiserror",
+]
+
+[[package]]
+name = "netlink-proto"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6"
+dependencies = [
+ "bytes",
+ "futures",
+ "log",
+ "netlink-packet-core",
+ "netlink-sys",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
+name = "netlink-sys"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411"
+dependencies = [
+ "async-io 1.13.0",
+ "bytes",
+ "futures",
+ "libc",
+ "log",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "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 = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi 0.3.3",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "oid-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
+[[package]]
+name = "pem"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310"
+dependencies = [
+ "base64",
+ "serde",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "petgraph"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "platforms"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
+
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "polling"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix 0.38.28",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "polyval"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
+dependencies = [
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prometheus"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
+dependencies = [
+ "cfg-if",
+ "fnv",
+ "lazy_static",
+ "memchr",
+ "parking_lot",
+ "protobuf",
+ "thiserror",
+]
+
+[[package]]
+name = "prost"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"
+dependencies = [
+ "bytes",
+ "heck 0.3.3",
+ "itertools",
+ "lazy_static",
+ "log",
+ "multimap",
+ "petgraph",
+ "prost",
+ "prost-types",
+ "regex",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"
+dependencies = [
+ "bytes",
+ "prost",
+]
+
+[[package]]
+name = "protobuf"
+version = "2.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quick-protobuf"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "quinn"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75"
+dependencies = [
+ "async-io 1.13.0",
+ "async-std",
+ "bytes",
+ "futures-io",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a"
+dependencies = [
+ "bytes",
+ "rand",
+ "ring 0.16.20",
+ "rustc-hash",
+ "rustls",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7"
+dependencies = [
+ "bytes",
+ "libc",
+ "socket2 0.5.5",
+ "tracing",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[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",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rcgen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6"
+dependencies = [
+ "pem",
+ "ring 0.16.20",
+ "time",
+ "yasna",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.3",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "resolv-conf"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
+dependencies = [
+ "hostname",
+ "quick-error",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin 0.5.2",
+ "untrusted 0.7.1",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin 0.9.8",
+ "untrusted 0.9.0",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rtnetlink"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0"
+dependencies = [
+ "async-global-executor",
+ "futures",
+ "log",
+ "netlink-packet-route",
+ "netlink-proto",
+ "nix",
+ "thiserror",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.12",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.21.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
+dependencies = [
+ "log",
+ "ring 0.17.7",
+ "rustls-webpki",
+ "sct",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring 0.17.7",
+ "untrusted 0.9.0",
+]
+
+[[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 = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sct"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
+dependencies = [
+ "ring 0.17.7",
+ "untrusted 0.9.0",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "smol"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
+dependencies = [
+ "async-channel 1.9.0",
+ "async-executor",
+ "async-fs",
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-net",
+ "async-process",
+ "blocking",
+ "futures-lite 1.13.0",
+]
+
+[[package]]
+name = "snow"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58021967fd0a5eeeb23b08df6cc244a4d4a5b4aec1d27c9e02fad1a58b4cd74e"
+dependencies = [
+ "aes-gcm",
+ "blake2",
+ "chacha20poly1305",
+ "curve25519-dalek",
+ "rand_core",
+ "ring 0.17.7",
+ "rustc_version",
+ "sha2",
+ "subtle",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "unicode-xid",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand 2.0.1",
+ "redox_syscall",
+ "rustix 0.38.28",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
+dependencies = [
+ "deranged",
+ "itoa",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.35.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[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"
+dependencies = [
+ "futures-io",
+ "futures-util",
+]
+
+[[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.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna 0.5.0",
+ "percent-encoding",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "value-bag"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
+
+[[package]]
+name = "web-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix 0.38.28",
+]
+
+[[package]]
+name = "widestring"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[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.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "winreg"
+version = "0.50.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "x25519-dalek"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core",
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[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",
+ "static_assertions",
+]
+
+[[package]]
+name = "yamux"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad1d0148b89300047e72994bee99ecdabd15a9166a7b70c8b8c37c314dcc9002"
+dependencies = [
+ "futures",
+ "instant",
+ "log",
+ "nohash-hasher",
+ "parking_lot",
+ "pin-project",
+ "rand",
+ "static_assertions",
+]
+
+[[package]]
+name = "yasna"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
+dependencies = [
+ "time",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
diff --git a/ext/libp2p-bitswap/Cargo.toml b/ext/libp2p-bitswap/Cargo.toml
new file mode 100644
index 000000000..bfad697c6
--- /dev/null
+++ b/ext/libp2p-bitswap/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "libp2p-bitswap"
+version = "0.25.1"
+authors = ["David Craven <david@craven.ch>"]
+edition = "2018"
+description = "Implementation of the ipfs bitswap protocol."
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/ipfs-rust/libp2p-bitswap"
+
+[features]
+compat = ["prost", "prost-build"]
+
+[build-dependencies]
+prost-build = { version = "0.9.0", optional = true }
+
+[dependencies]
+async-trait = "0.1.52"
+fnv = "1.0.7"
+futures = "0.3.19"
+lazy_static = "1.4.0"
+libipld = { version = "0.16.0", default-features = false }
+libp2p = { version = "0.53", features = ["request-response"] }
+prometheus = "0.13.0"
+prost = { version = "0.9.0", optional = true }
+thiserror = "1.0.30"
+tracing = "0.1.29"
+unsigned-varint = { version = "0.7.1", features = ["futures", "std"] }
+
+[dev-dependencies]
+async-std = { version = "1.10.0", features = ["attributes"] }
+env_logger = "0.9.0"
+libipld = { version = "0.14.0", default-features = false, features = ["dag-cbor"] }
+libp2p = { version = "0.53.0", features = ["tcp", "noise", "yamux", "rsa", "async-std"] }
+multihash = { version = "0.16.0", default-features = false, features = ["blake3", "sha2"] }
+tracing-subscriber = { version = "0.3.5", features = ["env-filter", "tracing-log"] }
diff --git a/ext/libp2p-bitswap/README.md b/ext/libp2p-bitswap/README.md
new file mode 100644
index 000000000..3ad2dd613
--- /dev/null
+++ b/ext/libp2p-bitswap/README.md
@@ -0,0 +1,98 @@
+[![Crates.io](https://img.shields.io/crates/v/libp2p-bitswap.svg)](https://crates.io/crates/libp2p-bitswap)
+[![docs.rs](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/libp2p-bitswap)
+
+# libp2p-bitswap
+
+Implementation of the bitswap protocol.
+
+## Efficiently syncing dags of blocks
+
+Bitswap is a very simple protocol. It was adapted and simplified for ipfs-embed. The message
+format can be represented by the following enums.
+
+```rust
+pub enum BitswapRequest {
+    Have(Cid),
+    Block(Cid),
+}
+
+pub enum BitswapResponse {
+    Have(bool),
+    Block(Vec<u8>),
+}
+```
+
+The mechanism for locating providers can be abstracted. A dht can be plugged in or a centralized
+db query. The bitswap api looks as follows:
+
+```rust
+#[derive(Debug)]
+pub enum BitswapEvent {
+    /// Received a block from a peer. Includes the number of known missing blocks for a
+    /// sync query. When a block is received and missing blocks is not empty the counter
+    /// is increased. If missing blocks is empty the counter is decremented.
+    Progress(QueryId, usize),
+    /// A get or sync query completed.
+    Complete(QueryId, Result<()>),
+}
+
+pub trait BitswapStore: Send + Sync + 'static {
+    /// The store params.
+    type Params: StoreParams;
+    /// A have query needs to know if the block store contains the block.
+    fn contains(&mut self, cid: &Cid) -> Result<bool>;
+    /// A block query needs to retrieve the block from the store.
+    fn get(&mut self, cid: &Cid) -> Result<Option<Vec<u8>>>;
+    /// A block response needs to insert the block into the store.
+    fn insert(&mut self, block: &Block<Self::Params>) -> Result<()>;
+    /// A sync query needs a list of missing blocks to make progress.
+    fn missing_blocks(&mut self, cid: &Cid) -> Result<Vec<Cid>>;
+}
+
+pub struct BitswapConfig {
+    /// Timeout of a request.
+    pub request_timeout: Duration,
+    /// Time a connection is kept alive.
+    pub connection_keep_alive: Duration,
+}
+
+impl<P: StoreParams> Bitswap<P> {
+    /// Creates a new `Bitswap` behaviour.
+    pub fn new(config: BitswapConfig) -> Self;
+
+    /// Adds an address for a peer.
+    pub fn add_address(&mut self, peer_id: &PeerId, addr: Multiaddr);
+
+    /// Removes an address for a peer.
+    pub fn remove_address(&mut self, peer_id: &PeerId, addr: &Multiaddr);
+
+    /// Starts a get query with an initial guess of providers.
+    pub fn get(&mut self, cid: Cid, peers: impl Iterator<Item = PeerId>) -> QueryId;
+
+    /// Starts a sync query with an the initial set of missing blocks.
+    pub fn sync(&mut self, cid: Cid, peers: Vec<PeerId>, missing: impl Iterator<Item = Cid>) -> QueryId;
+
+    /// Cancels an in progress query. Returns true if a query was cancelled.
+    pub fn cancel(&mut self, id: QueryId) -> bool;
+
+    /// Register bitswap stats in a prometheus registry.
+    pub fn register_metrics(&self, registry: &Registry) -> Result<()>;
+}
+```
+
+So what happens when you create a get request? First all the providers in the initial set
+are queried with the have request. As an optimization, in every batch of queries a block
+request is sent instead. If the get query finds a block it returns a query complete. If the
+block wasn't found in the initial set, a `Providers` event is emitted. This is where
+the bitswap consumer tries to locate providers by for example performing a dht lookup. After
+the locating of providers completes, it is signaled by calling `inject_providers`. The query
+manager then performs bitswap requests using the new provider set which results in the block
+being found or a `BlockNotFound` error.
+
+Often we want to sync an entire dag of blocks. We can efficiently sync dags of blocks by adding
+a sync query that runs get queries in parallel for all the references of a block. The set of
+providers that had a block is used as the initial set in a reference query.
+
+## License
+
+MIT OR Apache-2.0
diff --git a/ext/libp2p-bitswap/build.rs b/ext/libp2p-bitswap/build.rs
new file mode 100644
index 000000000..6b7fa7b80
--- /dev/null
+++ b/ext/libp2p-bitswap/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+    #[cfg(feature = "compat")]
+    prost_build::compile_protos(&["src/compat/bitswap_pb.proto"], &["src/compat"]).unwrap();
+}
diff --git a/ext/libp2p-bitswap/src/behaviour.rs b/ext/libp2p-bitswap/src/behaviour.rs
new file mode 100644
index 000000000..a195b4e08
--- /dev/null
+++ b/ext/libp2p-bitswap/src/behaviour.rs
@@ -0,0 +1,963 @@
+//! Handles the `/ipfs/bitswap/1.0.0` and `/ipfs/bitswap/1.1.0` protocols. This
+//! allows exchanging IPFS blocks.
+//!
+//! # Usage
+//!
+//! The `Bitswap` struct implements the `NetworkBehaviour` trait. When used, it
+//! will allow providing and reciving IPFS blocks.
+#[cfg(feature = "compat")]
+use crate::compat::{CompatMessage, CompatProtocol, InboundMessage};
+use crate::protocol::{
+    BitswapCodec, BitswapProtocol, BitswapRequest, BitswapResponse, RequestType,
+};
+use crate::query::{QueryEvent, QueryId, QueryManager, Request, Response};
+use crate::stats::*;
+use fnv::FnvHashMap;
+#[cfg(feature = "compat")]
+use fnv::FnvHashSet;
+use futures::{
+    channel::mpsc,
+    stream::{Stream, StreamExt},
+    task::{Context, Poll},
+};
+use libipld::{error::BlockNotFound, store::StoreParams, Block, Cid, Result};
+#[cfg(feature = "compat")]
+use libp2p::core::either::EitherOutput;
+use libp2p::core::{Endpoint, Multiaddr};
+use libp2p::request_response::OutboundRequestId;
+use libp2p::swarm::derive_prelude::{ConnectionClosed, DialFailure, FromSwarm, ListenFailure};
+use libp2p::swarm::{
+    ConnectionDenied, ConnectionId, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
+};
+#[cfg(feature = "compat")]
+use libp2p::swarm::{ConnectionHandlerSelect, NotifyHandler, OneShotHandler};
+use libp2p::PeerId;
+use libp2p::{
+    request_response::{
+        self, InboundFailure, InboundRequestId, OutboundFailure, ProtocolSupport, ResponseChannel,
+    },
+    swarm::NetworkBehaviour,
+};
+use prometheus::Registry;
+use std::{pin::Pin, time::Duration};
+
+/// Bitswap response channel.
+pub type Channel = ResponseChannel<BitswapResponse>;
+
+/// Event emitted by the bitswap behaviour.
+#[derive(Debug)]
+pub enum BitswapEvent {
+    /// Received a block from a peer. Includes the number of known missing blocks for a
+    /// sync query. When a block is received and missing blocks is not empty the counter
+    /// is increased. If missing blocks is empty the counter is decremented.
+    Progress(QueryId, usize),
+    /// A get or sync query completed.
+    Complete(QueryId, Result<()>),
+}
+
+/// Trait implemented by a block store.
+pub trait BitswapStore: Send + Sync + 'static {
+    /// The store params.
+    type Params: StoreParams;
+    /// A have query needs to know if the block store contains the block.
+    fn contains(&mut self, cid: &Cid) -> Result<bool>;
+    /// A block query needs to retrieve the block from the store.
+    fn get(&mut self, cid: &Cid) -> Result<Option<Vec<u8>>>;
+    /// A block response needs to insert the block into the store.
+    fn insert(&mut self, block: &Block<Self::Params>) -> Result<()>;
+    /// A sync query needs a list of missing blocks to make progress.
+    fn missing_blocks(&mut self, cid: &Cid) -> Result<Vec<Cid>>;
+}
+
+/// Bitswap configuration.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct BitswapConfig {
+    /// Timeout of a request.
+    pub request_timeout: Duration,
+}
+
+impl BitswapConfig {
+    /// Creates a new `BitswapConfig`.
+    pub fn new() -> Self {
+        Self {
+            request_timeout: Duration::from_secs(10),
+        }
+    }
+}
+
+impl Default for BitswapConfig {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+enum BitswapId {
+    Bitswap(OutboundRequestId),
+    #[cfg(feature = "compat")]
+    Compat(Cid),
+}
+
+enum BitswapChannel {
+    Bitswap(Channel),
+    #[cfg(feature = "compat")]
+    Compat(PeerId, Cid),
+}
+
+/// Network behaviour that handles sending and receiving blocks.
+pub struct Bitswap<P: StoreParams> {
+    /// Inner behaviour.
+    inner: request_response::Behaviour<BitswapCodec<P>>,
+    /// Query manager.
+    query_manager: QueryManager,
+    /// Requests.
+    requests: FnvHashMap<BitswapId, QueryId>,
+    /// Db request channel.
+    db_tx: mpsc::UnboundedSender<DbRequest<P>>,
+    /// Db response channel.
+    db_rx: mpsc::UnboundedReceiver<DbResponse>,
+    /// Compat peers.
+    #[cfg(feature = "compat")]
+    compat: FnvHashSet<PeerId>,
+}
+
+impl<P: StoreParams> Bitswap<P> {
+    /// Creates a new `Bitswap` behaviour.
+    pub fn new<S: BitswapStore<Params = P>>(config: BitswapConfig, store: S) -> Self {
+        let rr_config =
+            request_response::Config::default().with_request_timeout(config.request_timeout);
+        let protocols = std::iter::once((BitswapProtocol, ProtocolSupport::Full));
+        let inner = request_response::Behaviour::with_codec(
+            BitswapCodec::<P>::default(),
+            protocols,
+            rr_config,
+        );
+        let (db_tx, db_rx) = start_db_thread(store);
+        Self {
+            inner,
+            query_manager: Default::default(),
+            requests: Default::default(),
+            db_tx,
+            db_rx,
+            #[cfg(feature = "compat")]
+            compat: Default::default(),
+        }
+    }
+
+    /// Adds an address for a peer.
+    pub fn add_address(&mut self, peer_id: &PeerId, addr: Multiaddr) {
+        #[allow(deprecated)]
+        self.inner.add_address(peer_id, addr);
+    }
+
+    /// Removes an address for a peer.
+    pub fn remove_address(&mut self, peer_id: &PeerId, addr: &Multiaddr) {
+        #[allow(deprecated)]
+        self.inner.remove_address(peer_id, addr);
+    }
+
+    /// Starts a get query with an initial guess of providers.
+    pub fn get(&mut self, cid: Cid, peers: impl Iterator<Item = PeerId>) -> QueryId {
+        self.query_manager.get(None, cid, peers)
+    }
+
+    /// Starts a sync query with an the initial set of missing blocks.
+    pub fn sync(
+        &mut self,
+        cid: Cid,
+        peers: Vec<PeerId>,
+        missing: impl Iterator<Item = Cid>,
+    ) -> QueryId {
+        self.query_manager.sync(cid, peers, missing)
+    }
+
+    /// Cancels an in progress query. Returns true if a query was cancelled.
+    pub fn cancel(&mut self, id: QueryId) -> bool {
+        let res = self.query_manager.cancel(id);
+        if res {
+            REQUESTS_CANCELED.inc();
+        }
+        res
+    }
+
+    /// Registers prometheus metrics.
+    pub fn register_metrics(&self, registry: &Registry) -> Result<()> {
+        registry.register(Box::new(REQUESTS_TOTAL.clone()))?;
+        registry.register(Box::new(REQUEST_DURATION_SECONDS.clone()))?;
+        registry.register(Box::new(REQUESTS_CANCELED.clone()))?;
+        registry.register(Box::new(BLOCK_NOT_FOUND.clone()))?;
+        registry.register(Box::new(PROVIDERS_TOTAL.clone()))?;
+        registry.register(Box::new(MISSING_BLOCKS_TOTAL.clone()))?;
+        registry.register(Box::new(RECEIVED_BLOCK_BYTES.clone()))?;
+        registry.register(Box::new(RECEIVED_INVALID_BLOCK_BYTES.clone()))?;
+        registry.register(Box::new(SENT_BLOCK_BYTES.clone()))?;
+        registry.register(Box::new(RESPONSES_TOTAL.clone()))?;
+        registry.register(Box::new(THROTTLED_INBOUND.clone()))?;
+        registry.register(Box::new(THROTTLED_OUTBOUND.clone()))?;
+        registry.register(Box::new(OUTBOUND_FAILURE.clone()))?;
+        registry.register(Box::new(INBOUND_FAILURE.clone()))?;
+        Ok(())
+    }
+}
+
+enum DbRequest<P: StoreParams> {
+    Bitswap(BitswapChannel, BitswapRequest),
+    Insert(Block<P>),
+    MissingBlocks(QueryId, Cid),
+}
+
+enum DbResponse {
+    Bitswap(BitswapChannel, BitswapResponse),
+    MissingBlocks(QueryId, Result<Vec<Cid>>),
+}
+
+fn start_db_thread<S: BitswapStore>(
+    mut store: S,
+) -> (
+    mpsc::UnboundedSender<DbRequest<S::Params>>,
+    mpsc::UnboundedReceiver<DbResponse>,
+) {
+    let (tx, requests) = mpsc::unbounded();
+    let (responses, rx) = mpsc::unbounded();
+    std::thread::spawn(move || {
+        let mut requests: mpsc::UnboundedReceiver<DbRequest<S::Params>> = requests;
+        while let Some(request) = futures::executor::block_on(requests.next()) {
+            match request {
+                DbRequest::Bitswap(channel, request) => {
+                    let response = match request.ty {
+                        RequestType::Have => {
+                            let have = store.contains(&request.cid).ok().unwrap_or_default();
+                            if have {
+                                RESPONSES_TOTAL.with_label_values(&["have"]).inc();
+                            } else {
+                                RESPONSES_TOTAL.with_label_values(&["dont_have"]).inc();
+                            }
+                            tracing::trace!("have {}", have);
+                            BitswapResponse::Have(have)
+                        }
+                        RequestType::Block => {
+                            let block = store.get(&request.cid).ok().unwrap_or_default();
+                            if let Some(data) = block {
+                                RESPONSES_TOTAL.with_label_values(&["block"]).inc();
+                                SENT_BLOCK_BYTES.inc_by(data.len() as u64);
+                                tracing::trace!("block {}", data.len());
+                                BitswapResponse::Block(data)
+                            } else {
+                                RESPONSES_TOTAL.with_label_values(&["dont_have"]).inc();
+                                tracing::trace!("have false");
+                                BitswapResponse::Have(false)
+                            }
+                        }
+                    };
+                    responses
+                        .unbounded_send(DbResponse::Bitswap(channel, response))
+                        .ok();
+                }
+                DbRequest::Insert(block) => {
+                    if let Err(err) = store.insert(&block) {
+                        tracing::error!("error inserting blocks {}", err);
+                    }
+                }
+                DbRequest::MissingBlocks(id, cid) => {
+                    let res = store.missing_blocks(&cid);
+                    responses
+                        .unbounded_send(DbResponse::MissingBlocks(id, res))
+                        .ok();
+                }
+            }
+        }
+    });
+    (tx, rx)
+}
+
+impl<P: StoreParams> Bitswap<P> {
+    /// Processes an incoming bitswap request.
+    fn inject_request(&mut self, channel: BitswapChannel, request: BitswapRequest) {
+        self.db_tx
+            .unbounded_send(DbRequest::Bitswap(channel, request))
+            .ok();
+    }
+
+    /// Processes an incoming bitswap response.
+    fn inject_response(&mut self, id: BitswapId, peer: PeerId, response: BitswapResponse) {
+        if let Some(id) = self.requests.remove(&id) {
+            match response {
+                BitswapResponse::Have(have) => {
+                    self.query_manager
+                        .inject_response(id, Response::Have(peer, have));
+                }
+                BitswapResponse::Block(data) => {
+                    if let Some(info) = self.query_manager.query_info(id) {
+                        let len = data.len();
+                        if let Ok(block) = Block::new(info.cid, data) {
+                            RECEIVED_BLOCK_BYTES.inc_by(len as u64);
+                            self.db_tx.unbounded_send(DbRequest::Insert(block)).ok();
+                            self.query_manager
+                                .inject_response(id, Response::Block(peer, true));
+                        } else {
+                            tracing::error!("received invalid block");
+                            RECEIVED_INVALID_BLOCK_BYTES.inc_by(len as u64);
+                            self.query_manager
+                                .inject_response(id, Response::Block(peer, false));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn inject_outbound_failure(
+        &mut self,
+        peer: &PeerId,
+        request_id: OutboundRequestId,
+        error: &OutboundFailure,
+    ) {
+        tracing::debug!(
+            "bitswap outbound failure {} {} {:?}",
+            peer,
+            request_id,
+            error
+        );
+        match error {
+            OutboundFailure::DialFailure => {
+                OUTBOUND_FAILURE.with_label_values(&["dial_failure"]).inc();
+            }
+            OutboundFailure::Timeout => {
+                OUTBOUND_FAILURE.with_label_values(&["timeout"]).inc();
+            }
+            OutboundFailure::ConnectionClosed => {
+                OUTBOUND_FAILURE
+                    .with_label_values(&["connection_closed"])
+                    .inc();
+            }
+            OutboundFailure::UnsupportedProtocols => {
+                OUTBOUND_FAILURE
+                    .with_label_values(&["unsupported_protocols"])
+                    .inc();
+            }
+            OutboundFailure::Io(_) => {
+                INBOUND_FAILURE.with_label_values(&["io_error"]).inc();
+            }
+        }
+    }
+
+    fn inject_inbound_failure(
+        &mut self,
+        peer: &PeerId,
+        request_id: InboundRequestId,
+        error: &InboundFailure,
+    ) {
+        tracing::error!(
+            "bitswap inbound failure {} {} {:?}",
+            peer,
+            request_id,
+            error
+        );
+        match error {
+            InboundFailure::Timeout => {
+                INBOUND_FAILURE.with_label_values(&["timeout"]).inc();
+            }
+            InboundFailure::ConnectionClosed => {
+                INBOUND_FAILURE
+                    .with_label_values(&["connection_closed"])
+                    .inc();
+            }
+            InboundFailure::UnsupportedProtocols => {
+                INBOUND_FAILURE
+                    .with_label_values(&["unsupported_protocols"])
+                    .inc();
+            }
+            InboundFailure::ResponseOmission => {
+                INBOUND_FAILURE
+                    .with_label_values(&["response_omission"])
+                    .inc();
+            }
+            InboundFailure::Io(_) => {
+                INBOUND_FAILURE.with_label_values(&["io_error"]).inc();
+            }
+        }
+    }
+}
+
+impl<P: StoreParams> NetworkBehaviour for Bitswap<P> {
+    #[cfg(not(feature = "compat"))]
+    type ConnectionHandler =
+        <request_response::Behaviour<BitswapCodec<P>> as NetworkBehaviour>::ConnectionHandler;
+
+    #[cfg(feature = "compat")]
+    #[allow(clippy::type_complexity)]
+    type ConnectionHandler = ConnectionHandlerSelect<
+        <RequestResponse<BitswapCodec<P>> as NetworkBehaviour>::ConnectionHandler,
+        OneShotHandler<CompatProtocol, CompatMessage, InboundMessage>,
+    >;
+    type ToSwarm = BitswapEvent;
+
+    fn handle_pending_inbound_connection(
+        &mut self,
+        connection_id: ConnectionId,
+        local_addr: &Multiaddr,
+        remote_addr: &Multiaddr,
+    ) -> Result<(), ConnectionDenied> {
+        self.inner
+            .handle_pending_inbound_connection(connection_id, local_addr, remote_addr)
+    }
+
+    fn handle_established_inbound_connection(
+        &mut self,
+        connection_id: ConnectionId,
+        peer: PeerId,
+        local_addr: &Multiaddr,
+        remote_addr: &Multiaddr,
+    ) -> Result<THandler<Self>, ConnectionDenied> {
+        self.inner.handle_established_inbound_connection(
+            connection_id,
+            peer,
+            local_addr,
+            remote_addr,
+        )
+    }
+
+    fn handle_pending_outbound_connection(
+        &mut self,
+        connection_id: ConnectionId,
+        maybe_peer: Option<PeerId>,
+        addresses: &[Multiaddr],
+        effective_role: Endpoint,
+    ) -> Result<Vec<Multiaddr>, ConnectionDenied> {
+        self.inner.handle_pending_outbound_connection(
+            connection_id,
+            maybe_peer,
+            addresses,
+            effective_role,
+        )
+    }
+
+    fn handle_established_outbound_connection(
+        &mut self,
+        connection_id: ConnectionId,
+        peer: PeerId,
+        addr: &Multiaddr,
+        role_override: Endpoint,
+    ) -> Result<THandler<Self>, ConnectionDenied> {
+        self.inner
+            .handle_established_outbound_connection(connection_id, peer, addr, role_override)
+    }
+
+    fn on_swarm_event(&mut self, event: FromSwarm) {
+        match event {
+            FromSwarm::ConnectionEstablished(ev) => self
+                .inner
+                .on_swarm_event(FromSwarm::ConnectionEstablished(ev)),
+            FromSwarm::ConnectionClosed(ConnectionClosed {
+                peer_id,
+                connection_id,
+                endpoint,
+                remaining_established,
+            }) => {
+                #[cfg(feature = "compat")]
+                if remaining_established == 0 {
+                    self.compat.remove(&peer_id);
+                }
+                #[cfg(feature = "compat")]
+                let (handler, _oneshot) = handler.into_inner();
+                self.inner
+                    .on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed {
+                        peer_id,
+                        connection_id,
+                        endpoint,
+                        remaining_established,
+                    }));
+            }
+            FromSwarm::DialFailure(DialFailure {
+                peer_id,
+                connection_id,
+                error,
+            }) => {
+                #[cfg(feature = "compat")]
+                let (handler, _oneshot) = handler.into_inner();
+                self.inner
+                    .on_swarm_event(FromSwarm::DialFailure(DialFailure {
+                        peer_id,
+                        connection_id,
+                        error,
+                    }));
+            }
+            FromSwarm::AddressChange(ev) => self.inner.on_swarm_event(FromSwarm::AddressChange(ev)),
+            FromSwarm::ListenFailure(ListenFailure {
+                local_addr,
+                send_back_addr,
+                error,
+                connection_id,
+            }) => {
+                #[cfg(feature = "compat")]
+                let (handler, _oneshot) = handler.into_inner();
+                self.inner
+                    .on_swarm_event(FromSwarm::ListenFailure(ListenFailure {
+                        local_addr,
+                        send_back_addr,
+                        error,
+                        connection_id,
+                    }));
+            }
+            FromSwarm::NewListener(ev) => self.inner.on_swarm_event(FromSwarm::NewListener(ev)),
+            FromSwarm::NewListenAddr(ev) => self.inner.on_swarm_event(FromSwarm::NewListenAddr(ev)),
+            FromSwarm::ExpiredListenAddr(ev) => {
+                self.inner.on_swarm_event(FromSwarm::ExpiredListenAddr(ev))
+            }
+            FromSwarm::ListenerError(ev) => self.inner.on_swarm_event(FromSwarm::ListenerError(ev)),
+            FromSwarm::ListenerClosed(ev) => {
+                self.inner.on_swarm_event(FromSwarm::ListenerClosed(ev))
+            }
+            _ => {}
+        }
+    }
+
+    fn on_connection_handler_event(
+        &mut self,
+        peer_id: PeerId,
+        conn: ConnectionId,
+        event: THandlerOutEvent<Self>,
+    ) {
+        #[cfg(not(feature = "compat"))]
+        return self.inner.on_connection_handler_event(peer_id, conn, event);
+        #[cfg(feature = "compat")]
+        match event {
+            EitherOutput::First(event) => {
+                self.inner.on_connection_handler_event(peer_id, conn, event)
+            }
+            EitherOutput::Second(msg) => {
+                for msg in msg.0 {
+                    match msg {
+                        CompatMessage::Request(req) => {
+                            tracing::trace!("received compat request");
+                            self.inject_request(BitswapChannel::Compat(peer_id, req.cid), req);
+                        }
+                        CompatMessage::Response(cid, res) => {
+                            tracing::trace!("received compat response");
+                            self.inject_response(BitswapId::Compat(cid), peer_id, res);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
+        let mut exit = false;
+        while !exit {
+            exit = true;
+            while let Poll::Ready(Some(response)) = Pin::new(&mut self.db_rx).poll_next(cx) {
+                exit = false;
+                match response {
+                    DbResponse::Bitswap(channel, response) => match channel {
+                        BitswapChannel::Bitswap(channel) => {
+                            self.inner.send_response(channel, response).ok();
+                        }
+                        #[cfg(feature = "compat")]
+                        BitswapChannel::Compat(peer_id, cid) => {
+                            let compat = CompatMessage::Response(cid, response);
+                            return Poll::Ready(FromSwarm::NotifyHandler {
+                                peer_id,
+                                handler: NotifyHandler::Any,
+                                event: EitherOutput::Second(compat),
+                            });
+                        }
+                    },
+                    DbResponse::MissingBlocks(id, res) => match res {
+                        Ok(missing) => {
+                            MISSING_BLOCKS_TOTAL.inc_by(missing.len() as u64);
+                            self.query_manager
+                                .inject_response(id, Response::MissingBlocks(missing));
+                        }
+                        Err(err) => {
+                            self.query_manager.cancel(id);
+                            let event = BitswapEvent::Complete(id, Err(err));
+                            return Poll::Ready(ToSwarm::GenerateEvent(event));
+                        }
+                    },
+                }
+            }
+            while let Some(query) = self.query_manager.next() {
+                exit = false;
+                match query {
+                    QueryEvent::Request(id, req) => match req {
+                        Request::Have(peer_id, cid) => {
+                            let req = BitswapRequest {
+                                ty: RequestType::Have,
+                                cid,
+                            };
+                            let rid = self.inner.send_request(&peer_id, req);
+                            self.requests.insert(BitswapId::Bitswap(rid), id);
+                        }
+                        Request::Block(peer_id, cid) => {
+                            let req = BitswapRequest {
+                                ty: RequestType::Block,
+                                cid,
+                            };
+                            let rid = self.inner.send_request(&peer_id, req);
+                            self.requests.insert(BitswapId::Bitswap(rid), id);
+                        }
+                        Request::MissingBlocks(cid) => {
+                            self.db_tx
+                                .unbounded_send(DbRequest::MissingBlocks(id, cid))
+                                .ok();
+                        }
+                    },
+                    QueryEvent::Progress(id, missing) => {
+                        let event = BitswapEvent::Progress(id, missing);
+                        return Poll::Ready(ToSwarm::GenerateEvent(event));
+                    }
+                    QueryEvent::Complete(id, res) => {
+                        if res.is_err() {
+                            BLOCK_NOT_FOUND.inc();
+                        }
+                        let event = BitswapEvent::Complete(
+                            id,
+                            res.map_err(|cid| BlockNotFound(cid).into()),
+                        );
+                        return Poll::Ready(ToSwarm::GenerateEvent(event));
+                    }
+                }
+            }
+            while let Poll::Ready(event) = self.inner.poll(cx) {
+                exit = false;
+
+                let event = match event {
+                    ToSwarm::GenerateEvent(event) => event,
+                    other => return Poll::Ready(other.map_out(|_| unreachable!())),
+                };
+
+                match event {
+                    request_response::Event::Message { peer, message } => match message {
+                        request_response::Message::Request {
+                            request_id: _,
+                            request,
+                            channel,
+                        } => self.inject_request(BitswapChannel::Bitswap(channel), request),
+                        request_response::Message::Response {
+                            request_id,
+                            response,
+                        } => self.inject_response(BitswapId::Bitswap(request_id), peer, response),
+                    },
+                    request_response::Event::ResponseSent { .. } => {}
+                    request_response::Event::OutboundFailure {
+                        peer,
+                        request_id,
+                        error,
+                    } => {
+                        self.inject_outbound_failure(&peer, request_id, &error);
+                        #[cfg(feature = "compat")]
+                        if let OutboundFailure::UnsupportedProtocols = error {
+                            if let Some(id) = self.requests.remove(&BitswapId::Bitswap(request_id))
+                            {
+                                if let Some(info) = self.query_manager.query_info(id) {
+                                    let ty = match info.label {
+                                        "have" => RequestType::Have,
+                                        "block" => RequestType::Block,
+                                        _ => unreachable!(),
+                                    };
+                                    let request = BitswapRequest { ty, cid: info.cid };
+                                    self.requests.insert(BitswapId::Compat(info.cid), id);
+                                    tracing::trace!("adding compat peer {}", peer);
+                                    self.compat.insert(peer);
+                                    return Poll::Ready(FromSwarm::NotifyHandler {
+                                        peer_id: peer,
+                                        handler: NotifyHandler::Any,
+                                        event: EitherOutput::Second(CompatMessage::Request(
+                                            request,
+                                        )),
+                                    });
+                                }
+                            }
+                        }
+                        if let Some(id) = self.requests.remove(&BitswapId::Bitswap(request_id)) {
+                            self.query_manager
+                                .inject_response(id, Response::Have(peer, false));
+                        }
+                    }
+                    request_response::Event::InboundFailure {
+                        peer,
+                        request_id,
+                        error,
+                    } => {
+                        self.inject_inbound_failure(&peer, request_id, &error);
+                    }
+                }
+            }
+        }
+        Poll::Pending
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use async_std::task;
+    use futures::prelude::*;
+    use libipld::block::Block;
+    use libipld::cbor::DagCborCodec;
+    use libipld::ipld;
+    use libipld::ipld::Ipld;
+    use libipld::multihash::Code;
+    use libipld::store::DefaultParams;
+    use libp2p::identity;
+    use libp2p::swarm::SwarmEvent;
+    use libp2p::tcp::{self};
+    use libp2p::{noise, yamux, PeerId, Swarm};
+    use std::sync::{Arc, Mutex};
+    use std::time::Duration;
+
+    fn tracing_try_init() {
+        tracing_subscriber::fmt()
+            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+            .try_init()
+            .ok();
+    }
+
+    fn create_block(ipld: Ipld) -> Block<DefaultParams> {
+        Block::encode(DagCborCodec, Code::Blake3_256, &ipld).unwrap()
+    }
+
+    #[derive(Clone, Default)]
+    struct Store(Arc<Mutex<FnvHashMap<Cid, Vec<u8>>>>);
+
+    impl BitswapStore for Store {
+        type Params = DefaultParams;
+        fn contains(&mut self, cid: &Cid) -> Result<bool> {
+            Ok(self.0.lock().unwrap().contains_key(cid))
+        }
+        fn get(&mut self, cid: &Cid) -> Result<Option<Vec<u8>>> {
+            Ok(self.0.lock().unwrap().get(cid).cloned())
+        }
+        fn insert(&mut self, block: &Block<Self::Params>) -> Result<()> {
+            self.0
+                .lock()
+                .unwrap()
+                .insert(*block.cid(), block.data().to_vec());
+            Ok(())
+        }
+        fn missing_blocks(&mut self, cid: &Cid) -> Result<Vec<Cid>> {
+            let mut stack = vec![*cid];
+            let mut missing = vec![];
+            while let Some(cid) = stack.pop() {
+                if let Some(data) = self.get(&cid)? {
+                    let block = Block::<Self::Params>::new_unchecked(cid, data);
+                    block.references(&mut stack)?;
+                } else {
+                    missing.push(cid);
+                }
+            }
+            Ok(missing)
+        }
+    }
+
+    struct Peer {
+        peer_id: PeerId,
+        addr: Multiaddr,
+        store: Store,
+        swarm: Swarm<Bitswap<DefaultParams>>,
+    }
+
+    impl Peer {
+        fn new() -> Self {
+            // Create a public/private key pair, either random or based on a seed.
+            let id_keys = identity::Keypair::generate_ed25519();
+            let peer_id = id_keys.public().to_peer_id();
+            let store = Store::default();
+
+            let mut swarm = libp2p::SwarmBuilder::with_existing_identity(id_keys)
+                .with_async_std()
+                .with_tcp(
+                    tcp::Config::default(),
+                    noise::Config::new,
+                    yamux::Config::default,
+                )
+                .unwrap()
+                .with_behaviour(|_| Bitswap::new(BitswapConfig::new(), store.clone()))
+                .unwrap()
+                .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60)))
+                .build();
+
+            Swarm::listen_on(&mut swarm, "/ip4/127.0.0.1/tcp/0".parse().unwrap()).unwrap();
+            while swarm.next().now_or_never().is_some() {}
+            let addr = Swarm::listeners(&swarm).next().unwrap().clone();
+            Self {
+                peer_id,
+                addr,
+                store,
+                swarm,
+            }
+        }
+
+        fn add_address(&mut self, peer: &Peer) {
+            self.swarm
+                .behaviour_mut()
+                .add_address(&peer.peer_id, peer.addr.clone());
+        }
+
+        fn store(&mut self) -> impl std::ops::DerefMut<Target = FnvHashMap<Cid, Vec<u8>>> + '_ {
+            self.store.0.lock().unwrap()
+        }
+
+        fn swarm(&mut self) -> &mut Swarm<Bitswap<DefaultParams>> {
+            &mut self.swarm
+        }
+
+        fn spawn(mut self, name: &'static str) -> PeerId {
+            let peer_id = self.peer_id;
+            task::spawn(async move {
+                loop {
+                    let event = self.swarm.next().await;
+                    tracing::debug!("{}: {:?}", name, event);
+                }
+            });
+            peer_id
+        }
+
+        async fn next(&mut self) -> Option<BitswapEvent> {
+            loop {
+                let ev = self.swarm.next().await?;
+                if let SwarmEvent::Behaviour(event) = ev {
+                    return Some(event);
+                }
+            }
+        }
+    }
+
+    fn assert_progress(event: Option<BitswapEvent>, id: QueryId, missing: usize) {
+        if let Some(BitswapEvent::Progress(id2, missing2)) = event {
+            assert_eq!(id2, id);
+            assert_eq!(missing2, missing);
+        } else {
+            panic!("{:?} is not a progress event", event);
+        }
+    }
+
+    fn assert_complete_ok(event: Option<BitswapEvent>, id: QueryId) {
+        if let Some(BitswapEvent::Complete(id2, Ok(()))) = event {
+            assert_eq!(id2, id);
+        } else {
+            panic!("{:?} is not a complete event", event);
+        }
+    }
+
+    #[async_std::test]
+    async fn test_bitswap_get() {
+        tracing_try_init();
+        let mut peer1 = Peer::new();
+        let mut peer2 = Peer::new();
+        peer2.add_address(&peer1);
+
+        let block = create_block(ipld!(&b"hello world"[..]));
+        peer1.store().insert(*block.cid(), block.data().to_vec());
+        let peer1 = peer1.spawn("peer1");
+
+        let id = peer2
+            .swarm()
+            .behaviour_mut()
+            .get(*block.cid(), std::iter::once(peer1));
+
+        assert_complete_ok(peer2.next().await, id);
+    }
+
+    #[async_std::test]
+    async fn test_bitswap_cancel_get() {
+        tracing_try_init();
+        let mut peer1 = Peer::new();
+        let mut peer2 = Peer::new();
+        peer2.add_address(&peer1);
+
+        let block = create_block(ipld!(&b"hello world"[..]));
+        peer1.store().insert(*block.cid(), block.data().to_vec());
+        let peer1 = peer1.spawn("peer1");
+
+        let id = peer2
+            .swarm()
+            .behaviour_mut()
+            .get(*block.cid(), std::iter::once(peer1));
+        peer2.swarm().behaviour_mut().cancel(id);
+        let res = peer2.next().now_or_never();
+        println!("{:?}", res);
+        assert!(res.is_none());
+    }
+
+    #[async_std::test]
+    async fn test_bitswap_sync() {
+        tracing_try_init();
+        let mut peer1 = Peer::new();
+        let mut peer2 = Peer::new();
+        peer2.add_address(&peer1);
+
+        let b0 = create_block(ipld!({
+            "n": 0,
+        }));
+        let b1 = create_block(ipld!({
+            "prev": b0.cid(),
+            "n": 1,
+        }));
+        let b2 = create_block(ipld!({
+            "prev": b1.cid(),
+            "n": 2,
+        }));
+        peer1.store().insert(*b0.cid(), b0.data().to_vec());
+        peer1.store().insert(*b1.cid(), b1.data().to_vec());
+        peer1.store().insert(*b2.cid(), b2.data().to_vec());
+        let peer1 = peer1.spawn("peer1");
+
+        let id =
+            peer2
+                .swarm()
+                .behaviour_mut()
+                .sync(*b2.cid(), vec![peer1], std::iter::once(*b2.cid()));
+
+        assert_progress(peer2.next().await, id, 1);
+        assert_progress(peer2.next().await, id, 1);
+
+        assert_complete_ok(peer2.next().await, id);
+    }
+
+    #[async_std::test]
+    async fn test_bitswap_cancel_sync() {
+        tracing_try_init();
+        let mut peer1 = Peer::new();
+        let mut peer2 = Peer::new();
+        peer2.add_address(&peer1);
+
+        let block = create_block(ipld!(&b"hello world"[..]));
+        peer1.store().insert(*block.cid(), block.data().to_vec());
+        let peer1 = peer1.spawn("peer1");
+
+        let id = peer2.swarm().behaviour_mut().sync(
+            *block.cid(),
+            vec![peer1],
+            std::iter::once(*block.cid()),
+        );
+        peer2.swarm().behaviour_mut().cancel(id);
+        let res = peer2.next().now_or_never();
+        println!("{:?}", res);
+        assert!(res.is_none());
+    }
+
+    #[cfg(feature = "compat")]
+    #[async_std::test]
+    async fn compat_test() {
+        tracing_try_init();
+        let cid: Cid = "QmP8njGuyiw9cjkhwHD9nZhyBTHufXFanAvZgcy9xYoWiB"
+            .parse()
+            .unwrap();
+        let peer_id: PeerId = "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"
+            .parse()
+            .unwrap();
+        let multiaddr: Multiaddr = "/ip4/104.131.131.82/tcp/4001".parse().unwrap();
+
+        let mut peer = Peer::new();
+        peer.swarm()
+            .behaviour_mut()
+            .add_address(&peer_id, multiaddr);
+        let id = peer
+            .swarm()
+            .behaviour_mut()
+            .get(cid, std::iter::once(peer_id));
+        assert_complete_ok(peer.next().await, id);
+    }
+}
diff --git a/ext/libp2p-bitswap/src/compat/bitswap_pb.proto b/ext/libp2p-bitswap/src/compat/bitswap_pb.proto
new file mode 100644
index 000000000..9aae7866e
--- /dev/null
+++ b/ext/libp2p-bitswap/src/compat/bitswap_pb.proto
@@ -0,0 +1,44 @@
+syntax = "proto3";
+
+package bitswap_pb;
+
+message Message {
+
+  message Wantlist {
+    enum WantType {
+      Block = 0;
+      Have = 1;
+    }
+
+    message Entry {
+			bytes block = 1;		// the block cid (cidV0 in bitswap 1.0.0, cidV1 in bitswap 1.1.0)
+			int32 priority = 2;	// the priority (normalized). default to 1
+			bool cancel = 3;		// whether this revokes an entry
+      WantType wantType = 4; // Note: defaults to enum 0, ie Block
+      bool sendDontHave = 5; // Note: defaults to false
+		}
+
+    repeated Entry entries = 1;	// a list of wantlist entries
+    bool full = 2;							// whether this is the full wantlist. default to false
+  }
+
+  message Block {
+    bytes prefix = 1;		// CID prefix (cid version, multicodec and multihash prefix (type + length)
+    bytes data = 2;
+  }
+
+  enum BlockPresenceType {
+    Have = 0;
+    DontHave = 1;
+  }
+  message BlockPresence {
+    bytes cid = 1;
+    BlockPresenceType type = 2;
+  }
+
+  Wantlist wantlist = 1;
+  repeated bytes blocks = 2;		// used to send Blocks in bitswap 1.0.0
+  repeated Block payload = 3;		// used to send Blocks in bitswap 1.1.0
+  repeated BlockPresence blockPresences = 4;
+  int32 pendingBytes = 5;
+}
diff --git a/ext/libp2p-bitswap/src/compat/message.rs b/ext/libp2p-bitswap/src/compat/message.rs
new file mode 100644
index 000000000..0c3f1f944
--- /dev/null
+++ b/ext/libp2p-bitswap/src/compat/message.rs
@@ -0,0 +1,107 @@
+use crate::compat::other;
+use crate::compat::prefix::Prefix;
+use crate::protocol::{BitswapRequest, BitswapResponse, RequestType};
+use libipld::Cid;
+use prost::Message;
+use std::convert::TryFrom;
+use std::io;
+
+mod bitswap_pb {
+    include!(concat!(env!("OUT_DIR"), "/bitswap_pb.rs"));
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum CompatMessage {
+    Request(BitswapRequest),
+    Response(Cid, BitswapResponse),
+}
+
+impl CompatMessage {
+    pub fn to_bytes(&self) -> io::Result<Vec<u8>> {
+        let mut msg = bitswap_pb::Message::default();
+        match self {
+            CompatMessage::Request(BitswapRequest { ty, cid }) => {
+                let mut wantlist = bitswap_pb::message::Wantlist::default();
+                let entry = bitswap_pb::message::wantlist::Entry {
+                    block: cid.to_bytes(),
+                    want_type: match ty {
+                        RequestType::Have => bitswap_pb::message::wantlist::WantType::Have,
+                        RequestType::Block => bitswap_pb::message::wantlist::WantType::Block,
+                    } as _,
+                    send_dont_have: true,
+                    cancel: false,
+                    priority: 1,
+                };
+                wantlist.entries.push(entry);
+                msg.wantlist = Some(wantlist);
+            }
+            CompatMessage::Response(cid, BitswapResponse::Have(have)) => {
+                let block_presence = bitswap_pb::message::BlockPresence {
+                    cid: cid.to_bytes(),
+                    r#type: if *have {
+                        bitswap_pb::message::BlockPresenceType::Have
+                    } else {
+                        bitswap_pb::message::BlockPresenceType::DontHave
+                    } as _,
+                };
+                msg.block_presences.push(block_presence);
+            }
+            CompatMessage::Response(cid, BitswapResponse::Block(bytes)) => {
+                let payload = bitswap_pb::message::Block {
+                    prefix: Prefix::from(cid).to_bytes(),
+                    data: bytes.to_vec(),
+                };
+                msg.payload.push(payload);
+            }
+        }
+        let mut bytes = Vec::with_capacity(msg.encoded_len());
+        msg.encode(&mut bytes).map_err(other)?;
+        Ok(bytes)
+    }
+
+    pub fn from_bytes(bytes: &[u8]) -> io::Result<Vec<Self>> {
+        let msg = bitswap_pb::Message::decode(bytes)?;
+        let mut parts = vec![];
+        for entry in msg.wantlist.unwrap_or_default().entries {
+            if !entry.send_dont_have {
+                tracing::error!("message hasn't set `send_dont_have`: skipping");
+                continue;
+            }
+            let cid = Cid::try_from(entry.block).map_err(other)?;
+            let ty = match entry.want_type {
+                ty if bitswap_pb::message::wantlist::WantType::Have as i32 == ty => {
+                    RequestType::Have
+                }
+                ty if bitswap_pb::message::wantlist::WantType::Block as i32 == ty => {
+                    RequestType::Block
+                }
+                _ => {
+                    tracing::error!("invalid request type: skipping");
+                    continue;
+                }
+            };
+            parts.push(CompatMessage::Request(BitswapRequest { ty, cid }));
+        }
+        for payload in msg.payload {
+            let prefix = Prefix::new(&payload.prefix)?;
+            let cid = prefix.to_cid(&payload.data)?;
+            parts.push(CompatMessage::Response(
+                cid,
+                BitswapResponse::Block(payload.data.to_vec()),
+            ));
+        }
+        for presence in msg.block_presences {
+            let cid = Cid::try_from(presence.cid).map_err(other)?;
+            let have = match presence.r#type {
+                ty if bitswap_pb::message::BlockPresenceType::Have as i32 == ty => true,
+                ty if bitswap_pb::message::BlockPresenceType::DontHave as i32 == ty => false,
+                _ => {
+                    tracing::error!("invalid block presence type: skipping");
+                    continue;
+                }
+            };
+            parts.push(CompatMessage::Response(cid, BitswapResponse::Have(have)));
+        }
+        Ok(parts)
+    }
+}
diff --git a/ext/libp2p-bitswap/src/compat/mod.rs b/ext/libp2p-bitswap/src/compat/mod.rs
new file mode 100644
index 000000000..b3400c2a5
--- /dev/null
+++ b/ext/libp2p-bitswap/src/compat/mod.rs
@@ -0,0 +1,10 @@
+mod message;
+mod prefix;
+mod protocol;
+
+pub use message::CompatMessage;
+pub use protocol::{CompatProtocol, InboundMessage};
+
+fn other<E: std::error::Error + Send + Sync + 'static>(e: E) -> std::io::Error {
+    std::io::Error::new(std::io::ErrorKind::Other, e)
+}
diff --git a/ext/libp2p-bitswap/src/compat/prefix.rs b/ext/libp2p-bitswap/src/compat/prefix.rs
new file mode 100644
index 000000000..aeaab7d51
--- /dev/null
+++ b/ext/libp2p-bitswap/src/compat/prefix.rs
@@ -0,0 +1,71 @@
+use crate::compat::other;
+use libipld::cid::{Cid, Version};
+use libipld::multihash::{Code, MultihashDigest};
+use std::convert::TryFrom;
+use std::io::Result;
+use unsigned_varint::{decode as varint_decode, encode as varint_encode};
+
+/// Prefix represents all metadata of a CID, without the actual content.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct Prefix {
+    /// The version of CID.
+    pub version: Version,
+    /// The codec of CID.
+    pub codec: u64,
+    /// The multihash type of CID.
+    pub mh_type: u64,
+    /// The multihash length of CID.
+    pub mh_len: usize,
+}
+
+impl Prefix {
+    /// Create a new prefix from encoded bytes.
+    pub fn new(data: &[u8]) -> Result<Prefix> {
+        let (raw_version, remain) = varint_decode::u64(data).map_err(other)?;
+        let version = Version::try_from(raw_version).map_err(other)?;
+        let (codec, remain) = varint_decode::u64(remain).map_err(other)?;
+        let (mh_type, remain) = varint_decode::u64(remain).map_err(other)?;
+        let (mh_len, _remain) = varint_decode::usize(remain).map_err(other)?;
+        Ok(Prefix {
+            version,
+            codec,
+            mh_type,
+            mh_len,
+        })
+    }
+
+    /// Convert the prefix to encoded bytes.
+    pub fn to_bytes(&self) -> Vec<u8> {
+        let mut res = Vec::with_capacity(4);
+        let mut buf = varint_encode::u64_buffer();
+        let version = varint_encode::u64(self.version.into(), &mut buf);
+        res.extend_from_slice(version);
+        let mut buf = varint_encode::u64_buffer();
+        let codec = varint_encode::u64(self.codec, &mut buf);
+        res.extend_from_slice(codec);
+        let mut buf = varint_encode::u64_buffer();
+        let mh_type = varint_encode::u64(self.mh_type, &mut buf);
+        res.extend_from_slice(mh_type);
+        let mut buf = varint_encode::u64_buffer();
+        let mh_len = varint_encode::u64(self.mh_len as u64, &mut buf);
+        res.extend_from_slice(mh_len);
+        res
+    }
+
+    /// Create a CID out of the prefix and some data that will be hashed
+    pub fn to_cid(&self, data: &[u8]) -> Result<Cid> {
+        let mh = Code::try_from(self.mh_type).map_err(other)?.digest(data);
+        Cid::new(self.version, self.codec, mh).map_err(other)
+    }
+}
+
+impl From<&Cid> for Prefix {
+    fn from(cid: &Cid) -> Self {
+        Self {
+            version: cid.version(),
+            codec: cid.codec(),
+            mh_type: cid.hash().code(),
+            mh_len: cid.hash().digest().len(),
+        }
+    }
+}
diff --git a/ext/libp2p-bitswap/src/compat/protocol.rs b/ext/libp2p-bitswap/src/compat/protocol.rs
new file mode 100644
index 000000000..557e220b5
--- /dev/null
+++ b/ext/libp2p-bitswap/src/compat/protocol.rs
@@ -0,0 +1,116 @@
+use crate::compat::{other, CompatMessage};
+use futures::future::BoxFuture;
+use futures::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
+use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo};
+use std::{io, iter};
+
+// Undocumented, but according to JS we our messages have a max size of 512*1024
+// https://github.com/ipfs/js-ipfs-bitswap/blob/d8f80408aadab94c962f6b88f343eb9f39fa0fcc/src/decision-engine/index.js#L16
+const MAX_BUF_SIZE: usize = 524_288;
+
+#[derive(Clone, Debug, Default)]
+pub struct CompatProtocol;
+
+impl UpgradeInfo for CompatProtocol {
+    type Info = &'static [u8];
+    type InfoIter = iter::Once<Self::Info>;
+
+    fn protocol_info(&self) -> Self::InfoIter {
+        iter::once(b"/ipfs/bitswap/1.2.0")
+    }
+}
+
+impl<TSocket> InboundUpgrade<TSocket> for CompatProtocol
+where
+    TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static,
+{
+    type Output = InboundMessage;
+    type Error = io::Error;
+    type Future = BoxFuture<'static, Result<Self::Output, Self::Error>>;
+
+    fn upgrade_inbound(self, mut socket: TSocket, _info: Self::Info) -> Self::Future {
+        Box::pin(async move {
+            let packet = upgrade::read_length_prefixed(&mut socket, MAX_BUF_SIZE)
+                .await
+                .map_err(other)?;
+            socket.close().await?;
+            let message = CompatMessage::from_bytes(&packet)?;
+            Ok(InboundMessage(message))
+        })
+    }
+}
+
+impl UpgradeInfo for CompatMessage {
+    type Info = &'static [u8];
+    type InfoIter = iter::Once<Self::Info>;
+
+    fn protocol_info(&self) -> Self::InfoIter {
+        iter::once(b"/ipfs/bitswap/1.2.0")
+    }
+}
+
+impl<TSocket> OutboundUpgrade<TSocket> for CompatMessage
+where
+    TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static,
+{
+    type Output = ();
+    type Error = io::Error;
+    type Future = BoxFuture<'static, Result<Self::Output, Self::Error>>;
+
+    fn upgrade_outbound(self, mut socket: TSocket, _info: Self::Info) -> Self::Future {
+        Box::pin(async move {
+            let bytes = self.to_bytes()?;
+            upgrade::write_length_prefixed(&mut socket, bytes).await?;
+            socket.close().await?;
+            Ok(())
+        })
+    }
+}
+
+#[derive(Debug)]
+pub struct InboundMessage(pub Vec<CompatMessage>);
+
+impl From<()> for InboundMessage {
+    fn from(_: ()) -> Self {
+        Self(Default::default())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::protocol::{BitswapRequest, RequestType};
+    use async_std::net::{TcpListener, TcpStream};
+    use futures::prelude::*;
+    use libipld::Cid;
+    use libp2p::core::upgrade;
+
+    #[async_std::test]
+    async fn test_upgrade() {
+        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
+        let listener_addr = listener.local_addr().unwrap();
+
+        let server = async move {
+            let incoming = listener.incoming().into_future().await.0.unwrap().unwrap();
+            upgrade::apply_inbound(incoming, CompatProtocol)
+                .await
+                .unwrap();
+        };
+
+        let client = async move {
+            let stream = TcpStream::connect(&listener_addr).await.unwrap();
+            upgrade::apply_outbound(
+                stream,
+                CompatMessage::Request(BitswapRequest {
+                    ty: RequestType::Have,
+                    cid: Cid::default(),
+                }),
+                upgrade::Version::V1,
+            )
+            .await
+            .unwrap();
+        };
+
+        future::select(Box::pin(server), Box::pin(client)).await;
+    }
+}
diff --git a/ext/libp2p-bitswap/src/lib.rs b/ext/libp2p-bitswap/src/lib.rs
new file mode 100644
index 000000000..4c10d3e81
--- /dev/null
+++ b/ext/libp2p-bitswap/src/lib.rs
@@ -0,0 +1,15 @@
+//! Bitswap protocol implementation
+#![deny(missing_docs)]
+#![deny(warnings)]
+#![allow(clippy::derive_partial_eq_without_eq)]
+
+mod behaviour;
+#[cfg(feature = "compat")]
+mod compat;
+mod protocol;
+mod query;
+mod stats;
+
+pub use crate::behaviour::{Bitswap, BitswapConfig, BitswapEvent, BitswapStore, Channel};
+pub use crate::protocol::{BitswapRequest, BitswapResponse};
+pub use crate::query::QueryId;
diff --git a/ext/libp2p-bitswap/src/protocol.rs b/ext/libp2p-bitswap/src/protocol.rs
new file mode 100644
index 000000000..5209bcc9a
--- /dev/null
+++ b/ext/libp2p-bitswap/src/protocol.rs
@@ -0,0 +1,282 @@
+use async_trait::async_trait;
+use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
+use libipld::cid::Cid;
+use libipld::store::StoreParams;
+use libp2p::request_response;
+use std::convert::TryFrom;
+use std::io::{self, Write};
+use std::marker::PhantomData;
+use thiserror::Error;
+use unsigned_varint::{aio, io::ReadError};
+
+// version codec hash size (u64 varint is max 10 bytes) + digest
+const MAX_CID_SIZE: usize = 4 * 10 + 64;
+
+#[derive(Clone, Debug)]
+pub struct BitswapProtocol;
+
+impl AsRef<str> for BitswapProtocol {
+    fn as_ref(&self) -> &str {
+        "/ipfs-embed/bitswap/1.0.0"
+    }
+}
+
+#[derive(Clone)]
+pub struct BitswapCodec<P> {
+    _marker: PhantomData<P>,
+    buffer: Vec<u8>,
+}
+
+impl<P: StoreParams> Default for BitswapCodec<P> {
+    fn default() -> Self {
+        let capacity = usize::max(P::MAX_BLOCK_SIZE, MAX_CID_SIZE) + 1;
+        debug_assert!(capacity <= u32::MAX as usize);
+        Self {
+            _marker: PhantomData,
+            buffer: Vec::with_capacity(capacity),
+        }
+    }
+}
+
+#[async_trait]
+impl<P: StoreParams> request_response::Codec for BitswapCodec<P> {
+    type Protocol = BitswapProtocol;
+    type Request = BitswapRequest;
+    type Response = BitswapResponse;
+
+    async fn read_request<T>(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result<Self::Request>
+    where
+        T: AsyncRead + Send + Unpin,
+    {
+        let msg_len = u32_to_usize(aio::read_u32(&mut *io).await.map_err(|e| match e {
+            ReadError::Io(e) => e,
+            err => other(err),
+        })?);
+        if msg_len > MAX_CID_SIZE + 1 {
+            return Err(invalid_data(MessageTooLarge(msg_len)));
+        }
+        self.buffer.resize(msg_len, 0);
+        io.read_exact(&mut self.buffer).await?;
+        let request = BitswapRequest::from_bytes(&self.buffer).map_err(invalid_data)?;
+        Ok(request)
+    }
+
+    async fn read_response<T>(
+        &mut self,
+        _: &Self::Protocol,
+        io: &mut T,
+    ) -> io::Result<Self::Response>
+    where
+        T: AsyncRead + Send + Unpin,
+    {
+        let msg_len = u32_to_usize(aio::read_u32(&mut *io).await.map_err(|e| match e {
+            ReadError::Io(e) => e,
+            err => other(err),
+        })?);
+        if msg_len > P::MAX_BLOCK_SIZE + 1 {
+            return Err(invalid_data(MessageTooLarge(msg_len)));
+        }
+        self.buffer.resize(msg_len, 0);
+        io.read_exact(&mut self.buffer).await?;
+        let response = BitswapResponse::from_bytes(&self.buffer).map_err(invalid_data)?;
+        Ok(response)
+    }
+
+    async fn write_request<T>(
+        &mut self,
+        _: &Self::Protocol,
+        io: &mut T,
+        req: Self::Request,
+    ) -> io::Result<()>
+    where
+        T: AsyncWrite + Send + Unpin,
+    {
+        self.buffer.clear();
+        req.write_to(&mut self.buffer)?;
+        if self.buffer.len() > MAX_CID_SIZE + 1 {
+            return Err(invalid_data(MessageTooLarge(self.buffer.len())));
+        }
+        let mut buf = unsigned_varint::encode::u32_buffer();
+        let msg_len = unsigned_varint::encode::u32(self.buffer.len() as u32, &mut buf);
+        io.write_all(msg_len).await?;
+        io.write_all(&self.buffer).await?;
+        Ok(())
+    }
+
+    async fn write_response<T>(
+        &mut self,
+        _: &Self::Protocol,
+        io: &mut T,
+        res: Self::Response,
+    ) -> io::Result<()>
+    where
+        T: AsyncWrite + Send + Unpin,
+    {
+        self.buffer.clear();
+        res.write_to(&mut self.buffer)?;
+        if self.buffer.len() > P::MAX_BLOCK_SIZE + 1 {
+            return Err(invalid_data(MessageTooLarge(self.buffer.len())));
+        }
+        let mut buf = unsigned_varint::encode::u32_buffer();
+        let msg_len = unsigned_varint::encode::u32(self.buffer.len() as u32, &mut buf);
+        io.write_all(msg_len).await?;
+        io.write_all(&self.buffer).await?;
+        Ok(())
+    }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum RequestType {
+    Have,
+    Block,
+}
+
+/// A request sent to another peer.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct BitswapRequest {
+    /// type of request: have or block
+    pub ty: RequestType,
+    /// CID the request is for
+    pub cid: Cid,
+}
+
+impl BitswapRequest {
+    /// write binary representation of the request
+    pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
+        match self {
+            BitswapRequest {
+                ty: RequestType::Have,
+                cid,
+            } => {
+                w.write_all(&[0])?;
+                cid.write_bytes(&mut *w).map_err(other)?;
+            }
+            BitswapRequest {
+                ty: RequestType::Block,
+                cid,
+            } => {
+                w.write_all(&[1])?;
+                cid.write_bytes(&mut *w).map_err(other)?;
+            }
+        }
+        Ok(())
+    }
+
+    /// read back binary representation of the request
+    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
+        let ty = match bytes[0] {
+            0 => RequestType::Have,
+            1 => RequestType::Block,
+            c => return Err(invalid_data(UnknownMessageType(c))),
+        };
+        let cid = Cid::try_from(&bytes[1..]).map_err(invalid_data)?;
+        Ok(Self { ty, cid })
+    }
+}
+
+/// Response to a [BitswapRequest]
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum BitswapResponse {
+    /// block presence
+    Have(bool),
+    /// block bytes
+    Block(Vec<u8>),
+}
+
+impl BitswapResponse {
+    /// write binary representation of the request
+    pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
+        match self {
+            BitswapResponse::Have(have) => {
+                if *have {
+                    w.write_all(&[0])?;
+                } else {
+                    w.write_all(&[2])?;
+                }
+            }
+            BitswapResponse::Block(data) => {
+                w.write_all(&[1])?;
+                w.write_all(data)?;
+            }
+        };
+        Ok(())
+    }
+
+    /// read back binary representation of the request
+    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
+        let res = match bytes[0] {
+            0 | 2 => BitswapResponse::Have(bytes[0] == 0),
+            1 => BitswapResponse::Block(bytes[1..].to_vec()),
+            c => return Err(invalid_data(UnknownMessageType(c))),
+        };
+        Ok(res)
+    }
+}
+
+fn invalid_data<E: std::error::Error + Send + Sync + 'static>(e: E) -> io::Error {
+    io::Error::new(io::ErrorKind::InvalidData, e)
+}
+
+fn other<E: std::error::Error + Send + Sync + 'static>(e: E) -> io::Error {
+    io::Error::new(io::ErrorKind::Other, e)
+}
+
+#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
+fn u32_to_usize(n: u32) -> usize {
+    n as usize
+}
+
+#[derive(Debug, Error)]
+#[error("unknown message type {0}")]
+pub struct UnknownMessageType(u8);
+
+#[derive(Debug, Error)]
+#[error("message too large {0}")]
+pub struct MessageTooLarge(usize);
+
+#[cfg(test)]
+pub(crate) mod tests {
+    use super::*;
+    use libipld::multihash::Code;
+    use multihash::MultihashDigest;
+
+    pub fn create_cid(bytes: &[u8]) -> Cid {
+        let digest = Code::Blake3_256.digest(bytes);
+        Cid::new_v1(0x55, digest)
+    }
+
+    #[test]
+    fn test_request_encode_decode() {
+        let requests = [
+            BitswapRequest {
+                ty: RequestType::Have,
+                cid: create_cid(&b"have_request"[..]),
+            },
+            BitswapRequest {
+                ty: RequestType::Block,
+                cid: create_cid(&b"block_request"[..]),
+            },
+        ];
+        let mut buf = Vec::with_capacity(MAX_CID_SIZE + 1);
+        for request in &requests {
+            buf.clear();
+            request.write_to(&mut buf).unwrap();
+            assert_eq!(&BitswapRequest::from_bytes(&buf).unwrap(), request);
+        }
+    }
+
+    #[test]
+    fn test_response_encode_decode() {
+        let responses = [
+            BitswapResponse::Have(true),
+            BitswapResponse::Have(false),
+            BitswapResponse::Block(b"block_response".to_vec()),
+        ];
+        let mut buf = Vec::with_capacity(13 + 1);
+        for response in &responses {
+            buf.clear();
+            response.write_to(&mut buf).unwrap();
+            assert_eq!(&BitswapResponse::from_bytes(&buf).unwrap(), response);
+        }
+    }
+}
diff --git a/ext/libp2p-bitswap/src/query.rs b/ext/libp2p-bitswap/src/query.rs
new file mode 100644
index 000000000..1ed276d61
--- /dev/null
+++ b/ext/libp2p-bitswap/src/query.rs
@@ -0,0 +1,660 @@
+use crate::stats::{REQUESTS_TOTAL, REQUEST_DURATION_SECONDS};
+use fnv::{FnvHashMap, FnvHashSet};
+use libipld::Cid;
+use libp2p::PeerId;
+use prometheus::HistogramTimer;
+use std::collections::VecDeque;
+
+/// Query id.
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct QueryId(u64);
+
+impl std::fmt::Display for QueryId {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+/// Request.
+#[derive(Debug, Eq, PartialEq)]
+pub enum Request {
+    /// Have query.
+    Have(PeerId, Cid),
+    /// Block query.
+    Block(PeerId, Cid),
+    /// Missing blocks query.
+    MissingBlocks(Cid),
+}
+
+impl std::fmt::Display for Request {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self {
+            Self::Have(_, _) => write!(f, "have"),
+            Self::Block(_, _) => write!(f, "block"),
+            Self::MissingBlocks(_) => write!(f, "missing-blocks"),
+        }
+    }
+}
+
+/// Response.
+#[derive(Debug)]
+pub enum Response {
+    /// Have query.
+    Have(PeerId, bool),
+    /// Block query.
+    Block(PeerId, bool),
+    /// Missing blocks query.
+    MissingBlocks(Vec<Cid>),
+}
+
+impl std::fmt::Display for Response {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self {
+            Self::Have(_, have) => write!(f, "have {}", have),
+            Self::Block(_, block) => write!(f, "block {}", block),
+            Self::MissingBlocks(missing) => write!(f, "missing-blocks {}", missing.len()),
+        }
+    }
+}
+
+/// Event emitted by a query.
+#[derive(Debug)]
+pub enum QueryEvent {
+    /// A subquery to run.
+    Request(QueryId, Request),
+    /// A progress event.
+    Progress(QueryId, usize),
+    /// Complete event.
+    Complete(QueryId, Result<(), Cid>),
+}
+
+#[derive(Debug)]
+pub struct Header {
+    /// Query id.
+    pub id: QueryId,
+    /// Root query id.
+    pub root: QueryId,
+    /// Parent.
+    pub parent: Option<QueryId>,
+    /// Cid.
+    pub cid: Cid,
+    /// Timer.
+    #[allow(dead_code)]
+    pub timer: HistogramTimer,
+    /// Type.
+    pub label: &'static str,
+}
+
+impl Drop for Header {
+    fn drop(&mut self) {
+        REQUESTS_TOTAL.with_label_values(&[self.label]).inc();
+    }
+}
+
+/// Query.
+#[derive(Debug)]
+struct Query {
+    /// Header.
+    hdr: Header,
+    /// State.
+    state: State,
+}
+
+#[derive(Debug)]
+enum State {
+    None,
+    Get(GetState),
+    Sync(SyncState),
+}
+
+#[derive(Debug, Default)]
+struct GetState {
+    have: FnvHashSet<QueryId>,
+    block: Option<QueryId>,
+    providers: Vec<PeerId>,
+}
+
+#[derive(Debug, Default)]
+struct SyncState {
+    missing: FnvHashSet<QueryId>,
+    children: FnvHashSet<QueryId>,
+    providers: Vec<PeerId>,
+}
+
+enum Transition<S, C> {
+    Next(S),
+    Complete(C),
+}
+
+#[derive(Default)]
+pub struct QueryManager {
+    id_counter: u64,
+    queries: FnvHashMap<QueryId, Query>,
+    events: VecDeque<QueryEvent>,
+}
+
+impl QueryManager {
+    /// Start a new subquery.
+    fn start_query(
+        &mut self,
+        root: QueryId,
+        parent: Option<QueryId>,
+        cid: Cid,
+        req: Request,
+        label: &'static str,
+    ) -> QueryId {
+        let timer = REQUEST_DURATION_SECONDS
+            .with_label_values(&[label])
+            .start_timer();
+        let id = QueryId(self.id_counter);
+        self.id_counter += 1;
+        let query = Query {
+            hdr: Header {
+                id,
+                root,
+                parent,
+                cid,
+                timer,
+                label,
+            },
+            state: State::None,
+        };
+        self.queries.insert(id, query);
+        tracing::trace!("{} {} {}", root, id, req);
+        self.events.push_back(QueryEvent::Request(id, req));
+        id
+    }
+
+    /// Starts a new have query to ask a peer if it has a block.
+    fn have(&mut self, root: QueryId, parent: QueryId, peer_id: PeerId, cid: Cid) -> QueryId {
+        self.start_query(root, Some(parent), cid, Request::Have(peer_id, cid), "have")
+    }
+
+    /// Starts a new block query to request a block from a peer.
+    fn block(&mut self, root: QueryId, parent: QueryId, peer_id: PeerId, cid: Cid) -> QueryId {
+        self.start_query(
+            root,
+            Some(parent),
+            cid,
+            Request::Block(peer_id, cid),
+            "block",
+        )
+    }
+
+    /// Starts a query to determine the missing blocks of a dag.
+    fn missing_blocks(&mut self, parent: QueryId, cid: Cid) -> QueryId {
+        self.start_query(
+            parent,
+            Some(parent),
+            cid,
+            Request::MissingBlocks(cid),
+            "missing-blocks",
+        )
+    }
+
+    /// Starts a query to locate and retrieve a block. Panics if no providers are supplied.
+    pub fn get(
+        &mut self,
+        parent: Option<QueryId>,
+        cid: Cid,
+        providers: impl Iterator<Item = PeerId>,
+    ) -> QueryId {
+        let timer = REQUEST_DURATION_SECONDS
+            .with_label_values(&["get"])
+            .start_timer();
+        let id = QueryId(self.id_counter);
+        self.id_counter += 1;
+        let root = parent.unwrap_or(id);
+        tracing::trace!("{} {} get", root, id);
+        let mut state = GetState::default();
+        for peer in providers {
+            if state.block.is_none() {
+                state.block = Some(self.block(root, id, peer, cid));
+            } else {
+                state.have.insert(self.have(root, id, peer, cid));
+            }
+        }
+        assert!(state.block.is_some());
+        let query = Query {
+            hdr: Header {
+                id,
+                root,
+                parent,
+                cid,
+                timer,
+                label: "get",
+            },
+            state: State::Get(state),
+        };
+        self.queries.insert(id, query);
+        id
+    }
+
+    /// Starts a query to recursively retrieve a dag. The missing blocks are the first
+    /// blocks that need to be retrieved.
+    pub fn sync(
+        &mut self,
+        cid: Cid,
+        providers: Vec<PeerId>,
+        missing: impl Iterator<Item = Cid>,
+    ) -> QueryId {
+        let timer = REQUEST_DURATION_SECONDS
+            .with_label_values(&["sync"])
+            .start_timer();
+        let id = QueryId(self.id_counter);
+        self.id_counter += 1;
+        tracing::trace!("{} {} sync", id, id);
+        let mut state = SyncState::default();
+        for cid in missing {
+            state
+                .missing
+                .insert(self.get(Some(id), cid, providers.iter().copied()));
+        }
+        if state.missing.is_empty() {
+            state.children.insert(self.missing_blocks(id, cid));
+        }
+        state.providers = providers;
+        let query = Query {
+            hdr: Header {
+                id,
+                root: id,
+                parent: None,
+                cid,
+                timer,
+                label: "sync",
+            },
+            state: State::Sync(state),
+        };
+        self.queries.insert(id, query);
+        id
+    }
+
+    /// Cancels an in progress query.
+    pub fn cancel(&mut self, root: QueryId) -> bool {
+        let query = if let Some(query) = self.queries.remove(&root) {
+            query
+        } else {
+            return false;
+        };
+        let queries = &self.queries;
+        self.events.retain(|event| {
+            let (id, req) = match event {
+                QueryEvent::Request(id, req) => (id, req),
+                QueryEvent::Progress(id, _) => return *id != root,
+                QueryEvent::Complete(_, _) => return true,
+            };
+            if queries.get(id).map(|q| q.hdr.root) != Some(root) {
+                return true;
+            }
+            tracing::trace!("{} {} {} cancel", root, id, req);
+            false
+        });
+        match query.state {
+            State::Get(_) => {
+                tracing::trace!("{} {} get cancel", root, root);
+                true
+            }
+            State::Sync(state) => {
+                for id in state.missing {
+                    tracing::trace!("{} {} get cancel", root, id);
+                    self.queries.remove(&id);
+                }
+                tracing::trace!("{} {} sync cancel", root, root);
+                true
+            }
+            State::None => {
+                self.queries.insert(root, query);
+                false
+            }
+        }
+    }
+
+    /// Advances a get query state machine using a transition function.
+    fn get_query<F>(&mut self, id: QueryId, f: F)
+    where
+        F: FnOnce(&mut Self, &Header, GetState) -> Transition<GetState, Result<(), Cid>>,
+    {
+        if let Some(mut parent) = self.queries.remove(&id) {
+            let state = if let State::Get(state) = parent.state {
+                state
+            } else {
+                return;
+            };
+            match f(self, &parent.hdr, state) {
+                Transition::Next(state) => {
+                    parent.state = State::Get(state);
+                    self.queries.insert(id, parent);
+                }
+                Transition::Complete(res) => {
+                    match res {
+                        Ok(()) => tracing::trace!("{} {} get ok", parent.hdr.root, parent.hdr.id),
+                        Err(_) => tracing::trace!("{} {} get err", parent.hdr.root, parent.hdr.id),
+                    }
+                    self.recv_get(parent.hdr, res);
+                }
+            }
+        }
+    }
+
+    /// Advances a sync query state machine using a transition function.
+    fn sync_query<F>(&mut self, id: QueryId, f: F)
+    where
+        F: FnOnce(&mut Self, &Header, SyncState) -> Transition<SyncState, Result<(), Cid>>,
+    {
+        if let Some(mut parent) = self.queries.remove(&id) {
+            let state = if let State::Sync(state) = parent.state {
+                state
+            } else {
+                return;
+            };
+            match f(self, &parent.hdr, state) {
+                Transition::Next(state) => {
+                    parent.state = State::Sync(state);
+                    self.queries.insert(id, parent);
+                }
+                Transition::Complete(res) => {
+                    if res.is_ok() {
+                        tracing::trace!("{} {} sync ok", parent.hdr.root, parent.hdr.id);
+                    } else {
+                        tracing::trace!("{} {} sync err", parent.hdr.root, parent.hdr.id);
+                    }
+                    self.recv_sync(parent.hdr, res);
+                }
+            }
+        }
+    }
+
+    /// Processes the response of a have query.
+    ///
+    /// Marks the in progress query as complete and updates the set of peers that have
+    /// a block. If there isn't an in progress block query a new block query will be
+    /// started. If no block query can be started either a provider query is started or
+    /// the get query is marked as complete with a block-not-found error.
+    fn recv_have(&mut self, query: Header, peer_id: PeerId, have: bool) {
+        self.get_query(query.parent.unwrap(), |mgr, parent, mut state| {
+            state.have.remove(&query.id);
+            if state.block == Some(query.id) {
+                state.block = None;
+            }
+            if have {
+                state.providers.push(peer_id);
+            }
+            if state.block.is_none() && !state.providers.is_empty() {
+                state.block = Some(mgr.block(
+                    parent.root,
+                    parent.id,
+                    state.providers.pop().unwrap(),
+                    query.cid,
+                ));
+            }
+            if state.have.is_empty() && state.block.is_none() && state.providers.is_empty() {
+                if state.providers.is_empty() {
+                    return Transition::Complete(Err(query.cid));
+                } else {
+                    return Transition::Complete(Ok(()));
+                }
+            }
+            Transition::Next(state)
+        });
+    }
+
+    /// Processes the response of a block query.
+    ///
+    /// Either completes the get query or processes it like a have query response.
+    fn recv_block(&mut self, query: Header, peer_id: PeerId, block: bool) {
+        if block {
+            self.get_query(query.parent.unwrap(), |_mgr, _parent, mut state| {
+                state.providers.push(peer_id);
+                Transition::Complete(Ok(()))
+            });
+        } else {
+            self.recv_have(query, peer_id, block);
+        }
+    }
+
+    /// Processes the response of a missing blocks query.
+    ///
+    /// Starts a get query for each missing block. If there are no in progress queries
+    /// the sync query is marked as complete.
+    fn recv_missing_blocks(&mut self, query: Header, missing: Vec<Cid>) {
+        let mut num_missing = 0;
+        let num_missing_ref = &mut num_missing;
+        self.sync_query(query.parent.unwrap(), |mgr, parent, mut state| {
+            state.children.remove(&query.id);
+            for cid in missing {
+                state.missing.insert(mgr.get(
+                    Some(parent.root),
+                    cid,
+                    state.providers.iter().copied(),
+                ));
+            }
+            *num_missing_ref = state.missing.len();
+            if state.missing.is_empty() && state.children.is_empty() {
+                Transition::Complete(Ok(()))
+            } else {
+                Transition::Next(state)
+            }
+        });
+        if num_missing != 0 {
+            self.events
+                .push_back(QueryEvent::Progress(query.root, num_missing));
+        }
+    }
+
+    /// Processes the response of a get query.
+    ///
+    /// If it is part of a sync query a new missing blocks query is started. Otherwise
+    /// the get query emits a `complete` event.
+    fn recv_get(&mut self, query: Header, res: Result<(), Cid>) {
+        if let Some(id) = query.parent {
+            self.sync_query(id, |mgr, parent, mut state| {
+                state.missing.remove(&query.id);
+                if res.is_err() {
+                    Transition::Complete(res)
+                } else {
+                    state
+                        .children
+                        .insert(mgr.missing_blocks(parent.root, query.cid));
+                    Transition::Next(state)
+                }
+            });
+        } else {
+            self.events.push_back(QueryEvent::Complete(query.id, res));
+        }
+    }
+
+    /// Processes the response of a sync query.
+    ///
+    /// The sync query emits a `complete` event.
+    fn recv_sync(&mut self, query: Header, res: Result<(), Cid>) {
+        self.events.push_back(QueryEvent::Complete(query.id, res));
+    }
+
+    /// Dispatches the response to a query handler.
+    pub fn inject_response(&mut self, id: QueryId, res: Response) {
+        let query = if let Some(query) = self.queries.remove(&id) {
+            query.hdr
+        } else {
+            return;
+        };
+        tracing::trace!("{} {} {}", query.root, query.id, res);
+        match res {
+            Response::Have(peer, have) => {
+                self.recv_have(query, peer, have);
+            }
+            Response::Block(peer, block) => {
+                self.recv_block(query, peer, block);
+            }
+            Response::MissingBlocks(cids) => {
+                self.recv_missing_blocks(query, cids);
+            }
+        }
+    }
+
+    /// Returns the header of a query.
+    pub fn query_info(&self, id: QueryId) -> Option<&Header> {
+        self.queries.get(&id).map(|q| &q.hdr)
+    }
+
+    /// Retrieves the next query event.
+    pub fn next(&mut self) -> Option<QueryEvent> {
+        self.events.pop_front()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn tracing_try_init() {
+        tracing_subscriber::fmt()
+            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
+            .try_init()
+            .ok();
+    }
+
+    fn gen_peers(n: usize) -> Vec<PeerId> {
+        let mut peers = Vec::with_capacity(n);
+        for _ in 0..n {
+            peers.push(PeerId::random());
+        }
+        peers
+    }
+
+    fn assert_request(event: Option<QueryEvent>, req: Request) -> QueryId {
+        if let Some(QueryEvent::Request(id, req2)) = event {
+            assert_eq!(req2, req);
+            id
+        } else {
+            panic!("{:?} is not a request", event);
+        }
+    }
+
+    fn assert_complete(event: Option<QueryEvent>, id: QueryId, res: Result<(), Cid>) {
+        if let Some(QueryEvent::Complete(id2, res2)) = event {
+            assert_eq!(id, id2);
+            assert_eq!(res, res2);
+        } else {
+            panic!("{:?} is not a complete event", event);
+        }
+    }
+
+    #[test]
+    fn test_get_query_block_not_found() {
+        let mut mgr = QueryManager::default();
+        let initial_set = gen_peers(3);
+        let cid = Cid::default();
+
+        let id = mgr.get(None, cid, initial_set.iter().copied());
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[0], cid));
+        let id2 = assert_request(mgr.next(), Request::Have(initial_set[1], cid));
+        let id3 = assert_request(mgr.next(), Request::Have(initial_set[2], cid));
+
+        mgr.inject_response(id1, Response::Have(initial_set[0], false));
+        mgr.inject_response(id2, Response::Have(initial_set[1], false));
+        mgr.inject_response(id3, Response::Have(initial_set[2], false));
+
+        assert_complete(mgr.next(), id, Err(cid));
+    }
+
+    #[test]
+    fn test_cid_query_block_found() {
+        let mut mgr = QueryManager::default();
+        let initial_set = gen_peers(3);
+        let cid = Cid::default();
+
+        let id = mgr.get(None, cid, initial_set.iter().copied());
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[0], cid));
+        let id2 = assert_request(mgr.next(), Request::Have(initial_set[1], cid));
+        let id3 = assert_request(mgr.next(), Request::Have(initial_set[2], cid));
+
+        mgr.inject_response(id1, Response::Block(initial_set[0], true));
+        mgr.inject_response(id2, Response::Have(initial_set[1], false));
+        mgr.inject_response(id3, Response::Have(initial_set[2], false));
+
+        assert_complete(mgr.next(), id, Ok(()));
+    }
+
+    #[test]
+    fn test_get_query_gets_from_spare_if_block_request_fails() {
+        let mut mgr = QueryManager::default();
+        let initial_set = gen_peers(3);
+        let cid = Cid::default();
+
+        let id = mgr.get(None, cid, initial_set.iter().copied());
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[0], cid));
+        let id2 = assert_request(mgr.next(), Request::Have(initial_set[1], cid));
+        let id3 = assert_request(mgr.next(), Request::Have(initial_set[2], cid));
+
+        mgr.inject_response(id1, Response::Block(initial_set[0], false));
+        mgr.inject_response(id2, Response::Have(initial_set[1], true));
+        mgr.inject_response(id3, Response::Have(initial_set[2], false));
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[1], cid));
+        mgr.inject_response(id1, Response::Block(initial_set[1], true));
+
+        assert_complete(mgr.next(), id, Ok(()));
+    }
+
+    #[test]
+    fn test_get_query_gets_from_spare_if_block_request_fails_after_have_is_received() {
+        let mut mgr = QueryManager::default();
+        let initial_set = gen_peers(3);
+        let cid = Cid::default();
+
+        let id = mgr.get(None, cid, initial_set.iter().copied());
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[0], cid));
+        let id2 = assert_request(mgr.next(), Request::Have(initial_set[1], cid));
+        let id3 = assert_request(mgr.next(), Request::Have(initial_set[2], cid));
+
+        mgr.inject_response(id1, Response::Block(initial_set[0], false));
+        mgr.inject_response(id2, Response::Have(initial_set[1], true));
+        mgr.inject_response(id3, Response::Have(initial_set[2], true));
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[1], cid));
+        mgr.inject_response(id1, Response::Block(initial_set[1], false));
+
+        let id1 = assert_request(mgr.next(), Request::Block(initial_set[2], cid));
+        mgr.inject_response(id1, Response::Block(initial_set[2], true));
+
+        assert_complete(mgr.next(), id, Ok(()));
+    }
+
+    #[test]
+    fn test_sync_query() {
+        tracing_try_init();
+        let mut mgr = QueryManager::default();
+        let providers = gen_peers(3);
+        let cid = Cid::default();
+
+        let id = mgr.sync(cid, providers.clone(), std::iter::once(cid));
+
+        let id1 = assert_request(mgr.next(), Request::Block(providers[0], cid));
+        let id2 = assert_request(mgr.next(), Request::Have(providers[1], cid));
+        let id3 = assert_request(mgr.next(), Request::Have(providers[2], cid));
+
+        mgr.inject_response(id1, Response::Block(providers[0], true));
+        mgr.inject_response(id2, Response::Have(providers[1], false));
+        mgr.inject_response(id3, Response::Have(providers[2], false));
+
+        let id1 = assert_request(mgr.next(), Request::MissingBlocks(cid));
+        mgr.inject_response(id1, Response::MissingBlocks(vec![]));
+
+        assert_complete(mgr.next(), id, Ok(()));
+    }
+
+    #[test]
+    fn test_sync_query_empty() {
+        tracing_try_init();
+        let mut mgr = QueryManager::default();
+        let cid = Cid::default();
+        let id = mgr.sync(cid, vec![], std::iter::empty());
+        let id1 = assert_request(mgr.next(), Request::MissingBlocks(cid));
+        mgr.inject_response(id1, Response::MissingBlocks(vec![]));
+        assert_complete(mgr.next(), id, Ok(()));
+    }
+}
diff --git a/ext/libp2p-bitswap/src/stats.rs b/ext/libp2p-bitswap/src/stats.rs
new file mode 100644
index 000000000..432a804eb
--- /dev/null
+++ b/ext/libp2p-bitswap/src/stats.rs
@@ -0,0 +1,86 @@
+use lazy_static::lazy_static;
+use prometheus::{HistogramOpts, HistogramVec, IntCounter, IntCounterVec, Opts};
+
+lazy_static! {
+    pub static ref REQUESTS_TOTAL: IntCounterVec = IntCounterVec::new(
+        Opts::new(
+            "bitswap_requests_total",
+            "Number of bitswap requests labelled by type and result.",
+        ),
+        &["type"],
+    )
+    .unwrap();
+    pub static ref REQUEST_DURATION_SECONDS: HistogramVec = HistogramVec::new(
+        HistogramOpts::new(
+            "bitswap_request_duration_seconds",
+            "Duration of bitswap requests labelled by request type",
+        ),
+        &["type"],
+    )
+    .unwrap();
+    pub static ref REQUESTS_CANCELED: IntCounter = IntCounter::new(
+        "bitswap_requests_canceled_total",
+        "Number of canceled requests",
+    )
+    .unwrap();
+    pub static ref BLOCK_NOT_FOUND: IntCounter = IntCounter::new(
+        "bitswap_block_not_found_total",
+        "Number of block not found errors.",
+    )
+    .unwrap();
+    pub static ref PROVIDERS_TOTAL: IntCounter = IntCounter::new(
+        "bitswap_providers_total",
+        r#"Number of providers total. Using the number of provider requests, the average
+        number of providers per request can be computed."#
+    )
+    .unwrap();
+    pub static ref MISSING_BLOCKS_TOTAL: IntCounter = IntCounter::new(
+        "bitswap_missing_blocks_total",
+        r#"Number of missing blocks total. Using the number of missing blocks requests, the
+        average number of missing blocks per request can be computed."#
+    )
+    .unwrap();
+    pub static ref RECEIVED_BLOCK_BYTES: IntCounter =
+        IntCounter::new("bitswap_received_block_bytes", "Number of received bytes.",).unwrap();
+    pub static ref RECEIVED_INVALID_BLOCK_BYTES: IntCounter = IntCounter::new(
+        "bitswap_received_invalid_block_bytes",
+        "Number of received bytes that didn't match the hash.",
+    )
+    .unwrap();
+    pub static ref SENT_BLOCK_BYTES: IntCounter =
+        IntCounter::new("bitswap_sent_block_bytes", "Number of sent block bytes.",).unwrap();
+    pub static ref RESPONSES_TOTAL: IntCounterVec = IntCounterVec::new(
+        Opts::new(
+            "bitswap_responses_total",
+            "Number of bitswap responses sent to peers.",
+        ),
+        &["type"],
+    )
+    .unwrap();
+    pub static ref THROTTLED_INBOUND: IntCounter = IntCounter::new(
+        "bitswap_throttled_too_many_inbound_total",
+        "Number of too many inbound events.",
+    )
+    .unwrap();
+    pub static ref THROTTLED_OUTBOUND: IntCounter = IntCounter::new(
+        "bitswap_throttled_resume_send_total",
+        "Number of resume send events.",
+    )
+    .unwrap();
+    pub static ref OUTBOUND_FAILURE: IntCounterVec = IntCounterVec::new(
+        Opts::new(
+            "bitswap_outbound_failures_total",
+            "Number of outbound failures.",
+        ),
+        &["type"],
+    )
+    .unwrap();
+    pub static ref INBOUND_FAILURE: IntCounterVec = IntCounterVec::new(
+        Opts::new(
+            "bitswap_inbound_failures_total",
+            "Number of inbound failures.",
+        ),
+        &["type"],
+    )
+    .unwrap();
+}
diff --git a/ipld/resolver/Cargo.toml b/ipld/resolver/Cargo.toml
index 8c5e8a69c..dac26ec58 100644
--- a/ipld/resolver/Cargo.toml
+++ b/ipld/resolver/Cargo.toml
@@ -12,7 +12,6 @@ async-trait = { workspace = true }
 base64 = { workspace = true }
 blake2b_simd = { workspace = true }
 bloom = { workspace = true }
-gcra = { workspace = true }
 lazy_static = { workspace = true }
 libipld = { workspace = true }
 libp2p = { workspace = true }
@@ -35,6 +34,7 @@ fvm_ipld_blockstore = { workspace = true, optional = true }
 
 ipc-api = { path = "../../ipc/api", default-features = false }
 ipc-observability = { workspace = true }
+gcra = { path = "../../ext/gcra-rs" }
 
 [dev-dependencies]
 cid = { workspace = true }