diff --git a/integration-tests/Cargo.lock b/integration-tests/Cargo.lock index 8beed7649..3cb8ccc9f 100644 --- a/integration-tests/Cargo.lock +++ b/integration-tests/Cargo.lock @@ -184,7 +184,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io 2.6.0", "async-lock 3.4.2", @@ -434,7 +434,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals 0.3.0", + "bitcoin-internals", "bitcoin_hashes 0.14.1", ] @@ -480,7 +480,7 @@ dependencies = [ "base58ck", "base64 0.21.7", "bech32", - "bitcoin-internals 0.3.0", + "bitcoin-internals", "bitcoin-io", "bitcoin-units", "bitcoin_hashes 0.14.1", @@ -500,12 +500,6 @@ dependencies = [ "capnpc", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" - [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -527,7 +521,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals 0.3.0", + "bitcoin-internals", "serde", ] @@ -557,11 +551,10 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +checksum = "446819536d8121575eeb7e89efdbadb3f055e87e4bb66c6679a6d5cc2f4b64fd" dependencies = [ - "bitcoin-internals 0.2.0", "hex-conservative 0.1.2", ] @@ -723,9 +716,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "shlex", @@ -956,6 +949,22 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "corepc-client" version = "0.7.0" @@ -1288,9 +1297,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -1420,7 +1429,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-core", "futures-io", "parking", @@ -1510,6 +1519,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.3.1" @@ -1548,9 +1576,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hashlink" @@ -1674,23 +1702,24 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", + "want", ] [[package]] @@ -1699,23 +1728,35 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2 0.6.3", + "system-configuration", "tokio", + "tower-layer", "tower-service", + "tracing", + "windows-registry", ] [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1723,9 +1764,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1736,9 +1777,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1750,15 +1791,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1770,15 +1811,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1832,12 +1873,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1878,6 +1919,7 @@ dependencies = [ "pool_sv2", "primitive-types", "rand", + "serde_json", "sha2 0.10.9", "stratum-apps", "tokio", @@ -1898,6 +1940,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is-terminal" version = "0.4.17" @@ -1971,9 +2019,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.92" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "cfg-if", "futures-util", @@ -2021,15 +2069,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "libc", ] @@ -2054,9 +2102,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2416,7 +2464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand 2.4.1", "futures-io", ] @@ -2492,9 +2540,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2525,7 +2573,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.8+spec-1.1.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -2833,7 +2881,7 @@ version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.13.1", "rand", "secp256k1-sys 0.9.2", ] @@ -3087,6 +3135,9 @@ dependencies = [ "config", "dirs", "futures", + "http-body-util", + "hyper", + "hyper-util", "miniscript", "prometheus", "rand", @@ -3193,6 +3244,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -3205,7 +3277,7 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.1", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -3279,9 +3351,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3299,9 +3371,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -3317,9 +3389,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3363,9 +3435,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -3386,23 +3458,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "toml_datetime 1.1.0+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] @@ -3530,6 +3602,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.19.0" @@ -3694,6 +3772,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3702,9 +3789,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.115" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -3715,9 +3802,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.65" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1faf851e778dfa54db7cd438b70758eba9755cb47403f3496edd7c8fc212f0" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3725,9 +3812,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.115" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3735,9 +3822,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.115" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -3748,9 +3835,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.115" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -3807,6 +3894,35 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3975,18 +4091,18 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -4010,9 +4126,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4021,9 +4137,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4033,18 +4149,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4053,18 +4169,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4080,9 +4196,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4091,9 +4207,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4102,9 +4218,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 563e307aa..fe525b855 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -28,6 +28,7 @@ tracing = { version = "0.1.41", default-features = false } tracing-subscriber = { version = "0.3.19", default-features = false } hex = "0.4.3" clap = { version = "^4.5.4", features = ["derive"] } +serde_json = "1" # Direct dependencies kept only for the embedded `mining_device` module. # Remove this block when removing: diff --git a/integration-tests/lib/mod.rs b/integration-tests/lib/mod.rs index fe957c978..fb24faf41 100644 --- a/integration-tests/lib/mod.rs +++ b/integration-tests/lib/mod.rs @@ -121,6 +121,23 @@ pub async fn start_pool( supported_extensions: Vec, required_extensions: Vec, enable_monitoring: bool, +) -> (PoolSv2, SocketAddr, Option) { + start_pool_with_network_override( + template_provider_config, + supported_extensions, + required_extensions, + enable_monitoring, + None, + ) + .await +} + +pub async fn start_pool_with_network_override( + template_provider_config: TemplateProviderType, + supported_extensions: Vec, + required_extensions: Vec, + enable_monitoring: bool, + network: Option, ) -> (PoolSv2, SocketAddr, Option) { use pool_sv2::config::PoolConfig; let listening_address = get_available_address(); @@ -163,7 +180,8 @@ pub async fn start_pool( monitoring_address, monitoring_cache_refresh_secs, None, // no JDS - ); + ) + .with_network(network); let pool = PoolSv2::new(config); let pool_clone = pool.clone(); tokio::spawn(async move { @@ -198,6 +216,26 @@ pub fn start_jdc( required_extensions: Vec, enable_monitoring: bool, jdc_mode: Option, +) -> (JobDeclaratorClient, SocketAddr, Option) { + start_jdc_with_network_override( + pool, + template_provider_config, + supported_extensions, + required_extensions, + enable_monitoring, + jdc_mode, + None, + ) +} + +pub fn start_jdc_with_network_override( + pool: &[(SocketAddr, SocketAddr)], // (pool_address, jds_address) + template_provider_config: TemplateProviderType, + supported_extensions: Vec, + required_extensions: Vec, + enable_monitoring: bool, + jdc_mode: Option, + network: Option, ) -> (JobDeclaratorClient, SocketAddr, Option) { use jd_client_sv2::config::{JobDeclaratorClientConfig, PoolConfig, ProtocolConfig, Upstream}; let jdc_address = get_available_address(); @@ -261,7 +299,8 @@ pub fn start_jdc( required_extensions, monitoring_address, monitoring_cache_refresh_secs, - ); + ) + .with_network(network); let ret = jd_client_sv2::JobDeclaratorClient::new(jd_client_proxy); let ret_clone = ret.clone(); tokio::spawn(async move { ret_clone.start().await }); @@ -338,6 +377,27 @@ pub async fn start_sv2_translator( required_extensions: Vec, job_keepalive_interval_secs: Option, enable_monitoring: bool, +) -> (TranslatorSv2, SocketAddr, Option) { + start_sv2_translator_with_upstream_monitoring( + upstreams, + aggregate_channels, + supported_extensions, + required_extensions, + job_keepalive_interval_secs, + enable_monitoring, + None, + ) + .await +} + +pub async fn start_sv2_translator_with_upstream_monitoring( + upstreams: &[SocketAddr], + aggregate_channels: bool, + supported_extensions: Vec, + required_extensions: Vec, + job_keepalive_interval_secs: Option, + enable_monitoring: bool, + upstream_monitoring_url: Option, ) -> (TranslatorSv2, SocketAddr, Option) { let job_keepalive_interval_secs = job_keepalive_interval_secs.unwrap_or(60); let upstreams = upstreams @@ -395,7 +455,8 @@ pub async fn start_sv2_translator( required_extensions, monitoring_address, monitoring_cache_refresh_secs, - ); + ) + .with_upstream_monitoring_url(upstream_monitoring_url); let translator_v2 = translator_sv2::TranslatorSv2::new(config); let clone_translator_v2 = translator_v2.clone(); tokio::spawn(async move { diff --git a/integration-tests/tests/monitoring_integration.rs b/integration-tests/tests/monitoring_integration.rs index 1aca9c4c4..df2e085ff 100644 --- a/integration-tests/tests/monitoring_integration.rs +++ b/integration-tests/tests/monitoring_integration.rs @@ -245,3 +245,139 @@ async fn block_found_detected_in_pool_metrics() { shutdown_all!(pool, jdc, tproxy); } + +// --------------------------------------------------------------------------- +// 5. Pool exposes network via config override; translator inherits it on connect. +// --------------------------------------------------------------------------- +#[tokio::test] +async fn global_info_network_from_config_override() { + start_tracing(); + let (_tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); + let (_pool, pool_addr, pool_monitoring) = start_pool_with_network_override( + sv2_tp_config(tp_addr), + vec![], + vec![], + true, + Some("regtest".to_string()), + ) + .await; + let pool_mon = pool_monitoring.expect("pool monitoring should be enabled"); + + // Pool global endpoint must report network = "regtest" + let body = fetch_api(pool_mon, "/api/v1/global").await; + let json: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert_eq!( + json["network"], "regtest", + "pool global info should expose network" + ); + + // Start translator with upstream_monitoring_url pointing at pool's monitoring server. + // MonitoringServer fetches network from pool once at startup (no polling loop). + let upstream_monitoring_url = format!("http://{}", pool_mon); + let (_tproxy, _tproxy_addr, tproxy_monitoring) = start_sv2_translator_with_upstream_monitoring( + &[pool_addr], + false, + vec![], + vec![], + None, + true, + Some(upstream_monitoring_url), + ) + .await; + let tproxy_mon = tproxy_monitoring.expect("tproxy monitoring should be enabled"); + + // Wait up to 30 seconds for network to propagate (generous for slow CI machines). + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30); + loop { + let body = fetch_api(tproxy_mon, "/api/v1/global").await; + let json: serde_json::Value = serde_json::from_str(&body).unwrap(); + if json["network"] == "regtest" { + break; + } + if std::time::Instant::now() >= deadline { + panic!( + "tproxy global info did not propagate network within timeout; got: {}", + json + ); + } + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + } +} + +// --------------------------------------------------------------------------- +// 6. JDC exposes network via config override in GlobalInfo. +// --------------------------------------------------------------------------- +#[tokio::test] +async fn global_info_network_jdc_from_config_override() { + start_tracing(); + let (tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); + let (_pool, pool_addr, jds_addr, _pool_monitoring) = + start_pool_with_jds(tp.bitcoin_core(), vec![], vec![], false).await; + + let (jdc, _jdc_addr, jdc_monitoring) = start_jdc_with_network_override( + &[(pool_addr, jds_addr)], + sv2_tp_config(tp_addr), + vec![], + vec![], + true, + None, + Some("regtest".to_string()), + ); + let jdc_mon = jdc_monitoring.expect("jdc monitoring should be enabled"); + + // Wait up to 30 seconds for JDC to connect and serve the monitoring endpoint. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30); + loop { + let body = fetch_api(jdc_mon, "/api/v1/global").await; + let json: serde_json::Value = serde_json::from_str(&body).unwrap(); + if json["network"] == "regtest" { + break; + } + if std::time::Instant::now() >= deadline { + panic!( + "jdc global info did not expose network within timeout; got: {}", + json + ); + } + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + } + + shutdown_all!(jdc); +} + +// --------------------------------------------------------------------------- +// 7. Translator starts cleanly when upstream_monitoring_url is unreachable. +// --------------------------------------------------------------------------- +#[tokio::test] +async fn global_info_network_unreachable_upstream() { + start_tracing(); + let (_tp, tp_addr) = start_template_provider(None, DifficultyLevel::Low); + let (_pool, pool_addr, _pool_monitoring) = + start_pool(sv2_tp_config(tp_addr), vec![], vec![], false).await; + + // Point translator at a port where nothing is listening. + let dead_url = Some("http://127.0.0.1:19999".to_string()); + let (_tproxy, _tproxy_addr, tproxy_monitoring) = start_sv2_translator_with_upstream_monitoring( + &[pool_addr], + false, + vec![], + vec![], + None, + true, + dead_url, + ) + .await; + let tproxy_mon = tproxy_monitoring.expect("tproxy monitoring should be enabled"); + + // Give the fetch attempt time to fail and the server to stabilise. + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + + // Translator must still serve the monitoring endpoint; network must be null. + let body = fetch_api(tproxy_mon, "/api/v1/global").await; + let json: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert!( + json["network"].is_null(), + "network should be null when upstream is unreachable; got: {}", + json + ); +} diff --git a/miner-apps/Cargo.lock b/miner-apps/Cargo.lock index e21288ae9..8c4365b20 100644 --- a/miner-apps/Cargo.lock +++ b/miner-apps/Cargo.lock @@ -720,6 +720,22 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1128,6 +1144,25 @@ dependencies = [ "polyval", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlers_sv2" version = "0.2.2" @@ -1317,6 +1352,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1326,6 +1362,7 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", ] [[package]] @@ -1334,13 +1371,24 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", + "system-configuration", "tokio", + "tower-layer", "tower-service", + "tracing", + "windows-registry", ] [[package]] @@ -1492,6 +1540,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is-terminal" version = "0.4.17" @@ -2440,6 +2494,9 @@ dependencies = [ "config", "dirs", "futures", + "http-body-util", + "hyper", + "hyper-util", "miniscript", "prometheus", "rand", @@ -2546,6 +2603,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -2884,6 +2962,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.19.0" @@ -3036,6 +3120,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3134,6 +3227,35 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/miner-apps/jd-client/src/lib/config.rs b/miner-apps/jd-client/src/lib/config.rs index 49ba6081a..81d4d348c 100644 --- a/miner-apps/jd-client/src/lib/config.rs +++ b/miner-apps/jd-client/src/lib/config.rs @@ -8,7 +8,7 @@ use stratum_apps::{ config_helpers::{opt_path_from_toml, CoinbaseRewardScript}, key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}, stratum_core::bitcoin::{Amount, TxOut}, - tp_type::TemplateProviderType, + tp_type::{TemplateProviderType, VALID_NETWORKS}, utils::types::{SharesBatchSize, SharesPerMinute}, }; @@ -59,6 +59,15 @@ pub struct JobDeclaratorClientConfig { monitoring_address: Option, #[serde(default)] monitoring_cache_refresh_secs: Option, + /// Optional override for the Bitcoin network name exposed via `GET /api/v1/global`. + /// When absent the network is inferred from the template provider config: + /// - `Sv2Tp`: mapped from the sv2-tp port using well-known defaults (see + /// `stratum_apps::tp_type::network_from_tp_port`). Returns `None` for non-standard ports. + /// - `BitcoinCoreIpc`: taken directly from the `BitcoinNetwork` enum value. + /// Values follow bitcoin-cli convention: `"main"`, `"test"`, `"testnet4"`, + /// `"signet"`, `"regtest"`. + #[serde(default)] + network: Option, } impl JobDeclaratorClientConfig { @@ -100,6 +109,7 @@ impl JobDeclaratorClientConfig { required_extensions, monitoring_address, monitoring_cache_refresh_secs, + network: None, } } @@ -196,6 +206,35 @@ impl JobDeclaratorClientConfig { pub fn required_extensions(&self) -> &[u16] { &self.required_extensions } + + /// Set the Bitcoin network override (builder style). + /// Only needed for non-standard sv2-tp port setups; `BitcoinCoreIpc` configs derive the + /// network automatically from the `BitcoinNetwork` enum value. + pub fn with_network(mut self, network: Option) -> Self { + self.network = network; + self + } + + /// Returns the effective Bitcoin network name: the explicit `network` override if set, + /// otherwise inferred from `template_provider_type`. + /// + /// Returns `None` if the explicit override is not one of the known values, or if using + /// `Sv2Tp` with a non-standard port (set `network` explicitly for non-standard port setups). + pub fn effective_network(&self) -> Option { + if let Some(ref n) = self.network { + if !VALID_NETWORKS.contains(&n.as_str()) { + tracing::warn!( + "jdc config: network {:?} is not a recognised value \ + (expected one of {:?}); network will not be reported.", + n, + VALID_NETWORKS + ); + return None; + } + return Some(n.clone()); + } + self.template_provider_type.infer_network().map(|s| s.to_string()) + } } #[derive(Debug, Deserialize, Clone, Default, PartialEq)] @@ -302,3 +341,47 @@ impl Upstream { } } } + +#[cfg(test)] +mod tests { + use stratum_apps::tp_type::{BitcoinNetwork, TemplateProviderType}; + + fn sv2_tp(address: &str) -> TemplateProviderType { + TemplateProviderType::Sv2Tp { + address: address.to_string(), + public_key: None, + } + } + + fn ipc_tp(network: BitcoinNetwork) -> TemplateProviderType { + TemplateProviderType::BitcoinCoreIpc { + network, + data_dir: None, + fee_threshold: 0, + min_interval: 5, + } + } + + #[test] + fn infer_network_sv2tp_standard_ports() { + assert_eq!(sv2_tp("127.0.0.1:18447").infer_network(), Some("regtest")); + assert_eq!(sv2_tp("127.0.0.1:8442").infer_network(), Some("main")); + assert_eq!(sv2_tp("127.0.0.1:18442").infer_network(), Some("test")); + assert_eq!(sv2_tp("127.0.0.1:48442").infer_network(), Some("testnet4")); + assert_eq!(sv2_tp("127.0.0.1:38442").infer_network(), Some("signet")); + } + + #[test] + fn infer_network_sv2tp_nonstandard_port_returns_none() { + assert_eq!(sv2_tp("127.0.0.1:4444").infer_network(), None); + assert_eq!(sv2_tp("127.0.0.1:3333").infer_network(), None); + } + + #[test] + fn infer_network_bitcoin_core_ipc() { + assert_eq!(ipc_tp(BitcoinNetwork::Regtest).infer_network(), Some("regtest")); + assert_eq!(ipc_tp(BitcoinNetwork::Mainnet).infer_network(), Some("main")); + assert_eq!(ipc_tp(BitcoinNetwork::Testnet4).infer_network(), Some("testnet4")); + assert_eq!(ipc_tp(BitcoinNetwork::Signet).infer_network(), Some("signet")); + } +} diff --git a/miner-apps/jd-client/src/lib/mod.rs b/miner-apps/jd-client/src/lib/mod.rs index 3504478ce..142cccdd2 100644 --- a/miner-apps/jd-client/src/lib/mod.rs +++ b/miner-apps/jd-client/src/lib/mod.rs @@ -136,7 +136,8 @@ impl JobDeclaratorClient { self.config.monitoring_cache_refresh_secs().unwrap_or(15), ), ) - .expect("Failed to initialize monitoring server"); + .expect("Failed to initialize monitoring server") + .with_network(self.config.effective_network()); // Create shutdown signal using cancellation token let cancellation_token_clone = self.cancellation_token.clone(); @@ -506,7 +507,8 @@ impl JobDeclaratorClient { Some(Arc::new(channel_manager_clone.clone())), std::time::Duration::from_secs(self.config.monitoring_cache_refresh_secs().unwrap_or(15)), ) - .expect("Failed to initialize monitoring server"); + .expect("Failed to initialize monitoring server") + .with_network(self.config.effective_network()); let cancellation_token_clone = self.cancellation_token.clone(); let fallback_coordinator_token = fallback_coordinator.token(); diff --git a/miner-apps/translator/src/lib/config.rs b/miner-apps/translator/src/lib/config.rs index 5cf04c790..7bf40d653 100644 --- a/miner-apps/translator/src/lib/config.rs +++ b/miner-apps/translator/src/lib/config.rs @@ -59,6 +59,13 @@ pub struct TranslatorConfig { monitoring_address: Option, #[serde(default)] monitoring_cache_refresh_secs: Option, + /// URL of the upstream pool's monitoring server. + /// When set, the translator fetches `GET /api/v1/global` once + /// per upstream connection and propagates the pool's reported `network` value into its + /// own `GET /api/v1/global` response. + /// Example: `"http://127.0.0.1:9108"` + #[serde(default)] + upstream_monitoring_url: Option, } #[derive(Debug, Deserialize, Clone)] @@ -116,6 +123,7 @@ impl TranslatorConfig { log_file: None, monitoring_address, monitoring_cache_refresh_secs, + upstream_monitoring_url: None, } } @@ -129,6 +137,17 @@ impl TranslatorConfig { self.monitoring_cache_refresh_secs } + /// Returns the upstream pool monitoring URL (if configured). + pub fn upstream_monitoring_url(&self) -> Option { + self.upstream_monitoring_url.clone() + } + + /// Set the upstream pool monitoring URL (builder style). + pub fn with_upstream_monitoring_url(mut self, url: Option) -> Self { + self.upstream_monitoring_url = url; + self + } + pub fn set_log_dir(&mut self, log_dir: Option) { if let Some(dir) = log_dir { self.log_file = Some(dir); diff --git a/miner-apps/translator/src/lib/mod.rs b/miner-apps/translator/src/lib/mod.rs index aa65bf50d..4fd4d7efa 100644 --- a/miner-apps/translator/src/lib/mod.rs +++ b/miner-apps/translator/src/lib/mod.rs @@ -193,7 +193,8 @@ impl TranslatorSv2 { ) .expect("Failed to initialize monitoring server") .with_sv1_monitoring(sv1_server.clone()) // SV1 client connections - .expect("Failed to add SV1 monitoring"); + .expect("Failed to add SV1 monitoring") + .with_upstream_monitoring_url(self.config.upstream_monitoring_url()); // Create shutdown signal using cancellation token let cancellation_token_clone = cancellation_token.clone(); @@ -337,7 +338,8 @@ impl TranslatorSv2 { ) .expect("Failed to initialize monitoring server") .with_sv1_monitoring(sv1_server.clone()) - .expect("Failed to add SV1 monitoring"); + .expect("Failed to add SV1 monitoring") + .with_upstream_monitoring_url(self.config.upstream_monitoring_url()); let cancellation_token_clone = cancellation_token.clone(); let fallback_coordinator_token = fallback_coordinator.token(); @@ -619,3 +621,4 @@ impl Drop for TranslatorSv2 { self.cancellation_token.cancel(); } } + diff --git a/pool-apps/Cargo.lock b/pool-apps/Cargo.lock index 81e5d1abd..2323a8863 100644 --- a/pool-apps/Cargo.lock +++ b/pool-apps/Cargo.lock @@ -711,6 +711,22 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1119,6 +1135,25 @@ dependencies = [ "polyval", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlers_sv2" version = "0.2.2" @@ -1308,6 +1343,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1317,6 +1353,7 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", ] [[package]] @@ -1325,13 +1362,24 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", + "system-configuration", "tokio", + "tower-layer", "tower-service", + "tracing", + "windows-registry", ] [[package]] @@ -1483,6 +1531,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is-terminal" version = "0.4.17" @@ -2447,6 +2501,9 @@ dependencies = [ "config", "dirs", "futures", + "http-body-util", + "hyper", + "hyper-util", "miniscript", "prometheus", "rand", @@ -2525,6 +2582,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -2844,6 +2922,12 @@ dependencies = [ "syn", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.19.0" @@ -2996,6 +3080,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3094,6 +3187,35 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/pool-apps/pool/src/lib/config.rs b/pool-apps/pool/src/lib/config.rs index c80c1bd37..c1277d423 100644 --- a/pool-apps/pool/src/lib/config.rs +++ b/pool-apps/pool/src/lib/config.rs @@ -18,7 +18,7 @@ use stratum_apps::{ config_helpers::{opt_path_from_toml, CoinbaseRewardScript}, key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}, stratum_core::bitcoin::{Amount, TxOut}, - tp_type::TemplateProviderType, + tp_type::{TemplateProviderType, VALID_NETWORKS}, utils::types::{SharesBatchSize, SharesPerMinute}, }; @@ -50,6 +50,13 @@ pub struct PoolConfig { jds: Option, #[serde(default)] monitoring_cache_refresh_secs: Option, + /// Optional override for the Bitcoin network name exposed via `GET /api/v1/global`. + /// When absent the network is inferred from the sv2-tp port in `template_provider_type` + /// using well-known default ports (see `network_from_tp_port`). + /// Values follow bitcoin-cli convention: `"main"`, `"test"`, `"testnet4"`, + /// `"signet"`, `"regtest"`. + #[serde(default)] + network: Option, } impl PoolConfig { @@ -90,6 +97,7 @@ impl PoolConfig { monitoring_address, monitoring_cache_refresh_secs, jds, + network: None, } } @@ -186,6 +194,39 @@ impl PoolConfig { self.monitoring_cache_refresh_secs } + /// Returns the explicit network override if set. + pub fn network(&self) -> Option { + self.network.clone() + } + + /// Returns the effective Bitcoin network name: the explicit `network` override if set, + /// otherwise inferred from the sv2-tp port in `template_provider_type`. + /// + /// Returns `None` if the explicit override is not one of the known values + /// (`"main"`, `"test"`, `"testnet4"`, `"signet"`, `"regtest"`) or if the tp_address + /// port is not a well-known sv2-tp default port. + pub fn effective_network(&self) -> Option { + if let Some(ref n) = self.network { + if !VALID_NETWORKS.contains(&n.as_str()) { + tracing::warn!( + "pool config: network {:?} is not a recognised value \ + (expected one of {:?}); network will not be reported.", + n, + VALID_NETWORKS + ); + return None; + } + return Some(n.clone()); + } + self.template_provider_type.infer_network().map(|s| s.to_string()) + } + + /// Set the Bitcoin network override (builder style). + pub fn with_network(mut self, network: Option) -> Self { + self.network = network; + self + } + /// Builds a complete [`JDSConfig`] from the partial `[jds]` TOML section /// plus shared fields inherited from Pool config. /// @@ -239,3 +280,34 @@ impl ConnectionConfig { } } } + +#[cfg(test)] +mod tests { + use super::*; + use stratum_apps::tp_type::network_from_tp_port; + + fn sv2_tp_type(address: &str) -> TemplateProviderType { + TemplateProviderType::Sv2Tp { + address: address.to_string(), + public_key: None, + } + } + + #[test] + fn infer_network_standard_tp_ports() { + assert_eq!(sv2_tp_type("127.0.0.1:18447").infer_network(), Some("regtest")); + assert_eq!(sv2_tp_type("127.0.0.1:8442").infer_network(), Some("main")); + assert_eq!(sv2_tp_type("127.0.0.1:4444").infer_network(), None); + } + + #[test] + fn valid_networks_covers_known_port_outputs() { + for port in [8442u16, 18442, 48442, 38442, 18447] { + let name = network_from_tp_port(port).unwrap(); + assert!( + VALID_NETWORKS.contains(&name), + "port {port} maps to {name:?} which is not in VALID_NETWORKS" + ); + } + } +} diff --git a/pool-apps/pool/src/lib/mod.rs b/pool-apps/pool/src/lib/mod.rs index acd7ff9d0..78c47b3c8 100644 --- a/pool-apps/pool/src/lib/mod.rs +++ b/pool-apps/pool/src/lib/mod.rs @@ -175,7 +175,8 @@ impl PoolSv2 { self.config.monitoring_cache_refresh_secs().unwrap_or(15), ), ) - .expect("Failed to initialize monitoring server"); + .expect("Failed to initialize monitoring server") + .with_network(self.config.effective_network()); let cancellation_token_clone = cancellation_token.clone(); let shutdown_signal = async move { diff --git a/stratum-apps/Cargo.toml b/stratum-apps/Cargo.toml index 5850c4b9e..ce6eab780 100644 --- a/stratum-apps/Cargo.toml +++ b/stratum-apps/Cargo.toml @@ -63,7 +63,7 @@ default = ["network", "config", "std"] network = ["tokio-util", "core"] config = [] rpc = ["serde_json", "hex", "base64", "hyper", "hyper-util", "http-body-util"] -monitoring = ["serde_json", "axum", "prometheus", "utoipa", "utoipa-swagger-ui"] +monitoring = ["serde_json", "axum", "prometheus", "utoipa", "utoipa-swagger-ui", "hyper", "hyper-util", "http-body-util"] std = ["bs58/std", "secp256k1/rand-std", "rand/std", "rand/std_rng"] core = ["stratum-core"] diff --git a/stratum-apps/src/monitoring/http_server.rs b/stratum-apps/src/monitoring/http_server.rs index 7daf6ad02..20db3418f 100644 --- a/stratum-apps/src/monitoring/http_server.rs +++ b/stratum-apps/src/monitoring/http_server.rs @@ -20,16 +20,19 @@ use axum::{ routing::get, Router, }; +use http_body_util::{BodyExt, Empty}; +use hyper::{body::Bytes, Request, Uri}; +use hyper_util::rt::TokioIo; use prometheus::{Encoder, TextEncoder}; use serde::Deserialize; use std::{ future::Future, net::SocketAddr, - sync::Arc, + sync::{Arc, RwLock}, time::{Duration, SystemTime, UNIX_EPOCH}, }; -use tokio::net::TcpListener; -use tracing::info; +use tokio::net::{TcpListener, TcpStream}; +use tracing::{info, warn}; use utoipa::{IntoParams, OpenApi, ToSchema}; use utoipa_swagger_ui::SwaggerUi; @@ -88,6 +91,7 @@ struct ServerState { cache: Arc, start_time: u64, metrics: PrometheusMetrics, + network: Arc>>, } const DEFAULT_LIMIT: usize = 25; @@ -129,6 +133,7 @@ pub struct MonitoringServer { bind_address: SocketAddr, state: ServerState, refresh_interval: Duration, + upstream_monitoring_url: Option, } impl MonitoringServer { @@ -180,10 +185,50 @@ impl MonitoringServer { cache, start_time, metrics, + network: Arc::new(RwLock::new(None)), }, + upstream_monitoring_url: None, }) } + /// Set the Bitcoin network this application is operating on. + /// + /// Values follow bitcoin-cli convention: `"main"`, `"test"`, `"testnet4"`, `"regtest"`, + /// `"signet"`. The value is served as-is in the `network` field of `GET /api/v1/global`. + /// + /// This is optional — if not called, `network` will be `None` in the global response. + pub fn with_network(self, network: Option) -> Self { + *self.state.network.write().expect("network lock poisoned") = network; + self + } + + /// Configure the URL of an upstream application's monitoring server. + /// + /// When set, [`run`] performs a one-shot `GET /api/v1/global` at startup and + /// populates the `network` field in this server's `GET /api/v1/global` response from + /// the upstream's value. This is used by the translator to inherit the network from + /// the pool it connects to. + /// + /// Only plain `http://` URLs are supported — HTTPS is not. If the URL does not start + /// with `http://`, a warning is logged and the option is ignored. + /// + /// If the upstream is unreachable or returns an unexpected response, a warning is + /// logged and `network` remains `None`. + pub fn with_upstream_monitoring_url(mut self, url: Option) -> Self { + if let Some(ref u) = url { + if !u.starts_with("http://") { + warn!( + "upstream_monitoring_url {:?} is not an http:// URL — only plain HTTP is \ + supported. Upstream network fetch disabled.", + u + ); + return self; + } + } + self.upstream_monitoring_url = url; + self + } + /// Add Sv1 clients monitoring (optional, for Translator Proxy only) /// /// This must be called before `run()` if you want SV1 monitoring. @@ -230,6 +275,15 @@ impl MonitoringServer { info!("Starting monitoring server on http://{}", self.bind_address); info!("Cache refresh interval: {:?}", self.refresh_interval); + // If an upstream monitoring URL is configured, fetch the network field once at startup. + // The fetch runs concurrently; network stays None until it completes. + if let Some(url) = self.upstream_monitoring_url { + let network = self.state.network.clone(); + tokio::spawn(async move { + fetch_network_from_upstream(&url, network).await; + }); + } + // Spawn background task to refresh cache periodically let cache_for_refresh = self.state.cache.clone(); let refresh_interval = self.refresh_interval; @@ -287,6 +341,63 @@ impl MonitoringServer { } } +/// Fetch `GET /api/v1/global` from an upstream monitoring server and write the reported +/// `network` value into `network`. Called once at startup; if the upstream is unreachable +/// or returns an unexpected response a warning is logged and `network` stays `None`. +async fn fetch_network_from_upstream(url: &str, network: Arc>>) { + let full_url = format!("{}/api/v1/global", url.trim_end_matches('/')); + match fetch_global_info(&full_url).await { + Ok(info) => { + *network.write().expect("network lock poisoned") = info.network; + } + Err(e) => warn!("Failed to fetch network from upstream {}: {}", url, e), + } +} + +/// Perform a plain HTTP/1.1 GET to `url` and deserialize the response body as [`GlobalInfo`]. +/// +/// Only `http://` URLs are supported (TLS is not). Returns an error on connection failure, +/// non-2xx status, or JSON parse failure. +async fn fetch_global_info( + url: &str, +) -> Result> { + let uri: Uri = url.parse()?; + let host = uri.host().ok_or("URL missing host")?; + let port = uri.port_u16().unwrap_or(80); + let path = uri + .path_and_query() + .map(|pq| pq.as_str()) + .unwrap_or("/"); + + let stream = TcpStream::connect(format!("{}:{}", host, port)).await?; + let io = TokioIo::new(stream); + + let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?; + tokio::spawn(async move { + if let Err(e) = conn.await { + warn!("Upstream monitoring HTTP connection error: {}", e); + } + }); + + let req = Request::builder() + .method("GET") + .uri(path) + .header( + "host", + uri.authority().map(|a| a.as_str()).unwrap_or(host), + ) + .body(Empty::::new())?; + + let resp = sender.send_request(req).await?; + if !resp.status().is_success() { + return Err(format!("HTTP {}", resp.status()).into()); + } + + let body = resp.collect().await?.to_bytes(); + let info: GlobalInfo = serde_json::from_slice(&body)?; + Ok(info) +} + // Response types - used for both actual responses and OpenAPI documentation #[derive(serde::Serialize, ToSchema)] struct HealthResponse { @@ -423,6 +534,7 @@ async fn handle_global(State(state): State) -> Json { sv2_clients: snapshot.sv2_clients_summary, sv1_clients: snapshot.sv1_clients_summary, uptime_secs, + network: state.network.read().unwrap().clone(), }) } @@ -1065,6 +1177,15 @@ mod tests { server: Option>, clients: Option>, sv1: Option>, + ) -> Router { + build_test_app_with_options(server, clients, sv1, None) + } + + fn build_test_app_with_options( + server: Option>, + clients: Option>, + sv1: Option>, + network: Option, ) -> Router { let cache = Arc::new(SnapshotCache::new(Duration::from_secs(60), server, clients)); @@ -1095,6 +1216,7 @@ mod tests { cache, start_time, metrics, + network: Arc::new(RwLock::new(network)), }; let api_v1 = Router::new() @@ -1242,6 +1364,25 @@ mod tests { assert!(json["server"].is_null()); assert!(json["sv2_clients"].is_null()); assert!(json["uptime_secs"].as_u64().is_some()); + assert!(json["network"].is_null()); + } + + #[tokio::test] + async fn global_endpoint_network_field() { + // Without network set, field should be null + let app = build_test_app(None, None, None); + let response = app.oneshot(make_request("/api/v1/global")).await.unwrap(); + let body = get_body(response).await; + let json: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert!(json["network"].is_null()); + + // With network set, field should reflect the configured value + let app = build_test_app_with_options(None, None, None, Some("regtest".to_string())); + let response = app.oneshot(make_request("/api/v1/global")).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let body = get_body(response).await; + let json: serde_json::Value = serde_json::from_str(&body).unwrap(); + assert_eq!(json["network"], "regtest"); } #[tokio::test] diff --git a/stratum-apps/src/monitoring/mod.rs b/stratum-apps/src/monitoring/mod.rs index 435639e05..4c69c77dd 100644 --- a/stratum-apps/src/monitoring/mod.rs +++ b/stratum-apps/src/monitoring/mod.rs @@ -49,4 +49,9 @@ pub struct GlobalInfo { pub sv1_clients: Option, /// Uptime in seconds since the application started pub uptime_secs: u64, + /// Bitcoin network this application is operating on. + /// `None` if the application has not been configured with a network. + /// Values follow bitcoin-cli convention: `"main"`, `"test"`, `"testnet4"`, `"regtest"`, + /// `"signet"`. + pub network: Option, } diff --git a/stratum-apps/src/tp_type.rs b/stratum-apps/src/tp_type.rs index a7122bfcf..9b7c4428e 100644 --- a/stratum-apps/src/tp_type.rs +++ b/stratum-apps/src/tp_type.rs @@ -1,6 +1,27 @@ use crate::{config_helpers::opt_path_from_toml, key_utils::Secp256k1PublicKey}; use std::path::PathBuf; +/// Valid Bitcoin network names (bitcoin-cli / `getblockchaininfo` convention). +/// Covers all values that `network_from_tp_port` and `BitcoinNetwork::as_network_str` can return. +pub const VALID_NETWORKS: &[&str] = &["main", "test", "testnet4", "signet", "regtest"]; + +/// Maps a well-known sv2-tp default port to a Bitcoin network name. +/// Port assignments from `man sv2-tp`: +/// 8442 → main, 18442 → test, 48442 → testnet4, +/// 38442 → signet, 18447 → regtest +/// +/// Returns `None` for non-standard ports. +pub fn network_from_tp_port(port: u16) -> Option<&'static str> { + match port { + 8442 => Some("main"), + 18442 => Some("test"), + 48442 => Some("testnet4"), + 38442 => Some("signet"), + 18447 => Some("regtest"), + _ => None, + } +} + /// Bitcoin network for determining node.sock location #[derive(Clone, Debug, serde::Deserialize)] #[serde(rename_all = "lowercase")] @@ -12,6 +33,16 @@ pub enum BitcoinNetwork { } impl BitcoinNetwork { + /// Returns the bitcoin-cli / `getblockchaininfo` network name for this network. + pub fn as_network_str(&self) -> &'static str { + match self { + BitcoinNetwork::Mainnet => "main", + BitcoinNetwork::Testnet4 => "testnet4", + BitcoinNetwork::Signet => "signet", + BitcoinNetwork::Regtest => "regtest", + } + } + /// Returns the subdirectory name for this network. /// Mainnet uses the root data directory. fn subdir(&self) -> Option<&'static str> { @@ -75,10 +106,123 @@ pub enum TemplateProviderType { }, } +impl TemplateProviderType { + /// Infer the Bitcoin network name from this TP configuration. + /// + /// - `Sv2Tp`: maps the address port to a network using `network_from_tp_port`. + /// Returns `None` for non-standard ports. + /// - `BitcoinCoreIpc`: returns the network directly from the `BitcoinNetwork` enum. + pub fn infer_network(&self) -> Option<&'static str> { + match self { + TemplateProviderType::Sv2Tp { address, .. } => address + .parse::() + .ok() + .and_then(|a| network_from_tp_port(a.port())), + TemplateProviderType::BitcoinCoreIpc { network, .. } => { + Some(network.as_network_str()) + } + } + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn network_from_tp_port_known_ports() { + assert_eq!(network_from_tp_port(8442), Some("main")); + assert_eq!(network_from_tp_port(18442), Some("test")); + assert_eq!(network_from_tp_port(48442), Some("testnet4")); + assert_eq!(network_from_tp_port(38442), Some("signet")); + assert_eq!(network_from_tp_port(18447), Some("regtest")); + } + + #[test] + fn network_from_tp_port_unknown_returns_none() { + assert_eq!(network_from_tp_port(4444), None); + assert_eq!(network_from_tp_port(0), None); + assert_eq!(network_from_tp_port(3333), None); + } + + #[test] + fn valid_networks_covers_all_tp_port_outputs() { + for port in [8442u16, 18442, 48442, 38442, 18447] { + let name = network_from_tp_port(port).unwrap(); + assert!( + VALID_NETWORKS.contains(&name), + "port {port} maps to {name:?} which is not in VALID_NETWORKS" + ); + } + } + + #[test] + fn bitcoin_network_as_network_str() { + assert_eq!(BitcoinNetwork::Mainnet.as_network_str(), "main"); + assert_eq!(BitcoinNetwork::Testnet4.as_network_str(), "testnet4"); + assert_eq!(BitcoinNetwork::Signet.as_network_str(), "signet"); + assert_eq!(BitcoinNetwork::Regtest.as_network_str(), "regtest"); + } + + #[test] + fn valid_networks_covers_all_bitcoin_network_outputs() { + for network in [ + BitcoinNetwork::Mainnet, + BitcoinNetwork::Testnet4, + BitcoinNetwork::Signet, + BitcoinNetwork::Regtest, + ] { + let name = network.as_network_str(); + assert!( + VALID_NETWORKS.contains(&name), + "{name:?} is not in VALID_NETWORKS" + ); + } + } + + #[test] + fn infer_network_sv2tp_standard_ports() { + let tp = TemplateProviderType::Sv2Tp { + address: "127.0.0.1:18447".to_string(), + public_key: None, + }; + assert_eq!(tp.infer_network(), Some("regtest")); + + let tp = TemplateProviderType::Sv2Tp { + address: "127.0.0.1:8442".to_string(), + public_key: None, + }; + assert_eq!(tp.infer_network(), Some("main")); + } + + #[test] + fn infer_network_sv2tp_nonstandard_port_returns_none() { + let tp = TemplateProviderType::Sv2Tp { + address: "127.0.0.1:4444".to_string(), + public_key: None, + }; + assert_eq!(tp.infer_network(), None); + } + + #[test] + fn infer_network_bitcoin_core_ipc() { + let tp = TemplateProviderType::BitcoinCoreIpc { + network: BitcoinNetwork::Regtest, + data_dir: None, + fee_threshold: 0, + min_interval: 5, + }; + assert_eq!(tp.infer_network(), Some("regtest")); + + let tp = TemplateProviderType::BitcoinCoreIpc { + network: BitcoinNetwork::Mainnet, + data_dir: None, + fee_threshold: 0, + min_interval: 5, + }; + assert_eq!(tp.infer_network(), Some("main")); + } + #[test] fn network_with_data_dir_mainnet() { let result =