From 168ee31c606ba9514810bf41347d83b695642753 Mon Sep 17 00:00:00 2001 From: acud <12988138+acud@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:01:28 -0600 Subject: [PATCH] feat: k2pow service (#362) Added support to do k2pow via an external service that can be load-balanced. --- .github/workflows/ci.yml | 11 + Cargo.lock | 945 ++++++++++++++++++++++++++++++- Cargo.toml | 4 + benches/verifying.rs | 2 + certifier/tests/test_certify.rs | 2 + ffi/src/post_impl.rs | 2 + k2pow-service/Cargo.toml | 23 + k2pow-service/src/job_manager.rs | 219 +++++++ k2pow-service/src/main.rs | 296 ++++++++++ scrypt-ocl/src/lib.rs | 6 +- service/Cargo.toml | 1 + service/src/main.rs | 30 +- service/src/service.rs | 30 +- service/tests/test_client.rs | 1 + service/tests/test_operator.rs | 1 + service/tests/test_service.rs | 58 ++ src/pow/mod.rs | 12 + src/pow/randomx.rs | 24 +- src/pow/service.rs | 113 ++++ src/prove.rs | 59 +- tests/generate_and_verify.rs | 5 +- 21 files changed, 1790 insertions(+), 54 deletions(-) create mode 100644 k2pow-service/Cargo.toml create mode 100644 k2pow-service/src/job_manager.rs create mode 100644 k2pow-service/src/main.rs create mode 100644 src/pow/service.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ceace61..4c22ac5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -228,6 +228,17 @@ jobs: path: | target/release/post-service${{ matrix.os == 'windows-2019' && '.exe' || '' }} if-no-files-found: error + - name: Build k2pow service + run: cargo build -p k2pow-service --release + env: + RUSTFLAGS: ${{ matrix.rustflags }} + - name: Archive k2pow service artifacts + uses: actions/upload-artifact@v4 + with: + name: k2pow-service-${{ matrix.artifact-name }} + path: | + target/release/k2pow-service${{ matrix.os == 'windows-2019' && '.exe' || '' }} + if-no-files-found: error release: name: Publish release diff --git a/Cargo.lock b/Cargo.lock index 0a86787b..b0286ec4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -147,6 +156,171 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-object-pool" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.1", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.1", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -169,6 +343,12 @@ dependencies = [ "syn 2.0.74", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.78" @@ -209,9 +389,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.4.0", "hyper-util", "itoa", "matchit", @@ -242,7 +422,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "mime", "pin-project-lite", @@ -263,7 +443,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body", + "http-body 1.0.0", "matchit", "metrics", "metrics-exporter-prometheus", @@ -274,6 +454,34 @@ dependencies = [ "tower-http", ] +[[package]] +name = "axum-test" +version = "15.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac63648e380fd001402a02ec804e7686f9c4751f8cad85b7de0b53dae483a128" +dependencies = [ + "anyhow", + "auto-future", + "axum", + "bytes", + "cookie", + "http 1.1.0", + "http-body-util", + "hyper 1.4.0", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower 0.5.1", + "url", +] + [[package]] name = "axum-test" version = "16.0.0" @@ -288,7 +496,7 @@ dependencies = [ "cookie", "http 1.1.0", "http-body-util", - "hyper", + "hyper 1.4.0", "hyper-util", "mime", "pretty_assertions", @@ -336,6 +544,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "bindgen" version = "0.69.4" @@ -432,6 +651,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.15.4" @@ -501,7 +733,7 @@ version = "0.7.13" dependencies = [ "axum", "axum-prometheus", - "axum-test", + "axum-test 16.0.0", "base64 0.22.1", "clap", "config", @@ -654,6 +886,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.14.0" @@ -725,6 +966,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -975,6 +1226,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -1034,6 +1306,24 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum_primitive" version = "0.1.1" @@ -1082,6 +1372,33 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1140,6 +1457,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1215,6 +1547,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1295,6 +1640,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.6" @@ -1363,6 +1720,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -1400,6 +1763,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -1419,7 +1793,7 @@ dependencies = [ "bytes", "futures-core", "http 1.1.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -1435,12 +1809,63 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.30", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.4.0" @@ -1452,7 +1877,7 @@ dependencies = [ "futures-util", "h2", "http 1.1.0", - "http-body", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -1462,19 +1887,52 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper", + "hyper 1.4.0", "hyper-util", "pin-project-lite", "tokio", "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -1485,8 +1943,8 @@ dependencies = [ "futures-channel", "futures-util", "http 1.1.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.4.0", "pin-project-lite", "socket2", "tokio", @@ -1636,7 +2094,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.52.0", ] @@ -1650,6 +2108,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1684,14 +2151,74 @@ dependencies = [ ] [[package]] -name = "json5" -version = "0.4.1" +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "k2pow-service" +version = "0.1.0" +dependencies = [ + "axum", + "axum-test 15.7.4", + "clap", + "hex", + "mockall", + "post-rs", + "serde", + "serde_with", + "thiserror", + "tokio", + "tower-http", + "tracing", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "pest", - "pest_derive", - "serde", + "regex-automata 0.4.8", ] [[package]] @@ -1706,6 +2233,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" version = "0.2.159" @@ -1728,6 +2261,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1755,6 +2298,9 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] [[package]] name = "matchers" @@ -1804,7 +2350,7 @@ checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ "base64 0.22.1", "http-body-util", - "hyper", + "hyper 1.4.0", "hyper-util", "indexmap 2.2.5", "ipnet", @@ -1868,7 +2414,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -1906,6 +2452,29 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.26.4" @@ -2002,7 +2571,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -2066,6 +2635,50 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-multimap" version = "0.6.0" @@ -2108,6 +2721,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2208,6 +2827,21 @@ dependencies = [ "indexmap 2.2.5", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -2240,6 +2874,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2250,6 +2895,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "plotters" version = "0.3.5" @@ -2278,6 +2929,21 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "portable-atomic" version = "1.6.0" @@ -2307,6 +2973,7 @@ dependencies = [ "core_affinity", "criterion", "eyre", + "futures 0.3.30", "hex", "itertools 0.13.0", "log", @@ -2318,6 +2985,7 @@ dependencies = [ "randomx-rs", "rayon", "regex", + "reqwest", "rstest", "scrypt-jane", "serde", @@ -2326,6 +2994,7 @@ dependencies = [ "tempfile", "thiserror", "thread_local", + "tokio", ] [[package]] @@ -2362,6 +3031,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "3.1.0" @@ -2692,6 +3367,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.11.0" @@ -2742,6 +3428,49 @@ version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" +[[package]] +name = "reqwest" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.0", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "reserve-port" version = "2.0.1" @@ -2969,6 +3698,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3005,6 +3743,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.22" @@ -3053,6 +3814,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.5" @@ -3110,11 +3881,12 @@ version = "0.7.13" dependencies = [ "async-stream", "axum", - "axum-test", + "axum-test 16.0.0", "clap", "env_logger", "eyre", "hex", + "httpmock", "log", "mockall", "post-rs", @@ -3159,6 +3931,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -3168,6 +3949,18 @@ dependencies = [ "rand_core", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "sketches-ddsketch" version = "0.2.2" @@ -3233,6 +4026,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.10.0" @@ -3307,6 +4113,9 @@ name = "sync_wrapper" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" +dependencies = [ + "futures-core", +] [[package]] name = "sysinfo" @@ -3322,6 +4131,27 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.5.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" @@ -3341,6 +4171,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termtree" version = "0.4.1" @@ -3452,7 +4293,9 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -3469,6 +4312,16 @@ dependencies = [ "syn 2.0.74", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -3563,9 +4416,9 @@ dependencies = [ "bytes", "h2", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.4.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -3642,11 +4495,12 @@ dependencies = [ "bitflags 2.5.0", "bytes", "http 1.1.0", - "http-body", + "http-body 1.0.0", "http-body-util", "pin-project-lite", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -3795,6 +4649,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -3830,6 +4690,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -3895,6 +4767,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -4075,6 +4959,17 @@ dependencies = [ "syn 2.0.74", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index dd64080d..31e0c51b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "profiler", "service", "certifier", + "k2pow-service", ] [package] @@ -45,6 +46,9 @@ thread_local = "1.1.7" mockall = "0.13.0" core_affinity = "0.8.1" hex = "0.4.3" +reqwest = { version = "0.12.7" } +tokio = { version = "1.40.0", features = ["rt-multi-thread"]} +futures = "0.3.30" [dev-dependencies] criterion = "0.5" diff --git a/benches/verifying.rs b/benches/verifying.rs index 369261b2..11330188 100644 --- a/benches/verifying.rs +++ b/benches/verifying.rs @@ -44,6 +44,7 @@ fn verifying(c: &mut Criterion) { let pow_flags = RandomXFlag::get_recommended_flags(); // Generate a proof let stop = AtomicBool::new(false); + let pow_prover = post::pow::randomx::PoW::new(pow_flags).unwrap(); let proof = generate_proof( datadir.path(), challenge, @@ -53,6 +54,7 @@ fn verifying(c: &mut Criterion) { pow_flags, stop, NoopProgressReporter {}, + pow_prover, ) .unwrap(); let metadata = ProofMetadata::new(metadata, *challenge); diff --git a/certifier/tests/test_certify.rs b/certifier/tests/test_certify.rs index 3b1f9860..395c367f 100644 --- a/certifier/tests/test_certify.rs +++ b/certifier/tests/test_certify.rs @@ -42,6 +42,7 @@ fn gen_proof( // Generate a proof let pow_flags = RandomXFlag::get_recommended_flags(); let stop = AtomicBool::new(false); + let pow_prover = post::pow::randomx::PoW::new(pow_flags).unwrap(); let proof = generate_proof( datadir.path(), challenge, @@ -51,6 +52,7 @@ fn gen_proof( pow_flags, stop, prove::NoopProgressReporter {}, + &pow_prover, ) .unwrap(); let metadata = ProofMetadata::new(metadata, *challenge); diff --git a/ffi/src/post_impl.rs b/ffi/src/post_impl.rs index b41a8ef2..c94fe4aa 100644 --- a/ffi/src/post_impl.rs +++ b/ffi/src/post_impl.rs @@ -103,6 +103,7 @@ fn _generate_proof( let challenge = challenge.try_into()?; let stop = AtomicBool::new(false); + let pow_prover = post::pow::randomx::PoW::new(pow_flags).unwrap(); let proof = prove::generate_proof( datadir, challenge, @@ -112,6 +113,7 @@ fn _generate_proof( pow_flags, stop, prove::NoopProgressReporter {}, + &pow_prover, )?; Ok(Box::new(Proof::from(proof))) } diff --git a/k2pow-service/Cargo.toml b/k2pow-service/Cargo.toml new file mode 100644 index 00000000..dc0201e7 --- /dev/null +++ b/k2pow-service/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "k2pow-service" +version = "0.1.0" +edition = "2021" + +[dependencies] +post-rs = { path="../" } +clap = { version = "4.5.16", features = ["derive", "env"] } +serde_with = "3.9.0" +serde = { version = "1.0.210", features = ["derive", "serde_derive"] } +tokio = { version = "1.40.0", features = ["full"] } +tracing = "0.1.40" +tracing-log = "0.2.0" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +axum = "0.7.5" +tower-http = {version = "0.5.2", features = ["trace"]} +hex = "0.4.3" +thiserror = "1.0.63" + +[dev-dependencies] +axum-test = "15.7.1" +hex = "0.4.3" +mockall = "0.13.0" diff --git a/k2pow-service/src/job_manager.rs b/k2pow-service/src/job_manager.rs new file mode 100644 index 00000000..99fadd03 --- /dev/null +++ b/k2pow-service/src/job_manager.rs @@ -0,0 +1,219 @@ +use crate::{create_thread_pool, PoW}; +use post::pow::Prover; +use std::collections::HashMap; +use std::sync::Mutex; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum JobError { + #[error("too many active jobs")] + TooManyJobs, +} + +/// JobStatus is used to expose job state to external callers +#[derive(Clone, Debug, PartialEq)] +pub enum JobStatus { + Created, + InProgress, + Done(Result), +} + +#[derive(Debug)] +enum JobState { + InProgress(Option>>), + Done(Result), +} + +#[derive(Hash, Eq, PartialEq, Debug, Clone)] +pub struct Job { + pub nonce_group: u8, + pub challenge: [u8; 8], + pub difficulty: [u8; 32], + pub miner: [u8; 32], +} + +#[cfg_attr(test, mockall::automock)] +pub trait GetOrCreate { + fn get_or_create(&self, job: Job) -> Result; +} + +struct Jobs { + in_progress: Option, + states: HashMap, +} +pub struct JobManager { + jobs: Mutex, + cores: u8, + randomx_mode: crate::RandomXMode, + randomx_large_pages: bool, +} + +impl JobManager { + pub fn new(cores: u8, randomx_mode: crate::RandomXMode, randomx_large_pages: bool) -> Self { + JobManager { + jobs: Mutex::new(Jobs { + in_progress: None, + states: HashMap::new(), + }), + cores, + randomx_mode, + randomx_large_pages, + } + } + fn check_finished(&self) { + let mut hs = self.jobs.lock().unwrap(); + if hs.in_progress.is_none() { + return; + } + let job = hs.in_progress.as_ref().unwrap().clone(); + let entry = hs.states.get_mut(&job).unwrap(); + if let JobState::InProgress(handle) = entry { + if handle.as_ref().unwrap().is_finished() { + let val = match handle.take().unwrap().join() { + Ok(result) => JobState::Done(match result { + Ok(v) => Ok(v), + Err(e) => Err(e.to_string()), + }), + Err(e) => std::panic::resume_unwind(e), + }; + *entry = val; + hs.in_progress.take(); + } + }; + } +} + +impl GetOrCreate for JobManager { + fn get_or_create(&self, job: Job) -> Result { + self.check_finished(); + let mut hs = self.jobs.lock().unwrap(); + + match hs.states.get(&job) { + Some(JobState::InProgress(_)) => Ok(JobStatus::InProgress), + Some(JobState::Done(result)) => Ok(JobStatus::Done(result.clone())), + None => { + if hs.in_progress.is_some() { + // if we're here it means: + // - there's a job in progress + // - it's not this job (because we didn't get a result from HashMap.get) + // - it's not done either (covered by the earlier match arm) + return Err(JobError::TooManyJobs); + } + + let mut randomx_flags = match self.randomx_mode { + crate::RandomXMode::Fast => { + post::pow::randomx::RandomXFlag::get_recommended_flags() + | post::pow::randomx::RandomXFlag::FLAG_FULL_MEM + } + crate::RandomXMode::Light => { + post::pow::randomx::RandomXFlag::get_recommended_flags() + } + }; + if self.randomx_large_pages { + eprintln!("Using large pages for RandomX"); + randomx_flags |= post::pow::randomx::RandomXFlag::FLAG_LARGE_PAGES; + } + + eprintln!("RandomX flags: {}", randomx_flags); + + tracing::info!( + "took k2pow job: nonce group: {}, challenge: {}, difficulty: {}, miner {}", + job.nonce_group, + hex::encode(job.challenge), + hex::encode(job.difficulty), + hex::encode(job.miner) + ); + let cores = match self.cores { + 0 => crate::Cores::All, + v => crate::Cores::Any(v as usize), + }; + let job_clone = job.clone(); + let handle = std::thread::spawn(move || { + let pool = create_thread_pool(cores, |_| {}).unwrap(); + pool.install(|| -> Result { + let pow = PoW::new(randomx_flags).unwrap(); + tracing::debug!( + "proving k2pow: nonce group: {}, challenge: {}, difficulty: {}, miner {}", + job_clone.nonce_group, + hex::encode(job_clone.challenge), + hex::encode(job_clone.difficulty), + hex::encode(job_clone.miner) + ); + let res = pow.prove( + job_clone.nonce_group, + &job_clone.challenge, + &job_clone.difficulty, + &job_clone.miner, + )?; + tracing::debug!("k2pow result: {}", res); + Ok(res) + }) + }); + + hs.in_progress = Some(job.clone()); + hs.states.insert(job, JobState::InProgress(Some(handle))); + Ok(JobStatus::Created) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{GetOrCreate, JobError, JobStatus}; + + #[test] + fn test_job_manager() { + let job_manager = super::JobManager::new(1, crate::RandomXMode::Light, false); + let job = super::Job { + nonce_group: 11, + challenge: [1, 2, 3, 4, 5, 6, 7, 8], + difficulty: [0xff; 32], + miner: [ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, + 5, 6, 7, 8, + ], + }; + + match job_manager.get_or_create(job.clone()) { + Ok(JobStatus::Created) => (), + _ => panic!("shouldnt happen"), + }; + // try to insert the same one twice + match job_manager.get_or_create(job.clone()) { + Ok(JobStatus::InProgress) => (), + _ => panic!("shouldnt happen"), + }; + + // try to insert a new job but expect too many jobs + let mut job2 = job.clone(); + job2.nonce_group = 14; + match job_manager.get_or_create(job2.clone()) { + Err(JobError::TooManyJobs) => (), + _ => panic!("shouldnt happen"), + }; + match job_manager.get_or_create(job.clone()) { + Ok(JobStatus::InProgress) => (), + _ => panic!("shouldnt happen"), + }; + + // loop until the calculation sets the correct result in the hashmap. + // since the test difficulty is easy, this shouldn't take long. + loop { + match job_manager.get_or_create(job.clone()) { + Ok(JobStatus::Done(Ok(_))) => break, + Ok(JobStatus::Done(Err(_))) => panic!("shouldnt happen"), + Ok(JobStatus::Created) => panic!("shouldnt happen"), + Ok(JobStatus::InProgress) => { + std::thread::sleep(std::time::Duration::from_millis(50)) + } + Err(_) => panic!(), + } + } + // since the first job is now marked as errored, we can insert job 2 + match job_manager.get_or_create(job2) { + Ok(JobStatus::Created) => (), + _ => panic!("shouldnt happen"), + } + } +} diff --git a/k2pow-service/src/main.rs b/k2pow-service/src/main.rs new file mode 100644 index 00000000..d8ad69a7 --- /dev/null +++ b/k2pow-service/src/main.rs @@ -0,0 +1,296 @@ +use crate::job_manager::GetOrCreate; +use axum::extract::Path; +use axum::response::IntoResponse; +use axum::routing::{get, Router}; +use axum::{ + extract::State, + http::{Request, StatusCode}, + response::Response, +}; +use clap::{arg, Parser, ValueEnum}; +use post::config::Cores; +use post::pow::randomx::PoW; +use post::prove::create_thread_pool; +use serde::Deserialize; +use serde_with::serde_as; +use std::sync::Arc; +use std::time::Duration; +use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer}; +use tracing::{info_span, Span}; +use tracing_log::LogTracer; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +mod job_manager; +use job_manager::{JobError, JobStatus}; + +#[derive(Parser, Debug)] +#[command(version)] +struct Cli { + /// the address to listen to http job requests on. + #[arg(short, long, default_value = "0.0.0.0:3000")] + bind_address: String, + + /// the number of cores to use. the optimal value depends + /// on the type of CPU used. `0` means use all cores. + #[arg(long, default_value = "0")] + cores: u8, + + #[arg(long, default_value_t = RandomXMode::Fast)] + randomx_mode: RandomXMode, + + /// allocate RandomX memory in large pages. + #[arg(long, default_value = "false")] + randomx_large_pages: bool, +} + +/// RandomX modes of operation +/// +/// They are interchangeable as they give the same results but have different +/// purpose and memory requirements. +#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] +enum RandomXMode { + /// Fast mode for proving. Requires 2080 MiB of memory. + Fast, + /// Light mode for verification. Requires only 256 MiB of memory, but runs significantly slower + Light, +} + +impl std::fmt::Display for RandomXMode { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + RandomXMode::Fast => write!(f, "fast"), + RandomXMode::Light => write!(f, "light"), + } + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = Cli::parse(); + + LogTracer::init()?; + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("INFO")); + let subscriber = FmtSubscriber::builder() + .with_env_filter(env_filter) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; + let job_manager = Arc::new(job_manager::JobManager::new( + args.cores, + args.randomx_mode, + args.randomx_large_pages, + )); + let router = router(job_manager); + tracing::info!( + "starting http server with bind address: {}", + args.bind_address + ); + let listener = tokio::net::TcpListener::bind(args.bind_address) + .await + .unwrap(); + axum::serve(listener, router).await.unwrap(); + + Ok(()) +} + +fn router(job_manager: Arc) -> Router { + Router::new() + .route("/", get(root)) + .route( + "/job/:miner/:nonce_group/:challenge/:difficulty", + get(get_job), + ) + .with_state(job_manager) + .layer( + TraceLayer::new_for_http() + .make_span_with(|request: &Request<_>| { + let matched_path = request.uri().to_string(); + + info_span!( + "http_request", + method = ?request.method(), + uri = matched_path, + status = tracing::field::Empty, + ) + }) + .on_response(|response: &Response, _latency: Duration, span: &Span| { + span.record("status", response.status().as_str()); + tracing::trace!("served request"); + }) + .on_failure( + |error: ServerErrorsFailureClass, _latency: Duration, span: &Span| { + match error { + ServerErrorsFailureClass::StatusCode(code) => { + span.record("status", code.as_str()); + } + ServerErrorsFailureClass::Error(err) => { + span.record("error", &err); + } + } + tracing::error!("request fail"); + }, + ), + ) +} + +const ROOT_RESPONSE: &str = "{ 'message': 'ok' }"; +async fn root() -> impl IntoResponse { + ROOT_RESPONSE +} + +#[serde_as] +#[derive(Deserialize)] +struct HexStr(#[serde_as(as = "serde_with::hex::Hex")] [u8; COUNT]); + +impl std::ops::Deref for HexStr { + type Target = [u8; COUNT]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +async fn get_job( + State(manager): State>, + Path((miner, nonce_group, challenge, difficulty)): Path<( + HexStr<32>, + u8, + HexStr<8>, + HexStr<32>, + )>, +) -> Result { + manager.get_or_create(job_manager::Job { + nonce_group, + challenge: *challenge, + difficulty: *difficulty, + miner: *miner, + }) +} + +impl IntoResponse for job_manager::JobError { + fn into_response(self) -> Response { + match self { + JobError::TooManyJobs => (StatusCode::TOO_MANY_REQUESTS, "").into_response(), + } + } +} + +impl IntoResponse for job_manager::JobStatus { + fn into_response(self) -> Response { + match self { + JobStatus::Created => (StatusCode::CREATED, "").into_response(), + JobStatus::InProgress => (StatusCode::CREATED, "").into_response(), + JobStatus::Done(Ok(res)) => (StatusCode::OK, format!("{res}")).into_response(), + JobStatus::Done(Err(err)) => { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response() + } + } + } +} + +#[cfg(test)] +mod tests { + use super::job_manager::{Job, JobStatus}; + use super::router; + use crate::job_manager; + use axum_test::TestServer; + use mockall::predicate::eq; + use std::sync::Arc; + + const JOB: Job = Job { + nonce_group: 11, + challenge: [1, 2, 3, 4, 5, 6, 7, 8], + difficulty: [ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, + 6, 7, 8, + ], + miner: [ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, + 6, 7, 8, + ], + }; + + #[tokio::test] + async fn test_root() { + let mut mock_manager = job_manager::MockGetOrCreate::new(); + mock_manager.expect_get_or_create().times(0); + let job_manager = job_manager::JobManager::new(1, crate::RandomXMode::Light, false); + let router = router(Arc::new(job_manager)); + let server = TestServer::new(router).unwrap(); + let response = server.get("/").await; + assert_eq!(response.text(), super::ROOT_RESPONSE); + } + + #[tokio::test] + async fn test_get_job_created() { + let (nonce_group, challenge, difficulty, miner) = ( + JOB.nonce_group, + hex::encode(JOB.challenge), + hex::encode(JOB.difficulty), + hex::encode(JOB.miner), + ); + let mut mock_manager = job_manager::MockGetOrCreate::new(); + mock_manager + .expect_get_or_create() + .with(eq(JOB)) + .times(2) + .returning(|_| Ok(job_manager::JobStatus::Created)); + let router = router(Arc::new(mock_manager)); + let server = TestServer::new(router).unwrap(); + let url = format!("/job/{miner}/{nonce_group}/{challenge}/{difficulty}"); + let response = server.get(&url).await; + assert_eq!(response.status_code(), axum::http::StatusCode::CREATED); + // requesting the same is idempotent + let response = server.get(&url).await; + assert_eq!(response.status_code(), axum::http::StatusCode::CREATED); + } + + #[tokio::test] + async fn test_get_job_done() { + let (nonce_group, challenge, difficulty, miner) = ( + JOB.nonce_group, + hex::encode(JOB.challenge), + hex::encode(JOB.difficulty), + hex::encode(JOB.miner), + ); + const RESULT: u64 = 1111; + let mut mock_manager = job_manager::MockGetOrCreate::new(); + mock_manager + .expect_get_or_create() + .with(eq(JOB)) + .times(1) + .returning(|_| Ok(JobStatus::Done(Ok(RESULT)))); + let router = router(Arc::new(mock_manager)); + let server = TestServer::new(router).unwrap(); + let url = format!("/job/{miner}/{nonce_group}/{challenge}/{difficulty}"); + let response = server.get(&url).await; + assert_eq!(response.status_code(), axum::http::StatusCode::OK); + assert_eq!(response.text(), format!("{RESULT}")); + } + + #[tokio::test] + async fn test_get_job_error() { + let (nonce_group, challenge, difficulty, miner) = ( + JOB.nonce_group, + hex::encode(JOB.challenge), + hex::encode(JOB.difficulty), + hex::encode(JOB.miner), + ); + let err = String::from("error message"); + + let mut mock_manager = job_manager::MockGetOrCreate::new(); + mock_manager + .expect_get_or_create() + .with(eq(JOB)) + .times(1) + .returning(move |_| Ok(JobStatus::Done(Err(String::from("error message"))))); + let router = router(Arc::new(mock_manager)); + let server = TestServer::new(router).unwrap(); + let url = format!("/job/{miner}/{nonce_group}/{challenge}/{difficulty}"); + let response = server.get(&url).await; + assert_eq!( + response.status_code(), + axum::http::StatusCode::INTERNAL_SERVER_ERROR + ); + assert_eq!(response.text(), format!("{err}")); + } +} diff --git a/scrypt-ocl/src/lib.rs b/scrypt-ocl/src/lib.rs index 64bba168..92f7fe9a 100644 --- a/scrypt-ocl/src/lib.rs +++ b/scrypt-ocl/src/lib.rs @@ -120,7 +120,7 @@ pub fn get_providers(device_types: Option) -> Result, fn scan_for_vrf_nonce(labels: &[u8], mut difficulty: [u8; 32]) -> Option { let mut nonce = None; for (id, label) in labels.chunks(ENTIRE_LABEL_SIZE).enumerate() { - if label < &difficulty { + if label < difficulty.as_slice() { nonce = Some(VrfNonce { index: id as u64, label: label.try_into().unwrap(), @@ -550,8 +550,8 @@ mod tests { .unwrap(); assert_eq!(&nonce.label[..16], label.as_slice()); - assert!(nonce.label.as_slice() < &difficulty); - assert!(label.as_slice() < &difficulty); + assert!(nonce.label.as_slice() < difficulty.as_slice()); + assert!(label.as_slice() < difficulty.as_slice()); let mut sink = std::io::sink(); let cpu_nonce = cpu_initializer diff --git a/service/Cargo.toml b/service/Cargo.toml index fa978ef2..48275271 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -40,6 +40,7 @@ tonic-build = "0.12.3" [dev-dependencies] async-stream = "0.3.5" +httpmock = "0.7.0" axum-test = "16.0.0" rcgen = "0.12.1" rstest = "0.22.0" diff --git a/service/src/main.rs b/service/src/main.rs index e09a0c5e..03baa1ba 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -8,7 +8,7 @@ use tokio::sync::oneshot::{self, error::TryRecvError, Receiver}; use tonic::transport::{Certificate, Identity}; use post::pow::randomx::RandomXFlag; -use post_service::{client, operator}; +use post_service::{client, operator, service::K2powConfig}; /// Post Service #[derive(Parser, Debug)] @@ -45,6 +45,20 @@ struct Cli { #[command(flatten, next_help_heading = "TLS configuration")] tls: Option, + + /// Base URL for remote k2pow service. + #[arg(long)] + remote_k2pow: Option, + + /// How many remote k2pow jobs to execute in parallel. This highly depends on how many + /// remote k2pow workers are available. + #[arg(long, default_value = "5")] + remote_k2pow_parallelism: usize, + + /// Time to back off before trying the k2pow service again while waiting for a result or to + /// queue in a new job. + #[arg(long, default_value = "5")] + remote_k2pow_backoff: u64, } #[serde_as] @@ -202,7 +216,9 @@ async fn main() -> eyre::Result<()> { "POST proving settings: {}", serde_json::to_string(&args.post_settings).unwrap() ); - + if let Some(uri) = &args.remote_k2pow { + log::info!("remote k2pow uri: {}", uri); + } let scrypt = post::config::ScryptParams::new( args.post_config.scrypt.n, args.post_config.scrypt.r, @@ -229,6 +245,15 @@ async fn main() -> eyre::Result<()> { } }; + let remote_k2pow_config = match args.remote_k2pow { + Some(url) => Some(K2powConfig { + url, + parallelism: args.remote_k2pow_parallelism, + backoff: Duration::from_secs(args.remote_k2pow_backoff), + }), + None => None, + }; + let service = post_service::service::PostService::new( args.dir, post::config::ProofConfig { @@ -240,6 +265,7 @@ async fn main() -> eyre::Result<()> { args.post_settings.nonces, cores_config, args.post_settings.randomx_mode.into(), + remote_k2pow_config, ) .wrap_err("creating Post Service")?; diff --git a/service/src/service.rs b/service/src/service.rs index 4f958ca0..5e194230 100644 --- a/service/src/service.rs +++ b/service/src/service.rs @@ -4,6 +4,7 @@ use std::{ ops::{Range, RangeInclusive}, path::PathBuf, sync::{atomic::AtomicBool, Arc, Mutex}, + time::Duration, }; use eyre::Context; @@ -101,6 +102,12 @@ impl ProvingProgress { } } +pub struct K2powConfig { + pub url: String, + pub parallelism: usize, + pub backoff: Duration, +} + pub struct PostService { datadir: PathBuf, metadata: post::metadata::PostMetadata, @@ -110,6 +117,7 @@ pub struct PostService { threads: post::config::Cores, pow_flags: RandomXFlag, proof_generation: Mutex, + remote_k2pow_config: Option, stop: Arc, } @@ -122,6 +130,7 @@ impl PostService { nonces: usize, threads: post::config::Cores, pow_flags: RandomXFlag, + remote_k2pow_config: Option, ) -> eyre::Result { Ok(Self { metadata: post::metadata::load(&datadir).wrap_err("loading POST metadata")?, @@ -132,6 +141,8 @@ impl PostService { threads, pow_flags, proof_generation: Mutex::new(ProofGenProcess::Idle), + remote_k2pow_config, + stop: Arc::new(AtomicBool::new(false)), }) } @@ -166,12 +177,29 @@ impl crate::client::PostService for PostService { let threads = self.threads.clone(); let stop = self.stop.clone(); let progress = ProvingProgress::default(); + let pow_prover: Box = + match &self.remote_k2pow_config { + Some(cfg) => Box::new(post::pow::service::K2powService::new( + cfg.url.clone(), + cfg.parallelism, + cfg.backoff, + )), + None => Box::new(post::pow::randomx::PoW::new(pow_flags).unwrap()), + }; let reporter = progress.clone(); *proof_gen = ProofGenProcess::Running { challenge, handle: Some(std::thread::spawn(move || { post::prove::generate_proof( - &datadir, &challenge, cfg, nonces, threads, pow_flags, stop, reporter, + &datadir, + &challenge, + cfg, + nonces, + threads, + pow_flags, + stop, + reporter, + &*pow_prover, ) })), progress, diff --git a/service/tests/test_client.rs b/service/tests/test_client.rs index 291b38a7..f5a64585 100644 --- a/service/tests/test_client.rs +++ b/service/tests/test_client.rs @@ -280,6 +280,7 @@ async fn test_get_metadata(#[case] vrf_difficulty: Option<[u8; 32]>) { 16, post::config::Cores::Any(1), post::pow::randomx::RandomXFlag::get_recommended_flags(), + None, ) .unwrap(); diff --git a/service/tests/test_operator.rs b/service/tests/test_operator.rs index 2b60cac1..9071c623 100644 --- a/service/tests/test_operator.rs +++ b/service/tests/test_operator.rs @@ -40,6 +40,7 @@ async fn test_gen_proof_in_progress() { 16, post::config::Cores::Any(1), pow_flags, + None, ) .unwrap(), ); diff --git a/service/tests/test_service.rs b/service/tests/test_service.rs index a1ec578e..ab5442f7 100644 --- a/service/tests/test_service.rs +++ b/service/tests/test_service.rs @@ -7,6 +7,8 @@ use post::{ }; use post_service::{client::PostService, service::ProofGenState}; +use httpmock::prelude::*; + #[test] fn test_generate_and_verify() { // Initialize some data @@ -33,6 +35,7 @@ fn test_generate_and_verify() { 16, post::config::Cores::Any(1), pow_flags, + None, ) .unwrap(); @@ -73,6 +76,7 @@ fn reject_invalid_challenge() { 16, post::config::Cores::Any(1), RandomXFlag::get_recommended_flags(), + None, ) .unwrap(); assert!(service.gen_proof(&[0xCA; 5]).is_err()); @@ -102,6 +106,7 @@ fn cannot_run_parallel_proof_gens() { 16, post::config::Cores::Any(1), RandomXFlag::get_recommended_flags(), + None, ) .unwrap(); @@ -112,3 +117,56 @@ fn cannot_run_parallel_proof_gens() { // Try again with the same challenge assert!(matches!(result, Ok(ProofGenState::InProgress))); } + +#[tokio::test] +async fn remote_k2pow() { + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.path("/job/bebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebe/0/aaaaaaaaaaaaaaaa/3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + then.status(200).body("1234"); + }); + + // Initialize some data + let datadir = tempfile::tempdir().unwrap(); + + let cfg = ProofConfig { + k1: 8, + k2: 4, + pow_difficulty: [0xFF; 32], + }; + let scrypt = ScryptParams::new(2, 1, 1); + + CpuInitializer::new(scrypt) + .initialize(datadir.path(), &[0xBE; 32], &[0xCE; 32], 256, 4, 256, None) + .unwrap(); + + let service = post_service::service::PostService::new( + datadir.into_path(), + cfg, + scrypt, + 16, + post::config::Cores::Any(1), + RandomXFlag::get_recommended_flags(), + Some(post_service::service::K2powConfig { + url: server.url(""), + parallelism: 1, + backoff: Duration::from_millis(1), + }), + ) + .unwrap(); + + assert!(matches!( + service.gen_proof(&[0xAA; 32]), + Ok(ProofGenState::InProgress) + )); + + loop { + if let ProofGenState::Finished { proof: _ } = service.gen_proof(&[0xAA; 32]).unwrap() { + break; + } + sleep(Duration::from_millis(10)); + } + + m.assert(); +} diff --git a/src/pow/mod.rs b/src/pow/mod.rs index a7411639..fd79b589 100644 --- a/src/pow/mod.rs +++ b/src/pow/mod.rs @@ -7,7 +7,9 @@ //! without actually holding the whole POST data. pub mod randomx; +pub mod service; use mockall::*; +use std::ops::Range; use thiserror::Error; #[derive(Error, Debug)] @@ -29,6 +31,16 @@ pub trait Prover { difficulty: &[u8; 32], miner_id: &[u8; 32], ) -> Result; + + fn prove_many( + &self, + nonce_group: Range, + challenge: &[u8; 8], + difficulty: &[u8; 32], + miner_id: &[u8; 32], + ) -> Result, Error>; + + fn par(&self) -> bool; } #[automock] diff --git a/src/pow/randomx.rs b/src/pow/randomx.rs index bd2504ec..8679fe05 100644 --- a/src/pow/randomx.rs +++ b/src/pow/randomx.rs @@ -1,6 +1,7 @@ pub use randomx_rs::RandomXFlag; use randomx_rs::{RandomXCache, RandomXDataset, RandomXError, RandomXVM}; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; +use std::ops::Range; use std::sync::atomic::{AtomicUsize, Ordering}; use thread_local::ThreadLocal; @@ -62,6 +63,8 @@ impl Prover for PoW { ] .concat(); + // the call to difficulty.as_slice() below (in find_any) is needed because of a compiler bug: + // https://github.com/rust-lang/rust/issues/130464 let iterations = AtomicUsize::new(0); let (pow_nonce, _) = (0..2u64.pow(56)) .into_par_iter() @@ -79,14 +82,27 @@ impl Prover for PoW { }, ) .filter_map(|res| res) - .find_any(|(_, hash)| hash.as_slice() < difficulty) + .find_any(|(_, hash)| hash.as_slice() < difficulty.as_slice()) .ok_or(Error::PoWNotFound)?; - let total_iterations = iterations.load(Ordering::Relaxed); log::debug!("Took {total_iterations:?} PoW iterations to find a valid nonce"); Ok(pow_nonce) } + + fn prove_many( + &self, + _: Range, + _: &[u8; 8], + _: &[u8; 32], + _: &[u8; 32], + ) -> Result, Error> { + panic!("not implemented") + } + + fn par(&self) -> bool { + false + } } impl PowVerifier for PoW { @@ -109,7 +125,9 @@ impl PowVerifier for PoW { let vm = self.get_vm()?; let hash = vm.calculate_hash(pow_input.as_slice())?; - if hash.as_slice() >= difficulty { + // the call to difficulty.as_slice() is needed because of a compiler bug: + // https://github.com/rust-lang/rust/issues/130464 + if hash.as_slice() >= difficulty.as_slice() { return Err(Error::InvalidPoW); } Ok(()) diff --git a/src/pow/service.rs b/src/pow/service.rs new file mode 100644 index 00000000..24d74d72 --- /dev/null +++ b/src/pow/service.rs @@ -0,0 +1,113 @@ +use super::{Error, Prover}; +use futures::future; +use reqwest; +use std::ops::Range; +use std::sync::Arc; +use std::time::Duration; +use tokio::runtime::Runtime; +use tokio::sync::Semaphore; +use tokio::time::sleep; + +pub struct K2powService { + k2pow_service: String, + semaphore: Arc, + backoff: Duration, +} + +impl K2powService { + pub fn new(k2pow_service: String, parallelism: usize, backoff: Duration) -> Self { + let semaphore = Arc::new(Semaphore::new(parallelism)); + Self { + k2pow_service, + semaphore, + backoff, + } + } +} + +impl Prover for K2powService { + fn prove(&self, _: u8, _: &[u8; 8], _: &[u8; 32], _: &[u8; 32]) -> Result { + panic!("not implemented"); + } + + fn prove_many( + &self, + nonce_groups: Range, + challenge: &[u8; 8], + difficulty: &[u8; 32], + miner_id: &[u8; 32], + ) -> Result, Error> { + let rt = Runtime::new().unwrap(); + let k2p = self.k2pow_service.clone(); + rt.block_on(async { + let mut tasks = vec![]; + let backoff = self.backoff; + nonce_groups.into_iter().for_each(|nonce| { + let uri = format!( + "{}/job/{}/{}/{}/{}", + &k2p, + hex::encode(miner_id), + nonce, + hex::encode(challenge), + hex::encode(difficulty) + ); + let semaphore = self.semaphore.clone(); + + let task = async move { + let _permit = semaphore.acquire().await.unwrap(); + let client = reqwest::Client::new(); + + loop { + let res = match client.get(&uri).send().await { + Ok(res) => res, + Err(err) => { + log::warn!("get job error: {}. backing off before retry", err); + sleep(backoff).await; + continue; + } + }; + let status = res.status(); + let txt = match res.text().await { + Ok(text) => text, + Err(err) => { + log::warn!( + "read response error: {}. backing off before retry", + err + ); + sleep(backoff).await; + continue; + } + }; + + let res = match status { + reqwest::StatusCode::OK => Ok((nonce, txt.parse::().unwrap())), + reqwest::StatusCode::INTERNAL_SERVER_ERROR => { + Err(Error::Internal(txt.into())) + } + reqwest::StatusCode::CREATED => { + sleep(backoff).await; + continue; + } + reqwest::StatusCode::TOO_MANY_REQUESTS => { + sleep(backoff).await; + continue; + } + _ => Err(Error::Internal("unknown status code returned".into())), + }; + return res; + } + }; + tasks.push(task); + }); + + future::join_all(tasks) + .await + .into_iter() + .collect::, Error>>() + }) + } + + fn par(&self) -> bool { + true + } +} diff --git a/src/prove.rs b/src/prove.rs index fecf7f7c..53137b22 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -130,11 +130,11 @@ pub struct Prover8_56 { impl Prover8_56 { pub(crate) const NONCES_PER_AES: u32 = 16; - pub fn new( + pub fn new( challenge: &[u8; 32], nonces: Range, params: ProvingParams, - pow_prover: &P, + pow_prover: &(dyn pow::Prover + Send + Sync), miner_id: &[u8; 32], ) -> eyre::Result { // TODO consider to relax it to allow any range of nonces @@ -147,21 +147,37 @@ impl Prover8_56 { "nonces must be a multiple of 16" ); log::info!("calculating proof of work for nonces {nonces:?}",); - let ciphers: Vec = nonce_group_range(nonces.clone(), Self::NONCES_PER_AES) - .map(|nonce_group| { - log::debug!("calculating proof of work for nonce group {nonce_group}"); - let pow = pow_prover.prove( - nonce_group.try_into()?, - challenge[..8].try_into().unwrap(), + let map_fn = |nonce_group: u32| -> eyre::Result { + log::debug!("calculating proof of work for nonce group {nonce_group}"); + let pow = pow_prover.prove( + nonce_group.try_into()?, + challenge[..8].try_into().unwrap(), + ¶ms.pow_difficulty, + miner_id, + )?; + log::debug!("proof of work for nonce group {nonce_group}: {pow}"); + + Ok(AesCipher::new(challenge, nonce_group, pow)) + }; + + let ciphers: Vec = match pow_prover.par() { + true => pow_prover + .prove_many( + nonce_group_range(nonces.clone(), Self::NONCES_PER_AES), + challenge[..8].try_into()?, ¶ms.pow_difficulty, miner_id, - )?; - log::debug!("proof of work: {pow}"); - - Ok(AesCipher::new(challenge, nonce_group, pow)) - }) - .collect::>()?; - + ) + .unwrap() + .into_iter() + .map(|(nonce_group, pow)| -> eyre::Result { + Ok(AesCipher::new(challenge, nonce_group, pow)) + }) + .collect::>()?, + false => nonce_group_range(nonces.clone(), Self::NONCES_PER_AES) + .map(map_fn) + .collect::>()?, + }; let lazy_ciphers = nonces .map(|nonce| { let nonce_group = calc_nonce_group(nonce, Self::NONCES_PER_AES); @@ -290,6 +306,7 @@ pub fn generate_proof( pow_flags: RandomXFlag, stop: Stopper, reporter: Reporter, + pow_prover: &(dyn pow::Prover + Send + Sync), ) -> eyre::Result> where Stopper: Borrow, @@ -303,7 +320,6 @@ where params.difficulty, hex::encode_upper(params.pow_difficulty) ); - let pow_prover = pow::randomx::PoW::new(pow_flags)?; let mut nonces = 0..nonces_size as u32; @@ -325,7 +341,7 @@ where let pow_time = Instant::now(); let prover = pool.install(|| { let miner_id = &metadata.node_id; - Prover8_56::new(challenge, nonces.clone(), params, &pow_prover, miner_id) + Prover8_56::new(challenge, nonces.clone(), params, pow_prover, miner_id) .wrap_err("creating prover") })?; @@ -381,7 +397,7 @@ where } } -fn create_thread_pool( +pub fn create_thread_pool( cores: config::Cores, on_affinity_set_error: F, ) -> Result @@ -457,6 +473,7 @@ mod tests { }; let params = ProvingParams::new(&meta, &cfg).unwrap(); let mut pow_prover = pow::MockProver::new(); + pow_prover.expect_par().returning(|| false); pow_prover .expect_prove() @@ -470,6 +487,7 @@ mod tests { .with(eq(1), eq([0; 8]), eq(cfg.pow_difficulty), always()) .once() .returning(|_, _, _, _| Ok(0)); + assert!(Prover8_56::new(&[0; 32], 16..32, params, &pow_prover, &meta.node_id).is_ok()); assert!(Prover8_56::new(&[0; 32], 0..0, params, &pow_prover, &meta.node_id).is_err()); @@ -490,6 +508,7 @@ mod tests { pow_difficulty: [0xFF; 32], }; let mut pow_prover = pow::MockProver::new(); + pow_prover.expect_par().returning(|| false); pow_prover .expect_prove() .once() @@ -541,6 +560,7 @@ mod tests { pow_difficulty: [0xFF; 32], }; let mut pow_prover = pow::MockProver::new(); + pow_prover.expect_par().returning(|| false); pow_prover.expect_prove().returning(|_, _, _, _| Ok(0)); let prover = Prover8_56::new( @@ -585,8 +605,8 @@ mod tests { pow_difficulty: [0xFF; 32], }; let mut pow_prover = pow::MockProver::new(); + pow_prover.expect_par().returning(|| false); pow_prover.expect_prove().returning(|_, _, _, _| Ok(0)); - let indexes = loop { let mut indicies = HashMap::>::new(); @@ -649,6 +669,7 @@ mod tests { pow_difficulty: [0xFF; 32], }; let mut pow_prover = pow::MockProver::new(); + pow_prover.expect_par().returning(|| false); pow_prover .expect_prove() .once() diff --git a/tests/generate_and_verify.rs b/tests/generate_and_verify.rs index b8d205c0..6a9c954f 100644 --- a/tests/generate_and_verify.rs +++ b/tests/generate_and_verify.rs @@ -47,7 +47,7 @@ fn test_generate_and_verify() { let mut reporter = prove::MockProgressReporter::new(); reporter.expect_new_nonce_group().once().return_const(()); reporter.expect_finished_chunk().times(1..).return_const(()); - + let pow_prover = post::pow::randomx::PoW::new(pow_flags).unwrap(); let proof = generate_proof( datadir.path(), challenge, @@ -57,6 +57,7 @@ fn test_generate_and_verify() { pow_flags, stop, reporter, + &pow_prover, ) .unwrap(); @@ -146,6 +147,7 @@ fn test_generate_and_verify_difficulty_msb_not_zero() { let pow_flags = RandomXFlag::get_recommended_flags(); // Generate a proof let stop = AtomicBool::new(false); + let pow_prover = post::pow::randomx::PoW::new(pow_flags).unwrap(); let proof = generate_proof( datadir.path(), challenge, @@ -155,6 +157,7 @@ fn test_generate_and_verify_difficulty_msb_not_zero() { pow_flags, stop, prove::NoopProgressReporter {}, + &pow_prover, ) .unwrap();