diff --git a/Cargo.lock b/Cargo.lock index 0ac5cf1..5508e78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,8 +364,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals", - "bitcoin_hashes", + "bitcoin-internals 0.3.0", + "bitcoin_hashes 0.14.0", ] [[package]] @@ -393,39 +393,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +name = "binary_sv2" +version = "5.0.1" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" dependencies = [ - "bitflags 2.9.1", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.104", + "derive_codec_sv2", ] [[package]] name = "bindgen" -version = "0.71.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools", "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash", "shlex", "syn 2.0.104", ] @@ -438,16 +426,22 @@ checksum = "ad8929a18b8e33ea6b3c09297b687baaa71fb1b97353243a3f1029fad5c59c5b" dependencies = [ "base58ck", "bech32", - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes", - "hex-conservative", + "bitcoin_hashes 0.14.0", + "hex-conservative 0.2.1", "hex_lit", - "secp256k1", + "secp256k1 0.29.1", "serde", ] +[[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" @@ -469,10 +463,20 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals 0.2.0", + "hex-conservative 0.1.2", +] + [[package]] name = "bitcoin_hashes" version = "0.14.0" @@ -480,14 +484,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.1", "serde", ] [[package]] name = "bitcoindrpc" version = "0.1.0" -source = "git+https://github.com/p2poolv2/p2poolv2?tag=v0.7.0#88d671c0c8c06f31c1cc1a63c08bc52e64eebb0e" +source = "git+https://github.com/average-gary/p2pool-v2?branch=sv2-support#9736403abec9ccb6cee3e6fca3a8d49b6c1d8b61" dependencies = [ "base64 0.22.1", "bitcoin", @@ -530,7 +534,16 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", ] [[package]] @@ -565,6 +578,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +dependencies = [ + "sha2 0.9.9", +] + [[package]] name = "bs58" version = "0.5.1" @@ -574,12 +596,26 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "buffer_sv2" +version = "3.0.1" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "aes-gcm", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytecheck" version = "0.6.12" @@ -690,6 +726,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "channels_sv2" +version = "4.0.0" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", + "bitcoin", + "mining_sv2", + "primitive-types", + "template_distribution_sv2", + "tracing", +] + [[package]] name = "chrono" version = "0.4.42" @@ -766,12 +815,33 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "codec_sv2" +version = "5.0.0" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", + "buffer_sv2", + "framing_sv2", + "noise_sv2", + "rand 0.8.5", + "tracing", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "common_messages_sv2" +version = "7.0.0" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -806,6 +876,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -943,7 +1033,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -1034,13 +1124,27 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_codec_sv2" +version = "1.1.2" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -1087,7 +1191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -1113,7 +1217,7 @@ dependencies = [ "curve25519-dalek", "ed25519", "serde", - "sha2", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -1132,7 +1236,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -1201,6 +1305,14 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "extensions_sv2" +version = "0.1.0" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1223,6 +1335,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1259,6 +1383,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "framing_sv2" +version = "6.0.1" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", + "noise_sv2", +] + [[package]] name = "funty" version = "2.0.0" @@ -1501,6 +1634,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlers_sv2" +version = "0.2.2" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", + "common_messages_sv2", + "extensions_sv2", + "framing_sv2", + "mining_sv2", + "parsers_sv2", + "trait-variant", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1545,6 +1692,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hex-conservative" version = "0.2.1" @@ -1621,7 +1774,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1993,6 +2146,26 @@ dependencies = [ "xmltree", ] +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "indexmap" version = "2.10.0" @@ -2066,15 +2239,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -2141,21 +2305,28 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", + "sha2 0.10.9", "signature", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "key-utils" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "ffe8551792fd4461e519fdfd8b8f334d1bf480250786fd202baf418854ff7130" +dependencies = [ + "bs58 0.4.0", + "rand 0.8.5", + "rustversion", + "secp256k1 0.28.2", + "serde", +] [[package]] -name = "lazycell" -version = "1.3.0" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -2305,14 +2476,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774" dependencies = [ "asn1_der", - "bs58", + "bs58 0.5.1", "ed25519-dalek", "hkdf", "k256", "multihash", "quick-protobuf", "rand 0.8.5", - "sha2", + "sha2 0.10.9", "thiserror 2.0.12", "tracing", "zeroize", @@ -2339,11 +2510,11 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "rand 0.8.5", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror 1.0.69", "tracing", - "uint", + "uint 0.9.5", "void", ] @@ -2402,7 +2573,7 @@ dependencies = [ "once_cell", "quick-protobuf", "rand 0.8.5", - "sha2", + "sha2 0.10.9", "snow", "static_assertions", "thiserror 1.0.69", @@ -2560,11 +2731,11 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.17.1+9.9.3" +version = "0.17.3+10.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7869a512ae9982f4d46ba482c2a304f1efd80c6412a3d4bf57bb79a619679f" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" dependencies = [ - "bindgen 0.69.5", + "bindgen", "bzip2-sys", "cc", "libc", @@ -2685,6 +2856,14 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mining_sv2" +version = "7.0.0" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2857,6 +3036,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "noise_sv2" +version = "1.4.2" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "rand 0.8.5", + "secp256k1 0.28.2", +] + [[package]] name = "nom" version = "7.1.3" @@ -3020,7 +3210,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "p2poolv2_api" version = "0.1.0" -source = "git+https://github.com/p2poolv2/p2poolv2?tag=v0.7.0#88d671c0c8c06f31c1cc1a63c08bc52e64eebb0e" +source = "git+https://github.com/average-gary/p2pool-v2?branch=sv2-support#9736403abec9ccb6cee3e6fca3a8d49b6c1d8b61" dependencies = [ "axum", "base64 0.22.1", @@ -3031,7 +3221,7 @@ dependencies = [ "p2poolv2_lib", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tokio", "tracing", "tracing-subscriber", @@ -3040,7 +3230,7 @@ dependencies = [ [[package]] name = "p2poolv2_cli" version = "0.1.0" -source = "git+https://github.com/p2poolv2/p2poolv2?tag=v0.7.0#88d671c0c8c06f31c1cc1a63c08bc52e64eebb0e" +source = "git+https://github.com/average-gary/p2pool-v2?branch=sv2-support#9736403abec9ccb6cee3e6fca3a8d49b6c1d8b61" dependencies = [ "base64 0.22.1", "clap", @@ -3051,7 +3241,7 @@ dependencies = [ "rand 0.8.5", "rpassword", "serde", - "sha2", + "sha2 0.10.9", "tokio", "tracing", "tracing-subscriber", @@ -3060,7 +3250,7 @@ dependencies = [ [[package]] name = "p2poolv2_lib" version = "0.1.0" -source = "git+https://github.com/p2poolv2/p2poolv2?tag=v0.7.0#88d671c0c8c06f31c1cc1a63c08bc52e64eebb0e" +source = "git+https://github.com/average-gary/p2pool-v2?branch=sv2-support#9736403abec9ccb6cee3e6fca3a8d49b6c1d8b61" dependencies = [ "async-trait", "base64 0.22.1", @@ -3071,12 +3261,16 @@ dependencies = [ "dashmap", "futures", "hex", + "key-utils", "libp2p", + "rand 0.8.5", "rocksdb", "rust_decimal", "rust_decimal_macros", "serde", "serde_json", + "stratum-core", + "tempfile", "thiserror 2.0.12", "tokio", "tokio-stream", @@ -3089,6 +3283,34 @@ dependencies = [ "zmq", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "parking" version = "2.2.1" @@ -3118,6 +3340,18 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parsers_sv2" +version = "0.2.2" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", + "common_messages_sv2", + "extensions_sv2", + "framing_sv2", + "mining_sv2", +] + [[package]] name = "paste" version = "1.0.15" @@ -3187,7 +3421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -3299,6 +3533,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint 0.10.0", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -3394,7 +3639,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2 0.5.10", "thiserror 2.0.12", @@ -3414,7 +3659,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring 0.17.14", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", @@ -3728,9 +3973,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec73b20525cb235bad420f911473b69f9fe27cc856c5461bccd7e4af037f43" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" dependencies = [ "libc", "librocksdb-sys", @@ -3830,15 +4075,15 @@ checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustc-hash" -version = "2.1.1" +name = "rustc-hex" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" @@ -3983,18 +4228,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand 0.8.5", + "secp256k1-sys 0.9.2", +] + [[package]] name = "secp256k1" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.0", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -4107,6 +4372,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4115,7 +4393,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -4148,7 +4426,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -4183,7 +4461,7 @@ dependencies = [ "rand_core 0.6.4", "ring 0.17.14", "rustc_version", - "sha2", + "sha2 0.10.9", "subtle", ] @@ -4235,6 +4513,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stratum-core" +version = "0.2.1" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", + "bitcoin", + "buffer_sv2", + "channels_sv2", + "codec_sv2", + "common_messages_sv2", + "extensions_sv2", + "framing_sv2", + "handlers_sv2", + "mining_sv2", + "noise_sv2", + "parsers_sv2", +] + [[package]] name = "strsim" version = "0.11.1" @@ -4348,6 +4645,14 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "template_distribution_sv2" +version = "5.0.0" +source = "git+https://github.com/average-gary/stratum?branch=feature-flag-subprotocols#4d35e317ab3388fa92024615a51f9415581717c9" +dependencies = [ + "binary_sv2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4692,6 +4997,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4722,12 +5038,30 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -5623,7 +5957,6 @@ version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ - "bindgen 0.71.1", "cc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 2d48c96..f8e209d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,10 +31,10 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } bitcoin = { version = "0.32.5", features = ["serde", "rand"] } tokio = { version = "1.0", features = ["full"] } -p2poolv2_lib = { git = "https://github.com/p2poolv2/p2poolv2", package = "p2poolv2_lib", tag = "v0.7.0" } -p2poolv2_cli = { git = "https://github.com/p2poolv2/p2poolv2", package = "p2poolv2_cli", tag = "v0.7.0" } -p2poolv2_api = { git = "https://github.com/p2poolv2/p2poolv2", package = "p2poolv2_api", tag = "v0.7.0" } -bitcoindrpc = { git = "https://github.com/p2poolv2/p2poolv2", package = "bitcoindrpc", tag = "v0.7.0" } +p2poolv2_lib = { git = "https://github.com/average-gary/p2pool-v2", package = "p2poolv2_lib", branch = "sv2-support", features = ["sv2"] } +p2poolv2_cli = { git = "https://github.com/average-gary/p2pool-v2", package = "p2poolv2_cli", branch = "sv2-support" } +p2poolv2_api = { git = "https://github.com/average-gary/p2pool-v2", package = "p2poolv2_api", branch = "sv2-support" } +bitcoindrpc = { git = "https://github.com/average-gary/p2pool-v2", package = "bitcoindrpc", branch = "sv2-support" } [[bin]] name = "hydrapool" diff --git a/config-testnet4.toml b/config-testnet4.toml new file mode 100644 index 0000000..af8173e --- /dev/null +++ b/config-testnet4.toml @@ -0,0 +1,43 @@ +[store] +path = "./store-testnet4.db" +background_task_frequency_hours = 24 +pplns_ttl_days = 1 + +[stratum] +hostname = "0.0.0.0" +port = 3333 +start_difficulty = 1 +minimum_difficulty = 1 +bootstrap_address = "tb1qs5kpecpax8uv4vn5uqfyycmmmma7jhunmce0z4" +donation_address = "tb1qs5kpecpax8uv4vn5uqfyycmmmma7jhunmce0z4" +donation = 10000 +ignore_difficulty = true +zmqpubhashblock = "tcp://127.0.0.1:28334" +network = "testnet4" +version_mask = "1fffe000" +difficulty_multiplier = 1.0 +pool_signature = "hydrapool-sv2" + +[stratum_sv2] +enabled = true +hostname = "0.0.0.0" +port = 3334 +authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" +authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" +cert_validity_seconds = 86400 +server_id = 0 + +[bitcoinrpc] +url = "http://127.0.0.1:48335" +username = "myusername" +password = "mypassword" + +[logging] +level = "info" +stats_dir = "./logs/stats-testnet4" + +[api] +hostname = "0.0.0.0" +port = 46884 +auth_user = "hydrapool" +auth_token = "0d74058f74ad3b6768493cedb5e9492b$2a2599d5f4003c291e141762d0e43808cfd932f04a8260e94c375d1574599dbf" diff --git a/config.toml b/config.toml index c669d7e..9145c52 100644 --- a/config.toml +++ b/config.toml @@ -33,6 +33,24 @@ difficulty_multiplier = 1.0 # to add a pool signature. Maximum length 16 bytes. pool_signature = "hydrapool" +# Stratum V2 Mining Protocol (optional, disabled by default) +# Runs alongside the SV1 server on a separate port. Both protocols +# feed validated shares into the same PPLNS accounting pipeline. +# [stratum_sv2] +# enabled = true +# hostname = "0.0.0.0" +# port = 3334 +# Noise NX keypair for encrypted connections. REQUIRED when SV2 is +# enabled — the server will refuse to start without both keys. +# Generate a keypair with: +# openssl rand -hex 32 # -> authority_secret_key +# Then derive the x-only public key from the secret key using secp256k1. +# authority_public_key = "<64 hex chars (32-byte x-only pubkey)>" +# authority_secret_key = "<64 hex chars (32-byte secret key)>" +# cert_validity_seconds = 86400 +# default_extranonce_size = 16 +# server_id = 0 + [bitcoinrpc] # RPC credentials are loaded from env vars url = "http://127.0.0.1:38332" diff --git a/docker/Dockerfile.hydrapool b/docker/Dockerfile.hydrapool index 5a31ff3..395279e 100644 --- a/docker/Dockerfile.hydrapool +++ b/docker/Dockerfile.hydrapool @@ -68,7 +68,9 @@ USER hydrapool # Set working directory WORKDIR /var/lib/hydrapool -# Expose API port (adjust based on your config) +# Expose service ports +EXPOSE 3333 +EXPOSE 3334 EXPOSE 46884 # Set environment diff --git a/docker/Dockerfile.mining-device b/docker/Dockerfile.mining-device new file mode 100644 index 0000000..6d5c3b3 --- /dev/null +++ b/docker/Dockerfile.mining-device @@ -0,0 +1,39 @@ +# Build the SRI mining-device (CPU SV2 miner) from source. +# Used for interoperability testing against Hydrapool's SV2 server. +# +# Build: docker build -f docker/Dockerfile.mining-device -t mining-device . +# (No local context needed; this clones from GitHub.) + +FROM rust:1.85-slim-bookworm AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +# Clone the sv2-apps repo (pinned to main for reproducibility). +ARG SV2_APPS_REF=main +RUN git clone --depth 1 --branch ${SV2_APPS_REF} \ + https://github.com/stratum-mining/sv2-apps.git . + +# Build the mining-device binary. +WORKDIR /build/miner-apps/mining-device +RUN cargo build --release + +# --- Runtime stage --- +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libssl3 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /build/miner-apps/mining-device/target/release/mining_device \ + /usr/local/bin/mining_device + +RUN chmod +x /usr/local/bin/mining_device + +ENTRYPOINT ["/usr/local/bin/mining_device"] diff --git a/docker/README.md b/docker/README.md index bf6329f..f131e45 100644 --- a/docker/README.md +++ b/docker/README.md @@ -46,11 +46,16 @@ docker compose ps ### 5. Access services -- **Stratum mining:** `stratum+tcp://localhost:3333` +- **Stratum V1 mining:** `stratum+tcp://localhost:3333` +- **Stratum V2 mining:** `stratum2+tcp://localhost:3334` (Noise NX encrypted, requires SV2-compatible miner or translator proxy; enable in config first) - **API server:** `http://localhost:46884` - **Grafana dashboard:** `http://localhost:3000` (if running with dashboards, login with credentials from step 2) - **Prometheus:** `http://localhost:9090` (if running with dashboards) +> **Note:** Stratum V2 is disabled by default. Uncomment and configure the +> `[stratum_sv2]` section in `config.toml` to enable it. A Noise NX keypair +> (`authority_public_key` / `authority_secret_key`) is required. + ## Updating Configuration After editing `config.toml`, restart Hydrapool: diff --git a/docker/config-example.toml b/docker/config-example.toml index b3cfc3e..69c8599 100644 --- a/docker/config-example.toml +++ b/docker/config-example.toml @@ -47,6 +47,24 @@ difficulty_multiplier = 1.0 # Comment out for anonymous mining or replace/append your identifier. pool_signature = "hydrapool" +# Stratum V2 Mining Protocol (optional, disabled by default) +# Runs alongside the SV1 server on a separate port. Both protocols +# feed validated shares into the same PPLNS accounting pipeline. +# [stratum_sv2] +# enabled = true +# hostname = "0.0.0.0" +# port = 3334 +# Noise NX keypair for encrypted connections. REQUIRED when SV2 is +# enabled — the server will refuse to start without both keys. +# Generate a keypair with: +# openssl rand -hex 32 # -> authority_secret_key +# Then derive the x-only public key from the secret key using secp256k1. +# authority_public_key = "<64 hex chars (32-byte x-only pubkey)>" +# authority_secret_key = "<64 hex chars (32-byte secret key)>" +# cert_validity_seconds = 86400 +# default_extranonce_size = 16 +# server_id = 0 + [bitcoinrpc] # REQUIRED: Bitcoin RPC connection # For Docker: use "http://host.docker.internal:38332" to connect to host diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 5ad5d97..a7a70cb 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -8,7 +8,7 @@ services: dockerfile: docker/Dockerfile.hydrapool image: hydrapool:dev networks: - - p2pool_network + - hydrapool_network hydrapool-cli: build: @@ -16,7 +16,7 @@ services: dockerfile: docker/Dockerfile.hydrapool image: hydrapool:dev networks: - - p2pool_network + - hydrapool_network prometheus: build: @@ -26,7 +26,7 @@ services: ports: - "9090:9090" networks: - - p2pool_network + - hydrapool_network extra_hosts: - "host.docker.internal:host-gateway" @@ -39,10 +39,10 @@ services: ports: - "3000:3000" networks: - - p2pool_network + - hydrapool_network extra_hosts: - "host.docker.internal:host-gateway" networks: - p2pool_network: + hydrapool_network: driver: bridge diff --git a/docker/docker-compose.interop.yml b/docker/docker-compose.interop.yml new file mode 100644 index 0000000..c2306a7 --- /dev/null +++ b/docker/docker-compose.interop.yml @@ -0,0 +1,88 @@ +# Docker Compose for SV2 interoperability testing. +# +# Services: +# bitcoind - Bitcoin Core in regtest mode (generates blocks) +# hydrapool - Hydrapool with SV2 enabled (built from local source) +# mining-device - SRI CPU miner connecting to Hydrapool via SV2 +# +# Usage: +# scripts/test-interop-sv2.sh # automated test (recommended) +# docker compose -f docker/docker-compose.interop.yml up --build # manual + +services: + bitcoind: + image: lncm/bitcoind:v28.0 + container_name: interop-bitcoind + command: + - -conf=/bitcoin/.bitcoin/bitcoin.conf + - -regtest + volumes: + - ./interop/bitcoin-regtest.conf:/bitcoin/.bitcoin/bitcoin.conf:ro + - bitcoind_data:/bitcoin/.bitcoin + ports: + - "18443:18443" + - "28332:28332" + networks: + - interop_network + healthcheck: + test: ["CMD", "bitcoin-cli", "-regtest", "-rpcuser=hydrapool", "-rpcpassword=hydrapool", "getblockchaininfo"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 10s + + hydrapool: + build: + context: .. + dockerfile: docker/Dockerfile.hydrapool + container_name: interop-hydrapool + depends_on: + bitcoind: + condition: service_healthy + volumes: + - ./interop/config-regtest.toml:/etc/hydrapool/config.toml:ro + - hydrapool_data:/var/lib/hydrapool + - hydrapool_logs:/var/log/hydrapool + ports: + - "3333:3333" + - "3334:3334" + - "46884:46884" + networks: + - interop_network + environment: + - RUST_LOG=debug + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:46884/health"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 30s + + mining-device: + build: + context: .. + dockerfile: docker/Dockerfile.mining-device + container_name: interop-mining-device + depends_on: + hydrapool: + condition: service_healthy + command: + - --address-pool + - hydrapool:3334 + - --id-user + - "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk.interop-rig1" + - --nominal-hashrate-multiplier + - "0.01" + networks: + - interop_network + environment: + - RUST_LOG=debug + +volumes: + bitcoind_data: + hydrapool_data: + hydrapool_logs: + +networks: + interop_network: + driver: bridge diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index ab085d0..a390b3d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,7 +10,8 @@ services: - hydrapool_logs:/var/log/hydrapool restart: unless-stopped ports: - - "3333:3333" # Stratum mining port + - "3333:3333" # SV1 Stratum mining port + - "3334:3334" # SV2 Stratum mining port (Noise NX encrypted) - "46884:46884" # API server port extra_hosts: - "host.docker.internal:host-gateway" diff --git a/docker/interop/bitcoin-regtest.conf b/docker/interop/bitcoin-regtest.conf new file mode 100644 index 0000000..febeb4b --- /dev/null +++ b/docker/interop/bitcoin-regtest.conf @@ -0,0 +1,29 @@ +# Bitcoin Core regtest configuration for SV2 interoperability testing. +# This runs a fully self-contained regtest node inside Docker. + +regtest=1 + +[regtest] +# RPC credentials (must match Hydrapool's [bitcoinrpc] section) +rpcuser=hydrapool +rpcpassword=hydrapool +rpcallowip=0.0.0.0/0 +rpcbind=0.0.0.0 +rpcport=18443 + +# ZMQ notifications for new blocks +zmqpubhashblock=tcp://0.0.0.0:28332 + +# Minimal resource usage +prune=550 +dbcache=64 +maxmempool=50 + +# Accept non-standard transactions in regtest +acceptnonstdtxn=1 + +# Generate blocks quickly +fallbackfee=0.0001 + +# Allow connections +listen=1 diff --git a/docker/interop/config-regtest.toml b/docker/interop/config-regtest.toml new file mode 100644 index 0000000..8521275 --- /dev/null +++ b/docker/interop/config-regtest.toml @@ -0,0 +1,54 @@ +# Hydrapool configuration for SV2 interoperability testing. +# +# Bitcoind runs in regtest for fast block generation, but Hydrapool +# uses signet for the share chain (p2poolv2_lib doesn't support regtest). +# +# This config is used by the docker-compose.interop.yml setup. +# The authority keypair must be generated before starting the test +# (see scripts/test-interop-sv2.sh). + +[store] +path = "/var/lib/hydrapool/store.db" +background_task_frequency_hours = 24 +pplns_ttl_days = 1 + +[stratum] +hostname = "0.0.0.0" +port = 3333 +start_difficulty = 1 +minimum_difficulty = 1 +bootstrap_address = "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk" +donation_address = "tb1qyazxde6558qj6z3d9np5e6msmrspwpf6k0qggk" +donation = 10000 +ignore_difficulty = true +zmqpubhashblock = "tcp://bitcoind:28332" +network = "signet" +version_mask = "1fffe000" +difficulty_multiplier = 1.0 +pool_signature = "hydrapool-sv2" + +[stratum_sv2] +enabled = true +hostname = "0.0.0.0" +port = 3334 +# These keys are injected by the test script via sed before starting. +# Generate with: scripts/test-interop-sv2.sh (which calls keygen) +authority_public_key = "AUTHORITY_PUBLIC_KEY_PLACEHOLDER" +authority_secret_key = "AUTHORITY_SECRET_KEY_PLACEHOLDER" +cert_validity_seconds = 86400 +default_extranonce_size = 16 +server_id = 0 + +[bitcoinrpc] +url = "http://bitcoind:18443" +username = "hydrapool" +password = "hydrapool" + +[logging] +level = "debug" +stats_dir = "/var/log/hydrapool/stats" + +[api] +hostname = "0.0.0.0" +port = 46884 +# No auth for interop testing (auth_user and auth_token omitted) diff --git a/docs/sv2-interop.md b/docs/sv2-interop.md new file mode 100644 index 0000000..d96be20 --- /dev/null +++ b/docs/sv2-interop.md @@ -0,0 +1,185 @@ +# SV2 Interoperability Testing + +This document describes how to test Hydrapool's Stratum V2 (SV2) server +against third-party SV2 implementations and documents the compatibility +matrix. + +## Quick Start + +Run the automated interop test: + +```bash +./scripts/test-interop-sv2.sh +``` + +This spins up a Bitcoin Core regtest node, Hydrapool with SV2 enabled, and +the SRI mining-device in Docker containers. The script verifies the full +SV2 mining lifecycle completes successfully. + +### Prerequisites + +- Docker and Docker Compose (v2) +- ~4 GB free disk space (for Rust build caches) +- ~10 minutes for first run (building the mining-device from source) + +### Options + +```bash +./scripts/test-interop-sv2.sh # Run test, clean up after +./scripts/test-interop-sv2.sh --keep # Keep containers for debugging +``` + +### Manual Testing + +```bash +# Start services manually +docker compose -f docker/docker-compose.interop.yml up --build + +# In another terminal, generate blocks to trigger mining work +docker compose -f docker/docker-compose.interop.yml exec bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + generatetoaddress 1 $(docker compose -f docker/docker-compose.interop.yml exec bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool getnewaddress) + +# Watch logs +docker compose -f docker/docker-compose.interop.yml logs -f mining-device +docker compose -f docker/docker-compose.interop.yml logs -f hydrapool +``` + +## Architecture + +``` + regtest + +----------+ ZMQ/RPC +-----------+ SV2 (Noise NX) +----------------+ + | bitcoind | <-------------> | Hydrapool | <--------------------> | mining-device | + | (regtest)| | (pool) | port 3334 | (SRI CPU miner)| + +----------+ +-----------+ +----------------+ + | | + | SV1 port 3333 + | API port 46884 + | + generates blocks + via bitcoin-cli +``` + +Both SV1 and SV2 miners feed validated shares into the same PPLNS +accounting pipeline. The SV2 server runs on a separate port alongside the +SV1 server. + +## Compatibility Matrix + +### Supported SV2 Features + +| Feature | Status | Notes | +|---------|--------|-------| +| Noise NX encryption | Supported | Certificate validation optional | +| SetupConnection | Supported | Mining protocol only | +| Standard Mining Channels | Supported | One group per connection | +| NewMiningJob (standard) | Supported | Pre-computed merkle root | +| SetNewPrevHash | Supported | Sent on new blocks | +| SubmitSharesStandard | Supported | Full PoW validation | +| SubmitSharesSuccess/Error | Supported | | +| SetTarget (vardiff) | Supported | Automatic difficulty adjustment | +| Multiple channels per connection | Supported | Share same group | +| Late-connect bootstrap | Supported | New channels get current job | +| Future jobs | Partial | Infrastructure exists, not yet used | +| Extended channels | Not supported | Planned (Issue #8) | +| SetCustomMiningJob | Not supported | | +| Job negotiation subprotocol | Not supported | | +| Template distribution subprotocol | Not supported | | + +### Protocol Version + +- SV2 protocol version: 2 +- Minimum supported version: 2 +- Maximum supported version: 2 + +### Encryption + +- Noise NX handshake with secp256k1 keypairs +- Certificate validity configurable (default: 86400 seconds) +- Clients can connect with or without server pubkey validation + +## Tested Clients + +### SRI mining-device (stratum-mining/sv2-apps) + +The reference SV2 CPU mining device from the Stratum Reference +Implementation project. + +| Property | Value | +|----------|-------| +| Repository | https://github.com/stratum-mining/sv2-apps | +| Component | `miner-apps/mining-device` | +| Branch tested | `main` | +| Connection type | Standard channels, no pubkey validation | +| Test command | `--address-pool : --nominal-hashrate-multiplier 0.01` | + +**Recommended flags for testing:** + +- `--nominal-hashrate-multiplier 0.01` — Advertise 1% of real hashrate, + causing the pool to set very low difficulty. This makes shares arrive + within seconds instead of minutes. +- `--id-user .` — The pool parses this as + `btc_address.worker_name` for PPLNS accounting. + +### SRI translator (stratum-mining/sv2-apps) + +The SV1-to-SV2 translation proxy. Allows SV1 miners to connect to an +SV2 pool through the translator. + +| Property | Value | +|----------|-------| +| Repository | https://github.com/stratum-mining/sv2-apps | +| Component | `miner-apps/translator` | +| Status | Not yet tested | + +## Known Limitations + +1. **Standard channels only**: Extended channels (Issue #8) are not yet + implemented. Miners requiring custom coinbase space (e.g., for merged + mining) are not supported. + +2. **No job negotiation**: The pool fully controls the block template. + Miners cannot propose their own transaction sets. + +3. **Single protocol version**: Only SV2 protocol version 2 is supported. + Version negotiation always resolves to version 2. + +4. **Fixed pool-side coinbase**: The pool constructs the full coinbase + transaction. Standard channel miners can only manipulate nonce, ntime, + and version bits (BIP320). + +5. **No SetExtranoncePrefix**: The extranonce is assigned at channel open + time and cannot be changed during the channel's lifetime. + +## Troubleshooting + +### Mining-device can't connect + +Check that: +- Hydrapool is running and the SV2 port (default 3334) is accessible +- The `[stratum_sv2]` section is enabled in config +- The authority keypair is valid (both public and secret keys must be set) + +### No shares being submitted + +- Use `--nominal-hashrate-multiplier 0.01` to get low difficulty +- Generate blocks on the regtest node to trigger fresh templates +- Check Hydrapool logs for `SV2 standard mining channel opened` + +### Connection drops after handshake + +- Check for `SetupConnection` errors in Hydrapool debug logs +- Ensure the mining-device protocol version matches (version 2) +- Verify the `flags` field is compatible (standard jobs = 0x01) + +## File Locations + +| File | Purpose | +|------|---------| +| `docker/docker-compose.interop.yml` | Docker Compose for interop test | +| `docker/Dockerfile.mining-device` | Builds SRI mining-device from source | +| `docker/interop/config-regtest.toml` | Hydrapool config for regtest + SV2 | +| `docker/interop/bitcoin-regtest.conf` | Bitcoin Core regtest configuration | +| `scripts/test-interop-sv2.sh` | Automated interop test script | diff --git a/scripts/test-interop-sv2.sh b/scripts/test-interop-sv2.sh new file mode 100755 index 0000000..8e77cbd --- /dev/null +++ b/scripts/test-interop-sv2.sh @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +# SV2 Interoperability Test +# +# Runs a Bitcoin Core regtest node, Hydrapool with SV2 enabled, and +# the SRI mining-device (CPU SV2 miner) in Docker containers. Verifies +# the mining-device can complete the full SV2 mining lifecycle: +# 1. Noise NX handshake +# 2. SetupConnection +# 3. OpenStandardMiningChannel +# 4. Receive NewMiningJob + SetNewPrevHash +# 5. Submit shares +# +# Usage: +# ./scripts/test-interop-sv2.sh [--keep] +# +# Options: +# --keep Don't tear down containers after test (for debugging) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +COMPOSE_FILE="$REPO_DIR/docker/docker-compose.interop.yml" +CONFIG_FILE="$REPO_DIR/docker/interop/config-regtest.toml" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +KEEP_CONTAINERS=false +if [[ "${1:-}" == "--keep" ]]; then + KEEP_CONTAINERS=true +fi + +cleanup() { + if [[ "$KEEP_CONTAINERS" == false ]]; then + echo -e "${YELLOW}Cleaning up containers...${NC}" + docker compose -f "$COMPOSE_FILE" down -v --remove-orphans 2>/dev/null || true + else + echo -e "${YELLOW}Keeping containers running (--keep). Clean up with:${NC}" + echo " docker compose -f $COMPOSE_FILE down -v" + fi +} + +trap cleanup EXIT + +fail() { + echo -e "${RED}FAIL: $1${NC}" >&2 + # Dump logs on failure + echo -e "${YELLOW}--- Hydrapool logs ---${NC}" + docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | tail -50 || true + echo -e "${YELLOW}--- Mining-device logs ---${NC}" + docker compose -f "$COMPOSE_FILE" logs mining-device 2>/dev/null | tail -50 || true + exit 1 +} + +pass() { + echo -e "${GREEN}PASS: $1${NC}" +} + +info() { + echo -e "${YELLOW}>>> $1${NC}" +} + +# ----------------------------------------------------------------------- +# Step 0: Authority keypair for Noise NX +# ----------------------------------------------------------------------- +info "Using pre-computed Noise NX authority keypair..." + +# Pre-computed secp256k1 keypair with even-parity x-only public key. +# Derived from SHA-256("") with parity negation applied. +# Verified against Responder::from_authority_kp() in the SRI noise-sv2 crate. +SECRET_KEY_HEX="1c4f3bbd6703e3eb65040b37669046da93009b024aad0cef1b3cc57157e388ec" +PUBLIC_KEY_HEX="a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd" + +info "Authority secret key: ${SECRET_KEY_HEX:0:8}..." +info "Authority public key: ${PUBLIC_KEY_HEX:0:8}..." + +# ----------------------------------------------------------------------- +# Step 1: Inject keypair into config +# ----------------------------------------------------------------------- +info "Injecting keypair into regtest config..." + +# Create a temporary copy of the config with the real keys +TEMP_CONFIG=$(mktemp) +sed \ + -e "s/AUTHORITY_PUBLIC_KEY_PLACEHOLDER/${PUBLIC_KEY_HEX}/" \ + -e "s/AUTHORITY_SECRET_KEY_PLACEHOLDER/${SECRET_KEY_HEX}/" \ + "$CONFIG_FILE" > "$TEMP_CONFIG" + +# Copy back (the Docker volume mount reads from the original location) +cp "$TEMP_CONFIG" "$CONFIG_FILE.active" +rm "$TEMP_CONFIG" + +# We need to mount the .active file. Update the compose command to use it. +# Actually, let's just overwrite in-place and restore later. +cp "$CONFIG_FILE" "$CONFIG_FILE.bak" +cp "$CONFIG_FILE.active" "$CONFIG_FILE" + +restore_config() { + if [[ -f "$CONFIG_FILE.bak" ]]; then + mv "$CONFIG_FILE.bak" "$CONFIG_FILE" + fi + rm -f "$CONFIG_FILE.active" +} +trap 'restore_config; cleanup' EXIT + +# ----------------------------------------------------------------------- +# Step 2: Build and start services +# ----------------------------------------------------------------------- +info "Building Docker images (this may take several minutes on first run)..." +docker compose -f "$COMPOSE_FILE" build --quiet 2>&1 || \ + fail "Docker build failed" + +info "Starting bitcoind..." +docker compose -f "$COMPOSE_FILE" up -d bitcoind +info "Waiting for bitcoind to be ready..." + +# Wait for bitcoind healthcheck +for i in $(seq 1 60); do + if docker compose -f "$COMPOSE_FILE" exec -T bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + getblockchaininfo &>/dev/null; then + break + fi + if [[ $i -eq 60 ]]; then + fail "bitcoind did not become ready within 60 seconds" + fi + sleep 1 +done +pass "bitcoind is running (regtest)" + +# ----------------------------------------------------------------------- +# Step 3: Generate initial blocks (need 101 for coinbase maturity) +# ----------------------------------------------------------------------- +info "Generating 101 regtest blocks for coinbase maturity..." + +# Create a wallet first +docker compose -f "$COMPOSE_FILE" exec -T bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + createwallet "interop" 2>/dev/null || true + +MINER_ADDRESS=$(docker compose -f "$COMPOSE_FILE" exec -T bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + getnewaddress "miner" "bech32" 2>/dev/null) + +docker compose -f "$COMPOSE_FILE" exec -T bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + generatetoaddress 101 "$MINER_ADDRESS" >/dev/null 2>&1 || \ + fail "Failed to generate initial blocks" + +BLOCK_COUNT=$(docker compose -f "$COMPOSE_FILE" exec -T bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + getblockcount 2>/dev/null) +pass "Generated $BLOCK_COUNT regtest blocks" + +# ----------------------------------------------------------------------- +# Step 4: Start Hydrapool +# ----------------------------------------------------------------------- +info "Starting Hydrapool with SV2 enabled..." +docker compose -f "$COMPOSE_FILE" up -d hydrapool + +info "Waiting for Hydrapool to be ready..." +for i in $(seq 1 120); do + if docker compose -f "$COMPOSE_FILE" exec -T hydrapool \ + wget -q --spider http://127.0.0.1:46884/health 2>/dev/null; then + break + fi + if [[ $i -eq 120 ]]; then + echo -e "${YELLOW}--- Hydrapool logs ---${NC}" + docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | tail -30 + fail "Hydrapool did not become ready within 120 seconds" + fi + sleep 1 +done +pass "Hydrapool is running with SV2 on port 3334" + +# ----------------------------------------------------------------------- +# Step 5: Generate a block to trigger GBT template +# ----------------------------------------------------------------------- +info "Generating a block to trigger a fresh GBT template..." +docker compose -f "$COMPOSE_FILE" exec -T bitcoind \ + bitcoin-cli -regtest -rpcuser=hydrapool -rpcpassword=hydrapool \ + generatetoaddress 1 "$MINER_ADDRESS" >/dev/null 2>&1 + +# Give Hydrapool time to process the ZMQ notification and build the template +sleep 3 +pass "Block generated, Hydrapool should have a fresh template" + +# ----------------------------------------------------------------------- +# Step 6: Start the SRI mining-device +# ----------------------------------------------------------------------- +info "Starting SRI mining-device (SV2 CPU miner)..." +docker compose -f "$COMPOSE_FILE" up -d mining-device + +# ----------------------------------------------------------------------- +# Step 7: Wait for shares to be submitted +# ----------------------------------------------------------------------- +info "Waiting for mining-device to submit shares (up to 120s)..." + +# Helper: check pool logs for a pattern (pipes directly, avoids capturing 50MB+) +# Uses grep -m1 to stop after first match, and disables pipefail locally to +# avoid SIGPIPE errors when grep exits before docker compose finishes writing. +pool_log_has() { + set +o pipefail + docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | grep -q -m1 "$1" + local rc=$? + set -o pipefail + return $rc +} + +SHARES_FOUND=false +for i in $(seq 1 120); do + # Check Hydrapool logs for SV2 share validation (most reliable signal) + if pool_log_has "validated SV2 share"; then + SHARES_FOUND=true + break + fi + + # Check if mining-device exited (it may crash after submitting shares) + if ! docker compose -f "$COMPOSE_FILE" ps mining-device --status running 2>/dev/null | grep -q mining-device; then + # Container is not running — give pool a moment to flush logs, then check + sleep 2 + if pool_log_has "validated SV2 share" || pool_log_has "SV2 standard mining channel opened"; then + SHARES_FOUND=true + fi + break + fi + + if [[ $i -eq 120 ]]; then + echo -e "${YELLOW}--- Mining-device logs ---${NC}" + docker compose -f "$COMPOSE_FILE" logs mining-device 2>/dev/null | tail -30 + echo -e "${YELLOW}--- Hydrapool logs ---${NC}" + docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | tail -30 + fail "No SV2 mining activity detected within 120 seconds" + fi + sleep 1 +done + +# ----------------------------------------------------------------------- +# Step 8: Verify results +# ----------------------------------------------------------------------- +echo "" +echo "========================================" +echo " SV2 Interoperability Test Results" +echo "========================================" +echo "" + +# Check each phase (pipe directly into grep to avoid capturing huge logs) +PHASE_RESULTS=() + +# Phase 1: Noise handshake +if pool_log_has "SV2 Noise handshake completed"; then + pass "Phase 1: Noise NX handshake completed" + PHASE_RESULTS+=(1) +else + echo -e "${RED}FAIL: Phase 1: Noise NX handshake not detected${NC}" +fi + +# Phase 2: SetupConnection +if pool_log_has "SetupConnection succeeded"; then + pass "Phase 2: SetupConnection exchange succeeded" + PHASE_RESULTS+=(1) +else + echo -e "${RED}FAIL: Phase 2: SetupConnection not detected${NC}" +fi + +# Phase 3: OpenStandardMiningChannel +if pool_log_has "SV2 standard mining channel opened"; then + pass "Phase 3: OpenStandardMiningChannel succeeded" + PHASE_RESULTS+=(1) +else + echo -e "${RED}FAIL: Phase 3: Channel open not detected${NC}" +fi + +# Phase 4: Job distribution +if pool_log_has "built SV2 NewMiningJob"; then + pass "Phase 4: Job distribution working (NewMiningJob + SetNewPrevHash)" + PHASE_RESULTS+=(1) +else + echo -e "${RED}FAIL: Phase 4: Job distribution not detected${NC}" +fi + +# Phase 5: Share submission +if pool_log_has "validated SV2 share"; then + pass "Phase 5: Share submission and validation working" + PHASE_RESULTS+=(1) + # Count shares for extra info (disable pipefail for pipe safety) + set +o pipefail + SHARE_COUNT=$(docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | grep -c "validated SV2 share" || true) + EMITTED_COUNT=$(docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | grep -c "emitted SV2 share" || true) + NETWORK_COUNT=$(docker compose -f "$COMPOSE_FILE" logs hydrapool 2>/dev/null | grep -c "share meets Bitcoin network difficulty" || true) + set -o pipefail + info " Shares validated: $SHARE_COUNT, emitted to pipeline: $EMITTED_COUNT, met network difficulty: $NETWORK_COUNT" +else + echo -e "${YELLOW}NOTE: Phase 5: No share submissions detected (may need more time)${NC}" +fi + +echo "" +TOTAL_PHASES=${#PHASE_RESULTS[@]} +if [[ $TOTAL_PHASES -ge 3 ]]; then + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN} INTEROP TEST PASSED ($TOTAL_PHASES/5 phases verified)${NC}" + echo -e "${GREEN}========================================${NC}" + exit 0 +else + echo -e "${RED}========================================${NC}" + echo -e "${RED} INTEROP TEST INCOMPLETE ($TOTAL_PHASES/5 phases verified)${NC}" + echo -e "${RED}========================================${NC}" + echo "" + echo "Check container logs for details:" + echo " docker compose -f $COMPOSE_FILE logs hydrapool" + echo " docker compose -f $COMPOSE_FILE logs mining-device" + exit 1 +fi diff --git a/src/bin/hydrapool_cli.rs b/src/bin/hydrapool_cli.rs index b171e43..6b28fda 100644 --- a/src/bin/hydrapool_cli.rs +++ b/src/bin/hydrapool_cli.rs @@ -17,6 +17,7 @@ use p2poolv2_cli::commands; use std::error::Error; -fn main() -> Result<(), Box> { - commands::run() +#[tokio::main] +async fn main() -> Result<(), Box> { + commands::run().await } diff --git a/src/main.rs b/src/main.rs index b995919..0bf8a40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,9 +20,10 @@ use p2poolv2_lib::accounting::stats::metrics; use p2poolv2_lib::config::Config; use p2poolv2_lib::logging::setup_logging; use p2poolv2_lib::node::actor::NodeHandle; -use p2poolv2_lib::shares::chain::chain_store::ChainStore; +use p2poolv2_lib::shares::chain::chain_store_handle::ChainStoreHandle; use p2poolv2_lib::shares::share_block::ShareBlock; use p2poolv2_lib::store::Store; +use p2poolv2_lib::store::writer::{StoreHandle, StoreWriter, write_channel}; use p2poolv2_lib::stratum::client_connections::start_connections_handler; use p2poolv2_lib::stratum::emission::Emission; use p2poolv2_lib::stratum::server::StratumServerBuilder; @@ -30,6 +31,11 @@ use p2poolv2_lib::stratum::work::gbt::start_gbt; use p2poolv2_lib::stratum::work::notify::start_notify; use p2poolv2_lib::stratum::work::tracker::start_tracker_actor; use p2poolv2_lib::stratum::zmq_listener::{ZmqListener, ZmqListenerTrait}; +use p2poolv2_lib::stratum_sv2::channels::start_channel_manager; +use p2poolv2_lib::stratum_sv2::connection::{AuthorityKeypair, Sv2ServerConfig, run_accept_loop}; +use p2poolv2_lib::stratum_sv2::connections::start_sv2_connections_handler; +use p2poolv2_lib::stratum_sv2::handler::{Sv2ConnectionContext, handle_sv2_connection}; +use p2poolv2_lib::stratum_sv2::job_distributor::start_job_distributor; use std::process::exit; use std::sync::Arc; use std::time::Duration; @@ -122,14 +128,26 @@ async fn main() -> Result<(), String> { let genesis = ShareBlock::build_genesis_for_network(config.stratum.network); let store = Arc::new(Store::new(config.store.path.clone(), false).unwrap()); - let chain_store = Arc::new(ChainStore::new( - store.clone(), - genesis, - config.stratum.network, - )); - - let tip = chain_store.store.get_chain_tip(); - let height = chain_store.get_tip_height(); + + // Create StoreWriter for serialized database writes (runs on dedicated blocking thread) + let (write_tx, write_rx) = write_channel(); + let store_writer = StoreWriter::new(store.clone(), write_rx); + tokio::task::spawn_blocking(move || store_writer.run()); + + // Create StoreHandle and ChainStoreHandle + let store_handle = StoreHandle::new(store.clone(), write_tx); + let chain_store_handle = ChainStoreHandle::new(store_handle, config.stratum.network); + + if let Err(e) = chain_store_handle + .init_or_setup_genesis(genesis.clone()) + .await + { + error!("Failed to initialize chain: {e}"); + return Err(format!("Failed to initialize chain: {e}")); + } + + let tip = chain_store_handle.store_handle().get_chain_tip(); + let height = chain_store_handle.get_tip_height(); info!("Latest tip {:?} at height {:?}", tip, height); let background_tasks_store = store.clone(); @@ -176,26 +194,136 @@ async fn main() -> Result<(), String> { let connections_cloned = connections_handle.clone(); let tracker_handle_cloned = tracker_handle.clone(); - let store_for_notify = chain_store.clone(); + let chain_store_handle_for_notify = chain_store_handle.clone(); + let miner_pubkey = config + .miner + .as_ref() + .map(|miner_config| miner_config.pubkey); + + // SV2 job distributor handle will be passed to start_notify() so that new + // templates are forwarded to SV2 miners via the same GBT pipeline. + // We set this to Some(...) below if SV2 is enabled, otherwise it stays None. + let mut sv2_job_distributor_for_notify: Option< + p2poolv2_lib::stratum_sv2::job_distributor::Sv2JobDistributorHandle, + > = None; + // Placeholder — will be set in the SV2 startup block below, used + // when spawning the notify task after the SV2 section. + let _ = &sv2_job_distributor_for_notify; + + let (emissions_tx, emissions_rx) = + tokio::sync::mpsc::channel::(STRATUM_SHARES_BUFFER_SIZE); + + // --- SV2 Server Startup --- + // Clone emissions_tx before SV1 takes ownership, so SV2 shares feed + // into the same accounting pipeline. + let mut _sv2_shutdown_tx: Option> = None; + if let Some(ref sv2_config) = config.stratum_sv2 { + if sv2_config.enabled { + if let Err(e) = sv2_config.validate() { + error!("Invalid SV2 config: {e}"); + return Err(format!("Invalid SV2 config: {e}")); + } + + let sv2_emissions_tx = emissions_tx.clone(); + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + _sv2_shutdown_tx = Some(shutdown_tx); + + // Resolve authority keypair + let authority = match ( + &sv2_config.authority_public_key, + &sv2_config.authority_secret_key, + ) { + (Some(pk), Some(sk)) => { + AuthorityKeypair::from_config(pk, sk, sv2_config.cert_validity_seconds) + .map_err(|e| format!("SV2 keypair error: {e}"))? + } + _ => { + error!("SV2 enabled but authority_public_key/authority_secret_key not set"); + return Err( + "SV2 requires authority_public_key and authority_secret_key in config" + .to_string(), + ); + } + }; + + let sv2_server_config = Sv2ServerConfig { + hostname: sv2_config.hostname.clone(), + port: sv2_config.port, + authority, + }; + + // Start SV2 actors + let sv2_connections = start_sv2_connections_handler().await; + let sv2_job_dist = start_job_distributor(sv2_config.server_id); + + // Default target: difficulty 1 (all 0xff except first 4 bytes) + let mut default_target = [0xff; 32]; + default_target[0..4].copy_from_slice(&[0x00, 0x00, 0xff, 0xff]); + let sv2_channels = start_channel_manager(sv2_config.server_id, default_target); + + // Determine validate_addresses from donation config + let sv2_validate_addresses = + stratum_config.donation.unwrap_or_default() != FULL_DONATION_BIPS; + + // Wire the SV2 job distributor into the notify pipeline so + // new block templates are forwarded to SV2 miners. + sv2_job_distributor_for_notify = Some(sv2_job_dist.clone()); + + // Build the shared context for per-connection handlers + let sv2_ctx = Sv2ConnectionContext { + connections: sv2_connections, + channels: sv2_channels, + job_distributor: sv2_job_dist, + emissions_tx: sv2_emissions_tx, + chain_store: chain_store_handle.clone(), + validate_addresses: sv2_validate_addresses, + network: stratum_config.network, + }; + + // Start the TCP accept loop for SV2 + let (handshake_tx, mut handshake_rx) = tokio::sync::mpsc::channel(64); + tokio::spawn(async move { + if let Err(e) = run_accept_loop(sv2_server_config, handshake_tx, shutdown_rx).await + { + error!("SV2 accept loop error: {e}"); + } + }); + + // Spawn per-connection handlers as handshakes complete + let sv2_ctx_for_loop = sv2_ctx.clone(); + tokio::spawn(async move { + while let Some(handshake) = handshake_rx.recv().await { + let ctx = sv2_ctx_for_loop.clone(); + tokio::spawn(async move { + handle_sv2_connection(handshake, ctx).await; + }); + } + }); + + info!( + "SV2 server listening on {}:{} (Noise NX encrypted)", + sv2_config.hostname, sv2_config.port + ); + } + } + // Start the notify task AFTER the SV2 block so sv2_job_distributor_for_notify + // is set when SV2 is enabled. let cloned_stratum_config = stratum_config.clone(); tokio::spawn(async move { info!("Starting Stratum notifier..."); - // This will run indefinitely, sending new block templates to the Stratum server as they arrive start_notify( notify_rx, connections_cloned, - store_for_notify, + chain_store_handle_for_notify, tracker_handle_cloned, &cloned_stratum_config, - None, + miner_pubkey, + sv2_job_distributor_for_notify, ) .await; }); - let (emissions_tx, emissions_rx) = - tokio::sync::mpsc::channel::(STRATUM_SHARES_BUFFER_SIZE); - let metrics_handle = match metrics::start_metrics(config.logging.stats_dir.clone()).await { Ok(handle) => handle, Err(e) => { @@ -205,7 +333,7 @@ async fn main() -> Result<(), String> { let metrics_cloned = metrics_handle.clone(); let metrics_for_shutdown = metrics_handle.clone(); let stats_dir_for_shutdown = config.logging.stats_dir.clone(); - let store_for_stratum = chain_store.clone(); + let chain_store_handle_for_stratum = chain_store_handle.clone(); let tracker_handle_cloned = tracker_handle.clone(); tokio::spawn(async move { @@ -224,7 +352,7 @@ async fn main() -> Result<(), String> { )) // 100% donation in bips, skip address validation .network(stratum_config.network) .version_mask(stratum_config.version_mask) - .store(store_for_stratum) + .chain_store_handle(chain_store_handle_for_stratum) .build() .await .unwrap(); @@ -246,7 +374,7 @@ async fn main() -> Result<(), String> { let api_shutdown_tx = match start_api_server( config.api.clone(), - chain_store.clone(), + chain_store_handle.clone(), metrics_handle.clone(), tracker_handle, stratum_config.network, @@ -265,7 +393,7 @@ async fn main() -> Result<(), String> { config.api.hostname, config.api.port ); - match NodeHandle::new(config, chain_store, emissions_rx, metrics_handle).await { + match NodeHandle::new(config, chain_store_handle, emissions_rx, metrics_handle).await { Ok((node_handle, stopping_rx)) => { info!("Node started"); @@ -293,6 +421,11 @@ async fn main() -> Result<(), String> { .send(()) .expect("Failed to send shutdown signal to Stratum server"); + if let Some(sv2_tx) = _sv2_shutdown_tx.take() { + let _ = sv2_tx.send(()); + info!("SV2 server stopped"); + } + api_shutdown_tx .send(()) .expect("Failed to send shutdown signal to API server"); diff --git a/translator-testnet4.toml b/translator-testnet4.toml new file mode 100644 index 0000000..2ee408e --- /dev/null +++ b/translator-testnet4.toml @@ -0,0 +1,36 @@ +# SRI Translator Proxy Config — connects SV1 miners to hydrapool SV2 +# +# Upstream: hydrapool SV2 on 127.0.0.1:3334 +# Downstream: SV1 miners connect to 0.0.0.0:34255 + +downstream_address = "0.0.0.0" +downstream_port = 34255 + +max_supported_version = 2 +min_supported_version = 2 + +# Extranonce2 size for downstream SV1 miners (4 bytes is standard) +downstream_extranonce2_size = 4 + +# User identity sent to hydrapool — must be a valid testnet4 address +# since hydrapool validates usernames as Bitcoin addresses +user_identity = "tb1qs5kpecpax8uv4vn5uqfyycmmmma7jhunmce0z4" + +# Each miner gets its own upstream channel (not aggregated) +aggregate_channels = false + +supported_extensions = [] +required_extensions = [] + +# Very low hashrate floor for CPU mining on testnet4 +[downstream_difficulty_config] +min_individual_miner_hashrate = 1000.0 +shares_per_minute = 6.0 +enable_vardiff = true +job_keepalive_interval_secs = 60 + +# Connect to hydrapool SV2 +[[upstreams]] +address = "127.0.0.1" +port = 3334 +authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"