From 74a17880fe9ba277adaf9763885d2204503fe1b2 Mon Sep 17 00:00:00 2001 From: mugleproject Date: Sun, 24 Jul 2022 16:39:58 +0800 Subject: [PATCH] first commit --- .ci/install.yml | 12 + .ci/release.yml | 61 + .gitignore | 17 +- .gitmodules | 3 + CODE_OF_CONDUCT.md | 3 + Cargo.lock | 1683 +++++++++++++++++ Cargo.toml | 52 + Dockerfile | 52 + LICENSE | 201 -- README.md | 90 + azure-pipelines.yml | 57 + config/Cargo.toml | 17 + config/src/config.rs | 248 +++ config/src/lib.rs | 38 + config/src/types.rs | 156 ++ cuckoo-miner/Cargo.toml | 42 + cuckoo-miner/src/build.rs | 86 + cuckoo-miner/src/config/mod.rs | 24 + cuckoo-miner/src/config/types.rs | 104 + cuckoo-miner/src/cuckoo_sys/ffi.rs | 202 ++ cuckoo-miner/src/cuckoo_sys/mod.rs | 30 + .../src/cuckoo_sys/plugins/CMakeLists.txt | 245 +++ .../cmake/CudaComputeTargetFlags.cmake | 43 + .../cuckoo_sys/plugins/cmake/find_cuda.cmake | 69 + cuckoo-miner/src/cuckoo_sys/plugins/cuckoo | 1 + cuckoo-miner/src/error/mod.rs | 68 + cuckoo-miner/src/lib.rs | 60 + cuckoo-miner/src/miner/consensus.rs | 168 ++ cuckoo-miner/src/miner/miner.rs | 357 ++++ cuckoo-miner/src/miner/mod.rs | 27 + cuckoo-miner/src/miner/types.rs | 107 ++ cuckoo-miner/src/miner/util.rs | 56 + etc/crate-release.sh | 33 + install_ocl_plugins.sh | 25 + mugle-miner.toml | 211 +++ ocl_cuckaroo/.gitignore | 3 + ocl_cuckaroo/Cargo.toml | 21 + ocl_cuckaroo/src/finder.rs | 195 ++ ocl_cuckaroo/src/lib.rs | 183 ++ ocl_cuckaroo/src/main.rs | 44 + ocl_cuckaroo/src/trimmer.rs | 1168 ++++++++++++ ocl_cuckatoo/.gitignore | 3 + ocl_cuckatoo/Cargo.toml | 16 + ocl_cuckatoo/src/finder.rs | 273 +++ ocl_cuckatoo/src/lib.rs | 182 ++ ocl_cuckatoo/src/trimmer.rs | 327 ++++ plugin/Cargo.toml | 16 + plugin/src/lib.rs | 329 ++++ rustfmt.toml | 1 + src/bin/client.rs | 657 +++++++ src/bin/mining.rs | 182 ++ src/bin/mugle_miner.rs | 232 +++ src/bin/stats.rs | 131 ++ src/bin/tui/constants.rs | 31 + src/bin/tui/menu.rs | 70 + src/bin/tui/mining.rs | 247 +++ src/bin/tui/mod.rs | 29 + src/bin/tui/table.rs | 994 ++++++++++ src/bin/tui/types.rs | 38 + src/bin/tui/ui.rs | 186 ++ src/bin/tui/version.rs | 49 + src/bin/types.rs | 90 + src/build/build.rs | 26 + util/Cargo.toml | 19 + util/src/hex.rs | 73 + util/src/lib.rs | 51 + util/src/logger.rs | 158 ++ util/src/types.rs | 65 + 68 files changed, 10526 insertions(+), 211 deletions(-) create mode 100644 .ci/install.yml create mode 100644 .ci/release.yml create mode 100644 .gitmodules create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile delete mode 100644 LICENSE create mode 100644 README.md create mode 100644 azure-pipelines.yml create mode 100644 config/Cargo.toml create mode 100644 config/src/config.rs create mode 100644 config/src/lib.rs create mode 100644 config/src/types.rs create mode 100644 cuckoo-miner/Cargo.toml create mode 100644 cuckoo-miner/src/build.rs create mode 100644 cuckoo-miner/src/config/mod.rs create mode 100644 cuckoo-miner/src/config/types.rs create mode 100644 cuckoo-miner/src/cuckoo_sys/ffi.rs create mode 100644 cuckoo-miner/src/cuckoo_sys/mod.rs create mode 100644 cuckoo-miner/src/cuckoo_sys/plugins/CMakeLists.txt create mode 100644 cuckoo-miner/src/cuckoo_sys/plugins/cmake/CudaComputeTargetFlags.cmake create mode 100644 cuckoo-miner/src/cuckoo_sys/plugins/cmake/find_cuda.cmake create mode 160000 cuckoo-miner/src/cuckoo_sys/plugins/cuckoo create mode 100644 cuckoo-miner/src/error/mod.rs create mode 100644 cuckoo-miner/src/lib.rs create mode 100644 cuckoo-miner/src/miner/consensus.rs create mode 100644 cuckoo-miner/src/miner/miner.rs create mode 100644 cuckoo-miner/src/miner/mod.rs create mode 100644 cuckoo-miner/src/miner/types.rs create mode 100644 cuckoo-miner/src/miner/util.rs create mode 100755 etc/crate-release.sh create mode 100755 install_ocl_plugins.sh create mode 100644 mugle-miner.toml create mode 100644 ocl_cuckaroo/.gitignore create mode 100644 ocl_cuckaroo/Cargo.toml create mode 100644 ocl_cuckaroo/src/finder.rs create mode 100644 ocl_cuckaroo/src/lib.rs create mode 100644 ocl_cuckaroo/src/main.rs create mode 100644 ocl_cuckaroo/src/trimmer.rs create mode 100644 ocl_cuckatoo/.gitignore create mode 100644 ocl_cuckatoo/Cargo.toml create mode 100644 ocl_cuckatoo/src/finder.rs create mode 100644 ocl_cuckatoo/src/lib.rs create mode 100644 ocl_cuckatoo/src/trimmer.rs create mode 100644 plugin/Cargo.toml create mode 100644 plugin/src/lib.rs create mode 100644 rustfmt.toml create mode 100644 src/bin/client.rs create mode 100644 src/bin/mining.rs create mode 100644 src/bin/mugle_miner.rs create mode 100644 src/bin/stats.rs create mode 100644 src/bin/tui/constants.rs create mode 100644 src/bin/tui/menu.rs create mode 100644 src/bin/tui/mining.rs create mode 100644 src/bin/tui/mod.rs create mode 100644 src/bin/tui/table.rs create mode 100644 src/bin/tui/types.rs create mode 100644 src/bin/tui/ui.rs create mode 100644 src/bin/tui/version.rs create mode 100644 src/bin/types.rs create mode 100644 src/build/build.rs create mode 100644 util/Cargo.toml create mode 100644 util/src/hex.rs create mode 100644 util/src/lib.rs create mode 100644 util/src/logger.rs create mode 100644 util/src/types.rs diff --git a/.ci/install.yml b/.ci/install.yml new file mode 100644 index 0000000..eea9ea6 --- /dev/null +++ b/.ci/install.yml @@ -0,0 +1,12 @@ +steps: + - script: | + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.1.168-1_amd64.deb + sudo dpkg -i cuda-repo-ubuntu1804_10.1.168-1_amd64.deb + sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub + sudo apt-get update -yqq + sudo apt-get install cuda ocl-icd-opencl-dev libncursesw5-dev + displayName: Linux Install Dependencies + condition: eq( variables['Agent.OS'], 'Linux' ) + - script: | + git submodule update --init --recursive + displayName: Init Dependencies diff --git a/.ci/release.yml b/.ci/release.yml new file mode 100644 index 0000000..c827c3f --- /dev/null +++ b/.ci/release.yml @@ -0,0 +1,61 @@ +steps: + - script: 'cargo build --release --features opencl' + displayName: Build Release + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + - script: | + MY_TAG="$(Build.SourceBranch)" + MY_TAG=${MY_TAG#refs/tags/} + echo $MY_TAG + echo "##vso[task.setvariable variable=build.my_tag]$MY_TAG" + echo "##vso[task.setvariable variable=build.platform]$PLATFORM" + displayName: "Create my tag variable" + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + - task: CopyFiles@2 + displayName: Copy assets + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + inputs: + sourceFolder: '$(Build.SourcesDirectory)/target/release' + contents: 'mugle-miner' + targetFolder: '$(Build.BinariesDirectory)/mugle-miner' + - task: CopyFiles@2 + displayName: Copy plugins + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + inputs: + sourceFolder: '$(Build.SourcesDirectory)/target/release/plugins' + contents: '*' + targetFolder: '$(Build.BinariesDirectory)/mugle-miner/plugins' + - task: CopyFiles@2 + displayName: Copy config + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + inputs: + sourceFolder: '$(Build.SourcesDirectory)' + contents: 'mugle-miner.toml' + targetFolder: '$(Build.BinariesDirectory)/mugle-miner' + - task: ArchiveFiles@2 + displayName: Gather assets + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + inputs: + rootFolderOrFile: '$(Build.BinariesDirectory)/mugle-miner' + archiveType: 'tar' + tarCompression: 'gz' + archiveFile: '$(Build.ArtifactStagingDirectory)/mugle-miner-$(build.my_tag)-$(build.platform).tar.gz' + - script: | + openssl sha256 $(Build.ArtifactStagingDirectory)/mugle-miner-$(build.my_tag)-$(build.platform).tar.gz > $(Build.ArtifactStagingDirectory)/mugle-miner-$(build.my_tag)-$(build.platform)-sha256sum.txt + displayName: Create Checksum + condition: and(succeeded(), contains(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + - task: GithubRelease@0 + displayName: Github release + condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'), eq(variables['CI_JOB'], 'release' )) + inputs: + gitHubConnection: 'ignopeverell' + repositoryName: 'mimblewimble/mugle-miner' + action: 'edit' + target: '$(build.sourceVersion)' + tagSource: 'manual' + tag: '$(build.my_tag)' + assets: | + $(Build.ArtifactStagingDirectory)/mugle-miner-$(build.my_tag)-$(build.platform).tar.gz + $(Build.ArtifactStagingDirectory)/mugle-miner-$(build.my_tag)-$(build.platform)-sha256sum.txt + title: '$(build.my_tag)' + assetUploadMode: 'replace' + addChangeLog: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 088ba6b..0dd0db6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk +*.swp +.DS_Store +.mugle* +node* +target +*.iml +mugle-miner.log diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..50c6b33 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cuckoo-miner/src/cuckoo_sys/plugins/cuckoo"] + path = cuckoo-miner/src/cuckoo_sys/plugins/cuckoo + url = https://github.com/mugleproject/cuckoo.git diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..cd6db9e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +The Code of Conduct for this repository [can be found online](https://mugle.org/policies/code_of_conduct). diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1f553a0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1683 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gimli 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ahash" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ahash" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "array-macro" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "addr2line 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bufstream" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "built" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cargo-lock 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "git2 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cargo-lock" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cl-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cmake" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "const-random" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "const-random-macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cuckoo_miner" +version = "4.0.0" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cmake 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "const-cstr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mugle_miner_plugin 4.0.0", + "mugle_miner_util 4.0.0", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cursive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ahash 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "enum-map 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "enumset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pancurses 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xi-unicode 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs-sys 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enum-map" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "array-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enum-map-derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enumset" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "enumset_derive 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enumset_derive" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fs_extra" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "git2" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "libgit2-sys 0.12.7+1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mugle_miner" +version = "4.0.0" +dependencies = [ + "backtrace 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", + "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "built 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cuckoo_miner 4.0.0", + "cursive 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mugle_miner_config 4.0.0", + "mugle_miner_plugin 4.0.0", + "mugle_miner_util 4.0.0", + "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ocl_cuckaroo 1.0.2", + "ocl_cuckatoo 1.0.2", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mugle_miner_config" +version = "4.0.0" +dependencies = [ + "cuckoo_miner 4.0.0", + "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mugle_miner_util 4.0.0", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mugle_miner_plugin" +version = "4.0.0" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mugle_miner_util" +version = "4.0.0" +dependencies = [ + "backtrace 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-async 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-term 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hashbrown" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ahash 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.72" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libgit2-sys" +version = "0.12.7+1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libloading" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memoffset" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "native-tls" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ncurses" +version = "5.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-bigint" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ocl" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "ocl-core 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "qutex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ocl-core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cl-sys 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "ocl-core-vector 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ocl-core-vector" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ocl_cuckaroo" +version = "1.0.2" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mugle_miner_plugin 4.0.0", + "hashbrown 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "ocl 0.19.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ocl_cuckatoo" +version = "1.0.2" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mugle_miner_plugin 4.0.0", + "hashbrown 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "ocl 0.19.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl" +version = "0.10.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pancurses" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pdcurses-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pdcurses-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "qutex" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signal-hook" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slog-async" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-term" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winreg" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xi-unicode" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum addr2line 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +"checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +"checksum ahash 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6f33b5018f120946c1dcf279194f238a9f146725593ead1c08fa47ff22b0b5d3" +"checksum ahash 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" +"checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +"checksum arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" +"checksum array-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6" +"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum backtrace 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)" = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +"checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" +"checksum built 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "161483ae87631dd826cb40fc696d9e5a9fa94e19c2e69a372dcedd7dc68e7c0a" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +"checksum cargo-lock 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8504b63dd1249fd1745b7b4ef9b6f7b107ddeb3c95370043c7dbcc38653a2679" +"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" +"checksum cl-sys 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e8573fa3ff8acd6c49e8e113296c54277e82376b96c6ca6307848632cce38e44" +"checksum cmake 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" +"checksum const-cstr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" +"checksum const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a" +"checksum const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a" +"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +"checksum crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +"checksum cursive 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d62b2fdb98a428e33442612fba04fc7f5a5b67989f0cd3abcfd754dbbec88a85" +"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +"checksum dirs-sys 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +"checksum enum-map 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "70a375f899a53b9848ad9fb459b5bf90e4851ae5d9fea89134b062dc1828b26e" +"checksum enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e57001dfb2532f5a103ff869656887fae9a8defa7d236f3e39d2ee86ed629ad7" +"checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +"checksum enumset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "93182dcb6530c757e5879b22ebc5cfbd034861585b442819389614e223ac1c47" +"checksum enumset_derive 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "751a786cfcc7d5ceb9e0fe06f0e911da6ce3a3044633e029df4c370193c86a62" +"checksum failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +"checksum failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" +"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum gimli 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +"checksum git2 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)" = "11e4b2082980e751c4bf4273e9cbb4a02c655729c8ee8a79f66cad03c8f4d31e" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum hashbrown 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf" +"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" +"checksum libgit2-sys 0.12.7+1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcd07968649bcb7b9351ecfde53ca4d27673cccfdf57c84255ec18710f3153e0" +"checksum libloading 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" +"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" +"checksum miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" +"checksum ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82" +"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +"checksum num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +"checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" +"checksum num-complex 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" +"checksum num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +"checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +"checksum num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +"checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +"checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +"checksum object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" +"checksum ocl 0.19.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa54742f44f5813a4cb4c8c16f7b4069cb3a17a88a354754a50d6b951ae32e31" +"checksum ocl-core 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "81bc628faf959b5e07b1251252926dfe0dd1b3f2709cef8998c97936ddbdaa74" +"checksum ocl-core-vector 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4072920739958adeec5abedec51af70febc58f7fff0601aaa0827c1f3c8fefd" +"checksum openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)" = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +"checksum owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +"checksum pancurses 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d3058bc37c433096b2ac7afef1c5cdfae49ede0a4ffec3dfc1df1df0959d0ff0" +"checksum pdcurses-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +"checksum pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" +"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +"checksum proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" +"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +"checksum qutex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "084325f9fb9f2c23e4c225be1a4799583badd1666c3c6bbc67bacc6147f20ba1" +"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +"checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +"checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +"checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +"checksum serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)" = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +"checksum signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" +"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +"checksum slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cc9c640a4adbfbcc11ffb95efe5aa7af7309e002adab54b185507dbf2377b99" +"checksum slog-async 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b3336ce47ce2f96673499fc07eb85e3472727b9a7a2959964b002c2ce8fbbb" +"checksum slog-term 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bab1d807cf71129b05ce36914e1dbb6fbfbdecaf686301cb457f4fa967f9f5b6" +"checksum stable_deref_trait 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" +"checksum syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" +"checksum synstructure 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" +"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +"checksum tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +"checksum vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" +"checksum xi-unicode 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e71b85d8b1b8bfaf4b5c834187554d201a8cd621c2bbfa33efd41a3ecabd48b2" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2aec0f6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "mugle_miner" +version = "4.0.0" +authors = ["Mugle Developers "] +description = "Mining software for Mugle, supports CPU and CUDA GPUs." +build = "src/build/build.rs" +license = "Apache-2.0" +repository = "https://github.com/mugleproject/mugle-miner" +keywords = [ "crypto", "mugle", "mimblewimble", "mining"] +autobins = false + +[workspace] +members = ["config", "util", "plugin"] + +[features] +default = ["tui"] +opencl = ["ocl_cuckatoo", "ocl_cuckaroo"] +tui = ["cursive"] + +[[bin]] +name = "mugle-miner" +path = "src/bin/mugle_miner.rs" + +[dependencies] +backtrace = "0.3" +bufstream = "0.1" +native-tls = "0.2" +serde = "1" +serde_derive = "1" +serde_json = "1" +slog = { version = "2", features = ["max_level_trace", "release_max_level_trace"] } +term = "0.6" +time = "0.1" + +mugle_miner_util = { path = "./util", version = "4.0.0" } +mugle_miner_plugin = { path = "./plugin", version = "4.0.0" } +mugle_miner_config = { path = "./config", version = "4.0.0" } +#cuckoo_miner = { path = "./cuckoo-miner", version = "4.0.0" } +#use this alternative inclusion below to build cuda plugins +cuckoo_miner = { path = "./cuckoo-miner", version = "4.0.0", features = ["build-cuda-plugins"]} +ocl_cuckatoo = { path = "./ocl_cuckatoo", version = "1.0.2", optional = true} +ocl_cuckaroo = { path = "./ocl_cuckaroo", version = "1.0.2", optional = true} + +[dependencies.cursive] +version = "0.14" +default-features = false +features = ["pancurses-backend"] +optional = true + + +[build-dependencies] +built = { version= "0.4", features = ["git2","chrono"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ab9be40 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Multistage docker build, requires docker 17.05 + +# builder stage +FROM nvidia/cuda:10.0-devel as builder + +RUN set -ex && \ + apt-get update && \ + apt-get --no-install-recommends --yes install \ + libncurses5-dev \ + libncursesw5-dev \ + cmake \ + git \ + curl \ + libssl-dev \ + pkg-config + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y + +RUN git clone https://github.com/mugleproject/mugle-miner && cd mugle-miner && git submodule update --init + +RUN cd mugle-miner && sed -i '/^cuckoo_miner = {/s/^/#/' Cargo.toml && sed -i '/^#.*build-cuda-plugins"]/s/^#//' Cargo.toml + +RUN cd mugle-miner && $HOME/.cargo/bin/cargo build --release + +# runtime stage +FROM nvidia/cuda:10.0-base + +RUN set -ex && \ + apt-get update && \ + apt-get --no-install-recommends --yes install \ + libncurses5 \ + libncursesw5 + +COPY --from=builder /mugle-miner/target/release/mugle-miner /mugle-miner/target/release/mugle-miner +COPY --from=builder /mugle-miner/target/release/plugins/* /mugle-miner/target/release/plugins/ +COPY --from=builder /mugle-miner/mugle-miner.toml /mugle-miner/mugle-miner.toml + +WORKDIR /mugle-miner + +RUN sed -i -e 's/run_tui = true/run_tui = false/' mugle-miner.toml + +RUN echo '#!/bin/bash\n\ +if [ $# -eq 1 ]\n\ + then\n\ +sed -i -e 's/127.0.0.1/\$1/g' mugle-miner.toml\n\ +fi\n\ +./target/release/mugle-miner' > run.sh + +# If the mugle server is not at 127.0.0.1 provide the ip or hostname to the container +# by command line (i.e. docker run --name miner1 --rm -i -t miner_image 1.2.3.4) + +ENTRYPOINT ["sh", "run.sh"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..02f656c --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +[![Build Status](https://dev.azure.com/mimblewimble/mugle-miner/_apis/build/status/mimblewimble.mugle-miner?branchName=master)](https://dev.azure.com/mimblewimble/mugle-miner/_build/latest?definitionId=5&branchName=master) + +# Mugle Miner + +A standalone mining implementation intended for mining Mugle against a running Mugle node. + +## Supported Platforms + +At present, only mining plugins for linux-x86_64 and MacOS exist. This will likely change over time as the community creates more solvers for different platforms. + +## Requirements + +- rust 1.30+ (use [rustup]((https://www.rustup.rs/))- i.e. `curl https://sh.rustup.rs -sSf | sh; source $HOME/.cargo/env`) +- cmake 3.2+ (for [Cuckoo mining plugins]((https://github.com/mugleproject/cuckoo-miner))) +- ncurses and libs (ncurses, ncursesw5) +- zlib libs (zlib1g-dev or zlib-devel) +- linux-headers (reported needed on Alpine linux) + +And a [running Mugle node](https://github.com/mugleproject/mugle/blob/master/doc/build.md) to mine into! + +## Build steps + +```sh +git clone https://github.com/mugleproject/mugle-miner.git +cd mugle-miner +git submodule update --init +cargo build +``` + +### Building the Cuckoo-Miner plugins + +Mugle-miner automatically builds x86_64 CPU plugins. Cuda plugins are also provided, but are +not enabled by default. To enable them, modify `Cargo.toml` as follows: + +``` +change: +cuckoo_miner = { path = "./cuckoo-miner" } +to: +cuckoo_miner = { path = "./cuckoo-miner", features = ["build-cuda-plugins"]} +``` + +The Cuda toolkit 9+ must be installed on your system (check with `nvcc --version`) + +### Building the OpenCL plugins +OpenCL plugins are not enabled by default. Run `install_ocl_plugins.sh` script to build and install them. + +``` +./install_ocl_plugins.sh +``` +You must install OpenCL libraries for your operating system before. +If you just need to compile them (for development or testing purposes) build mugle-miner the following way: + +``` +cargo build --features opencl +``` + +### Build errors + +See [Troubleshooting](https://github.com/mugleproject/docs/wiki/Troubleshooting) + +## What was built? + +A successful build gets you: + + - `target/debug/mugle-miner` - the main mugle-miner binary + - `target/debug/plugins/*` - mining plugins + +Make sure you always run mugle-miner within a directory that contains a +`mugle-miner.toml` configuration file. + +While testing, put the mugle-miner binary on your path like this: + +``` +export PATH=/path/to/mugle-miner/dir/target/debug:$PATH +``` + +You can then run `mugle-miner` directly. + +# Configuration + +Mugle-miner can be further configured via the `mugle-miner.toml` file. +This file contains contains inline documentation on all configuration +options, and should be the first point of reference. + +You should always ensure that this file exists in the directory from which you're +running mugle-miner. + +# Using mugle-miner + +There is a [Mugle forum post](https://www.mugle-forum.org/t/how-to-mine-cuckoo-30-in-mugle-help-us-test-and-collect-stats/152) with further detail on how to configure mugle-miner and mine mugle's testnet. diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..0120535 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,57 @@ +# Copyright 2019 The Mugle Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +trigger: + branches: + include: + - master + - milestone/* + tags: + include: ['*'] + +pr: + branches: + include: ['*'] + +variables: + RUST_BACKTRACE: '1' + RUSTFLAGS: '-C debug-assertions' + +jobs: +- job: linux + pool: + vmImage: ubuntu-latest + strategy: + matrix: + test: + CI_JOB: test + release: + CI_JOB: release + PLATFORM: linux-amd64 + steps: + - template: '.ci/install.yml' + - script: cd cuckoo-miner && cargo test --release + displayName: Cargo Test + - template: '.ci/release.yml' +- job: macos + pool: + vmImage: macos-latest + strategy: + matrix: + release: + CI_JOB: release + PLATFORM: macos + steps: + - template: '.ci/install.yml' + - template: '.ci/release.yml' \ No newline at end of file diff --git a/config/Cargo.toml b/config/Cargo.toml new file mode 100644 index 0000000..5f56c2a --- /dev/null +++ b/config/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mugle_miner_config" +version = "4.0.0" +authors = ["Mugle Developers "] +description = "Configuration handling for the mugle miner" +license = "Apache-2.0" +repository = "https://github.com/mugleproject/mugle-miner" +workspace = ".." + +[dependencies] +cuckoo_miner = { path = "../cuckoo-miner", version = "4.0.0" } +dirs = "2" +mugle_miner_util = { path = "../util", version = "4.0.0" } +serde = "1" +serde_derive = "1" +slog = { version = "2", features = ["max_level_trace", "release_max_level_trace"] } +toml = "0.5" diff --git a/config/src/config.rs b/config/src/config.rs new file mode 100644 index 0000000..ead7946 --- /dev/null +++ b/config/src/config.rs @@ -0,0 +1,248 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Configuration file management + +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; + +use cuckoo::{CuckooMinerError, PluginConfig}; +use toml; +use types::MinerConfig; +use types::{ConfigError, ConfigMembers, GlobalConfig, MugleMinerPluginConfig}; +use util::{LoggingConfig, LOGGER}; + +extern crate dirs; + +/// The default file name to use when trying to derive +/// the config file location + +const CONFIG_FILE_NAME: &str = "mugle-miner.toml"; +const MUGLE_HOME: &str = ".mugle"; + +/// resolve a read parameter to a solver param, (or not if it isn't found) +fn resolve_param(config: &mut PluginConfig, name: &str, value: u32) { + match name { + "nthreads" => config.params.nthreads = value, + "ntrims" => config.params.ntrims = value, + "cpuload" => { + config.params.cpuload = match value { + 1 => true, + _ => false, + } + } + "device" => config.params.device = value, + "blocks" => config.params.blocks = value, + "tbp" => config.params.tpb = value, + "expand" => config.params.expand = value, + "genablocks" => config.params.genablocks = value, + "genatpb" => config.params.genatpb = value, + "genbtpb" => config.params.genbtpb = value, + "trimtpb" => config.params.trimtpb = value, + "tailtpb" => config.params.tailtpb = value, + "recoverblocks" => config.params.recoverblocks = value, + "recovertpb" => config.params.recovertpb = value, + "platform" => config.params.platform = value, + "edge_bits" => config.params.edge_bits = value, + n => { + warn!(LOGGER, "Configuration param: {} unknown. Ignored.", n); + } + }; +} + +/// Transforms a set of mugle-miner plugin configs to cuckoo-miner plugins configs +pub fn read_configs( + plugin_dir: Option, + conf_in: Vec, +) -> Result, CuckooMinerError> { + // Resolve a final plugin path, either config-provided or from the current executable path + let plugin_dir_absolute_path = match plugin_dir { + Some(path) => { + let absolute_path = path.canonicalize().map_err(CuckooMinerError::from); + if let Ok(path) = &absolute_path { + debug!( + LOGGER, + "Using mining plugin dir provided by config: {:?}", path + ); + }; + absolute_path + } + None => { + let absolute_path = + env::current_exe() + .map_err(CuckooMinerError::from) + .map(|mut env_path| { + env_path.pop(); + // cargo test exes are a directory further down + if env_path.ends_with("deps") { + env_path.pop(); + } + env_path.push("plugins"); + env_path + }); + if let Ok(path) = &absolute_path { + debug!( + LOGGER, + "No mining plugin dir provided by config. Using default plugin dir: {:?}", path + ); + }; + absolute_path + } + }?; + + let mut return_vec = vec![]; + for conf in conf_in { + let res = PluginConfig::new(plugin_dir_absolute_path.clone(), &conf.plugin_name); + match res { + Err(e) => { + error!(LOGGER, "Error reading plugin config: {:?}", e); + return Err(e); + } + Ok(mut c) => { + if conf.parameters.is_some() { + let params = conf.parameters.unwrap(); + for k in params.keys() { + resolve_param(&mut c, k, *params.get(k).unwrap()); + } + } + return_vec.push(c) + } + } + } + Ok(return_vec) +} + +/// Returns the defaults, as strewn throughout the code +impl Default for ConfigMembers { + fn default() -> ConfigMembers { + ConfigMembers { + mining: MinerConfig::default(), + logging: Some(LoggingConfig::default()), + } + } +} + +impl Default for GlobalConfig { + fn default() -> GlobalConfig { + GlobalConfig { + config_file_path: None, + using_config_file: false, + members: Some(ConfigMembers::default()), + } + } +} + +impl GlobalConfig { + /// Need to decide on rules where to read the config file from, + /// but will take a stab at logic for now + + fn derive_config_location(&mut self) -> Result<(), ConfigError> { + // First, check working directory + let mut config_path = env::current_dir().unwrap(); + config_path.push(CONFIG_FILE_NAME); + if config_path.exists() { + self.config_file_path = Some(config_path); + return Ok(()); + } + // Next, look in directory of executable + let mut config_path = env::current_exe().unwrap(); + config_path.pop(); + config_path.push(CONFIG_FILE_NAME); + if config_path.exists() { + self.config_file_path = Some(config_path); + return Ok(()); + } + // Then look in {user_home}/.mugle + let config_path = dirs::home_dir(); + if let Some(mut p) = config_path { + p.push(MUGLE_HOME); + p.push(CONFIG_FILE_NAME); + if p.exists() { + self.config_file_path = Some(p); + return Ok(()); + } + } + + // Give up + Err(ConfigError::FileNotFoundError(String::from(""))) + } + + /// Takes the path to a config file, or if NONE, tries + /// to determine a config file based on rules in + /// derive_config_location + + pub fn new(file_path: Option<&str>) -> Result { + let mut return_value = GlobalConfig::default(); + if let Some(fp) = file_path { + return_value.config_file_path = Some(PathBuf::from(&fp)); + } else { + let _result = return_value.derive_config_location(); + } + + // No attempt at a config file, just return defaults + if return_value.config_file_path.is_none() { + return Ok(return_value); + } + + // Config file path is given but not valid + if !return_value.config_file_path.as_mut().unwrap().exists() { + return Err(ConfigError::FileNotFoundError( + return_value + .config_file_path + .unwrap() + .to_str() + .unwrap() + .to_string(), + )); + } + + // Try to parse the config file if it exists + // explode if it does exist but something's wrong + // with it + return_value.read_config() + } + + /// Read config + pub fn read_config(mut self) -> Result { + let mut file = File::open(self.config_file_path.as_mut().unwrap())?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let decoded: Result = toml::from_str(&contents); + match decoded { + Ok(gc) => { + // Put the struct back together, because the config + // file was flattened a bit + self.using_config_file = true; + self.members = Some(gc); + Ok(self) + } + Err(e) => Err(ConfigError::ParseError( + self.config_file_path.unwrap().to_str().unwrap().to_string(), + format!("{}", e), + )), + } + } + + /// Serialize config + pub fn ser_config(&mut self) -> Result { + let encoded: Result = + toml::to_string(self.members.as_mut().unwrap()); + match encoded { + Ok(enc) => Ok(enc), + Err(e) => Err(ConfigError::SerializationError(format!("{}", e))), + } + } +} diff --git a/config/src/lib.rs b/config/src/lib.rs new file mode 100644 index 0000000..4ab18ca --- /dev/null +++ b/config/src/lib.rs @@ -0,0 +1,38 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Crate wrapping up mining configuration file + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate toml; + +#[macro_use] +extern crate slog; + +extern crate cuckoo_miner as cuckoo; +extern crate mugle_miner_util as util; + +mod config; +mod types; + +pub use config::read_configs; +pub use types::{ConfigError, ConfigMembers, GlobalConfig, MugleMinerPluginConfig, MinerConfig}; diff --git a/config/src/types.rs b/config/src/types.rs new file mode 100644 index 0000000..1cd53cb --- /dev/null +++ b/config/src/types.rs @@ -0,0 +1,156 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Public types for config modules + +use std::collections::HashMap; +use std::path::PathBuf; +use std::{fmt, io}; + +use util; + +/// CuckooMinerPlugin configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MugleMinerPluginConfig { + /// The type of plugin to load (i.e. filters on filename) + pub plugin_name: String, + + /// + pub parameters: Option>, +} + +impl Default for MugleMinerPluginConfig { + fn default() -> MugleMinerPluginConfig { + MugleMinerPluginConfig { + plugin_name: String::new(), + parameters: None, + } + } +} + +/// Error type wrapping config errors. +#[derive(Debug)] +pub enum ConfigError { + /// Error with parsing of config file + ParseError(String, String), + + /// Error with fileIO while reading config file + FileIOError(String, String), + + /// No file found + FileNotFoundError(String), + + /// Error serializing config values + SerializationError(String), +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ConfigError::ParseError(ref file_name, ref message) => write!( + f, + "Error parsing configuration file at {} - {}", + file_name, message + ), + ConfigError::FileIOError(ref file_name, ref message) => { + write!(f, "{} {}", message, file_name) + } + ConfigError::FileNotFoundError(ref file_name) => { + write!(f, "Configuration file not found: {}", file_name) + } + ConfigError::SerializationError(ref message) => { + write!(f, "Error serializing configuration: {}", message) + } + } + } +} + +impl From for ConfigError { + fn from(error: io::Error) -> ConfigError { + ConfigError::FileIOError( + String::from(""), + format!("Error loading config file: {}", error), + ) + } +} + +/// basic mining configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MinerConfig { + /// Whether to run the tui + pub run_tui: bool, + + /// mining loop by adding a sleep to the thread + pub stratum_server_addr: String, + + /// login for the stratum server + pub stratum_server_login: Option, + + /// password for the stratum server + pub stratum_server_password: Option, + + /// whether tls is enabled for the stratum server + pub stratum_server_tls_enabled: Option, + + /// plugin dir + pub miner_plugin_dir: Option, + + /// Cuckoo miner plugin configuration, one for each plugin + pub miner_plugin_config: Vec, +} + +impl Default for MinerConfig { + fn default() -> MinerConfig { + MinerConfig { + run_tui: false, + miner_plugin_dir: None, + miner_plugin_config: vec![], + stratum_server_addr: String::from("http://127.0.0.1:13416"), + stratum_server_login: None, + stratum_server_password: None, + stratum_server_tls_enabled: None, + } + } +} + +/// separately for now, then put them together as a single +/// ServerConfig object afterwards. This is to flatten +/// out the configuration file into logical sections, +/// as they tend to be quite nested in the code +/// Most structs optional, as they may or may not +/// be needed depending on what's being run +#[derive(Debug, Serialize, Deserialize)] +pub struct GlobalConfig { + /// Keep track of the file we've read + pub config_file_path: Option, + /// keep track of whether we're using + /// a config file or just the defaults + /// for each member + pub using_config_file: bool, + /// Global member config + pub members: Option, +} + +/// Keeping an 'inner' structure here, as the top +/// level GlobalConfigContainer options might want to keep +/// internal state that we don't necessarily +/// want serialised or deserialised +#[derive(Debug, Serialize, Deserialize)] +pub struct ConfigMembers { + /// Server config + /// Mining config + pub mining: MinerConfig, + /// Logging config + pub logging: Option, +} diff --git a/cuckoo-miner/Cargo.toml b/cuckoo-miner/Cargo.toml new file mode 100644 index 0000000..156c944 --- /dev/null +++ b/cuckoo-miner/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "cuckoo_miner" +version = "4.0.0" +authors = ["yeastplume"] +license = "MIT/Apache-2.0/BSD-3-Clause" +description = "Rust bindings to John Tromp's Cuckoo Cycle Implementations" +repository = "https://github.com/mugleproject/mugle-miner" +readme = "../README.md" +build = "src/build.rs" + +[features] +default = [] +#feature to allow turing off plugin builds +no-plugin-build = [] +#whether to test avx2 CPU plugins +test-avx2 = [] +#feature which defines whether to build cuda libs +build-cuda-plugins = [] + +[dependencies] +byteorder = "1" +blake2-rfc = "0.2" +glob = "0.3" +mugle_miner_util = { path = "../util", version = "4.0.0" } +mugle_miner_plugin = { path = "../plugin", version = "4.0.0" } +libc = "0.2" +libloading = "0.6" +serde = "1" +serde_derive = "1" +serde_json = "1" +slog = { version = "2", features = ["max_level_trace", "release_max_level_trace"] } +rand = "0.3" +regex = "1.3" +rust-crypto = "0.2" +time = "0.1" + +[dev-dependencies] +const-cstr = "0.3" + +[build-dependencies] +cmake = "0.1" +fs_extra = "1" diff --git a/cuckoo-miner/src/build.rs b/cuckoo-miner/src/build.rs new file mode 100644 index 0000000..12b0b9b --- /dev/null +++ b/cuckoo-miner/src/build.rs @@ -0,0 +1,86 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! cmake wrapper for build + +extern crate cmake; +extern crate fs_extra; + +use cmake::Config; +use fs_extra::dir::*; +use std::path::PathBuf; +use std::{env, fs}; + +#[cfg(feature = "build-cuda-plugins")] +const BUILD_CUDA_PLUGINS: &str = "TRUE"; +#[cfg(not(feature = "build-cuda-plugins"))] +const BUILD_CUDA_PLUGINS: &str = "FALSE"; + +/// Tests whether source cuckoo directory exists + +pub fn fail_on_empty_directory(name: &str) { + if fs::read_dir(name).unwrap().count() == 0 { + println!( + "The `{}` directory is empty. Did you forget to pull the submodules?", + name + ); + println!("Try `git submodule update --init --recursive`"); + panic!(); + } +} + +fn main() { + #[cfg(feature = "no-plugin-build")] + return; + fail_on_empty_directory("src/cuckoo_sys/plugins/cuckoo"); + let path_str = env::var("OUT_DIR").unwrap(); + let mut out_path = PathBuf::from(&path_str); + out_path.pop(); + out_path.pop(); + out_path.pop(); + let mut plugin_path = PathBuf::from(&path_str); + plugin_path.push("build"); + plugin_path.push("plugins"); + // Collect the files and directories we care about + let p = PathBuf::from("src/cuckoo_sys/plugins"); + let dir_content = match get_dir_content(p) { + Ok(c) => c, + Err(e) => panic!("Error getting directory content: {}", e), + }; + for d in dir_content.directories { + let file_content = get_dir_content(d).unwrap(); + for f in file_content.files { + println!("cargo:rerun-if-changed={}", f); + } + } + for f in dir_content.files { + println!("cargo:rerun-if-changed={}", f); + } + + let dst = Config::new("src/cuckoo_sys/plugins") + .define("BUILD_CUDA_PLUGINS", BUILD_CUDA_PLUGINS) //whatever flags go here + //.cflag("-foo") //and here + .build_target("") + .build(); + + println!("Plugin path: {:?}", plugin_path); + println!("OUT PATH: {:?}", out_path); + let mut options = CopyOptions::new(); + options.overwrite = true; + if let Err(e) = copy(plugin_path, out_path, &options) { + println!("{:?}", e); + } + + println!("cargo:rustc-link-search=native={}", dst.display()); +} diff --git a/cuckoo-miner/src/config/mod.rs b/cuckoo-miner/src/config/mod.rs new file mode 100644 index 0000000..f2b997b --- /dev/null +++ b/cuckoo-miner/src/config/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Configuration types +//! for loading a mining plugin and performing a cuckoo mining call. + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +pub mod types; diff --git a/cuckoo-miner/src/config/types.rs b/cuckoo-miner/src/config/types.rs new file mode 100644 index 0000000..e9c121f --- /dev/null +++ b/cuckoo-miner/src/config/types.rs @@ -0,0 +1,104 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Public Types used for cuckoo-miner module + +use plugin::SolverParams; +use std::path::PathBuf; +use std::{fmt, io}; +use {CuckooMinerError, PluginLibrary}; + +pub static SO_SUFFIX: &str = ".cuckooplugin"; + +/// CuckooMinerPlugin configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PluginConfig { + /// The display name of the plugin to load + pub name: String, + + /// The path to the file + pub file: String, + + /// device params + pub params: SolverParams, +} + +impl PluginConfig { + /// create new! + pub fn new(mut plugin_dir: PathBuf, name: &str) -> Result { + plugin_dir.push(format!("{}{}", name, SO_SUFFIX).as_str()); + let plugin_file_str = plugin_dir.to_str().ok_or_else(|| { + CuckooMinerError::PluginNotFoundError( + "Invalid plugin path. Paths must be valid unicode".to_owned(), + ) + })?; + + PluginLibrary::new(plugin_file_str).map(|plugin_library| { + let params = plugin_library.get_default_params(); + plugin_library.unload(); + PluginConfig { + name: name.to_owned(), + file: plugin_file_str.to_owned(), + params, + } + }) + } +} + +/// Error type wrapping config errors. +#[derive(Debug)] +#[allow(dead_code)] +pub enum ConfigError { + /// Error with parsing of config file + ParseError(String, String), + + /// Error with fileIO while reading config file + FileIOError(String, String), + + /// No file found + FileNotFoundError(String), + + /// Error serializing config values + SerializationError(String), +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ConfigError::ParseError(ref file_name, ref message) => write!( + f, + "Error parsing configuration file at {} - {}", + file_name, message + ), + ConfigError::FileIOError(ref file_name, ref message) => { + write!(f, "{} {}", message, file_name) + } + ConfigError::FileNotFoundError(ref file_name) => { + write!(f, "Configuration file not found: {}", file_name) + } + ConfigError::SerializationError(ref message) => { + write!(f, "Error serializing configuration: {}", message) + } + } + } +} + +impl From for ConfigError { + fn from(error: io::Error) -> ConfigError { + ConfigError::FileIOError( + String::from(""), + format!("Error loading config file: {}", error), + ) + } +} diff --git a/cuckoo-miner/src/cuckoo_sys/ffi.rs b/cuckoo-miner/src/cuckoo_sys/ffi.rs new file mode 100644 index 0000000..2d89b4d --- /dev/null +++ b/cuckoo-miner/src/cuckoo_sys/ffi.rs @@ -0,0 +1,202 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Low-Level manager for loading and unloading plugins. These functions +//! should generally not be called directly by most consumers, who should +//! be using the high level interfaces found in the config, manager, and +//! miner modules. These functions are meant for internal cuckoo-miner crates, +//! and will not be exposed to other projects including the cuckoo-miner crate. + +use plugin::*; +use std::sync::{Arc, Mutex}; +use util::LOGGER; + +use libloading; + +use error::CuckooMinerError; + +/// Struct to hold instances of loaded plugins + +pub struct PluginLibrary { + ///The full file path to the plugin loaded by this instance + pub lib_full_path: String, + + loaded_library: Arc>, + cuckoo_create_solver_ctx: Arc>, + cuckoo_destroy_solver_ctx: Arc>, + cuckoo_run_solver: Arc>, + cuckoo_stop_solver: Arc>, + cuckoo_fill_default_params: Arc>, +} + +impl PluginLibrary { + /// Loads the specified library + + pub fn new(lib_full_path: &str) -> Result { + debug!(LOGGER, "Loading miner plugin: {}", &lib_full_path); + + let result = libloading::Library::new(lib_full_path); + + if let Err(e) = result { + return Err(CuckooMinerError::PluginNotFoundError(format!( + "{} - {:?}", + lib_full_path, e + ))); + } + + let loaded_library = result.unwrap(); + PluginLibrary::load_symbols(loaded_library, lib_full_path) + } + + fn load_symbols( + loaded_library: libloading::Library, + path: &str, + ) -> Result { + unsafe { + let ret_val = PluginLibrary { + lib_full_path: String::from(path), + + cuckoo_create_solver_ctx: { + let cuckoo_create_solver_ctx: libloading::Symbol = + loaded_library.get(b"create_solver_ctx\0").unwrap(); + Arc::new(Mutex::new(*cuckoo_create_solver_ctx.into_raw())) + }, + + cuckoo_destroy_solver_ctx: { + let cuckoo_destroy_solver_ctx: libloading::Symbol = + loaded_library.get(b"destroy_solver_ctx\0").unwrap(); + Arc::new(Mutex::new(*cuckoo_destroy_solver_ctx.into_raw())) + }, + + cuckoo_run_solver: { + let cuckoo_run_solver: libloading::Symbol = + loaded_library.get(b"run_solver\0").unwrap(); + Arc::new(Mutex::new(*cuckoo_run_solver.into_raw())) + }, + + cuckoo_stop_solver: { + let cuckoo_stop_solver: libloading::Symbol = + loaded_library.get(b"stop_solver\0").unwrap(); + Arc::new(Mutex::new(*cuckoo_stop_solver.into_raw())) + }, + + cuckoo_fill_default_params: { + let cuckoo_fill_default_params: libloading::Symbol = + loaded_library.get(b"fill_default_params\0").unwrap(); + Arc::new(Mutex::new(*cuckoo_fill_default_params.into_raw())) + }, + + loaded_library: Arc::new(Mutex::new(loaded_library)), + }; + + Ok(ret_val) + } + } + + /// #Description + /// + /// Unloads the currently loaded plugin and all symbols. + /// + /// #Arguments + /// + /// None + /// + /// #Returns + /// + /// Nothing + /// + + pub fn unload(&self) { + let cuckoo_create_solver_ref = self.cuckoo_create_solver_ctx.lock().unwrap(); + drop(cuckoo_create_solver_ref); + + let cuckoo_destroy_solver_ref = self.cuckoo_destroy_solver_ctx.lock().unwrap(); + drop(cuckoo_destroy_solver_ref); + + let cuckoo_run_solver_ref = self.cuckoo_run_solver.lock().unwrap(); + drop(cuckoo_run_solver_ref); + + let cuckoo_stop_solver_ref = self.cuckoo_stop_solver.lock().unwrap(); + drop(cuckoo_stop_solver_ref); + + let cuckoo_fill_default_params_ref = self.cuckoo_fill_default_params.lock().unwrap(); + drop(cuckoo_fill_default_params_ref); + + let loaded_library_ref = self.loaded_library.lock().unwrap(); + drop(loaded_library_ref); + } + + /// Create a solver context + pub fn create_solver_ctx(&self, params: &mut SolverParams) -> *mut SolverCtx { + let call_ref = self.cuckoo_create_solver_ctx.lock().unwrap(); + unsafe { call_ref(params) } + } + + /// Destroy solver context + pub fn destroy_solver_ctx(&self, ctx: *mut SolverCtx) { + let call_ref = self.cuckoo_destroy_solver_ctx.lock().unwrap(); + unsafe { call_ref(ctx) } + } + + /// Run Solver + pub fn run_solver( + &self, + ctx: *mut SolverCtx, + header: Vec, + nonce: u64, + range: u32, + solutions: &mut SolverSolutions, + stats: &mut SolverStats, + ) -> u32 { + let call_ref = self.cuckoo_run_solver.lock().unwrap(); + unsafe { + call_ref( + ctx, + header.as_ptr(), + header.len() as u32, + nonce, + range, + solutions, + stats, + ) + } + } + + /// Stop solver + pub fn stop_solver(&self, ctx: *mut SolverCtx) { + let call_ref = self.cuckoo_stop_solver.lock().unwrap(); + unsafe { call_ref(ctx) } + } + + /// Get default params + pub fn get_default_params(&self) -> SolverParams { + let mut ret_params = SolverParams::default(); + let call_ref = self.cuckoo_fill_default_params.lock().unwrap(); + unsafe { + call_ref(&mut ret_params); + ret_params + } + } + + /// Get an instance of the stop function, to allow it to run in another thread + pub fn get_stop_solver_instance(&self) -> Arc> { + self.cuckoo_stop_solver.clone() + } + + /// Stop solver from a "detached" instance + pub fn stop_solver_from_instance(inst: Arc>, ctx: *mut SolverCtx) { + let call_ref = inst.lock().unwrap(); + unsafe { call_ref(ctx) } + } +} diff --git a/cuckoo-miner/src/cuckoo_sys/mod.rs b/cuckoo-miner/src/cuckoo_sys/mod.rs new file mode 100644 index 0000000..7388a9f --- /dev/null +++ b/cuckoo-miner/src/cuckoo_sys/mod.rs @@ -0,0 +1,30 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +//! Crate containing the low level calls to cuckoo-miner plugins, including +//! functions +//! for loading and unloading plugins, querying what plugins are installed on +//! the system, +//! as well as the actual mining calls to a plugin. This crate should be used +//! by other +//! cuckoo-miner crates, but should not be exposed to external consumers of the +//! crate. + +pub mod ffi; diff --git a/cuckoo-miner/src/cuckoo_sys/plugins/CMakeLists.txt b/cuckoo-miner/src/cuckoo_sys/plugins/CMakeLists.txt new file mode 100644 index 0000000..c41a530 --- /dev/null +++ b/cuckoo-miner/src/cuckoo_sys/plugins/CMakeLists.txt @@ -0,0 +1,245 @@ +cmake_minimum_required(VERSION 3.2) +project (CuckooMinerPlugins) + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins) +set (CMAKE_CXX_FLAGS "--std=c++11") + +set (OPT "-O3") +set (DOPT "-DPREFETCH") + +set (FLAGS "-Wno-format -Wno-deprecated-declarations -D_POSIX_C_SOURCE=200112L ${OPT} ${DOPT} -I. ${CPPFLAGS} -pthread") +set (GPP_FLAGS "-march=native -m64 ${FLAGS}") +set (CFLAGS "-Wno-format -fomit-frame-pointer ${OPT}") +set (GCC_FLAGS "-m64 -std=gnu11 ${CFLAGS}") + +set (CUDA_HOST_COMPILER_OVERRIDE $ENV{CUDA_HOST_COMPILER}) +set (SKIP_CUCKATOO_GPU $ENV{SKIP_CUCKATOO_GPU}) +set (SKIP_CUCKAROO_GPU $ENV{SKIP_CUCKAROO_GPU}) +set (SKIP_CUCKAROOD_GPU $ENV{SKIP_CUCKAROOD_GPU}) +set (SKIP_CUCKAROOM_CPU "1") +set (SKIP_CUCKAROOM_GPU $ENV{SKIP_CUCKAROOM_GPU}) +set (SKIP_CUCKAROOZ_CPU "1") +set (SKIP_CUCKAROOZ_GPU $ENV{SKIP_CUCKAROOZ_GPU}) + +#blake2b prerequisite +set (BLAKE_2B "cuckoo/src/crypto/blake2b-ref.c") + +#common to all plugins +set (PLUGIN_BUILD_FLAGS "-DC_CALL_CONVENTION=1 -DSQUASH_OUTPUT=1") + +#build CPU target +function (build_cpu_target sources target props) + add_library(${target} SHARED ${sources}) + set_target_properties(${target} PROPERTIES COMPILE_FLAGS "${GPP_FLAGS} ${props} ${PLUGIN_BUILD_FLAGS}" PREFIX "" SUFFIX ".cuckooplugin") +endfunction() + +function (build_cuda_target sources target props) + if (BUILD_CUDA_PLUGINS) + include("cmake/find_cuda.cmake") + if (CUDA_FOUND) + set (CUDA_PROPAGATE_HOST_FLAGS ON) + cuda_add_library (${target} SHARED ${sources} OPTIONS "${props} ${PLUGIN_BUILD_FLAGS}") + set_target_properties(${target} PROPERTIES PREFIX "" SUFFIX ".cuckooplugin") + endif (CUDA_FOUND) + endif (BUILD_CUDA_PLUGINS) +endfunction() + +################################################################################## +### CUCKATOO (Asic Tuned) ######################################################## +################################################################################## + +### AT LEAN CPU TARGETS ######################################### + +set (AT_LEAN_CPU_SRC + cuckoo/src/cuckatoo/cuckatoo.h + cuckoo/src/cuckatoo/bitmap.hpp + cuckoo/src/cuckatoo/graph.hpp + cuckoo/src/cuckatoo/compress.hpp + cuckoo/src/threads/barrier.hpp + cuckoo/src/crypto/siphash.hpp + cuckoo/src/cuckatoo/lean.hpp + cuckoo/src/cuckatoo/lean.cpp + ${BLAKE_2B}) + +build_cpu_target("${AT_LEAN_CPU_SRC}" cuckatoo_lean_cpu_compat_19 "-mno-avx2 -DNSIPHASH=4 -DATOMIC -DEDGEBITS=19") +build_cpu_target("${AT_LEAN_CPU_SRC}" cuckatoo_lean_cpu_compat_31 "-mno-avx2 -DNSIPHASH=4 -DATOMIC -DEDGEBITS=31") +build_cpu_target("${AT_LEAN_CPU_SRC}" cuckatoo_lean_cpu_avx2_31 "-mavx2 -DNSIPHASH=8 -DATOMIC -DEDGEBITS=31") + +### AT MEAN CPU TARGETS ######################################### + +set (AT_MEAN_CPU_SRC + cuckoo/src/cuckatoo/cuckatoo.h + cuckoo/src/cuckatoo/bitmap.hpp + cuckoo/src/cuckatoo/graph.hpp + cuckoo/src/cuckatoo/compress.hpp + cuckoo/src/threads/barrier.hpp + cuckoo/src/crypto/siphash.hpp + cuckoo/src/cuckatoo/mean.hpp + cuckoo/src/cuckatoo/mean.cpp + ${BLAKE_2B}) + +build_cpu_target("${AT_MEAN_CPU_SRC}" cuckatoo_mean_cpu_compat_19 "-mno-avx2 -DXBITS=2 -DNSIPHASH=4 -DSAVEEDGES -DEDGEBITS=19") +build_cpu_target("${AT_MEAN_CPU_SRC}" cuckatoo_mean_cpu_avx2_19 "-mavx2 -DXBITS=2 -DNSIPHASH=8 -DSAVEEDGES -DEDGEBITS=19") +build_cpu_target("${AT_MEAN_CPU_SRC}" cuckatoo_mean_cpu_compat_31 "-mno-avx2 -DXBITS=8 -DNSIPHASH=4 -DEXPANDROUND=8 -DCOMPRESSROUND=22 -DSAVEEDGES -DEDGEBITS=31") +build_cpu_target("${AT_MEAN_CPU_SRC}" cuckatoo_mean_cpu_avx2_31 "-mavx2 -DXBITS=8 -DNSIPHASH=8 -DEXPANDROUND=8 -DCOMPRESSROUND=22 -DSAVEEDGES -DEDGEBITS=31") + +### AT LEAN CUDA TARGETS ######################################### + +set (AT_LEAN_CUDA_SRC + cuckoo/src/crypto/siphash.cuh + cuckoo/src/cuckatoo/lean.cu + ${BLAKE_2B} ) + +if (NOT SKIP_CUCKATOO_GPU) + build_cuda_target("${AT_LEAN_CUDA_SRC}" cuckatoo_lean_cuda_19 "-DEDGEBITS=19") + build_cuda_target("${AT_LEAN_CUDA_SRC}" cuckatoo_lean_cuda_31 "-DEDGEBITS=31") +endif() + +### AT MEAN CUDA TARGETS ######################################### + +set (AT_MEAN_CUDA_SRC + cuckoo/src/crypto/siphash.cuh + cuckoo/src/cuckatoo/mean.cu + ${BLAKE_2B} ) + +if (NOT SKIP_CUCKATOO_GPU) + build_cuda_target("${AT_MEAN_CUDA_SRC}" cuckatoo_mean_cuda_gtx_31 "-DNEPS_A=135 -DNEPS_B=88 -DPART_BITS=1 -DFLUSHA=2 -DEDGEBITS=31") + build_cuda_target("${AT_MEAN_CUDA_SRC}" cuckatoo_mean_cuda_rtx_31 "-DNEPS_A=135 -DNEPS_B=88 -DPART_BITS=0 -DEDGEBITS=31") + build_cuda_target("${AT_MEAN_CUDA_SRC}" cuckatoo_mean_cuda_rtx_32 "-DNEPS_A=135 -DNEPS_B=88 -DPART_BITS=1 -DEDGEBITS=32") +endif() + +################################################################################## +### CUCKAROO (Asic Resistant) ################################################### +################################################################################## + +### AR CPU BUILDING ######################################### + +set (AR_CPU_SRC + cuckoo/src/cuckaroo/cuckaroo.hpp + cuckoo/src/cuckaroo/bitmap.hpp + cuckoo/src/cuckaroo/graph.hpp + cuckoo/src/threads/barrier.hpp + cuckoo/src/crypto/siphash.hpp + cuckoo/src/cuckaroo/mean.hpp + cuckoo/src/cuckaroo/mean.cpp + ${BLAKE_2B}) + +### AR CPU TARGETS ######################################### + +build_cpu_target("${AR_CPU_SRC}" cuckaroo_cpu_compat_19 "-mno-avx2 -DXBITS=2 -DNSIPHASH=4 -DEDGEBITS=19 -DSAVEEDGES") +build_cpu_target("${AR_CPU_SRC}" cuckaroo_cpu_avx2_19 "-mavx2 -DXBITS=2 -DNSIPHASH=8 -DEDGEBITS=19 -DSAVEEDGES") +build_cpu_target("${AR_CPU_SRC}" cuckaroo_cpu_compat_29 "-mno-avx2 -DNSIPHASH=4 -DEDGEBITS=29 -DSAVEEDGES") +build_cpu_target("${AR_CPU_SRC}" cuckaroo_cpu_avx2_29 "-mavx2 -DNSIPHASH=8 -DEDGEBITS=29 -DSAVEEDGES") + +### AR CUDA TARGETS ######################################### + +set (AR_CUDA_SRC cuckoo/src/cuckaroo/mean.cu ${BLAKE_2B} ) + +if (NOT SKIP_CUCKAROO_GPU) + build_cuda_target("${AR_CUDA_SRC}" cuckaroo_cuda_19 "-DEPS_A=4 -DEPS_B=3 -DIDXSHIFT=2 -DEDGEBITS=19") + build_cuda_target("${AR_CUDA_SRC}" cuckaroo_cuda_29 "-DEDGEBITS=29") +endif() + +################################################################################## +### CUCKAROOD (Asic Resistant) ################################################## +################################################################################## + +### AR CPU BUILDING ######################################### + +set (AR2_CPU_SRC + cuckoo/src/cuckarood/cuckarood.hpp + cuckoo/src/cuckarood/bitmap.hpp + cuckoo/src/cuckarood/graph.hpp + cuckoo/src/threads/barrier.hpp + cuckoo/src/crypto/siphash.hpp + cuckoo/src/crypto/siphashxN.h + cuckoo/src/cuckarood/mean.hpp + cuckoo/src/cuckarood/mean.cpp + ${BLAKE_2B}) + +### AR CPU TARGETS ######################################### + +build_cpu_target("${AR2_CPU_SRC}" cuckarood_cpu_compat_19 "-mno-avx2 -DXBITS=2 -DNSIPHASH=4 -DEDGEBITS=19 -DSAVEEDGES") +build_cpu_target("${AR2_CPU_SRC}" cuckarood_cpu_avx2_19 "-mavx2 -DXBITS=2 -DNSIPHASH=8 -DEDGEBITS=19 -DSAVEEDGES") +build_cpu_target("${AR2_CPU_SRC}" cuckarood_cpu_compat_29 "-mno-avx2 -DNSIPHASH=4 -DEDGEBITS=29 -DSAVEEDGES") +build_cpu_target("${AR2_CPU_SRC}" cuckarood_cpu_avx2_29 "-mavx2 -DNSIPHASH=8 -DEDGEBITS=29 -DSAVEEDGES") + +### AR CUDA TARGETS ######################################### + +set (AR2_CUDA_SRC cuckoo/src/cuckarood/kernel.cuh cuckoo/src/cuckarood/photon.cu ${BLAKE_2B} ) + +if (NOT SKIP_CUCKAROOD_GPU) + build_cuda_target("${AR2_CUDA_SRC}" cuckarood_cuda_19 "-DXBITS=1 -DEPS_A=4 -DEPS_B=3 -DIDXSHIFT=8 -DEDGEBITS=19") + build_cuda_target("${AR2_CUDA_SRC}" cuckarood_cuda_29 "-DEDGEBITS=29") +endif() + +################################################################################## +### CUCKAROOM (Asic Resistant) ################################################## +################################################################################## + +### AR CPU BUILDING ######################################### + +set (AR2_CPU_SRC + cuckoo/src/cuckaroom/cuckaroom.hpp + cuckoo/src/cuckaroom/bitmap.hpp + cuckoo/src/cuckaroom/graph.hpp + cuckoo/src/threads/barrier.hpp + cuckoo/src/crypto/siphash.hpp + cuckoo/src/crypto/siphashxN.h + cuckoo/src/cuckaroom/mean.hpp + cuckoo/src/cuckaroom/mean.cpp + ${BLAKE_2B}) + +### AR CPU TARGETS ######################################### + +if (NOT SKIP_CUCKAROOM_CPU) + build_cpu_target("${AR2_CPU_SRC}" cuckaroom_cpu_compat_19 "-mno-avx2 -DXBITS=2 -DNSIPHASH=4 -DEDGEBITS=19 -DSAVEEDGES") + build_cpu_target("${AR2_CPU_SRC}" cuckaroom_cpu_avx2_19 "-mavx2 -DXBITS=2 -DNSIPHASH=8 -DEDGEBITS=19 -DSAVEEDGES") + build_cpu_target("${AR2_CPU_SRC}" cuckaroom_cpu_compat_29 "-mno-avx2 -DNSIPHASH=4 -DEDGEBITS=29 -DSAVEEDGES") + build_cpu_target("${AR2_CPU_SRC}" cuckaroom_cpu_avx2_29 "-mavx2 -DNSIPHASH=8 -DEDGEBITS=29 -DSAVEEDGES") +endif() + +### AR CUDA TARGETS ######################################### + +set (AR2_CUDA_SRC cuckoo/src/cuckaroom/meaner.cu ${BLAKE_2B} ) +set (AR2_CUDA_SRC2 cuckoo/src/cuckaroom/kernel.cuh cuckoo/src/cuckaroom/mean.cu ${BLAKE_2B} ) + +if (NOT SKIP_CUCKAROOM_GPU) + build_cuda_target("${AR2_CUDA_SRC}" cuckaroom_cuda_29 "-DEDGEBITS=29") + build_cuda_target("${AR2_CUDA_SRC2}" cuckaroom_old_cuda_29 "-DEDGEBITS=29") + build_cuda_target("${AR2_CUDA_SRC2}" cuckaroom_cuda_19 "-DXBITS=1 -DEPS_A=4 -DEPS_B=3 -DIDXSHIFT=8 -DEDGEBITS=19") +endif() + +################################################################################## +### CUCKAROOZ (Asic Resistant) ################################################## +################################################################################## + +### AR CPU BUILDING ######################################### + +set (AR2_CPU_SRC + cuckoo/src/cuckarooz/cuckarooz.hpp + cuckoo/src/cuckarooz/bitmap.hpp + cuckoo/src/cuckarooz/graph.hpp + cuckoo/src/threads/barrier.hpp + cuckoo/src/crypto/siphash.hpp + cuckoo/src/crypto/siphashxN.h + cuckoo/src/cuckarooz/mean.hpp + cuckoo/src/cuckarooz/mean.cpp + ${BLAKE_2B}) + +### AR CPU TARGETS ######################################### + +if (NOT SKIP_CUCKAROOZ_CPU) + build_cpu_target("${AR2_CPU_SRC}" cuckarooz_cpu_compat_19 "-mno-avx2 -DXBITS=2 -DNSIPHASH=4 -DEDGEBITS=19 -DSAVEEDGES") + build_cpu_target("${AR2_CPU_SRC}" cuckarooz_cpu_avx2_19 "-mavx2 -DXBITS=2 -DNSIPHASH=8 -DEDGEBITS=19 -DSAVEEDGES") + build_cpu_target("${AR2_CPU_SRC}" cuckarooz_cpu_compat_29 "-mno-avx2 -DNSIPHASH=4 -DEDGEBITS=29 -DSAVEEDGES") + build_cpu_target("${AR2_CPU_SRC}" cuckarooz_cpu_avx2_29 "-mavx2 -DNSIPHASH=8 -DEDGEBITS=29 -DSAVEEDGES") +endif() + +### AR CUDA TARGETS ######################################### + +set (AR2_CUDA_SRC cuckoo/src/cuckarooz/mean.cu ${BLAKE_2B} ) + +if (NOT SKIP_CUCKAROOZ_GPU) + build_cuda_target("${AR2_CUDA_SRC}" cuckarooz_cuda_29 "-DEDGEBITS=29") +endif() diff --git a/cuckoo-miner/src/cuckoo_sys/plugins/cmake/CudaComputeTargetFlags.cmake b/cuckoo-miner/src/cuckoo_sys/plugins/cmake/CudaComputeTargetFlags.cmake new file mode 100644 index 0000000..674741e --- /dev/null +++ b/cuckoo-miner/src/cuckoo_sys/plugins/cmake/CudaComputeTargetFlags.cmake @@ -0,0 +1,43 @@ +# +# Compute target flags macros by Anatoly Baksheev +# +# Usage in CmakeLists.txt: +# include(CudaComputeTargetFlags.cmake) +# APPEND_TARGET_ARCH_FLAGS() + +#compute flags macros +MACRO(CUDA_COMPUTE_TARGET_FLAGS arch_bin arch_ptx cuda_nvcc_target_flags) + string(REGEX REPLACE "\\." "" ARCH_BIN_WITHOUT_DOTS "${${arch_bin}}") + string(REGEX REPLACE "\\." "" ARCH_PTX_WITHOUT_DOTS "${${arch_ptx}}") + + set(cuda_computer_target_flags_temp "") + + # Tell NVCC to add binaries for the specified GPUs + string(REGEX MATCHALL "[0-9()]+" ARCH_LIST "${ARCH_BIN_WITHOUT_DOTS}") + foreach(ARCH IN LISTS ARCH_LIST) + if (ARCH MATCHES "([0-9]+)\\(([0-9]+)\\)") + # User explicitly specified PTX for the concrete BIN + set(cuda_computer_target_flags_temp ${cuda_computer_target_flags_temp} -gencode arch=compute_${CMAKE_MATCH_2},code=sm_${CMAKE_MATCH_1}) + else() + # User didn't explicitly specify PTX for the concrete BIN, we assume PTX=BIN + set(cuda_computer_target_flags_temp ${cuda_computer_target_flags_temp} -gencode arch=compute_${ARCH},code=sm_${ARCH}) + endif() + endforeach() + + # Tell NVCC to add PTX intermediate code for the specified architectures + string(REGEX MATCHALL "[0-9]+" ARCH_LIST "${ARCH_PTX_WITHOUT_DOTS}") + foreach(ARCH IN LISTS ARCH_LIST) + set(cuda_computer_target_flags_temp ${cuda_computer_target_flags_temp} -gencode arch=compute_${ARCH},code=compute_${ARCH}) + endforeach() + + set(${cuda_nvcc_target_flags} ${cuda_computer_target_flags_temp}) +ENDMACRO() + +MACRO(APPEND_TARGET_ARCH_FLAGS) + set(cuda_nvcc_target_flags "") + CUDA_COMPUTE_TARGET_FLAGS(CUDA_ARCH_BIN CUDA_ARCH_PTX cuda_nvcc_target_flags) + if (cuda_nvcc_target_flags) + message(STATUS "CUDA NVCC target flags: ${cuda_nvcc_target_flags}") + list(APPEND CUDA_NVCC_FLAGS ${cuda_nvcc_target_flags}) + endif() +ENDMACRO() diff --git a/cuckoo-miner/src/cuckoo_sys/plugins/cmake/find_cuda.cmake b/cuckoo-miner/src/cuckoo_sys/plugins/cmake/find_cuda.cmake new file mode 100644 index 0000000..0b82e9c --- /dev/null +++ b/cuckoo-miner/src/cuckoo_sys/plugins/cmake/find_cuda.cmake @@ -0,0 +1,69 @@ +#from: https://github.com/PointCloudLibrary/pcl/blob/master/cmake/pcl_find_cuda.cmake +# Find CUDA +if(MSVC) + # Setting this to true brakes Visual Studio builds. + set(CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE OFF CACHE BOOL "CUDA_ATTACH_VS_BUILD_RULE_TO_CUDA_FILE") +endif() + +set(CUDA_FIND_QUIETLY TRUE) +find_package(CUDA 4) + +if(CUDA_FOUND) + message(STATUS "Found CUDA Toolkit v${CUDA_VERSION_STRING}") + + # CUDA 9.1 + installs a symlink to its preferred compiler, so use that if it exists + # Otherwise, try to default to /usr/bin/gcc if one hasn't been supplied at the command line + # This will not override an existing cache + # value if the user has passed CUDA_HOST_COMPILER_OVERRIDE on the command line. + if (CUDA_HOST_COMPILER_OVERRIDE) + set (CUDA_HOST_COMPILER ${CUDA_HOST_COMPILER_OVERRIDE}) + elseif (EXISTS /opt/cuda/bin/gcc) + set(CUDA_HOST_COMPILER /opt/cuda/bin/gcc) + elseif (EXISTS /usr/bin/gcc) + set(CUDA_HOST_COMPILER /usr/bin/gcc) + elseif (EXISTS /usr/bin/cc) + set(CUDA_HOST_COMPILER /usr/bin/cc) + endif() + + message(STATUS "Setting CMAKE_HOST_COMPILER to ${CUDA_HOST_COMPILER}.") + + # Send a warning if CUDA_HOST_COMPILER is set to a compiler that is known + # to be unsupported. + if (CUDA_HOST_COMPILER STREQUAL CMAKE_C_COMPILER AND CMAKE_C_COMPILER_ID STREQUAL "Clang") + message(WARNING "CUDA_HOST_COMPILER is set to an unsupported compiler: ${CMAKE_C_COMPILER}.") + endif() + + # CUDA_ARCH_BIN is a space separated list of versions to include in output so-file. So you can set CUDA_ARCH_BIN = 10 11 12 13 20 + # Also user can specify virtual arch in parenthesis to limit instructions set, + # for example CUDA_ARCH_BIN = 11(11) 12(11) 13(11) 20(11) 21(11) -> forces using only sm_11 instructions. + # The CMake scripts interpret XX as XX (XX). This allows user to omit parenthesis. + # Arch 21 is an exceptional case since it doesn't have own sm_21 instructions set. + # So 21 = 21(21) is an invalid configuration and user has to explicitly force previous sm_20 instruction set via 21(20). + # CUDA_ARCH_BIN adds support of only listed GPUs. As alternative CMake scripts also parse 'CUDA_ARCH_PTX' variable, + # which is a list of intermediate PTX codes to include in final so-file. The PTX code can/will be JIT compiled for any current or future GPU. + # To add support of older GPU for kinfu, I would embed PTX 11 and 12 into so-file. GPU with sm_13 will run PTX 12 code (no difference for kinfu) + + # Find a complete list for CUDA compute capabilities at http://developer.nvidia.com/cuda-gpus + + if(NOT ${CUDA_VERSION_STRING} VERSION_LESS "10.0") + set(__cuda_arch_bin "3.5 3.7 5.0 5.2 6.0 6.1 7.0 7.2 7.5") + elseif(NOT ${CUDA_VERSION_STRING} VERSION_LESS "9.0") + set(__cuda_arch_bin "3.5 3.7 5.0 5.2 6.0 6.1 7.0") + elseif(NOT ${CUDA_VERSION_STRING} VERSION_LESS "8.0") + set(__cuda_arch_bin "3.5 5.0 5.2 5.3 6.0 6.1") + else() + set(__cuda_arch_bin "3.5") + endif() + + set(CUDA_ARCH_BIN ${__cuda_arch_bin} CACHE STRING "Specify 'real' GPU architectures to build binaries for, BIN(PTX) format is supported") + + set(CUDA_ARCH_PTX "" CACHE STRING "Specify 'virtual' PTX arch to build PTX intermediate code for. Example: 1.0 1.2 or 10 12") + #set(CUDA_ARCH_PTX "1.1 1.2" CACHE STRING "Specify 'virtual' PTX arch to build PTX intermediate code for. Example: 1.0 1.2 or 10 12") + + # Guess this macros will be included in cmake distributive + include(cmake/CudaComputeTargetFlags.cmake) + APPEND_TARGET_ARCH_FLAGS() + + # Prevent compilation issues between recent gcc versions and old CUDA versions + list(APPEND CUDA_NVCC_FLAGS "-D_FORCE_INLINES") +endif() diff --git a/cuckoo-miner/src/cuckoo_sys/plugins/cuckoo b/cuckoo-miner/src/cuckoo_sys/plugins/cuckoo new file mode 160000 index 0000000..652563a --- /dev/null +++ b/cuckoo-miner/src/cuckoo_sys/plugins/cuckoo @@ -0,0 +1 @@ +Subproject commit 652563addb227a0ae61dc1cb70b3481cae9df5a1 diff --git a/cuckoo-miner/src/error/mod.rs b/cuckoo-miner/src/error/mod.rs new file mode 100644 index 0000000..aa21cf3 --- /dev/null +++ b/cuckoo-miner/src/error/mod.rs @@ -0,0 +1,68 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Common error type used by all cuckoo-miner modules, as well as any exernal +//! consumers of the cuckoo-miner crate. + +use std::io; +use std::string; + +/// #Description +/// +/// Top level enum for all errors that the cuckoo-miner crate can return. +/// + +#[derive(Debug)] +pub enum CuckooMinerError { + /// Occurs when trying to call a plugin function when a + /// mining plugin is not loaded. + PluginNotLoadedError(String), + + /// Occurs when trying to load plugin function that doesn't exist + PluginSymbolNotFoundError(String), + + /// Occurs when attempting to load a plugin that doesn't exist + PluginNotFoundError(String), + + /// Occurs when trying to load a plugin directory that doesn't + /// contain any plugins + NoPluginsFoundError(String), + + /// Unexpected return code from a plugin + UnexpectedResultError(u32), + + /// Error setting a parameter + ParameterError(String), + + /// IO Error + PluginIOError(String), + + /// Plugin processing can't start + PluginProcessingError(String), + + /// Error getting stats or stats not implemented + StatsError(String), +} + +impl From for CuckooMinerError { + fn from(error: io::Error) -> Self { + CuckooMinerError::PluginIOError(format!("Error loading plugin: {}", error)) + } +} + +impl From for CuckooMinerError { + fn from(error: string::FromUtf8Error) -> Self { + CuckooMinerError::PluginIOError(format!("Error loading plugin description: {}", error)) + } +} diff --git a/cuckoo-miner/src/lib.rs b/cuckoo-miner/src/lib.rs new file mode 100644 index 0000000..d15b050 --- /dev/null +++ b/cuckoo-miner/src/lib.rs @@ -0,0 +1,60 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Overview +//! +//! cuckoo-miner is a Rust wrapper around John Tromp's Cuckoo Miner +//! C implementations, intended primarily for use in the Mugle MimbleWimble +//! blockhain development project. However, it is also suitable for use as +//! a standalone miner or by any other project needing to use the +//! cuckoo cycle proof of work. cuckoo-miner is plugin based, and provides +//! a high level interface to load and work with C mining implementations. + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +extern crate mugle_miner_plugin as plugin; +extern crate mugle_miner_util as util; + +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; + +extern crate blake2_rfc as blake2; +extern crate byteorder; +extern crate crypto; +extern crate rand; +extern crate regex; + +extern crate libc; +extern crate libloading as libloading; + +#[macro_use] +extern crate slog; + +extern crate glob; + +mod config; +mod cuckoo_sys; +mod error; +mod miner; + +pub use config::types::PluginConfig; +pub use cuckoo_sys::ffi::PluginLibrary; +pub use error::CuckooMinerError; +pub use miner::miner::CuckooMiner; diff --git a/cuckoo-miner/src/miner/consensus.rs b/cuckoo-miner/src/miner/consensus.rs new file mode 100644 index 0000000..5d028be --- /dev/null +++ b/cuckoo-miner/src/miner/consensus.rs @@ -0,0 +1,168 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Difficulty calculation as from Mugle +use blake2::blake2b::Blake2b; +use byteorder::{BigEndian, ByteOrder}; +use std::cmp::{max, min}; +use std::fmt; + +// constants from mugle +const PROOF_SIZE: usize = 42; + +/// The difficulty is defined as the maximum target divided by the block hash. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] +pub struct Difficulty { + num: u64, +} + +impl Difficulty { + /// Convert a `u32` into a `Difficulty` + pub fn from_num(num: u64) -> Difficulty { + // can't have difficulty lower than 1 + Difficulty { num: max(num, 1) } + } + + /// unscaled proof + fn from_proof_unscaled(proof: &Proof) -> Difficulty { + Difficulty::from_num(proof.scaled_difficulty(1u64)) + } + + /// Converts the difficulty into a u64 + pub fn to_num(self) -> u64 { + self.num + } +} + +impl fmt::Display for Difficulty { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.num) + } +} + +/// A Cuck(at)oo Cycle proof of work, consisting of the edge_bits to get the graph +/// size (i.e. the 2-log of the number of edges) and the nonces +/// of the graph solution. While being expressed as u64 for simplicity, +/// nonces a.k.a. edge indices range from 0 to (1 << edge_bits) - 1 +/// +/// The hash of the `Proof` is the hash of its packed nonces when serializing +/// them at their exact bit size. The resulting bit sequence is padded to be +/// byte-aligned. + +#[derive(Clone, PartialOrd, PartialEq)] +pub struct Proof { + /// Power of 2 used for the size of the cuckoo graph + pub edge_bits: u8, + /// The nonces + pub nonces: Vec, +} + +impl fmt::Debug for Proof { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Cuckoo{}(", self.edge_bits)?; + for (i, val) in self.nonces[..].iter().enumerate() { + write!(f, "{:x}", val)?; + if i < self.nonces.len() - 1 { + write!(f, " ")?; + } + } + write!(f, ")") + } +} + +impl Eq for Proof {} + +impl Proof { + /// Difficulty achieved by this proof with given scaling factor + fn scaled_difficulty(&self, scale: u64) -> u64 { + let diff = ((scale as u128) << 64) / (max(1, self.hash().to_u64()) as u128); + min(diff, ::max_value() as u128) as u64 + } + + /// Hash, as in Mugle + fn hash(&self) -> Hash { + let nonce_bits = self.edge_bits as usize; + let mut bitvec = BitVec::new(nonce_bits * PROOF_SIZE); + for (n, nonce) in self.nonces.iter().enumerate() { + for bit in 0..nonce_bits { + if nonce & (1 << bit) != 0 { + bitvec.set_bit_at(n * nonce_bits + (bit as usize)) + } + } + } + let mut blake2b = Blake2b::new(32); + blake2b.update(&bitvec.bits); + let mut ret = [0; 32]; + ret.copy_from_slice(blake2b.finalize().as_bytes()); + Hash(ret) + } + + /// unscaled difficulty + pub fn to_difficulty_unscaled(&self) -> Difficulty { + Difficulty::from_proof_unscaled(&self) + } +} + +struct BitVec { + bits: Vec, +} + +impl BitVec { + /// Number of bytes required to store the provided number of bits + fn bytes_len(bits_len: usize) -> usize { + (bits_len + 7) / 8 + } + + fn new(bits_len: usize) -> BitVec { + BitVec { + bits: vec![0; BitVec::bytes_len(bits_len)], + } + } + + fn set_bit_at(&mut self, pos: usize) { + self.bits[pos / 8] |= 1 << (pos % 8) as u8; + } +} + +impl Hash { + /// to u64 + pub fn to_u64(&self) -> u64 { + BigEndian::read_u64(&self.0) + } + + /// to hex + pub fn to_hex(&self) -> String { + util::to_hex(self.0.to_vec()) + } +} + +/// A hash to uniquely (or close enough) identify one of the main blockchain +/// constructs. Used pervasively for blocks, transactions and outputs. +#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +pub struct Hash([u8; 32]); + +impl fmt::Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hash_hex = self.to_hex(); + const NUM_SHOW: usize = 12; + + write!(f, "{}", &hash_hex[..NUM_SHOW]) + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} diff --git a/cuckoo-miner/src/miner/miner.rs b/cuckoo-miner/src/miner/miner.rs new file mode 100644 index 0000000..1a1b2db --- /dev/null +++ b/cuckoo-miner/src/miner/miner.rs @@ -0,0 +1,357 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Main interface for callers into cuckoo-miner. Provides functionality +//! to load a mining plugin, send it a Cuckoo Cycle POW problem, and +//! return any resulting solutions. + +use std::ptr::NonNull; +use std::sync::{mpsc, Arc, RwLock}; +use std::{thread, time}; +use util::LOGGER; + +use config::types::PluginConfig; +use miner::types::{JobSharedData, JobSharedDataType, SolverInstance}; + +use miner::consensus::Proof; +use miner::util; +use plugin::{Solution, SolverCtxWrapper, SolverSolutions, SolverStats}; +use {CuckooMinerError, PluginLibrary}; + +/// Miner control Messages +#[derive(Debug)] +enum ControlMessage { + /// Stop everything, pull down, exis + Stop, + /// Stop current mining iteration, set solver threads to paused + Pause, + /// Resume + Resume, + /// Solver reporting stopped + SolverStopped(usize), +} + +/// An instance of a miner, which loads a cuckoo-miner plugin +/// and calls its mine function according to the provided configuration + +pub struct CuckooMiner { + /// Configurations + configs: Vec, + + /// Data shared across threads + pub shared_data: Arc>, + + /// Job control tx + control_txs: Vec>, + + /// solver loop tx + solver_loop_txs: Vec>, + + /// Solver has stopped and cleanly shutdown + solver_stopped_rxs: Vec>, +} + +impl CuckooMiner { + /// Creates a new instance of a CuckooMiner with the given configuration. + /// One PluginConfig per device + + pub fn new(configs: Vec) -> CuckooMiner { + let len = configs.len(); + CuckooMiner { + configs, + shared_data: Arc::new(RwLock::new(JobSharedData::new(len))), + control_txs: vec![], + solver_loop_txs: vec![], + solver_stopped_rxs: vec![], + } + } + + /// Solver's instance of a thread + fn solver_thread( + mut solver: SolverInstance, + instance: usize, + shared_data: JobSharedDataType, + control_rx: mpsc::Receiver, + solver_loop_rx: mpsc::Receiver, + solver_stopped_tx: mpsc::Sender, + ) { + { + let mut s = shared_data.write().unwrap(); + s.stats[instance].set_plugin_name(&solver.config.name); + } + // "Detach" a stop function from the solver, to let us keep a control thread going + let ctx = solver.lib.create_solver_ctx(&mut solver.config.params); + let control_ctx = SolverCtxWrapper(NonNull::new(ctx).unwrap()); + + let stop_fn = solver.lib.get_stop_solver_instance(); + + // monitor whether to send a stop signal to the solver, which should + // end the current solve attempt below + let stop_handle = thread::spawn(move || loop { + let ctx_ptr = control_ctx.0.as_ptr(); + while let Some(message) = control_rx.iter().next() { + match message { + ControlMessage::Stop => { + PluginLibrary::stop_solver_from_instance(stop_fn.clone(), ctx_ptr); + return; + } + ControlMessage::Pause => { + PluginLibrary::stop_solver_from_instance(stop_fn.clone(), ctx_ptr); + } + _ => {} + }; + } + }); + + let mut iter_count = 0; + let mut paused = true; + loop { + if let Some(message) = solver_loop_rx.try_iter().next() { + debug!( + LOGGER, + "solver_thread - solver_loop_rx got msg: {:?}", message + ); + match message { + ControlMessage::Stop => break, + ControlMessage::Pause => paused = true, + ControlMessage::Resume => paused = false, + _ => {} + } + } + if paused { + thread::sleep(time::Duration::from_micros(100)); + continue; + } + { + let mut s = shared_data.write().unwrap(); + s.stats[instance].set_plugin_name(&solver.config.name); + } + let header_pre = { shared_data.read().unwrap().pre_nonce.clone() }; + let header_post = { shared_data.read().unwrap().post_nonce.clone() }; + let height = { shared_data.read().unwrap().height }; + let job_id = { shared_data.read().unwrap().job_id }; + let target_difficulty = { shared_data.read().unwrap().difficulty }; + let header = util::get_next_header_data(&header_pre, &header_post); + let nonce = header.0; + //let sec_scaling = header.2; + solver.lib.run_solver( + ctx, + header.1, + 0, + 1, + &mut solver.solutions, + &mut solver.stats, + ); + iter_count += 1; + let still_valid = { height == shared_data.read().unwrap().height }; + if still_valid { + let mut s = shared_data.write().unwrap(); + s.stats[instance] = solver.stats.clone(); + s.stats[instance].iterations = iter_count; + if solver.solutions.num_sols > 0 { + // Filter solutions that don't meet difficulty check + let mut filtered_sols: Vec = vec![]; + for i in 0..solver.solutions.num_sols { + filtered_sols.push(solver.solutions.sols[i as usize]); + } + let mut filtered_sols: Vec = filtered_sols + .iter() + .filter(|s| { + let proof = Proof { + edge_bits: solver.solutions.edge_bits as u8, + nonces: s.proof.to_vec(), + }; + proof.to_difficulty_unscaled().to_num() >= target_difficulty + }) + .cloned() + .collect(); + for mut ss in filtered_sols.iter_mut() { + ss.nonce = nonce; + ss.id = job_id as u64; + } + solver.solutions.num_sols = filtered_sols.len() as u32; + for (i, _) in filtered_sols + .iter() + .enumerate() + .take(solver.solutions.num_sols as usize) + { + solver.solutions.sols[i] = filtered_sols[i]; + } + s.solutions.push(solver.solutions.clone()); + } + if s.stats[instance].has_errored { + s.stats[instance].set_plugin_name(&solver.config.name); + error!( + LOGGER, + "Plugin {} has errored, device: {}. Reason: {}", + s.stats[instance].get_plugin_name(), + s.stats[instance].get_device_name(), + s.stats[instance].get_error_reason(), + ); + break; + } + } + solver.solutions = SolverSolutions::default(); + thread::sleep(time::Duration::from_micros(100)); + } + + let _ = stop_handle.join(); + solver.lib.destroy_solver_ctx(ctx); + solver.unload(); + let _ = solver_stopped_tx.send(ControlMessage::SolverStopped(instance)); + } + + /// Starts solvers, ready for jobs via job control + pub fn start_solvers(&mut self) -> Result<(), CuckooMinerError> { + let mut solvers = Vec::new(); + for c in self.configs.clone() { + solvers.push(SolverInstance::new(c)?); + } + let mut i = 0; + for s in solvers { + let sd = self.shared_data.clone(); + let (control_tx, control_rx) = mpsc::channel::(); + let (solver_tx, solver_rx) = mpsc::channel::(); + let (solver_stopped_tx, solver_stopped_rx) = mpsc::channel::(); + self.control_txs.push(control_tx); + self.solver_loop_txs.push(solver_tx); + self.solver_stopped_rxs.push(solver_stopped_rx); + thread::spawn(move || { + CuckooMiner::solver_thread(s, i, sd, control_rx, solver_rx, solver_stopped_tx); + }); + i += 1; + } + Ok(()) + } + + /// An asynchronous -esque version of the plugin miner, which takes + /// parts of the header and the target difficulty as input, and begins + /// asyncronous processing to find a solution. The loaded plugin is + /// responsible + /// for how it wishes to manage processing or distribute the load. Once + /// called + /// this function will continue to find solutions over the target difficulty + /// for the given inputs and place them into its output queue until + /// instructed to stop. + + pub fn notify( + &mut self, + job_id: u32, // Job id + height: u64, // Job height + pre_nonce: &str, // Pre-nonce portion of header + post_nonce: &str, // Post-nonce portion of header + difficulty: u64, /* The target difficulty, only sols greater than this difficulty will + * be returned. */ + ) -> Result<(), CuckooMinerError> { + let mut sd = self.shared_data.write().unwrap(); + let paused = if height != sd.height { + // stop/pause any existing jobs if job is for a new + // height + self.pause_solvers(); + true + } else { + false + }; + + sd.job_id = job_id; + sd.height = height; + sd.pre_nonce = pre_nonce.to_owned(); + sd.post_nonce = post_nonce.to_owned(); + sd.difficulty = difficulty; + if paused { + self.resume_solvers(); + } + Ok(()) + } + + /// Returns solutions if currently waiting. + + pub fn get_solutions(&self) -> Option { + // just to prevent endless needless locking of this + // when using fast test miners, in real cuckoo30 terms + // this shouldn't be an issue + // TODO: Make this less blocky + // let time_pre_lock=Instant::now(); + { + let mut s = self.shared_data.write().unwrap(); + // let time_elapsed=Instant::now()-time_pre_lock; + // println!("Get_solution Time spent waiting for lock: {}", + // time_elapsed.as_secs()*1000 +(time_elapsed.subsec_nanos()/1_000_000)as u64); + if !s.solutions.is_empty() { + let sol = s.solutions.pop().unwrap(); + return Some(sol); + } + } + None + } + + /// get stats for all running solvers + pub fn get_stats(&self) -> Result, CuckooMinerError> { + let s = self.shared_data.read().unwrap(); + Ok(s.stats.clone()) + } + + /// #Description + /// + /// Stops the current job, and signals for the loaded plugin to stop + /// processing and perform any cleanup it needs to do. + /// + /// #Returns + /// + /// Nothing + + pub fn stop_solvers(&self) { + for t in self.control_txs.iter() { + let _ = t.send(ControlMessage::Stop); + } + for t in self.solver_loop_txs.iter() { + let _ = t.send(ControlMessage::Stop); + } + debug!(LOGGER, "Stop message sent"); + } + + /// Tells current solvers to stop and wait + pub fn pause_solvers(&self) { + for t in self.control_txs.iter() { + let _ = t.send(ControlMessage::Pause); + } + for t in self.solver_loop_txs.iter() { + let _ = t.send(ControlMessage::Pause); + } + debug!(LOGGER, "Pause message sent"); + } + + /// Tells current solvers to stop and wait + pub fn resume_solvers(&self) { + for t in self.control_txs.iter() { + let _ = t.send(ControlMessage::Resume); + } + for t in self.solver_loop_txs.iter() { + let _ = t.send(ControlMessage::Resume); + } + debug!(LOGGER, "Resume message sent"); + } + + /// block until solvers have all exited + pub fn wait_for_solver_shutdown(&self) { + for r in self.solver_stopped_rxs.iter() { + while let Some(message) = r.iter().next() { + if let ControlMessage::SolverStopped(i) = message { + debug!(LOGGER, "Solver stopped: {}", i); + break; + } + } + } + } +} diff --git a/cuckoo-miner/src/miner/mod.rs b/cuckoo-miner/src/miner/mod.rs new file mode 100644 index 0000000..bd6e37d --- /dev/null +++ b/cuckoo-miner/src/miner/mod.rs @@ -0,0 +1,27 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The main miner module of cuckoo-miner, which provides an interface +//! for loading a mining plugin and performing a cuckoo mining call. + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +pub mod consensus; +pub mod miner; +pub mod types; +pub mod util; diff --git a/cuckoo-miner/src/miner/types.rs b/cuckoo-miner/src/miner/types.rs new file mode 100644 index 0000000..bd513db --- /dev/null +++ b/cuckoo-miner/src/miner/types.rs @@ -0,0 +1,107 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Miner types +use std::sync::{Arc, RwLock}; + +use error::CuckooMinerError; +use plugin::{SolverSolutions, SolverStats}; +use {PluginConfig, PluginLibrary}; + +pub type JobSharedDataType = Arc>; + +/// Holds a loaded lib + config + stats +/// 1 instance = 1 device on 1 controlling thread +pub struct SolverInstance { + /// The loaded plugin + pub lib: PluginLibrary, + /// Associated config + pub config: PluginConfig, + /// Last stats output + pub stats: SolverStats, + /// Last solution output + pub solutions: SolverSolutions, +} + +impl SolverInstance { + /// Create a new solver instance with the given config + pub fn new(config: PluginConfig) -> Result { + let l = PluginLibrary::new(&config.file)?; + Ok(SolverInstance { + lib: l, + config, + stats: SolverStats::default(), + solutions: SolverSolutions::default(), + }) + } + + /// Release the lib + pub fn unload(&mut self) { + self.lib.unload(); + } +} + +/// Data intended to be shared across threads +pub struct JobSharedData { + /// ID of the current running job (not currently used) + pub job_id: u32, + + /// block height of current running job + pub height: u64, + + /// The part of the header before the nonce, which this + /// module will mutate in search of a solution + pub pre_nonce: String, + + /// The part of the header after the nonce + pub post_nonce: String, + + /// The target difficulty. Only solutions >= this + /// target will be put into the output queue + pub difficulty: u64, + + /// Output solutions + pub solutions: Vec, + + /// Current stats + pub stats: Vec, +} + +impl Default for JobSharedData { + fn default() -> JobSharedData { + JobSharedData { + job_id: 0, + height: 0, + pre_nonce: String::from(""), + post_nonce: String::from(""), + difficulty: 0, + solutions: Vec::new(), + stats: vec![], + } + } +} + +impl JobSharedData { + pub fn new(num_solvers: usize) -> JobSharedData { + JobSharedData { + job_id: 0, + height: 0, + pre_nonce: String::from(""), + post_nonce: String::from(""), + difficulty: 1, + solutions: Vec::new(), + stats: vec![SolverStats::default(); num_solvers], + } + } +} diff --git a/cuckoo-miner/src/miner/util.rs b/cuckoo-miner/src/miner/util.rs new file mode 100644 index 0000000..9d04b5c --- /dev/null +++ b/cuckoo-miner/src/miner/util.rs @@ -0,0 +1,56 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! header manipulation utility functions + +use byteorder::{BigEndian, ByteOrder}; +use rand::{self, Rng}; + +pub fn header_data(pre_nonce: &str, post_nonce: &str, nonce: u64) -> (Vec, u32) { + // Turn input strings into vectors + let mut pre_vec = from_hex_string(pre_nonce); + let mut post_vec = from_hex_string(post_nonce); + + let sec_scaling_bytes = &pre_vec.clone()[pre_vec.len() - 4..pre_vec.len()]; + let sec_scaling = BigEndian::read_u32(&sec_scaling_bytes); + + let mut nonce_bytes = [0; 8]; + BigEndian::write_u64(&mut nonce_bytes, nonce); + let mut nonce_vec = nonce_bytes.to_vec(); + + // Generate new header + pre_vec.append(&mut nonce_vec); + pre_vec.append(&mut post_vec); + + (pre_vec, sec_scaling) +} + +pub fn get_next_header_data(pre_nonce: &str, post_nonce: &str) -> (u64, Vec, u32) { + let nonce: u64 = rand::OsRng::new().unwrap().gen(); + let (hd, sec_scaling) = header_data(pre_nonce, post_nonce, nonce); + (nonce, hd, sec_scaling) +} + +/// Helper to convert a hex string +pub fn from_hex_string(in_str: &str) -> Vec { + let mut bytes = Vec::new(); + for i in 0..(in_str.len() / 2) { + let res = u8::from_str_radix(&in_str[2 * i..2 * i + 2], 16); + match res { + Ok(v) => bytes.push(v), + Err(e) => println!("Problem with hex: {}", e), + } + } + bytes +} diff --git a/etc/crate-release.sh b/etc/crate-release.sh new file mode 100755 index 0000000..8b38226 --- /dev/null +++ b/etc/crate-release.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +# check we're in the mugle root +if [ ! -f "LICENSE" ] ; then + echo "Script must be run from Mugle-miner's root directory" + exit 1 +fi + +echo "Going to package and publish each crate, if you're not logged in crates.io (missing ~/.cargo/credentials, this will fail." + +read -p "Continue? " -n 1 -r +if [[ ! $REPLY =~ ^[Yy]$ ]] +then + printf "\nbye\n" + exit 1 +fi + +echo +crates=( config cuckoo-miner plugin util ) + +for crate in "${crates[@]}" +do + echo "** Publishing $crate" + cd $crate + cargo package + cargo publish + cd .. +done + +cargo package +cargo publish + +echo "Done." diff --git a/install_ocl_plugins.sh b/install_ocl_plugins.sh new file mode 100755 index 0000000..9b4fdad --- /dev/null +++ b/install_ocl_plugins.sh @@ -0,0 +1,25 @@ +plugins_dir=$(egrep '^miner_plugin_dir' mugle-miner.toml | awk '{ print $NF }' | xargs echo) +if [ -z "$plugins_dir" ]; then + plugins_dir="target/release/plugins" +fi +mkdir -p "$plugins_dir"; + +# Install ocl_cuckatoo +cd ocl_cuckatoo +cargo build --release +cd .. +if [ "$(uname)" = "Darwin" ]; then + cp target/release/libocl_cuckatoo.dylib $plugins_dir/ocl_cuckatoo.cuckooplugin +else + cp target/release/libocl_cuckatoo.so $plugins_dir/ocl_cuckatoo.cuckooplugin +fi + +# Install ocl_cuckaroo +cd ocl_cuckaroo +cargo build --release +cd .. +if [ "$(uname)" = "Darwin" ]; then + cp target/release/libocl_cuckaroo.dylib $plugins_dir/ocl_cuckaroo.cuckooplugin +else + cp target/release/libocl_cuckaroo.so $plugins_dir/ocl_cuckaroo.cuckooplugin +fi diff --git a/mugle-miner.toml b/mugle-miner.toml new file mode 100644 index 0000000..5b2791c --- /dev/null +++ b/mugle-miner.toml @@ -0,0 +1,211 @@ +# Sample Server Configuration File for Mugle-Miner +# +# Mugle-Miner will look for this file in these places: in the following +# order: +# +# -The working directory +# -The directory in which the executable resides + +######################################### +### LOGGING CONFIGURATION ### +######################################### + +[logging] + +# Whether to log to stdout +log_to_stdout = true + +# Log level for stdout: Critical, Error, Warning, Info, Debug, Trace +stdout_log_level = "Info" + +# Whether to log to a file +log_to_file = true + +# Log level for file: Critical, Error, Warning, Info, Debug, Trace +file_log_level = "Debug" + +# Log file path +log_file_path = "mugle-miner.log" + +# Whether to append to the log file (true), or replace it on every run (false) +log_file_append = true + +######################################### +### MINING CLIENT CONFIGURATION ### +######################################### + +[mining] + +# whether to run the tui +run_tui = true + +# listening mugle stratum server url +stratum_server_addr = "127.0.0.1:6816" + +# login for the stratum server (if required) +#stratum_server_login = "http://192.168.1.100:6815" + +# password for the stratum server (if required) +#stratum_server_password = "x" + +# whether tls is enabled for the stratum server +stratum_server_tls_enabled = false + +#The directory in which mining plugins are installed +#if not specified, mugle miner will look in the directory /deps relative +#to the executable + +#miner_plugin_dir = "target/debug/plugins" + +################################################################ +### CUCKAROO* (i.e. GPU-Friendly) MINER PLUGIN CONFIGURATION ### +################################################################ + +# Multiple plugins can be specified, (e.g. a cpu +# miner and a gpu miner running in parallel) +# Use a single plugin instance per device, as +# demonstrated below. + +# Multiple instances of the same plugin can be loaded +# and used with different devices. On CPU plugins +# you'll likely only be using a single instance +# but in CUDA plugins the device number can be set +# corresponding to the device ID. (use nvidia-smi to find this) + +### CUCKAROO* CPU SOLVERS (Asic Resist, or GPU-Friendly) + +# cpu mean algorithm for processors supporting sse2 + +#[[mining.miner_plugin_config]] +#plugin_name = "cuckarood_cpu_compat_29" +#[mining.miner_plugin_config.parameters] +#nthreads = 4 + +# As above, but for processors supporting avx2 + +#[[mining.miner_plugin_config]] +#plugin_name = "cuckarood_cpu_avx2_29" +#[mining.miner_plugin_config.parameters] +#nthreads = 4 + +# CUCKAROO* CUDA SOLVER +# +# CUDA plugins are not built by default. To build: +#1) Ensure the latest cuda toolkit is installed +# (nvcc should be in your PATH) +# Wrong gcc? install gcc-5 g++-5; export CC=`which gcc-5`; # then build +#2) Ensure the 'build-cuda-plugin' feature is included in Cargo.toml, e.g: +# cuckoo_miner = { path = "./cuckoo-miner", features = ["build-cuda-plugins"]} +# +# Parameters can be set individually for each device by using multiple +# instance of each plugin. device 0 is used by default +# + +# currently requires 6GB GPU memory +[[mining.miner_plugin_config]] +plugin_name = "cuckarooz_cuda_29" +[mining.miner_plugin_config.parameters] +device = 0 +#cpuload = 1 +#ntrims = 15 +#genablocks = 1024 +#recoverblocks = 2048 +#recovertpb = 256 + +# e.g. To enable multiple devices (copy params from above as needed) + +#[[mining.miner_plugin_config]] +#plugin_name = "cuckarooz_cuda_29" +#[mining.miner_plugin_config.parameters] +#device = 1 + + +# mean OpenCL supports both NVidia and AMD +# to install run ./install_ocl_plugins.sh script +#[[mining.miner_plugin_config]] +#plugin_name = "ocl_cuckarood" +#[mining.miner_plugin_config.parameters] +# 0 for default, 1 for AMD, 2 for NVidia, specify if you have +# cards from both vendors +#platform = 0 +# ID withing the platform +#device = 0 + +############################################################### +### CUCKATOO (i.e. ASIC-Friendly) MINER PLUGIN CONFIGURATION ## +############################################################### + +#mean cpu +#[[mining.miner_plugin_config]] +#plugin_name = "cuckatoo_mean_cpu_compat_31" +#[mining.miner_plugin_config.parameters] +#nthreads = 4 + +#mean cpu avx2 +#[[mining.miner_plugin_config]] +#plugin_name = "cuckatoo_mean_cpu_avx2_31" +#[mining.miner_plugin_config.parameters] + +#mean cuda, will work on a 1080TI with expand rounds set to 2 +#memory requirements are tight, don't drive a display +#off the same card while trying to mine with an 11GB card + +#[[mining.miner_plugin_config]] +#plugin_name = "cuckatoo_mean_cuda_gtx_31" +#[mining.miner_plugin_config.parameters] +#device = 0 +#cpuload = 1 +#ntrims = 31 +#genablocks = 1024 +#recoverblocks = 2048 +#recovertpb = 256 + +#mean cuda optimised to use slightly less memory, +#will work on a 2080TI with expand rounds set to 2 +#as above, memory requirements are tight, don't drive a display +#off the same card while trying to mine with an 11GB card + +#[[mining.miner_plugin_config]] +#plugin_name = "cuckatoo_mean_cuda_rtx_31" +#[mining.miner_plugin_config.parameters] +#device = 0 +#cpuload = 1 +#ntrims = 31 +#genablocks = 1024 +#recoverblocks = 2048 +#recovertpb = 256 + +#the C32 reference miner requires 20GB of memory +#runs on the RTX Titan with 24GB + +#[[mining.miner_plugin_config]] +#plugin_name = "cuckatoo_mean_cuda_rtx_32" +#[mining.miner_plugin_config.parameters] +#device = 0 +#cpuload = 1 +#ntrims = 31 +#genablocks = 1024 +#recoverblocks = 2048 +#recovertpb = 256 + +#lean cuda +#[[mining.miner_plugin_config]] +#plugin_name = "cuckatoo_lean_cuda_31" +#[mining.miner_plugin_config.parameters] +#device = 0 +#cpuload = 1 +#ntrims = 176 + +# lean OpenCL supports both NVidia and AMD +# very slow but requires ~ 3GB of RAM +# to install run ./install_ocl_plugins.sh script +#[[mining.miner_plugin_config]] +#plugin_name = "ocl_cuckatoo" +#[mining.miner_plugin_config.parameters] +# 0 for default, 1 for AMD, 2 for NVidia, specify if you have +# cards from both vendors +#platform = 0 +# ID withing the platform +#device = 0 +#edge_bits = 31 + diff --git a/ocl_cuckaroo/.gitignore b/ocl_cuckaroo/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/ocl_cuckaroo/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/ocl_cuckaroo/Cargo.toml b/ocl_cuckaroo/Cargo.toml new file mode 100644 index 0000000..3e0b61a --- /dev/null +++ b/ocl_cuckaroo/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "ocl_cuckaroo" +version = "1.0.2" +workspace = ".." +edition = "2018" + +[features] +profile = [] + + +[dependencies] +blake2-rfc = "0.2" +byteorder = "1" +mugle_miner_plugin = { path = "../plugin", version = "4.0.0" } +libc = "0.2" +hashbrown = "0.7" +ocl = "0.19" + +[lib] +name = "ocl_cuckaroo" +crate-type = ["cdylib", "rlib"] diff --git a/ocl_cuckaroo/src/finder.rs b/ocl_cuckaroo/src/finder.rs new file mode 100644 index 0000000..a022092 --- /dev/null +++ b/ocl_cuckaroo/src/finder.rs @@ -0,0 +1,195 @@ +use hashbrown::HashMap; + +#[derive(Clone)] +pub struct Solution { + pub nodes: Vec, +} + +pub struct Graph { + adj_index: HashMap, + adj_store: Vec, +} + +struct Search { + path: Vec, + solutions: Vec, + + state: HashMap, + node_visited: usize, +} + +#[derive(Clone, Copy)] +enum NodeState { + NotVisited, + Visited, +} + +impl Search { + fn new(node_count: usize) -> Search { + Search { + path: Vec::with_capacity(node_count), + solutions: vec![], + state: HashMap::with_capacity_and_hasher(node_count, Default::default()), + node_visited: 0, + } + } + + #[inline] + fn visit(&mut self, node: u32) { + self.state.insert(node, NodeState::Visited); + self.path.push(node); + self.node_visited += 1; + } + + #[inline] + fn leave(&mut self, node: u32) { + self.path.pop(); + self.state.insert(node, NodeState::NotVisited); + } + + #[inline] + fn state(&self, node: u32) -> NodeState { + match self.state.get(&node) { + None => NodeState::NotVisited, + Some(state) => *state, + } + } + + #[inline] + fn is_visited(&self, node: u32) -> bool { + match self.state(node) { + NodeState::NotVisited => false, + _ => true, + } + } +} + +struct AdjNode { + value: u32, + next: Option, +} + +impl AdjNode { + #[inline] + fn first(value: u32) -> AdjNode { + AdjNode { value, next: None } + } + + #[inline] + fn next(value: u32, next: usize) -> AdjNode { + AdjNode { + value, + next: Some(next), + } + } +} + +struct AdjList<'a> { + current: Option<&'a AdjNode>, + adj_store: &'a Vec, +} + +impl<'a> AdjList<'a> { + #[inline] + pub fn new(current: Option<&'a AdjNode>, adj_store: &'a Vec) -> AdjList<'a> { + AdjList { current, adj_store } + } +} + +impl<'a> Iterator for AdjList<'a> { + type Item = u32; + + fn next(&mut self) -> Option { + match self.current { + None => None, + Some(node) => { + let val = node.value; + match node.next { + None => self.current = None, + Some(next_index) => self.current = Some(&self.adj_store[next_index]), + } + Some(val) + } + } + } +} + +impl Graph { + pub fn search(nodes: &[u32]) -> Result, String> { + let edge_count = nodes.len() / 2; + let mut g = Graph { + adj_index: HashMap::with_capacity_and_hasher(nodes.len(), Default::default()), + adj_store: Vec::with_capacity(nodes.len()), + }; + let mut search = Search::new(nodes.len()); + const STEP: usize = 2; + for i in 0..edge_count { + let n1 = nodes[i * STEP]; + let n2 = nodes[i * STEP + 1]; + g.walk_graph(n1, n2, &mut search)?; + g.add_edge(n1, n2); + } + + Ok(search.solutions.clone()) + } + + #[inline] + pub fn node_count(&self) -> usize { + self.adj_index.len() + } + + #[inline] + pub fn edge_count(&self) -> usize { + self.adj_store.len() / 2 + } + + #[inline] + fn add_edge(&mut self, node1: u32, node2: u32) { + self.add_half_edge(node1, node2); + self.add_half_edge(node2, node1); + } + + fn add_half_edge(&mut self, from: u32, to: u32) { + if let Some(index) = self.adj_index.get(&from) { + self.adj_store.push(AdjNode::next(to, *index)); + } else { + self.adj_store.push(AdjNode::first(to)); + } + self.adj_index.insert(from, self.adj_store.len() - 1); + } + + fn neighbors(&self, node: u32) -> Option + '_> { + let node = match self.adj_index.get(&node) { + Some(index) => Some(&self.adj_store[*index]), + None => return None, + }; + Some(AdjList::new(node, &self.adj_store)) + } + + fn walk_graph(&self, current: u32, target: u32, search: &mut Search) -> Result<(), String> { + if search.path.len() > 41 { + return Ok(()); + } + + let neighbors = match self.neighbors(current) { + None => return Ok(()), + Some(it) => it, + }; + search.visit(current); + for ns in neighbors { + if ns == target && search.path.len() == 41 { + search.path.push(ns); + search.solutions.push(Solution { + nodes: search.path.clone(), + }); + search.leave(current); + return Ok(()); + } + if !search.is_visited(ns) { + self.walk_graph(ns, target, search)?; + } + } + search.leave(current); + Ok(()) + } +} diff --git a/ocl_cuckaroo/src/lib.rs b/ocl_cuckaroo/src/lib.rs new file mode 100644 index 0000000..ea1e0b1 --- /dev/null +++ b/ocl_cuckaroo/src/lib.rs @@ -0,0 +1,183 @@ +extern crate blake2_rfc; +extern crate byteorder; +extern crate mugle_miner_plugin as plugin; +extern crate hashbrown; +extern crate libc; +extern crate ocl; + +use blake2_rfc::blake2b::blake2b; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use libc::*; +use plugin::*; +use std::io::Cursor; +use std::io::Error; +use std::mem; +use std::ptr; +use std::time::{Duration, SystemTime}; + +pub use self::finder::Graph; +pub use self::trimmer::Trimmer; + +mod finder; +mod trimmer; + +#[repr(C)] +struct Solver { + trimmer: Trimmer, + graph: Option, + mutate_nonce: bool, +} + +#[no_mangle] +pub unsafe extern "C" fn create_solver_ctx(params: *mut SolverParams) -> *mut SolverCtx { + let platform = match (*params).platform { + 1 => Some("AMD"), + 2 => Some("NVIDIA"), + _ => None, + }; + let device_id = Some((*params).device as usize); + + let trimmer = Trimmer::build(platform, device_id).expect("can't build trimmer"); + let solver = Solver { + trimmer: trimmer, + graph: None, + mutate_nonce: (*params).mutate_nonce, + }; + let solver_box = Box::new(solver); + let solver_ref = Box::leak(solver_box); + mem::transmute::<&mut Solver, *mut SolverCtx>(solver_ref) +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_solver_ctx(solver_ctx_ptr: *mut SolverCtx) { + // create box to clear memory + let solver_ptr = mem::transmute::<*mut SolverCtx, *mut Solver>(solver_ctx_ptr); + let _solver_box = Box::from_raw(solver_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn stop_solver(_solver_ctx_ptr: *mut SolverCtx) {} + +#[no_mangle] +pub unsafe extern "C" fn fill_default_params(params: *mut SolverParams) { + (*params).device = 0; + (*params).platform = 0; + (*params).edge_bits = 29; +} + +#[no_mangle] +pub unsafe extern "C" fn run_solver( + ctx: *mut SolverCtx, + header_ptr: *const c_uchar, + header_length: u32, + nonce: u64, + _range: u32, + solutions: *mut SolverSolutions, + stats: *mut SolverStats, +) -> u32 { + let start = SystemTime::now(); + let solver_ptr = mem::transmute::<*mut SolverCtx, *mut Solver>(ctx); + let solver = &*solver_ptr; + let mut header = Vec::with_capacity(header_length as usize + 32); + let r_ptr = header.as_mut_ptr(); + ptr::copy_nonoverlapping(header_ptr, r_ptr, header_length as usize); + header.set_len(header_length as usize); + let n = nonce as u64; + let k = match set_header_nonce(&header, Some(n), solver.mutate_nonce) { + Err(_e) => { + return 2; + } + Ok(v) => v, + }; + let res = solver.trimmer.run(&k).unwrap(); + + let sols = Graph::search(&res).unwrap(); + let mut i = 0; + (*solutions).edge_bits = 29; + for sol in sols { + let (nonces_cand, valid) = solver.trimmer.recover(sol.nodes, &k).unwrap(); + if valid { + let nonces = nonces_cand + .into_iter() + .map(|v| v as u64) + .collect::>(); + (*solutions).sols[i].nonce = nonce; + (*solutions).sols[i].proof.copy_from_slice(&nonces[..]); + i += 1; + } + } + (*solutions).num_sols = i as u32; + let end = SystemTime::now(); + let elapsed = end.duration_since(start).unwrap(); + (*stats).edge_bits = 29; + (*stats).device_id = solver.trimmer.device_id as u32; + let name_bytes = solver.trimmer.device_name.as_bytes(); + let n = std::cmp::min((*stats).device_name.len(), name_bytes.len()); + (*stats).device_name[..n].copy_from_slice(&solver.trimmer.device_name.as_bytes()[..n]); + (*stats).last_solution_time = duration_to_u64(elapsed); + (*stats).last_start_time = + duration_to_u64(start.duration_since(SystemTime::UNIX_EPOCH).unwrap()); + (*stats).last_end_time = duration_to_u64(end.duration_since(SystemTime::UNIX_EPOCH).unwrap()); + 0 +} + +fn duration_to_u64(elapsed: Duration) -> u64 { + elapsed.as_secs() * 1_000_000_000 + elapsed.subsec_nanos() as u64 +} + +pub fn set_header_nonce( + header: &[u8], + nonce: Option, + mutate_nonce: bool, +) -> Result<[u64; 4], Error> { + if let Some(n) = nonce { + let len = header.len(); + let mut header = header.to_owned(); + if mutate_nonce { + header.truncate(len - 4); + header.write_u32::(n as u32)?; + } + create_siphash_keys(&header) + } else { + create_siphash_keys(&header) + } +} + +pub fn create_siphash_keys(header: &[u8]) -> Result<[u64; 4], Error> { + let h = blake2b(32, &[], &header); + let hb = h.as_bytes(); + let mut rdr = Cursor::new(hb); + Ok([ + rdr.read_u64::()?, + rdr.read_u64::()?, + rdr.read_u64::()?, + rdr.read_u64::()?, + ]) +} + +#[cfg(test)] +mod tests { + use super::*; + #[ignore] + // results in Error executing function: clEnqueueNDRangeKernel("LeanRound") + // Status error code: CL_INVALID_WORK_GROUP_SIZE (-54) + // on MacOSX + #[test] + fn test_solve() { + let trimmer = Trimmer::build(None, None).expect("can't build trimmer"); + let k = [ + 0x27580576fe290177, + 0xf9ea9b2031f4e76e, + 0x1663308c8607868f, + 0xb88839b0fa180d0e, + ]; + + unsafe { + let res = trimmer.run(&k).unwrap(); + println!("Trimmed to {}", res.len()); + + let sols = Graph::search(&res).unwrap(); + assert_eq!(1, sols.len()); + } + } +} diff --git a/ocl_cuckaroo/src/main.rs b/ocl_cuckaroo/src/main.rs new file mode 100644 index 0000000..ffb1c5d --- /dev/null +++ b/ocl_cuckaroo/src/main.rs @@ -0,0 +1,44 @@ +extern crate ocl_cuckaroo; + +use ocl_cuckaroo::{Graph, Trimmer}; +use std::time::SystemTime; + +fn main() -> Result<(), String> { + let trimmer = Trimmer::build(None, None).expect("can't build trimmer"); + let k = [ + 0xf4956dc403730b01, + 0xe6d45de39c2a5a3e, + 0xcbf626a8afee35f6, + 0x4307b94b1a0c9980, + ]; + + unsafe { + let mut start = SystemTime::now(); + let res = trimmer.run(&k).unwrap(); + let mut end = SystemTime::now(); + let elapsed = end.duration_since(start).unwrap(); + println!("Time: {:?}", elapsed); + println!("Trimmed to {}", res.len()); + + start = SystemTime::now(); + let sols = Graph::search(&res).unwrap(); + end = SystemTime::now(); + let elapsed = end.duration_since(start).unwrap(); + println!("Finder: {:?}", elapsed); + for sol in sols { + println!("Solution: {:x?}", sol.nodes); + start = SystemTime::now(); + let (nonces_c, valid) = trimmer.recover(sol.nodes.clone(), &k).unwrap(); + if valid { + let nonces = nonces_c.into_iter().map(|v| v as u64).collect::>(); + let end = SystemTime::now(); + let elapsed = end.duration_since(start).unwrap(); + println!("Recovering: {:?}", elapsed); + println!("Nonces: {:?}", nonces); + } else { + println!("Not valid"); + } + } + } + Ok(()) +} diff --git a/ocl_cuckaroo/src/trimmer.rs b/ocl_cuckaroo/src/trimmer.rs new file mode 100644 index 0000000..5f5beb9 --- /dev/null +++ b/ocl_cuckaroo/src/trimmer.rs @@ -0,0 +1,1168 @@ +use ocl; +use ocl::enums::{ArgVal, DeviceInfo, DeviceInfoResult}; +use ocl::flags::{CommandQueueProperties, MemFlags}; +use ocl::prm::{Uint2, Ulong4}; +use ocl::{ + Buffer, Context, Device, Event, EventList, Kernel, Platform, Program, Queue, SpatialDims, +}; +use std::collections::HashMap; +use std::env; + +const DUCK_SIZE_A: usize = 129; // AMD 126 + 3 +const DUCK_SIZE_B: usize = 83; +const BUFFER_SIZE_A1: usize = DUCK_SIZE_A * 1024 * (4096 - 128) * 2; +const BUFFER_SIZE_A2: usize = DUCK_SIZE_A * 1024 * 256 * 2; +const BUFFER_SIZE_B: usize = DUCK_SIZE_B * 1024 * 4096 * 2; +const INDEX_SIZE: usize = 256 * 256 * 4; + +pub struct Trimmer { + q: Queue, + program: Program, + buffer_a1: Buffer, + buffer_a2: Buffer, + buffer_b: Buffer, + buffer_i1: Buffer, + buffer_i2: Buffer, + buffer_r: Buffer, + buffer_nonces: Buffer, + pub device_name: String, + pub device_id: usize, + is_nvidia: bool, +} + +struct ClBufferParams { + size: usize, + flags: MemFlags, +} + +macro_rules! clear_buffer ( + ($buf:expr) => ( + $buf.cmd().fill(0, None).enq()?; + )); + +macro_rules! kernel_enq( + ($kernel:expr, $event_list:expr, $names:expr, $msg:expr) => ( + #[cfg(feature = "profile")] + { + $kernel.cmd().enew(&mut $event_list).enq()?; + $names.push($msg); + } + #[cfg(not(feature = "profile"))] + { + $kernel.cmd().enq()?; + } + )); + +macro_rules! get_device_info { + ($dev:ident, $name:ident) => {{ + match $dev.info(DeviceInfo::$name) { + Ok(DeviceInfoResult::$name(value)) => value, + _ => panic!("Failed to retrieve device {}", stringify!($name)), + } + }}; +} + +#[cfg(feature = "profile")] +fn queue_props() -> Option { + Some(CommandQueueProperties::PROFILING_ENABLE) +} + +#[cfg(not(feature = "profile"))] +fn queue_props() -> Option { + None +} + +macro_rules! kernel_builder( + ($obj: expr, $kernel: expr, $global_works_size: expr) => ( + Kernel::builder() + .name($kernel) + .program(&$obj.program) + .queue($obj.q.clone()) + .global_work_size($global_works_size) +)); + +impl Trimmer { + pub fn build(platform_name: Option<&str>, device_id: Option) -> ocl::Result { + env::set_var("GPU_MAX_HEAP_SIZE", "100"); + env::set_var("GPU_USE_SYNC_OBJECTS", "1"); + env::set_var("GPU_MAX_ALLOC_PERCENT", "100"); + env::set_var("GPU_SINGLE_ALLOC_PERCENT", "100"); + env::set_var("GPU_64BIT_ATOMICS", "1"); + env::set_var("GPU_MAX_WORKGROUP_SIZE", "1024"); + let platform = find_platform(platform_name) + .ok_or::("Can't find OpenCL platform".into())?; + let p_name = platform.name()?; + let device = find_device(&platform, device_id)?; + let mut buffers = HashMap::new(); + buffers.insert( + "A1".to_string(), + ClBufferParams { + size: BUFFER_SIZE_A1, + flags: MemFlags::empty(), + }, + ); + buffers.insert( + "A2".to_string(), + ClBufferParams { + size: BUFFER_SIZE_A2, + flags: MemFlags::empty(), + }, + ); + buffers.insert( + "B".to_string(), + ClBufferParams { + size: BUFFER_SIZE_B, + flags: MemFlags::empty(), + }, + ); + buffers.insert( + "I1".to_string(), + ClBufferParams { + size: INDEX_SIZE, + flags: MemFlags::empty(), + }, + ); + buffers.insert( + "I2".to_string(), + ClBufferParams { + size: INDEX_SIZE, + flags: MemFlags::empty(), + }, + ); + buffers.insert( + "R".to_string(), + ClBufferParams { + size: 42 * 2, + flags: MemFlags::READ_ONLY, + }, + ); + buffers.insert( + "NONCES".to_string(), + ClBufferParams { + size: INDEX_SIZE, + flags: MemFlags::empty(), + }, + ); + + check_device_compatibility(&device, &buffers)?; + + let context = Context::builder() + .platform(platform) + .devices(device) + .build()?; + + let q = Queue::new(&context, device, queue_props())?; + + let program = Program::builder() + .devices(device) + .src(SRC) + .build(&context)?; + + let buffer_a1 = build_buffer(buffers.get("A1"), &q)?; + let buffer_a2 = build_buffer(buffers.get("A2"), &q)?; + let buffer_b = build_buffer(buffers.get("B"), &q)?; + let buffer_i1 = build_buffer(buffers.get("I1"), &q)?; + let buffer_i2 = build_buffer(buffers.get("I2"), &q)?; + let buffer_r = build_buffer(buffers.get("R"), &q)?; + let buffer_nonces = build_buffer(buffers.get("NONCES"), &q)?; + + Ok(Trimmer { + q, + program, + buffer_a1, + buffer_a2, + buffer_b, + buffer_i1, + buffer_i2, + buffer_r, + buffer_nonces, + device_name: device.name()?, + device_id: device_id.unwrap_or(0), + is_nvidia: p_name.to_lowercase().contains("nvidia"), + }) + } + + pub unsafe fn recover( + &self, + mut nodes: Vec, + k: &[u64; 4], + ) -> ocl::Result<(Vec, bool)> { + let event_list = EventList::new(); + let names = vec![]; + + let mut kernel_recovery = kernel_builder!(self, "FluffyRecovery", 2048 * 256) + .arg(k[0]) + .arg(k[1]) + .arg(k[2]) + .arg(k[3]) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .build()?; + + if self.is_nvidia { + kernel_recovery.set_default_local_work_size(SpatialDims::One(256)); + } + + kernel_recovery.set_arg_unchecked(4, ArgVal::mem(&self.buffer_r))?; + kernel_recovery.set_arg_unchecked(5, ArgVal::mem(&self.buffer_nonces))?; + + nodes.push(nodes[0]); + + let edges = nodes.windows(2).flatten().map(|v| *v).collect::>(); + self.buffer_r.cmd().write(edges.as_slice()).enq()?; + self.buffer_nonces.cmd().fill(0, None).enq()?; + kernel_enq!(kernel_recovery, event_list, names, "recovery"); + let mut nonces: Vec = vec![0; 42]; + + self.buffer_nonces.cmd().read(&mut nonces).enq()?; + self.q.finish()?; + for i in 0..names.len() { + print_event(names[i], &event_list[i]); + } + nonces.sort(); + let valid = nonces.windows(2).all(|entry| match entry { + [p, n] => p < n, + _ => true, + }); + Ok((nonces, valid)) + } + + pub unsafe fn run(&self, k: &[u64; 4]) -> ocl::Result> { + let mut kernel_seed_a = kernel_builder!(self, "FluffySeed2A", 2048 * 128) + .arg(k[0]) + .arg(k[1]) + .arg(k[2]) + .arg(k[3]) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .build()?; + if self.is_nvidia { + kernel_seed_a.set_default_local_work_size(SpatialDims::One(128)); + } + kernel_seed_a.set_arg_unchecked(4, ArgVal::mem(&self.buffer_b))?; + kernel_seed_a.set_arg_unchecked(5, ArgVal::mem(&self.buffer_a1))?; + kernel_seed_a.set_arg_unchecked(6, ArgVal::mem(&self.buffer_i1))?; + + let mut kernel_seed_b1 = kernel_builder!(self, "FluffySeed2B", 1024 * 128) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(32) + .build()?; + if self.is_nvidia { + kernel_seed_b1.set_default_local_work_size(SpatialDims::One(128)); + } + kernel_seed_b1.set_arg_unchecked(0, ArgVal::mem(&self.buffer_a1))?; + kernel_seed_b1.set_arg_unchecked(1, ArgVal::mem(&self.buffer_a1))?; + kernel_seed_b1.set_arg_unchecked(2, ArgVal::mem(&self.buffer_a2))?; + kernel_seed_b1.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i1))?; + kernel_seed_b1.set_arg_unchecked(4, ArgVal::mem(&self.buffer_i2))?; + + let mut kernel_seed_b2 = kernel_builder!(self, "FluffySeed2B", 1024 * 128) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(0) + .build()?; + if self.is_nvidia { + kernel_seed_b2.set_default_local_work_size(SpatialDims::One(128)); + } + + kernel_seed_b2.set_arg_unchecked(0, ArgVal::mem(&self.buffer_b))?; + kernel_seed_b2.set_arg_unchecked(1, ArgVal::mem(&self.buffer_a1))?; + kernel_seed_b2.set_arg_unchecked(2, ArgVal::mem(&self.buffer_a2))?; + kernel_seed_b2.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i1))?; + kernel_seed_b2.set_arg_unchecked(4, ArgVal::mem(&self.buffer_i2))?; + + let mut kernel_round1 = kernel_builder!(self, "FluffyRound1", 4096 * 1024) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg((DUCK_SIZE_A * 1024) as i32) + .arg((DUCK_SIZE_B * 1024) as i32) + .build()?; + if self.is_nvidia { + kernel_round1.set_default_local_work_size(SpatialDims::One(1024)); + } + + kernel_round1.set_arg_unchecked(0, ArgVal::mem(&self.buffer_a1))?; + kernel_round1.set_arg_unchecked(1, ArgVal::mem(&self.buffer_a2))?; + kernel_round1.set_arg_unchecked(2, ArgVal::mem(&self.buffer_b))?; + kernel_round1.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i2))?; + kernel_round1.set_arg_unchecked(4, ArgVal::mem(&self.buffer_i1))?; + + let mut kernel_round0 = kernel_builder!(self, "FluffyRoundNO1", 4096 * 1024) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .build()?; + if self.is_nvidia { + kernel_round0.set_default_local_work_size(SpatialDims::One(1024)); + } + kernel_round0.set_arg_unchecked(0, ArgVal::mem(&self.buffer_b))?; + kernel_round0.set_arg_unchecked(1, ArgVal::mem(&self.buffer_a1))?; + kernel_round0.set_arg_unchecked(2, ArgVal::mem(&self.buffer_i1))?; + kernel_round0.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i2))?; + + let mut kernel_round_na = kernel_builder!(self, "FluffyRoundNON", 4096 * 1024) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .build()?; + if self.is_nvidia { + kernel_round_na.set_default_local_work_size(SpatialDims::One(1024)); + } + kernel_round_na.set_arg_unchecked(0, ArgVal::mem(&self.buffer_b))?; + kernel_round_na.set_arg_unchecked(1, ArgVal::mem(&self.buffer_a1))?; + kernel_round_na.set_arg_unchecked(2, ArgVal::mem(&self.buffer_i1))?; + kernel_round_na.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i2))?; + + let mut kernel_round_nb = kernel_builder!(self, "FluffyRoundNON", 4096 * 1024) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .build()?; + if self.is_nvidia { + kernel_round_nb.set_default_local_work_size(SpatialDims::One(1024)); + } + kernel_round_nb.set_arg_unchecked(0, ArgVal::mem(&self.buffer_a1))?; + kernel_round_nb.set_arg_unchecked(1, ArgVal::mem(&self.buffer_b))?; + kernel_round_nb.set_arg_unchecked(2, ArgVal::mem(&self.buffer_i2))?; + kernel_round_nb.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i1))?; + + let mut kernel_tail = kernel_builder!(self, "FluffyTailO", 4096 * 1024) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .arg(None::<&Buffer>) + .build()?; + if self.is_nvidia { + kernel_tail.set_default_local_work_size(SpatialDims::One(1024)); + } + kernel_tail.set_arg_unchecked(0, ArgVal::mem(&self.buffer_b))?; + kernel_tail.set_arg_unchecked(1, ArgVal::mem(&self.buffer_a1))?; + kernel_tail.set_arg_unchecked(2, ArgVal::mem(&self.buffer_i1))?; + kernel_tail.set_arg_unchecked(3, ArgVal::mem(&self.buffer_i2))?; + + let event_list = EventList::new(); + let names = vec![]; + + let mut edges_count: Vec = vec![0; 1]; + clear_buffer!(self.buffer_i1); + clear_buffer!(self.buffer_i2); + kernel_enq!(kernel_seed_a, event_list, names, "seedA"); + kernel_enq!(kernel_seed_b1, event_list, names, "seedB1"); + kernel_enq!(kernel_seed_b2, event_list, names, "seedB2"); + clear_buffer!(self.buffer_i1); + kernel_enq!(kernel_round1, event_list, names, "round1"); + clear_buffer!(self.buffer_i2); + kernel_enq!(kernel_round0, event_list, names, "roundN0"); + clear_buffer!(self.buffer_i1); + kernel_enq!(kernel_round_nb, event_list, names, "roundNB"); + for _ in 0..120 { + clear_buffer!(self.buffer_i2); + kernel_enq!(kernel_round_na, event_list, names, "roundNA"); + clear_buffer!(self.buffer_i1); + kernel_enq!(kernel_round_nb, event_list, names, "roundNB"); + } + clear_buffer!(self.buffer_i2); + kernel_enq!(kernel_tail, event_list, names, "tail"); + + self.buffer_i2.cmd().read(&mut edges_count).enq()?; + + let mut edges_left: Vec = vec![0; (edges_count[0] * 2) as usize]; + + self.buffer_a1.cmd().read(&mut edges_left).enq()?; + self.q.finish()?; + for i in 0..names.len() { + print_event(names[i], &event_list[i]); + } + clear_buffer!(self.buffer_i1); + clear_buffer!(self.buffer_i2); + self.q.finish()?; + Ok(edges_left) + } +} + +#[cfg(feature = "profile")] +fn print_event(name: &str, ev: &Event) { + let submit = ev + .profiling_info(ProfilingInfo::Submit) + .unwrap() + .time() + .unwrap(); + let queued = ev + .profiling_info(ProfilingInfo::Queued) + .unwrap() + .time() + .unwrap(); + let start = ev + .profiling_info(ProfilingInfo::Start) + .unwrap() + .time() + .unwrap(); + let end = ev + .profiling_info(ProfilingInfo::End) + .unwrap() + .time() + .unwrap(); + println!( + "{}\t total {}ms \t queued->submit {}mc \t submit->start {}ms \t start->end {}ms", + name, + (end - queued) / 1_000_000, + (submit - queued) / 1_000, + (start - submit) / 1_000_000, + (end - start) / 1_000_000 + ); +} + +#[cfg(not(feature = "profile"))] +fn print_event(_name: &str, _ev: &Event) {} + +fn find_platform(selector: Option<&str>) -> Option { + match selector { + None => Some(Platform::default()), + Some(sel) => Platform::list().into_iter().find(|p| { + if let Ok(vendor) = p.name() { + vendor.contains(sel) + } else { + false + } + }), + } +} + +fn find_device(platform: &Platform, selector: Option) -> ocl::Result { + match selector { + None => Device::first(platform), + Some(index) => Device::by_idx_wrap(platform, index), + } +} + +fn check_device_compatibility( + device: &Device, + buffers: &HashMap, +) -> ocl::Result<()> { + let max_alloc_size: u64 = get_device_info!(device, MaxMemAllocSize); + let global_memory_size: u64 = get_device_info!(device, GlobalMemSize); + let mut total_alloc: u64 = 0; + + // Check that no buffer is bigger than the max memory allocation size + for (k, v) in buffers { + total_alloc += v.size as u64; + if v.size as u64 > max_alloc_size { + return Err(ocl::Error::from(format!( + "Buffer {} is bigger than maximum alloc size ({})", + k, max_alloc_size + ))); + } + } + + // Check that total buffer allocation does not exceed global memory size + if total_alloc > global_memory_size { + return Err(ocl::Error::from(format!( + "Total needed memory is bigger than device's capacity ({})", + global_memory_size + ))); + } + + Ok(()) +} + +fn build_buffer(params: Option<&ClBufferParams>, q: &Queue) -> ocl::Result> { + match params { + None => Err(ocl::Error::from("Invalid parameters")), + Some(p) => Buffer::::builder() + .queue(q.clone()) + .len(p.size) + .flags(p.flags) + .fill_val(0) + .build(), + } +} + +const SRC: &str = r#" +// Cuckaroo Cycle, a memory-hard proof-of-work by John Tromp and team Mugle +// Copyright (c) 2018 Jiri Photon Vadura and John Tromp +// This GGM miner file is covered by the FAIR MINING license + +#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable +#pragma OPENCL EXTENSION cl_khr_int64_extended_atomics : enable + +typedef uint8 u8; +typedef uint16 u16; +typedef uint u32; +typedef ulong u64; + +typedef u32 node_t; +typedef u64 nonce_t; + + +#define DUCK_SIZE_A 129L +#define DUCK_SIZE_B 83L + +#define DUCK_A_EDGES (DUCK_SIZE_A * 1024L) +#define DUCK_A_EDGES_64 (DUCK_A_EDGES * 64L) + +#define DUCK_B_EDGES (DUCK_SIZE_B * 1024L) +#define DUCK_B_EDGES_64 (DUCK_B_EDGES * 64L) + +#define EDGE_BLOCK_SIZE (64) +#define EDGE_BLOCK_MASK (EDGE_BLOCK_SIZE - 1) + +#define EDGEBITS 29 +// number of edges +#define NEDGES ((node_t)1 << EDGEBITS) +// used to mask siphash output +#define EDGEMASK (NEDGES - 1) + +#define CTHREADS 1024 +#define BKTMASK4K (4096-1) +#define BKTGRAN 32 + +#define SIPROUND \ + do { \ + v0 += v1; v2 += v3; v1 = rotate(v1,(ulong)13); \ + v3 = rotate(v3,(ulong)16); v1 ^= v0; v3 ^= v2; \ + v0 = rotate(v0,(ulong)32); v2 += v1; v0 += v3; \ + v1 = rotate(v1,(ulong)17); v3 = rotate(v3,(ulong)21); \ + v1 ^= v2; v3 ^= v0; v2 = rotate(v2,(ulong)32); \ + } while(0) + + +void Increase2bCounter(__local u32 * ecounters, const int bucket) +{ + int word = bucket >> 5; + unsigned char bit = bucket & 0x1F; + u32 mask = 1 << bit; + + u32 old = atomic_or(ecounters + word, mask) & mask; + + if (old > 0) + atomic_or(ecounters + word + 4096, mask); +} + +bool Read2bCounter(__local u32 * ecounters, const int bucket) +{ + int word = bucket >> 5; + unsigned char bit = bucket & 0x1F; + u32 mask = 1 << bit; + + return (ecounters[word + 4096] & mask) > 0; +} + +__attribute__((reqd_work_group_size(128, 1, 1))) +__kernel void FluffySeed2A(const u64 v0i, const u64 v1i, const u64 v2i, const u64 v3i, __global ulong4 * bufferA, __global ulong4 * bufferB, __global u32 * indexes) +{ + const int gid = get_global_id(0); + const short lid = get_local_id(0); + + __global ulong4 * buffer; + __local u64 tmp[64][16]; + __local u32 counters[64]; + u64 sipblock[64]; + + u64 v0; + u64 v1; + u64 v2; + u64 v3; + + if (lid < 64) + counters[lid] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < 1024 * 2; i += EDGE_BLOCK_SIZE) + { + u64 blockNonce = gid * (1024 * 2) + i; + + v0 = v0i; + v1 = v1i; + v2 = v2i; + v3 = v3i; + + for (u32 b = 0; b < EDGE_BLOCK_SIZE; b++) + { + v3 ^= blockNonce + b; + for (int r = 0; r < 2; r++) + SIPROUND; + v0 ^= blockNonce + b; + v2 ^= 0xff; + for (int r = 0; r < 4; r++) + SIPROUND; + + sipblock[b] = (v0 ^ v1) ^ (v2 ^ v3); + + } + u64 last = sipblock[EDGE_BLOCK_MASK]; + + for (short s = 0; s < EDGE_BLOCK_SIZE; s++) + { + u64 lookup = s == EDGE_BLOCK_MASK ? last : sipblock[s] ^ last; + uint2 hash = (uint2)(lookup & EDGEMASK, (lookup >> 32) & EDGEMASK); + int bucket = hash.x & 63; + + barrier(CLK_LOCAL_MEM_FENCE); + + int counter = atomic_add(counters + bucket, (u32)1); + int counterLocal = counter % 16; + tmp[bucket][counterLocal] = hash.x | ((u64)hash.y << 32); + + barrier(CLK_LOCAL_MEM_FENCE); + + if ((counter > 0) && (counterLocal == 0 || counterLocal == 8)) + { + int cnt = min((int)atomic_add(indexes + bucket, 8), (int)(DUCK_A_EDGES_64 - 8)); + int idx = ((bucket < 32 ? bucket : bucket - 32) * DUCK_A_EDGES_64 + cnt) / 4; + buffer = bucket < 32 ? bufferA : bufferB; + + buffer[idx] = (ulong4)( + atom_xchg(&tmp[bucket][8 - counterLocal], (u64)0), + atom_xchg(&tmp[bucket][9 - counterLocal], (u64)0), + atom_xchg(&tmp[bucket][10 - counterLocal], (u64)0), + atom_xchg(&tmp[bucket][11 - counterLocal], (u64)0) + ); + buffer[idx + 1] = (ulong4)( + atom_xchg(&tmp[bucket][12 - counterLocal], (u64)0), + atom_xchg(&tmp[bucket][13 - counterLocal], (u64)0), + atom_xchg(&tmp[bucket][14 - counterLocal], (u64)0), + atom_xchg(&tmp[bucket][15 - counterLocal], (u64)0) + ); + } + + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + if (lid < 64) + { + int counter = counters[lid]; + int counterBase = (counter % 16) >= 8 ? 8 : 0; + int counterCount = (counter % 8); + for (int i = 0; i < (8 - counterCount); i++) + tmp[lid][counterBase + counterCount + i] = 0; + int cnt = min((int)atomic_add(indexes + lid, 8), (int)(DUCK_A_EDGES_64 - 8)); + int idx = ( (lid < 32 ? lid : lid - 32) * DUCK_A_EDGES_64 + cnt) / 4; + buffer = lid < 32 ? bufferA : bufferB; + buffer[idx] = (ulong4)(tmp[lid][counterBase], tmp[lid][counterBase + 1], tmp[lid][counterBase + 2], tmp[lid][counterBase + 3]); + buffer[idx + 1] = (ulong4)(tmp[lid][counterBase + 4], tmp[lid][counterBase + 5], tmp[lid][counterBase + 6], tmp[lid][counterBase + 7]); + } + +} + +__attribute__((reqd_work_group_size(128, 1, 1))) +__kernel void FluffySeed2B(const __global uint2 * source, __global ulong4 * destination1, __global ulong4 * destination2, const __global int * sourceIndexes, __global int * destinationIndexes, int startBlock) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + __global ulong4 * destination = destination1; + __local u64 tmp[64][16]; + __local int counters[64]; + + if (lid < 64) + counters[lid] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + int offsetMem = startBlock * DUCK_A_EDGES_64; + int offsetBucket = 0; + const int myBucket = group / BKTGRAN; + const int microBlockNo = group % BKTGRAN; + const int bucketEdges = min(sourceIndexes[myBucket + startBlock], (int)(DUCK_A_EDGES_64)); + const int microBlockEdgesCount = (DUCK_A_EDGES_64 / BKTGRAN); + const int loops = (microBlockEdgesCount / 128); + + if ((startBlock == 32) && (myBucket >= 30)) + { + offsetMem = 0; + destination = destination2; + offsetBucket = 30; + } + + for (int i = 0; i < loops; i++) + { + int edgeIndex = (microBlockNo * microBlockEdgesCount) + (128 * i) + lid; + + { + uint2 edge = source[/*offsetMem + */(myBucket * DUCK_A_EDGES_64) + edgeIndex]; + bool skip = (edgeIndex >= bucketEdges) || (edge.x == 0 && edge.y == 0); + + int bucket = (edge.x >> 6) & (64 - 1); + + barrier(CLK_LOCAL_MEM_FENCE); + + int counter = 0; + int counterLocal = 0; + + if (!skip) + { + counter = atomic_add(counters + bucket, (u32)1); + counterLocal = counter % 16; + tmp[bucket][counterLocal] = edge.x | ((u64)edge.y << 32); + } + + barrier(CLK_LOCAL_MEM_FENCE); + + if ((counter > 0) && (counterLocal == 0 || counterLocal == 8)) + { + int cnt = min((int)atomic_add(destinationIndexes + startBlock * 64 + myBucket * 64 + bucket, 8), (int)(DUCK_A_EDGES - 8)); + int idx = (offsetMem + (((myBucket - offsetBucket) * 64 + bucket) * DUCK_A_EDGES + cnt)) / 4; + + destination[idx] = (ulong4)( + atom_xchg(&tmp[bucket][8 - counterLocal], 0), + atom_xchg(&tmp[bucket][9 - counterLocal], 0), + atom_xchg(&tmp[bucket][10 - counterLocal], 0), + atom_xchg(&tmp[bucket][11 - counterLocal], 0) + ); + destination[idx + 1] = (ulong4)( + atom_xchg(&tmp[bucket][12 - counterLocal], 0), + atom_xchg(&tmp[bucket][13 - counterLocal], 0), + atom_xchg(&tmp[bucket][14 - counterLocal], 0), + atom_xchg(&tmp[bucket][15 - counterLocal], 0) + ); + } + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + if (lid < 64) + { + int counter = counters[lid]; + int counterBase = (counter % 16) >= 8 ? 8 : 0; + int cnt = min((int)atomic_add(destinationIndexes + startBlock * 64 + myBucket * 64 + lid, 8), (int)(DUCK_A_EDGES - 8)); + int idx = (offsetMem + (((myBucket - offsetBucket) * 64 + lid) * DUCK_A_EDGES + cnt)) / 4; + destination[idx] = (ulong4)(tmp[lid][counterBase], tmp[lid][counterBase + 1], tmp[lid][counterBase + 2], tmp[lid][counterBase + 3]); + destination[idx + 1] = (ulong4)(tmp[lid][counterBase + 4], tmp[lid][counterBase + 5], tmp[lid][counterBase + 6], tmp[lid][counterBase + 7]); + } +} + +__attribute__((reqd_work_group_size(1024, 1, 1))) +__kernel void FluffyRound1(const __global uint2 * source1, const __global uint2 * source2, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes, const int bktInSize, const int bktOutSize) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + const __global uint2 * source = group < (62 * 64) ? source1 : source2; + int groupRead = group < (62 * 64) ? group : group - (62 * 64); + + __local u32 ecounters[8192]; + + const int edgesInBucket = min(sourceIndexes[group], bktInSize); + const int loops = (edgesInBucket + CTHREADS) / CTHREADS; + + for (int i = 0; i < 8; i++) + ecounters[lid + (1024 * i)] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + + const int index = (bktInSize * groupRead) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + Increase2bCounter(ecounters, (edge.x & EDGEMASK) >> 12); + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + const int index = (bktInSize * groupRead) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + if (Read2bCounter(ecounters, (edge.x & EDGEMASK) >> 12)) + { + const int bucket = edge.y & BKTMASK4K; + const int bktIdx = min(atomic_add(destinationIndexes + bucket, 1), bktOutSize - 1); + destination[(bucket * bktOutSize) + bktIdx] = (uint2)(edge.y, edge.x); + } + } + } + +} + +__attribute__((reqd_work_group_size(1024, 1, 1))) +__kernel void FluffyRoundN(const __global uint2 * source, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + const int bktInSize = DUCK_B_EDGES; + const int bktOutSize = DUCK_B_EDGES; + + __local u32 ecounters[8192]; + + const int edgesInBucket = min(sourceIndexes[group], bktInSize); + const int loops = (edgesInBucket + CTHREADS) / CTHREADS; + + for (int i = 0; i < 8; i++) + ecounters[lid + (1024 * i)] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + + const int index = (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + Increase2bCounter(ecounters, (edge.x & EDGEMASK) >> 12); + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + const int index = (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + if (Read2bCounter(ecounters, (edge.x & EDGEMASK) >> 12)) + { + const int bucket = edge.y & BKTMASK4K; + const int bktIdx = min(atomic_add(destinationIndexes + bucket, 1), bktOutSize - 1); + destination[(bucket * bktOutSize) + bktIdx] = (uint2)(edge.y, edge.x); + } + } + } + +} + +__attribute__((reqd_work_group_size(64, 1, 1))) +__kernel void FluffyRoundN_64(const __global uint2 * source, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + const int bktInSize = DUCK_B_EDGES; + const int bktOutSize = DUCK_B_EDGES; + + __local u32 ecounters[8192]; + + const int edgesInBucket = min(sourceIndexes[group], bktInSize); + const int loops = (edgesInBucket + 64) / 64; + + for (int i = 0; i < 8*16; i++) + ecounters[lid + (64 * i)] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * 64) + lid; + + if (lindex < edgesInBucket) + { + + const int index = (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + Increase2bCounter(ecounters, (edge.x & EDGEMASK) >> 12); + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * 64) + lid; + + if (lindex < edgesInBucket) + { + const int index = (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + if (Read2bCounter(ecounters, (edge.x & EDGEMASK) >> 12)) + { + const int bucket = edge.y & BKTMASK4K; + const int bktIdx = min(atomic_add(destinationIndexes + bucket, 1), bktOutSize - 1); + destination[(bucket * bktOutSize) + bktIdx] = (uint2)(edge.y, edge.x); + } + } + } + +} + +__attribute__((reqd_work_group_size(1024, 1, 1))) +__kernel void FluffyTail(const __global uint2 * source, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + int myEdges = sourceIndexes[group]; + __local int destIdx; + + if (lid == 0) + destIdx = atomic_add(destinationIndexes, myEdges); + + barrier(CLK_LOCAL_MEM_FENCE); + + if (lid < myEdges) + { + destination[destIdx + lid] = source[group * DUCK_B_EDGES + lid]; + } +} + +__attribute__((reqd_work_group_size(256, 1, 1))) +__kernel void FluffyRecovery(const u64 v0i, const u64 v1i, const u64 v2i, const u64 v3i, const __constant u64 * recovery, __global int * indexes) +{ + const int gid = get_global_id(0); + const short lid = get_local_id(0); + + __local u32 nonces[42]; + u64 sipblock[64]; + + u64 v0; + u64 v1; + u64 v2; + u64 v3; + + if (lid < 42) nonces[lid] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < 1024; i += EDGE_BLOCK_SIZE) + { + u64 blockNonce = gid * 1024 + i; + + v0 = v0i; + v1 = v1i; + v2 = v2i; + v3 = v3i; + + for (u32 b = 0; b < EDGE_BLOCK_SIZE; b++) + { + v3 ^= blockNonce + b; + SIPROUND; SIPROUND; + v0 ^= blockNonce + b; + v2 ^= 0xff; + SIPROUND; SIPROUND; SIPROUND; SIPROUND; + + sipblock[b] = (v0 ^ v1) ^ (v2 ^ v3); + + } + const u64 last = sipblock[EDGE_BLOCK_MASK]; + + for (short s = EDGE_BLOCK_MASK; s >= 0; s--) + { + u64 lookup = s == EDGE_BLOCK_MASK ? last : sipblock[s] ^ last; + u64 u = lookup & EDGEMASK; + u64 v = (lookup >> 32) & EDGEMASK; + + u64 a = u | (v << 32); + u64 b = v | (u << 32); + + for (int i = 0; i < 42; i++) + { + if ((recovery[i] == a) || (recovery[i] == b)) + nonces[i] = blockNonce + s; + } + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + if (lid < 42) + { + if (nonces[lid] > 0) + indexes[lid] = nonces[lid]; + } +} + + + +// --------------- +#define BKT_OFFSET 255 +#define BKT_STEP 32 +__attribute__((reqd_work_group_size(1024, 1, 1))) +__kernel void FluffyRoundNO1(const __global uint2 * source, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + const int bktInSize = DUCK_B_EDGES; + const int bktOutSize = DUCK_B_EDGES; + + __local u32 ecounters[8192]; + + const int edgesInBucket = min(sourceIndexes[group], bktInSize); + const int loops = (edgesInBucket + CTHREADS) / CTHREADS; + + for (int i = 0; i < 8; i++) + ecounters[lid + (1024 * i)] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + + const int index =(bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + Increase2bCounter(ecounters, (edge.x & EDGEMASK) >> 12); + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + const int index = (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + if (Read2bCounter(ecounters, (edge.x & EDGEMASK) >> 12)) + { + const int bucket = edge.y & BKTMASK4K; + const int bktIdx = min(atomic_add(destinationIndexes + bucket, 1), bktOutSize - 1 - ((bucket & BKT_OFFSET) * BKT_STEP)); + destination[((bucket & BKT_OFFSET) * BKT_STEP) + (bucket * bktOutSize) + bktIdx] = (uint2)(edge.y, edge.x); + } + } + } + +} + +__attribute__((reqd_work_group_size(1024, 1, 1))) +__kernel void FluffyRoundNON(const __global uint2 * source, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + const int bktInSize = DUCK_B_EDGES; + const int bktOutSize = DUCK_B_EDGES; + + __local u32 ecounters[8192]; + + const int edgesInBucket = min(sourceIndexes[group], bktInSize); + const int loops = (edgesInBucket + CTHREADS) / CTHREADS; + + for (int i = 0; i < 8; i++) + ecounters[lid + (1024 * i)] = 0; + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + + const int index = ((group & BKT_OFFSET) * BKT_STEP) + (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + Increase2bCounter(ecounters, (edge.x & EDGEMASK) >> 12); + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < loops; i++) + { + const int lindex = (i * CTHREADS) + lid; + + if (lindex < edgesInBucket) + { + const int index = ((group & BKT_OFFSET) * BKT_STEP) + (bktInSize * group) + lindex; + + uint2 edge = source[index]; + + if (edge.x == 0 && edge.y == 0) continue; + + if (Read2bCounter(ecounters, (edge.x & EDGEMASK) >> 12)) + { + const int bucket = edge.y & BKTMASK4K; + const int bktIdx = min(atomic_add(destinationIndexes + bucket, 1), bktOutSize - 1 - ((bucket & BKT_OFFSET) * BKT_STEP)); + destination[((bucket & BKT_OFFSET) * BKT_STEP) + (bucket * bktOutSize) + bktIdx] = (uint2)(edge.y, edge.x); + } + } + } + +} + +__attribute__((reqd_work_group_size(1024, 1, 1))) +__kernel void FluffyTailO(const __global uint2 * source, __global uint2 * destination, const __global int * sourceIndexes, __global int * destinationIndexes) +{ + const int lid = get_local_id(0); + const int group = get_group_id(0); + + int myEdges = sourceIndexes[group]; + __local int destIdx; + + if (lid == 0) + destIdx = atomic_add(destinationIndexes, myEdges); + + barrier(CLK_LOCAL_MEM_FENCE); + + if (lid < myEdges) + { + destination[destIdx + lid] = source[((group & BKT_OFFSET) * BKT_STEP) + group * DUCK_B_EDGES + lid]; + } +} + +"#; diff --git a/ocl_cuckatoo/.gitignore b/ocl_cuckatoo/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/ocl_cuckatoo/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/ocl_cuckatoo/Cargo.toml b/ocl_cuckatoo/Cargo.toml new file mode 100644 index 0000000..9a777b0 --- /dev/null +++ b/ocl_cuckatoo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ocl_cuckatoo" +version = "1.0.2" +workspace = ".." + +[dependencies] +blake2-rfc = "0.2" +byteorder = "1" +mugle_miner_plugin = { path = "../plugin", version = "4.0.0" } +libc = "0.2" +ocl = "0.19" +hashbrown = "0.7" + +[lib] +name = "ocl_cuckatoo" +crate-type = ["cdylib", "rlib"] diff --git a/ocl_cuckatoo/src/finder.rs b/ocl_cuckatoo/src/finder.rs new file mode 100644 index 0000000..cf123a9 --- /dev/null +++ b/ocl_cuckatoo/src/finder.rs @@ -0,0 +1,273 @@ +use hashbrown::HashMap; + +#[derive(Clone)] +pub struct Solution { + pub nonces: Vec, +} + +pub struct Graph { + adj_index: HashMap, + adj_store: Vec, + nonces: HashMap<(u32, u32), u32>, +} + +struct Search { + length: usize, + path: Vec, + solutions: Vec, + + state: HashMap, + node_visited: usize, + node_explored: usize, +} + +#[derive(Clone, Copy)] +enum NodeState { + NotVisited, + Visited, + Explored, +} + +impl Search { + fn new(node_count: usize, length: usize) -> Search { + Search { + path: Vec::with_capacity(node_count), + solutions: vec![], + length: length * 2, + state: HashMap::with_capacity_and_hasher(node_count, Default::default()), + node_visited: 0, + node_explored: 0, + } + } + + #[inline] + fn visit(&mut self, node: u32) { + self.state.insert(node, NodeState::Visited); + self.path.push(node); + self.node_visited += 1; + } + + #[inline] + fn explore(&mut self, node: u32) { + self.state.insert(node, NodeState::Explored); + self.path.push(node); + self.node_explored += 1; + } + + #[inline] + fn leave(&mut self, node: u32) { + self.path.pop(); + self.state.insert(node, NodeState::NotVisited); + } + + #[inline] + fn state(&self, node: u32) -> NodeState { + match self.state.get(&node) { + None => NodeState::NotVisited, + Some(state) => *state, + } + } + + #[inline] + fn is_visited(&self, node: u32) -> bool { + match self.state(node) { + NodeState::NotVisited => false, + _ => true, + } + } + + #[inline] + fn is_explored(&self, node: u32) -> bool { + match self.state(node) { + NodeState::Explored => true, + _ => false, + } + } + + fn is_cycle(&mut self, node: u32, is_first: bool) -> bool { + let res = + self.path.len() > self.length - 1 && self.path[self.path.len() - self.length] == node; + if res && !is_first { + self.path.push(node); + } + res + } +} + +struct AdjNode { + value: u32, + next: Option, +} + +impl AdjNode { + #[inline] + fn first(value: u32) -> AdjNode { + AdjNode { value, next: None } + } + + #[inline] + fn next(value: u32, next: usize) -> AdjNode { + AdjNode { + value, + next: Some(next), + } + } +} + +struct AdjList<'a> { + current: Option<&'a AdjNode>, + adj_store: &'a Vec, +} + +impl<'a> AdjList<'a> { + #[inline] + pub fn new(current: Option<&'a AdjNode>, adj_store: &'a Vec) -> AdjList<'a> { + AdjList { current, adj_store } + } +} + +impl<'a> Iterator for AdjList<'a> { + type Item = u32; + + fn next(&mut self) -> Option { + match self.current { + None => None, + Some(node) => { + let val = node.value; + match node.next { + None => self.current = None, + Some(next_index) => self.current = Some(&self.adj_store[next_index]), + } + Some(val) + } + } + } +} + +fn nonce_key(node1: u32, node2: u32) -> (u32, u32) { + if node1 < node2 { + (node1, node2) + } else { + (node2, node1) + } +} + +impl Graph { + pub fn search(edges: &[u32]) -> Result, String> { + let edge_count = edges[1] as usize; + let mut g = Graph { + adj_index: HashMap::with_capacity_and_hasher(edge_count * 2, Default::default()), + nonces: HashMap::with_capacity_and_hasher(edge_count, Default::default()), + adj_store: Vec::with_capacity(edge_count * 2), + }; + let mut search = Search::new(edge_count * 2, 42); + const STEP: usize = 4; + for i in 1..=edge_count { + let n1 = edges[i * STEP]; + let n2 = edges[i * STEP + 1]; + let nonce = edges[i * STEP + 2]; + g.add_edge(n1, n2); + g.nonces.insert(nonce_key(n1, n2), nonce); + g.check_pair(n1, n2, &mut search)?; + } + + // for i in 1..=edge_count { + // let n1 = edges[i * STEP]; + // let n2 = edges[i * STEP + 1]; + // } + Ok(search.solutions.clone()) + } + + fn get_nonce(&self, node1: u32, node2: u32) -> Result { + match self.nonces.get(&nonce_key(node1, node2)) { + None => Err(format!("can not find a nonce for {}:{}", node1, node2)), + Some(v) => Ok(*v as u64), + } + } + + #[inline] + pub fn node_count(&self) -> usize { + self.adj_index.len() + } + + #[inline] + pub fn edge_count(&self) -> usize { + self.adj_store.len() / 2 + } + + #[inline] + fn add_edge(&mut self, node1: u32, node2: u32) { + self.add_half_edge(node1, node2); + self.add_half_edge(node2, node1); + } + + fn add_half_edge(&mut self, from: u32, to: u32) { + if let Some(index) = self.adj_index.get(&from) { + self.adj_store.push(AdjNode::next(to, *index)); + } else { + self.adj_store.push(AdjNode::first(to)); + } + self.adj_index.insert(from, self.adj_store.len() - 1); + } + + fn neighbors(&self, node: u32) -> Option + '_> { + let node = match self.adj_index.get(&node) { + Some(index) => Some(&self.adj_store[*index]), + None => return None, + }; + Some(AdjList::new(node, &self.adj_store)) + } + + fn check_pair(&self, u: u32, _v: u32, search: &mut Search) -> Result<(), String> { + self.walk_graph(u, search) + //self.walk_graph(v, search) + } + + fn add_solution(&self, s: &mut Search) -> Result<(), String> { + let res: Result, _> = s.path[s.path.len() - s.length..] + .chunks(2) + .map(|pair| match pair { + &[n1, n2] => self.get_nonce(n1, n2), + _ => Err("not an edge".to_string()), + }) + .collect(); + let mut nonces = match res { + Ok(v) => v, + Err(e) => { + return Err(format!("Failed to get nonce {:?}", e)); + } + }; + nonces.sort(); + let sol = Solution { nonces }; + s.solutions.push(sol); + Ok(()) + } + + fn walk_graph(&self, current: u32, search: &mut Search) -> Result<(), String> { + if search.is_explored(current) || search.path.len() > 84 { + if search.is_cycle(current, true) { + self.add_solution(search)?; + } + return Ok(()); + } + + let neighbors = match self.neighbors(current) { + None => return Ok(()), + Some(it) => it, + }; + search.explore(current); + for ns in neighbors { + if !search.is_visited(ns) { + search.visit(ns); + self.walk_graph(ns ^ 1, search)?; + search.leave(ns); + } else { + if search.is_cycle(ns, false) { + self.add_solution(search)?; + } + } + } + search.leave(current); + Ok(()) + } +} diff --git a/ocl_cuckatoo/src/lib.rs b/ocl_cuckatoo/src/lib.rs new file mode 100644 index 0000000..7c83fcd --- /dev/null +++ b/ocl_cuckatoo/src/lib.rs @@ -0,0 +1,182 @@ +extern crate blake2_rfc; +extern crate byteorder; +extern crate mugle_miner_plugin as plugin; +extern crate hashbrown; +extern crate libc; +extern crate ocl; + +use blake2_rfc::blake2b::blake2b; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use libc::*; +use plugin::*; +use std::io::Cursor; +use std::io::Error; +use std::mem; +use std::ptr; +use std::time::{Duration, SystemTime}; + +pub use self::finder::Graph; +pub use self::trimmer::Trimmer; + +mod finder; +mod trimmer; + +#[repr(C)] +struct Solver { + trimmer: Trimmer, + graph: Option, + mutate_nonce: bool, +} + +#[no_mangle] +pub unsafe extern "C" fn create_solver_ctx(params: *mut SolverParams) -> *mut SolverCtx { + let platform = match (*params).platform { + 1 => Some("AMD"), + 2 => Some("NVIDIA"), + _ => None, + }; + let device_id = Some((*params).device as usize); + let mut edge_bits = (*params).edge_bits as u8; + if edge_bits < 31 || edge_bits > 64 { + edge_bits = 31; + } + let trimmer = Trimmer::build(platform, device_id, edge_bits).expect("can't build trimmer"); + let solver = Solver { + trimmer: trimmer, + graph: None, + mutate_nonce: (*params).mutate_nonce, + }; + let solver_box = Box::new(solver); + let solver_ref = Box::leak(solver_box); + mem::transmute::<&mut Solver, *mut SolverCtx>(solver_ref) +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_solver_ctx(solver_ctx_ptr: *mut SolverCtx) { + // create box to clear memory + let solver_ptr = mem::transmute::<*mut SolverCtx, *mut Solver>(solver_ctx_ptr); + let _solver_box = Box::from_raw(solver_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn stop_solver(_solver_ctx_ptr: *mut SolverCtx) {} + +#[no_mangle] +pub unsafe extern "C" fn fill_default_params(params: *mut SolverParams) { + (*params).device = 0; + (*params).platform = 0; + (*params).edge_bits = 31; +} + +#[no_mangle] +pub unsafe extern "C" fn run_solver( + ctx: *mut SolverCtx, + header_ptr: *const c_uchar, + header_length: u32, + nonce: u64, + _range: u32, + solutions: *mut SolverSolutions, + stats: *mut SolverStats, +) -> u32 { + let start = SystemTime::now(); + let solver_ptr = mem::transmute::<*mut SolverCtx, *mut Solver>(ctx); + let solver = &*solver_ptr; + let mut header = Vec::with_capacity(header_length as usize); + let r_ptr = header.as_mut_ptr(); + ptr::copy_nonoverlapping(header_ptr, r_ptr, header_length as usize); + header.set_len(header_length as usize); + let n = nonce as u32; + let k = match set_header_nonce(&header, Some(n), solver.mutate_nonce) { + Err(_e) => { + return 2; + } + Ok(v) => v, + }; + let res = solver.trimmer.run(&k).unwrap(); + + let sols = Graph::search(&res).unwrap(); + let end = SystemTime::now(); + let elapsed = end.duration_since(start).unwrap(); + let mut i = 0; + (*solutions).edge_bits = 31; + (*solutions).num_sols = sols.len() as u32; + for sol in sols { + (*solutions).sols[i].nonce = nonce; + (*solutions).sols[i] + .proof + .copy_from_slice(&sol.nonces[..sol.nonces.len()]); + i += 1; + } + (*stats).edge_bits = 31; + (*stats).device_id = solver.trimmer.device_id as u32; + let name_bytes = solver.trimmer.device_name.as_bytes(); + let n = std::cmp::min((*stats).device_name.len(), name_bytes.len()); + (*stats).device_name[..n].copy_from_slice(&solver.trimmer.device_name.as_bytes()[..n]); + (*stats).last_solution_time = duration_to_u64(elapsed); + (*stats).last_start_time = + duration_to_u64(start.duration_since(SystemTime::UNIX_EPOCH).unwrap()); + (*stats).last_end_time = duration_to_u64(end.duration_since(SystemTime::UNIX_EPOCH).unwrap()); + 0 +} + +fn duration_to_u64(elapsed: Duration) -> u64 { + elapsed.as_secs() * 1_000_000_000 + elapsed.subsec_nanos() as u64 +} + +pub fn set_header_nonce( + header: &[u8], + nonce: Option, + mutate_nonce: bool, +) -> Result<[u64; 4], Error> { + if let Some(n) = nonce { + let len = header.len(); + let mut header = header.to_owned(); + if mutate_nonce { + header.truncate(len - 4); + header.write_u32::(n)?; + } + create_siphash_keys(&header) + } else { + create_siphash_keys(&header) + } +} + +pub fn create_siphash_keys(header: &[u8]) -> Result<[u64; 4], Error> { + let h = blake2b(32, &[], &header); + let hb = h.as_bytes(); + let mut rdr = Cursor::new(hb); + Ok([ + rdr.read_u64::()?, + rdr.read_u64::()?, + rdr.read_u64::()?, + rdr.read_u64::()?, + ]) +} + +#[cfg(test)] +mod tests { + use super::*; + #[ignore] + // results in Error executing function: clEnqueueNDRangeKernel("LeanRound") + // Status error code: CL_INVALID_WORK_GROUP_SIZE (-54) + // on MacOSX + #[test] + fn test_solve() { + let trimmer = Trimmer::build(None, None, 29).expect("can't build trimmer"); + let k = [ + 0x27580576fe290177, + 0xf9ea9b2031f4e76e, + 0x1663308c8607868f, + 0xb88839b0fa180d0e, + ]; + + let res = trimmer.run(&k).unwrap(); + println!("Trimmed to {}", res.len()); + + let sols = Graph::search(&res).unwrap(); + assert_eq!(1, sols.len()); + for sol in sols { + println!("Solution: {:x?}", sol.nonces); + } + } +} diff --git a/ocl_cuckatoo/src/trimmer.rs b/ocl_cuckatoo/src/trimmer.rs new file mode 100644 index 0000000..f67d6b9 --- /dev/null +++ b/ocl_cuckatoo/src/trimmer.rs @@ -0,0 +1,327 @@ +use ocl; +use ocl::{Buffer, Context, Device, Kernel, Platform, Program, Queue, SpatialDims}; + +const RES_BUFFER_SIZE: usize = 4_000_000; +const LOCAL_WORK_SIZE: usize = 256; +const GLOBAL_WORK_SIZE: usize = 1024 * LOCAL_WORK_SIZE; + +enum Mode { + SetCnt = 1, + Trim = 2, + Extract = 3, +} + +pub struct Trimmer { + edge_bits: u8, + q: Queue, + program: Program, + edges: Buffer, + counters: Buffer, + result: Buffer, + res_buf: Vec, + pub device_name: String, + pub device_id: usize, +} + +impl Trimmer { + pub fn build( + platform_name: Option<&str>, + device_id: Option, + edge_bits: u8, + ) -> ocl::Result { + let platform = find_platform(platform_name) + .ok_or::("Can't find OpenCL platform".into())?; + let device = find_device(&platform, device_id)?; + + let el_count = (1024 * 1024 * 16) << (edge_bits - 29); + let res_buf: Vec = vec![0; RES_BUFFER_SIZE]; + + let context = Context::builder() + .platform(platform) + .devices(device) + .build()?; + + let q = Queue::new(&context, device, None)?; + + let program = Program::builder() + .devices(device) + .src(SRC) + .cmplr_def("EDGEBITS", edge_bits as i32) + .build(&context)?; + + let edges = Buffer::::builder() + .queue(q.clone()) + .len(el_count) + .fill_val(0xFFFFFFFF) + .build()?; + let counters = Buffer::::builder() + .queue(q.clone()) + .len(el_count) + .fill_val(0) + .build()?; + let result = unsafe { + Buffer::::builder() + .queue(q.clone()) + .len(RES_BUFFER_SIZE) + .fill_val(0) + .use_host_slice(&res_buf[..]) + .build()? + }; + + Ok(Trimmer { + edge_bits, + q, + program, + edges, + counters, + result, + res_buf, + device_name: device.name()?, + device_id: device_id.unwrap_or(0), + }) + } + + pub fn run(&self, k: &[u64; 4]) -> ocl::Result> { + let mut current_mode = Mode::SetCnt; + let mut current_uorv: u32 = 0; + let trims = if self.edge_bits >= 29 { 128 } else { 256 }; + let enqs = 8 << (self.edge_bits - 29); + + let mut kernel = Kernel::builder() + .name("LeanRound") + .program(&self.program) + .queue(self.q.clone()) + .global_work_size(GLOBAL_WORK_SIZE) + .local_work_size(SpatialDims::One(LOCAL_WORK_SIZE)) + .arg(k[0]) + .arg(k[1]) + .arg(k[2]) + .arg(k[3]) + .arg(&self.edges) + .arg(&self.counters) + .arg(&self.result) + .arg(current_mode as u32) + .arg(current_uorv) + .build()?; + + let mut offset; + + macro_rules! kernel_enq ( + ($num:expr) => ( + for i in 0..$num { + offset = i * GLOBAL_WORK_SIZE; + unsafe { + kernel + .set_default_global_work_offset(SpatialDims::One(offset)) + .enq()?; + } + } + )); + + for l in 0..trims { + current_uorv = l & 1 as u32; + current_mode = Mode::SetCnt; + kernel.set_arg(7, current_mode as u32)?; + kernel.set_arg(8, current_uorv)?; + kernel_enq!(enqs); + + current_mode = if l == (trims - 1) { + Mode::Extract + } else { + Mode::Trim + }; + kernel.set_arg(7, current_mode as u32)?; + kernel_enq!(enqs); + // prepare for the next round + self.counters.cmd().fill(0, None).enq()?; + } + unsafe { + self.result.map().enq()?; + } + self.q.finish()?; + let ret = self.res_buf.clone(); + self.edges.cmd().fill(0xFFFFFFFF, None).enq()?; + self.result.cmd().fill(0, None).enq()?; + self.q.finish()?; + Ok(ret) + } +} + +fn find_platform(selector: Option<&str>) -> Option { + match selector { + None => Some(Platform::default()), + Some(sel) => Platform::list().into_iter().find(|p| { + if let Ok(vendor) = p.name() { + vendor.contains(sel) + } else { + false + } + }), + } +} + +fn find_device(platform: &Platform, selector: Option) -> ocl::Result { + match selector { + None => Device::first(platform), + Some(index) => Device::by_idx_wrap(platform, index), + } +} + +const SRC: &str = r#" +typedef uint8 u8; +typedef uint16 u16; +typedef uint u32; +typedef ulong u64; +typedef u32 node_t; +typedef u64 nonce_t; + +#define DEBUG 0 + +// number of edges +#define NEDGES ((u64)1 << EDGEBITS) +// used to mask siphash output +#define EDGEMASK (NEDGES - 1) + +#define SIPROUND \ + do { \ + v0 += v1; v2 += v3; v1 = rotate(v1,(ulong)13); \ + v3 = rotate(v3,(ulong)16); v1 ^= v0; v3 ^= v2; \ + v0 = rotate(v0,(ulong)32); v2 += v1; v0 += v3; \ + v1 = rotate(v1,(ulong)17); v3 = rotate(v3,(ulong)21); \ + v1 ^= v2; v3 ^= v0; v2 = rotate(v2,(ulong)32); \ + } while(0) + +u64 dipnode(ulong v0i, ulong v1i, ulong v2i, ulong v3i, u64 nce, uint uorv) { + ulong nonce = 2 * nce + uorv; + ulong v0 = v0i, v1 = v1i, v2 = v2i, v3 = v3i ^ nonce; + SIPROUND; SIPROUND; + v0 ^= nonce; + v2 ^= 0xff; + SIPROUND; SIPROUND; SIPROUND; SIPROUND; + return (v0 ^ v1 ^ v2 ^ v3) & EDGEMASK; +} + +#define MODE_SETCNT 1 +#define MODE_TRIM 2 +#define MODE_EXTRACT 3 + +// Minimalistic cuckatoo lean trimmer +// This implementation is not optimal! +// +// 8 global kernel executions (hardcoded ATM) +// 1024 thread blocks, 256 threads each, 256 edges for each thread +// 8*1024*256*256 = 536 870 912 edges = cuckatoo29 +__attribute__((reqd_work_group_size(256, 1, 1))) +__kernel void LeanRound(const u64 v0i, const u64 v1i, const u64 v2i, const u64 v3i, __global uint8 * edges, __global uint * counters, __global u32 * aux, const u32 mode, const u32 uorv) +{ + const int blocks = NEDGES / 32; + const int gid = get_global_id(0); + const int lid = get_local_id(0); + __local u32 el[256][8]; + + { + int lCount = 0; + // what 256 nit block of edges are we processing + u64 index = gid; + u64 start = index * 256; + // load all 256 bits (edges) to registers + uint8 load = edges[index]; + // map to an array for easier indexing (depends on compiler/GPU, could be pushed out to cache) + el[lid][0] = load.s0; + el[lid][1] = load.s1; + el[lid][2] = load.s2; + el[lid][3] = load.s3; + el[lid][4] = load.s4; + el[lid][5] = load.s5; + el[lid][6] = load.s6; + el[lid][7] = load.s7; + + // process as 8 x 32bit segment, GPUs have 32bit ALUs + for (short i = 0; i < 8; i++) + { + // shortcut to current 32bit value + uint ee = el[lid][i]; + // how many edges we process in the block + short lEdges = popcount(ee); + // whole warp will always execute worst case scenario, but it will help in the long run (not benched) + + // now a loop for every single living edge in current 32 edge block + for (short e = 0; e < lEdges; e++) + { + // bit position of next living edge + short pos = clz(ee); + // position in the 256 edge block + int subPos = (i * 32) + pos; + // reconstruct value of noce for this edge + int nonce = start + subPos; + // calculate siphash24 for either U or V (host device control) + u32 hash = dipnode(v0i, v1i, v2i, v3i, nonce, uorv); + + // this time we set edge bit counters - PASS 1 + if (mode == MODE_SETCNT) + { + // what global memory 32bit block we need to access + int block = hash / 32; + // what bit in the block we need to set + u32 bit = hash % 32; + // create a bitmask from that bit + u32 mask = (u32)1 << bit; + // global atomic or (set bit to 1 no matter what it was) + atomic_or(&counters[block], mask); + } + // this time counters are already set so need to figure out if the edge lives - PASS 2 + else if ((mode == MODE_TRIM) || (mode == MODE_EXTRACT)) + { + // cuckatoo XOR thing + hash = hash ^ 1; + // what global memory 32bit block we need to read + int block = hash / 32; + // what bit in the block we need to read + u32 bit = hash % 32; + // create a bitmask from that bit + u32 mask = (u32)1 << bit; + // does the edge live or not + bool lives = ((counters[block]) & mask) > 0; + // if edge is not alive, kill it (locally in registers) + if (!lives) + { + el[lid][i] ^= ((u32)1<<31) >> pos; // 1 XOR 1 is 0 + } + else + { + // debug counter of alive edges + if (DEBUG) + lCount++; + + // if this is last lean round we do, store all edges in one long list + if (mode == MODE_EXTRACT) // PASS N_rounds + { + // obtain global pointer to final edge list + int edgePos = atomic_inc(aux+1); + // position in output array as multiple of 128bits (32bits will be empty) + int auxIndex = 4 + (edgePos * 4); + // debug failsafe + //if (!(DEBUG && (auxIndex > (1024 * 1024)))) + { + // store all information to global memory + aux[auxIndex + 0] = dipnode(v0i, v1i, v2i, v3i, nonce, 0); + aux[auxIndex + 1] = dipnode(v0i, v1i, v2i, v3i, nonce, 1); + aux[auxIndex + 2] = nonce; + aux[auxIndex + 3] = 0; // for clarity, competely useless operation + } + } + } + } + // clear current edge position so that we can skip it in next run of ctz() + ee ^= ((u32)1<<31) >> pos; // 1 XOR 1 is 0 + } + } + // return edge bits back to global memory if we are in second stage + if (mode == MODE_TRIM) + edges[index] = (uint8)(el[lid][0], el[lid][1], el[lid][2], el[lid][3], el[lid][4], el[lid][5], el[lid][6], el[lid][7]); + // debug only, use aux buffer to count alive edges in this round + if (DEBUG) + atomic_add(aux, lCount); + } +} +"#; diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml new file mode 100644 index 0000000..ef9fe6c --- /dev/null +++ b/plugin/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mugle_miner_plugin" +version = "4.0.0" +authors = ["Mugle Developers "] +repository = "https://github.com/mugleproject/mugle-miner" +description = "Device specific plugins for the mugle miner" +license = "Apache-2.0" +workspace = ".." + +[dependencies] +byteorder = "1" +blake2-rfc = "0.2" +libc = "0.2" +serde = "1" +serde_derive = "1" +serde_json = "1" diff --git a/plugin/src/lib.rs b/plugin/src/lib.rs new file mode 100644 index 0000000..0b4d37a --- /dev/null +++ b/plugin/src/lib.rs @@ -0,0 +1,329 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Crate wrapping up the Mugle miner plugins + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate blake2_rfc as blake2; +extern crate byteorder; +extern crate libc; +extern crate serde_json; + +use libc::*; +use std::ffi::CString; +use std::ptr::NonNull; +use std::{cmp, fmt, marker}; + +use blake2::blake2b::Blake2b; +use byteorder::{BigEndian, ByteOrder}; + +/// Size of proof +pub const PROOFSIZE: usize = 42; +/// Maximin length of plugin name w +pub const MAX_NAME_LEN: usize = 256; +/// Maximum number of solutions +pub const MAX_SOLS: usize = 4; + +// Type definitions corresponding to each function that the plugin/solver implements +/// Create solver function +pub type CuckooCreateSolverCtx = unsafe extern "C" fn(*mut SolverParams) -> *mut SolverCtx; +/// Destroy solver function +pub type CuckooDestroySolverCtx = unsafe extern "C" fn(*mut SolverCtx); +/// Run solver function +pub type CuckooRunSolver = unsafe extern "C" fn( + *mut SolverCtx, // Solver context + *const c_uchar, // header + u32, // header length + u64, // nonce + u32, // range + *mut SolverSolutions, // reference to any found solutions + *mut SolverStats, // solver stats +) -> u32; +/// Stop solver function +pub type CuckooStopSolver = unsafe extern "C" fn(*mut SolverCtx); +/// Fill default params of solver +pub type CuckooFillDefaultParams = unsafe extern "C" fn(*mut SolverParams); + +/// A solver context, opaque reference to C++ type underneath +#[derive(Copy, Clone, Debug)] +pub enum SolverCtx {} +/// wrap ctx to send across threads +pub struct SolverCtxWrapper(pub NonNull); +unsafe impl marker::Send for SolverCtxWrapper {} + +/// Common parameters for a solver +#[derive(Clone, Debug, Serialize, Deserialize)] +#[repr(C)] +pub struct SolverParams { + /// threads + pub nthreads: u32, + /// trims + pub ntrims: u32, + /// Whether to show cycle (should be true to get solutions) + pub showcycle: bool, + /// allrounds + pub allrounds: bool, + /// whether to apply the nonce to the header, or leave as is, + /// letting caller mutate nonce + pub mutate_nonce: bool, + /// reduce cpuload + pub cpuload: bool, + + /// Common Cuda params + pub device: u32, + + /// Lean cuda params + pub blocks: u32, + /// + pub tpb: u32, + + /// Mean cuda params + pub expand: u32, + /// + pub genablocks: u32, + /// + pub genatpb: u32, + /// + pub genbtpb: u32, + /// + pub trimtpb: u32, + /// + pub tailtpb: u32, + /// + pub recoverblocks: u32, + /// + pub recovertpb: u32, + /// OCL platform ID, 0 - default, 1 - AMD, 2 - NVIDIA + pub platform: u32, + /// edge bits for OCL plugins + pub edge_bits: u32, +} + +impl Default for SolverParams { + fn default() -> SolverParams { + SolverParams { + nthreads: 0, + ntrims: 0, + showcycle: true, + allrounds: false, + mutate_nonce: false, + cpuload: true, + device: 0, + blocks: 0, + tpb: 0, + expand: 0, + genablocks: 0, + genatpb: 0, + genbtpb: 0, + trimtpb: 0, + tailtpb: 0, + recoverblocks: 0, + recovertpb: 0, + platform: 0, + edge_bits: 31, + } + } +} + +/// Common stats collected by solvers +#[derive(Clone)] +#[repr(C)] +pub struct SolverStats { + /// device Id + pub device_id: u32, + /// graph size + pub edge_bits: u32, + /// plugin name + pub plugin_name: [c_uchar; MAX_NAME_LEN], + /// device name + pub device_name: [c_uchar; MAX_NAME_LEN], + /// whether device has reported an error + pub has_errored: bool, + /// reason for error + pub error_reason: [c_uchar; MAX_NAME_LEN], + /// number of searched completed by device + pub iterations: u32, + /// last solution start time + pub last_start_time: u64, + /// last solution end time + pub last_end_time: u64, + /// last solution elapsed time + pub last_solution_time: u64, +} + +impl Default for SolverStats { + fn default() -> SolverStats { + SolverStats { + device_id: 0, + edge_bits: 0, + plugin_name: [0; MAX_NAME_LEN], + device_name: [0; MAX_NAME_LEN], + has_errored: false, + error_reason: [0; MAX_NAME_LEN], + iterations: 0, + last_start_time: 0, + last_end_time: 0, + last_solution_time: 0, + } + } +} + +impl SolverStats { + fn get_name(&self, c_str: &[u8; MAX_NAME_LEN]) -> String { + // remove all null zeroes + let v = c_str.clone().to_vec(); + let mut i = 0; + for j in 0..v.len() { + if v.get(j) == Some(&0) { + i = j; + break; + } + } + let v = v.split_at(i).0; + match CString::new(v) { + Ok(s) => s.to_str().unwrap().to_owned(), + Err(_) => String::from("Unknown Device Name"), + } + } + /// return device name as rust string + pub fn get_device_name(&self) -> String { + self.get_name(&self.device_name) + } + /// return plugin name as rust string + pub fn get_plugin_name(&self) -> String { + self.get_name(&self.plugin_name) + } + /// return plugin name as rust string + pub fn get_error_reason(&self) -> String { + self.get_name(&self.error_reason) + } + /// set plugin name + pub fn set_plugin_name(&mut self, name: &str) { + let c_vec = CString::new(name).unwrap().into_bytes(); + for (i, _) in c_vec.iter().enumerate() { + self.plugin_name[i] = c_vec[i]; + } + } +} + +/// A single solution +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Solution { + /// Optional ID + pub id: u64, + /// Nonce + pub nonce: u64, + /// Proof + pub proof: [u64; PROOFSIZE], +} + +impl Default for Solution { + fn default() -> Solution { + Solution { + id: 0, + nonce: 0, + proof: [0u64; PROOFSIZE], + } + } +} + +impl fmt::Display for Solution { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut comma_separated = String::new(); + + for num in &self.proof[0..self.proof.len()] { + comma_separated.push_str(&format!("0x{:X}", &num)); + comma_separated.push_str(", "); + } + comma_separated.pop(); + comma_separated.pop(); + + write!(f, "Nonce:{} [{}]", self.nonce, comma_separated) + } +} + +impl fmt::Debug for Solution { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", &self.proof[..]) + } +} + +impl cmp::PartialEq for Solution { + fn eq(&self, other: &Solution) -> bool { + for i in 0..PROOFSIZE { + if self.proof[i] != other.proof[i] { + return false; + } + } + true + } +} + +impl Solution { + /// Converts the proof to a vector of u64s + pub fn to_u64s(&self) -> Vec { + let mut nonces = Vec::with_capacity(PROOFSIZE); + for n in self.proof.iter() { + nonces.push(*n as u64); + } + nonces + } + + /// Returns the hash of the solution, as performed in + /// mugle + /// TODO: Check whether mugle sticks to u32s like this + pub fn hash(&self) -> [u8; 32] { + // Hash + let mut blake2b = Blake2b::new(32); + for n in 0..self.proof.len() { + let mut bytes = [0; 4]; + BigEndian::write_u32(&mut bytes, self.proof[n] as u32); + blake2b.update(&bytes); + } + let mut ret = [0; 32]; + ret.copy_from_slice(blake2b.finalize().as_bytes()); + ret + } +} + +/// All solutions returned +#[derive(Clone, Copy)] +#[repr(C)] +pub struct SolverSolutions { + /// graph size + pub edge_bits: u32, + /// number of solutions + pub num_sols: u32, + /// solutions themselves + pub sols: [Solution; MAX_SOLS], +} + +impl Default for SolverSolutions { + fn default() -> SolverSolutions { + SolverSolutions { + edge_bits: 0, + num_sols: 0, + sols: [Solution::default(); MAX_SOLS], + } + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/src/bin/client.rs b/src/bin/client.rs new file mode 100644 index 0000000..dba68a9 --- /dev/null +++ b/src/bin/client.rs @@ -0,0 +1,657 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Client network controller, controls requests and responses from the +//! stratum server + +use bufstream::BufStream; +use native_tls::{TlsConnector, TlsStream}; +use serde_json; +use stats; +use std; +use std::io::{self, BufRead, ErrorKind, Read, Write}; +use std::net::TcpStream; +use std::sync::{mpsc, Arc, RwLock}; +use std::thread; +use time; +use types; +use util::LOGGER; + +#[derive(Debug)] +pub enum Error { + ConnectionError(String), + RequestError(String), + ResponseError(String), + JsonError(String), + GeneralError(String), +} + +impl From for Error { + fn from(error: serde_json::error::Error) -> Self { + Error::JsonError(format!("Failed to parse JSON: {:?}", error)) + } +} + +impl From> for Error { + fn from(error: std::sync::PoisonError) -> Self { + Error::GeneralError(format!("Failed to get lock: {:?}", error)) + } +} + +impl From> for Error { + fn from(error: std::sync::mpsc::SendError) -> Self { + Error::GeneralError(format!("Failed to send to a channel: {:?}", error)) + } +} + +struct Stream { + stream: Option>, + tls_stream: Option>>, +} + +impl Stream { + fn new() -> Stream { + Stream { + stream: None, + tls_stream: None, + } + } + fn try_connect(&mut self, server_url: &str, tls: Option) -> Result<(), Error> { + match TcpStream::connect(server_url) { + Ok(conn) => { + if tls.is_some() && tls.unwrap() { + let connector = TlsConnector::new().map_err(|e| { + Error::ConnectionError(format!("Can't create TLS connector: {:?}", e)) + })?; + let url_port: Vec<&str> = server_url.split(':').collect(); + let splitted_url: Vec<&str> = url_port[0].split('.').collect(); + let base_host = format!( + "{}.{}", + splitted_url[splitted_url.len() - 2], + splitted_url[splitted_url.len() - 1] + ); + let mut stream = connector.connect(&base_host, conn).map_err(|e| { + Error::ConnectionError(format!("Can't establish TLS connection: {:?}", e)) + })?; + stream.get_mut().set_nonblocking(true).map_err(|e| { + Error::ConnectionError(format!("Can't switch to nonblocking mode: {:?}", e)) + })?; + self.tls_stream = Some(BufStream::new(stream)); + } else { + conn.set_nonblocking(true).map_err(|e| { + Error::ConnectionError(format!("Can't switch to nonblocking mode: {:?}", e)) + })?; + self.stream = Some(BufStream::new(conn)); + } + Ok(()) + } + Err(e) => Err(Error::ConnectionError(format!("{}", e))), + } + } +} + +impl Write for Stream { + fn write(&mut self, b: &[u8]) -> Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().write(b) + } else { + self.stream.as_mut().unwrap().write(b) + } + } + fn flush(&mut self) -> Result<(), std::io::Error> { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().flush() + } else { + self.stream.as_mut().unwrap().flush() + } + } +} +impl Read for Stream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().read(buf) + } else { + self.stream.as_mut().unwrap().read(buf) + } + } +} + +impl BufRead for Stream { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().fill_buf() + } else { + self.stream.as_mut().unwrap().fill_buf() + } + } + fn consume(&mut self, amt: usize) { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().consume(amt) + } else { + self.stream.as_mut().unwrap().consume(amt) + } + } + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().read_until(byte, buf) + } else { + self.stream.as_mut().unwrap().read_until(byte, buf) + } + } + fn read_line(&mut self, string: &mut String) -> io::Result { + if self.tls_stream.is_some() { + self.tls_stream.as_mut().unwrap().read_line(string) + } else { + self.stream.as_mut().unwrap().read_line(string) + } + } +} + +pub struct Controller { + _id: u32, + server_url: String, + server_login: Option, + server_password: Option, + server_tls_enabled: Option, + stream: Option, + rx: mpsc::Receiver, + pub tx: mpsc::Sender, + miner_tx: mpsc::Sender, + last_request_id: u32, + stats: Arc>, +} + +fn invalid_error_response() -> types::RpcError { + types::RpcError { + code: 0, + message: "Invalid error response received".to_owned(), + } +} + +impl Controller { + pub fn new( + server_url: &str, + server_login: Option, + server_password: Option, + server_tls_enabled: Option, + miner_tx: mpsc::Sender, + stats: Arc>, + ) -> Result { + let (tx, rx) = mpsc::channel::(); + Ok(Controller { + _id: 0, + server_url: server_url.to_string(), + server_login, + server_password, + server_tls_enabled, + stream: None, + tx, + rx, + miner_tx, + last_request_id: 0, + stats, + }) + } + + pub fn try_connect(&mut self) -> Result<(), Error> { + self.stream = Some(Stream::new()); + self.stream + .as_mut() + .unwrap() + .try_connect(&self.server_url, self.server_tls_enabled)?; + Ok(()) + } + + fn read_message(&mut self) -> Result, Error> { + if self.stream.is_none() { + return Err(Error::ConnectionError("broken pipe".to_string())); + } + let mut line = String::new(); + match self.stream.as_mut().unwrap().read_line(&mut line) { + Ok(_) => { + // stream is not returning a proper error on disconnect + if line == "" { + return Err(Error::ConnectionError("broken pipe".to_string())); + } + Ok(Some(line)) + } + Err(ref e) if e.kind() == ErrorKind::BrokenPipe => { + Err(Error::ConnectionError("broken pipe".to_string())) + } + Err(ref e) if e.kind() == ErrorKind::WouldBlock => Ok(None), + Err(e) => { + error!(LOGGER, "Communication error with stratum server: {}", e); + Err(Error::ConnectionError("broken pipe".to_string())) + } + } + } + + fn send_message(&mut self, message: &str) -> Result<(), Error> { + if self.stream.is_none() { + return Err(Error::ConnectionError(String::from("No server connection"))); + } + debug!(LOGGER, "sending request: {}", message); + let _ = self.stream.as_mut().unwrap().write(message.as_bytes()); + let _ = self.stream.as_mut().unwrap().write(b"\n"); + let _ = self.stream.as_mut().unwrap().flush(); + Ok(()) + } + + fn send_message_get_job_template(&mut self) -> Result<(), Error> { + let req = types::RpcRequest { + id: self.last_request_id.to_string(), + jsonrpc: "2.0".to_string(), + method: "getjobtemplate".to_string(), + params: None, + }; + let req_str = serde_json::to_string(&req)?; + { + let mut stats = self.stats.write()?; + stats.client_stats.last_message_sent = "Last Message Sent: Get New Job".to_string(); + } + self.send_message(&req_str) + } + + fn send_login(&mut self) -> Result<(), Error> { + // only send the login request if a login string is configured + let login_str = match self.server_login.clone() { + None => "".to_string(), + Some(server_login) => server_login, + }; + if login_str == "" { + return Ok(()); + } + let password_str = match self.server_password.clone() { + None => "".to_string(), + Some(server_password) => server_password, + }; + let params = types::LoginParams { + login: login_str, + pass: password_str, + agent: "mugle-miner".to_string(), + }; + let req = types::RpcRequest { + id: self.last_request_id.to_string(), + jsonrpc: "2.0".to_string(), + method: "login".to_string(), + params: Some(serde_json::to_value(params)?), + }; + let req_str = serde_json::to_string(&req)?; + { + let mut stats = self.stats.write()?; + stats.client_stats.last_message_sent = "Last Message Sent: Login".to_string(); + } + self.send_message(&req_str) + } + + fn send_message_get_status(&mut self) -> Result<(), Error> { + let req = types::RpcRequest { + id: self.last_request_id.to_string(), + jsonrpc: "2.0".to_string(), + method: "status".to_string(), + params: None, + }; + let req_str = serde_json::to_string(&req)?; + self.send_message(&req_str) + } + + fn send_message_submit( + &mut self, + height: u64, + job_id: u64, + edge_bits: u32, + nonce: u64, + pow: Vec, + ) -> Result<(), Error> { + let params_in = types::SubmitParams { + height, + job_id, + edge_bits, + nonce, + pow, + }; + let params = serde_json::to_string(¶ms_in)?; + let req = types::RpcRequest { + id: self.last_request_id.to_string(), + jsonrpc: "2.0".to_string(), + method: "submit".to_string(), + params: Some(serde_json::from_str(¶ms)?), + }; + let req_str = serde_json::to_string(&req)?; + { + let mut stats = self.stats.write()?; + stats.client_stats.last_message_sent = format!( + "Last Message Sent: Found share for height: {} - nonce: {}", + params_in.height, params_in.nonce + ); + } + self.send_message(&req_str) + } + + fn send_miner_job(&mut self, job: types::JobTemplate) -> Result<(), Error> { + let miner_message = + types::MinerMessage::ReceivedJob(job.height, job.job_id, job.difficulty, job.pre_pow); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = format!( + "Last Message Received: Start Job for Height: {}, Difficulty: {}", + job.height, job.difficulty + ); + self.miner_tx.send(miner_message).map_err(|e| e.into()) + } + + fn send_miner_stop(&mut self) -> Result<(), Error> { + let miner_message = types::MinerMessage::StopJob; + self.miner_tx.send(miner_message).map_err(|e| e.into()) + } + + pub fn handle_request(&mut self, req: types::RpcRequest) -> Result<(), Error> { + debug!(LOGGER, "Received request type: {}", req.method); + match req.method.as_str() { + "job" => match req.params { + None => Err(Error::RequestError("No params in job request".to_owned())), + Some(params) => { + let job = serde_json::from_value::(params)?; + info!(LOGGER, "Got a new job: {:?}", job); + self.send_miner_job(job) + } + }, + _ => Err(Error::RequestError("Unknonw method".to_owned())), + } + } + + pub fn handle_response(&mut self, res: types::RpcResponse) -> Result<(), Error> { + debug!(LOGGER, "Received response with id: {}", res.id); + match res.method.as_str() { + // "status" response can be used to further populate stats object + "status" => { + if let Some(result) = res.result { + let st = serde_json::from_value::(result)?; + info!( + LOGGER, + "Status for worker {} - Height: {}, Difficulty: {}, ({}/{}/{})", + st.id, + st.height, + st.difficulty, + st.accepted, + st.rejected, + st.stale + ); + // Add these status to the stats + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = format!( + "Last Message Received: Accepted: {}, Rejected: {}, Stale: {}", + st.accepted, st.rejected, st.stale + ); + } else { + let err = res.error.unwrap_or_else(invalid_error_response); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = + format!("Last Message Received: Failed to get status: {:?}", err); + error!(LOGGER, "Failed to get status: {:?}", err); + } + Ok(()) + } + // "getjobtemplate" response gets sent to miners to work on + "getjobtemplate" => { + if let Some(result) = res.result { + let job: types::JobTemplate = serde_json::from_value(result)?; + { + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = format!( + "Last Message Received: Got job for block {} at difficulty {}", + job.height, job.difficulty + ); + } + info!( + LOGGER, + "Got a job at height {} and difficulty {}", job.height, job.difficulty + ); + self.send_miner_job(job) + } else { + let err = res.error.unwrap_or_else(invalid_error_response); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = format!( + "Last Message Received: Failed to get job template: {:?}", + err + ); + error!(LOGGER, "Failed to get a job template: {:?}", err); + Ok(()) + } + } + // "submit" response + "submit" => { + if let Some(result) = res.result { + info!(LOGGER, "Share Accepted!!"); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = + "Last Message Received: Share Accepted!!".to_string(); + stats.mining_stats.solution_stats.num_shares_accepted += 1; + let result = serde_json::to_string(&result)?; + if result.contains("blockfound") { + info!(LOGGER, "Block Found!!"); + stats.client_stats.last_message_received = + "Last Message Received: Block Found!!".to_string(); + stats.mining_stats.solution_stats.num_blocks_found += 1; + } + } else { + let err = res.error.unwrap_or_else(invalid_error_response); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = format!( + "Last Message Received: Failed to submit a solution: {:?}", + err.message + ); + if err.message.contains("too late") { + stats.mining_stats.solution_stats.num_staled += 1; + } else { + stats.mining_stats.solution_stats.num_rejected += 1; + } + error!(LOGGER, "Failed to submit a solution: {:?}", err); + } + Ok(()) + } + // "keepalive" response + "keepalive" => { + if res.result.is_some() { + // Nothing to do for keepalive "ok" + // dont update last_message_received with good keepalive response + } else { + let err = res.error.unwrap_or_else(invalid_error_response); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = format!( + "Last Message Received: Failed to request keepalive: {:?}", + err + ); + error!(LOGGER, "Failed to request keepalive: {:?}", err); + } + Ok(()) + } + // "login" response + "login" => { + if res.result.is_some() { + // Nothing to do for login "ok" + // dont update last_message_received with good login response + } else { + // This is a fatal error + let err = res.error.unwrap_or_else(invalid_error_response); + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = + format!("Last Message Received: Failed to log in: {:?}", err); + stats.client_stats.connection_status = + "Connection Status: Server requires login".to_string(); + stats.client_stats.connected = false; + error!(LOGGER, "Failed to log in: {:?}", err); + } + Ok(()) + } + // unknown method response + _ => { + let mut stats = self.stats.write()?; + stats.client_stats.last_message_received = + format!("Last Message Received: Unknown Response: {:?}", res); + warn!(LOGGER, "Unknown Response: {:?}", res); + Ok(()) + } + } + } + + pub fn run(mut self) { + let server_read_interval = 1; + let server_retry_interval = 5; + let mut next_server_read = time::get_time().sec + server_read_interval; + let status_interval = 30; + let mut next_status_request = time::get_time().sec + status_interval; + let mut next_server_retry = time::get_time().sec; + // Request the first job template + thread::sleep(std::time::Duration::from_secs(1)); + let mut was_disconnected = true; + loop { + // Check our connection status, and try to correct if possible + if self.stream.is_none() { + if !was_disconnected { + let _ = self.send_miner_stop(); + } + was_disconnected = true; + if time::get_time().sec > next_server_retry { + if self.try_connect().is_err() { + let status = format!("Connection Status: Can't establish server connection to {}. Will retry every {} seconds", + self.server_url, + server_retry_interval); + warn!(LOGGER, "{}", status); + let mut stats = self.stats.write().unwrap(); + stats.client_stats.connection_status = status; + stats.client_stats.connected = false; + self.stream = None; + } else { + let status = format!( + "Connection Status: Connected to Mugle server at {}.", + self.server_url + ); + warn!(LOGGER, "{}", status); + let mut stats = self.stats.write().unwrap(); + stats.client_stats.connection_status = status; + } + next_server_retry = time::get_time().sec + server_retry_interval; + if self.stream.is_none() { + thread::sleep(std::time::Duration::from_secs(1)); + continue; + } + } + } else { + // get new job template + if was_disconnected { + let _ = self.send_login(); + let _ = self.send_message_get_job_template(); + was_disconnected = false; + } + // read messages from server + if time::get_time().sec > next_server_read { + match self.read_message() { + Ok(message) => { + match message { + Some(m) => { + { + let mut stats = self.stats.write().unwrap(); + stats.client_stats.connected = true; + } + // figure out what kind of message, + // and dispatch appropriately + debug!(LOGGER, "Received message: {}", m); + // Deserialize to see what type of object it is + if let Ok(v) = serde_json::from_str::(&m) { + // Is this a response or request? + if v["method"] == "job" { + // this is a request + match serde_json::from_str::(&m) { + Err(e) => error!( + LOGGER, + "Error parsing request {} : {:?}", m, e + ), + Ok(request) => { + if let Err(err) = self.handle_request(request) { + error!( + LOGGER, + "Error handling request {} : :{:?}", + m, + err + ) + } + } + } + continue; + } else { + // this is a response + match serde_json::from_str::(&m) { + Err(e) => error!( + LOGGER, + "Error parsing response {} : {:?}", m, e + ), + Ok(response) => { + if let Err(err) = self.handle_response(response) + { + error!( + LOGGER, + "Error handling response {} : :{:?}", + m, + err + ) + } + } + } + continue; + } + } else { + error!(LOGGER, "Error parsing message: {}", m) + } + } + None => {} // No messages from the server at this time + } + } + Err(e) => { + error!(LOGGER, "Error reading message: {:?}", e); + self.stream = None; + continue; + } + } + next_server_read = time::get_time().sec + server_read_interval; + } + + // Request a status message from the server + if time::get_time().sec > next_status_request { + let _ = self.send_message_get_status(); + next_status_request = time::get_time().sec + status_interval; + } + } + + // Talk to the cuckoo miner plugin + while let Some(message) = self.rx.try_iter().next() { + debug!(LOGGER, "Client received message: {:?}", message); + let result = match message { + types::ClientMessage::FoundSolution(height, job_id, edge_bits, nonce, pow) => { + self.send_message_submit(height, job_id, edge_bits, nonce, pow) + } + types::ClientMessage::Shutdown => { + //TODO: Inform server? + debug!(LOGGER, "Shutting down client controller"); + return; + } + }; + if let Err(e) = result { + error!(LOGGER, "Mining Controller Error {:?}", e); + self.stream = None; + } + } + thread::sleep(std::time::Duration::from_millis(10)); + } // loop + } +} diff --git a/src/bin/mining.rs b/src/bin/mining.rs new file mode 100644 index 0000000..f6cf857 --- /dev/null +++ b/src/bin/mining.rs @@ -0,0 +1,182 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Plugin controller, listens for messages sent from the stratum +/// server, controls plugins and responds appropriately +use std::sync::{mpsc, Arc, RwLock}; +use std::{self, thread}; +use time; +use util::LOGGER; +use {config, stats, types}; + +use cuckoo::{CuckooMiner, CuckooMinerError}; + +use plugin::SolverStats; + +pub struct Controller { + _config: config::MinerConfig, + rx: mpsc::Receiver, + pub tx: mpsc::Sender, + client_tx: Option>, + current_height: u64, + current_job_id: u64, + current_target_diff: u64, + stats: Arc>, +} + +impl Controller { + pub fn new( + config: config::MinerConfig, + stats: Arc>, + ) -> Result { + { + let mut stats_w = stats.write().unwrap(); + stats_w.client_stats.server_url = config.stratum_server_addr.clone(); + } + let (tx, rx) = mpsc::channel::(); + Ok(Controller { + _config: config, + rx, + tx, + client_tx: None, + current_height: 0, + current_job_id: 0, + current_target_diff: 0, + stats, + }) + } + + pub fn set_client_tx(&mut self, client_tx: mpsc::Sender) { + self.client_tx = Some(client_tx); + } + + /// Run the mining controller, solvers in miner should already be going + pub fn run(&mut self, mut miner: CuckooMiner) -> Result<(), CuckooMinerError> { + // how often to output stats + let stat_output_interval = 2; + let mut next_stat_output = time::get_time().sec + stat_output_interval; + + loop { + while let Some(message) = self.rx.try_iter().next() { + debug!(LOGGER, "Miner received message: {:?}", message); + let result = match message { + types::MinerMessage::ReceivedJob(height, job_id, diff, pre_pow) => { + self.current_height = height; + self.current_job_id = job_id; + self.current_target_diff = diff; + miner.notify( + self.current_job_id as u32, + self.current_height, + &pre_pow, + "", + diff, + ) + } + types::MinerMessage::StopJob => { + debug!(LOGGER, "Stopping jobs"); + miner.pause_solvers(); + Ok(()) + } + types::MinerMessage::Shutdown => { + debug!(LOGGER, "Stopping jobs and Shutting down mining controller"); + miner.stop_solvers(); + miner.wait_for_solver_shutdown(); + return Ok(()); + } + }; + if let Err(e) = result { + error!(LOGGER, "Mining Controller Error {:?}", e); + } + } + + if time::get_time().sec > next_stat_output { + self.output_job_stats(miner.get_stats().unwrap()); + next_stat_output = time::get_time().sec + stat_output_interval; + } + + let solutions = miner.get_solutions(); + if let Some(ss) = solutions { + let edge_bits = ss.edge_bits; + for i in 0..ss.num_sols { + let _ = + self.client_tx + .as_mut() + .unwrap() + .send(types::ClientMessage::FoundSolution( + self.current_height, + ss.sols[i as usize].id, + edge_bits, + ss.sols[i as usize].nonce, + ss.sols[i as usize].proof.to_vec(), + )); + } + let mut s_stats = self.stats.write().unwrap(); + s_stats.mining_stats.solution_stats.num_solutions_found += ss.num_sols; + } + thread::sleep(std::time::Duration::from_millis(100)); + } + } + + fn output_job_stats(&mut self, stats: Vec) { + let mut sps_total = 0.0; + let mut i = 0; + for s in stats.clone() { + let last_solution_time_secs = s.last_solution_time as f64 / 1_000_000_000.0; + let last_hashes_per_sec = 1.0 / last_solution_time_secs; + let status = if s.has_errored { "ERRORED" } else { "OK" }; + if !s.has_errored { + debug!( + LOGGER, + "Mining: Plugin {} - Device {} ({}) at Cucka{}{} - Status: {} : Last Graph time: {}s; \ + Graphs per second: {:.*} - Total Attempts: {}", + i, + s.device_id, + s.get_device_name(), + if s.edge_bits < 30 { "rooz" } else { "too" }, + s.edge_bits, + status, + last_solution_time_secs, + 3, + last_hashes_per_sec, + s.iterations + ); + if last_hashes_per_sec.is_finite() { + sps_total += last_hashes_per_sec; + } + } else { + debug!( + LOGGER, + "Mining: Plugin {} - Device {} ({}) Has ERRORED! Reason: {}", + i, + s.device_id, + s.get_device_name(), + s.get_error_reason(), + ); + } + i += 1; + } + info!( + LOGGER, + "Mining: Cucka*oo* at {} gps (graphs per second)", sps_total + ); + + if sps_total.is_finite() { + let mut s_stats = self.stats.write().unwrap(); + s_stats.mining_stats.add_combined_gps(sps_total); + s_stats.mining_stats.target_difficulty = self.current_target_diff; + s_stats.mining_stats.block_height = self.current_height; + s_stats.mining_stats.device_stats = stats; + } + } +} diff --git a/src/bin/mugle_miner.rs b/src/bin/mugle_miner.rs new file mode 100644 index 0000000..e7a077a --- /dev/null +++ b/src/bin/mugle_miner.rs @@ -0,0 +1,232 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Stratum client implementation, for standalone mining against a running +//! mugle node +extern crate cuckoo_miner as cuckoo; +extern crate mugle_miner_config as config; +extern crate mugle_miner_plugin as plugin; +extern crate mugle_miner_util as util; + +extern crate bufstream; +extern crate native_tls; +extern crate time; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; +#[macro_use] +extern crate slog; + +#[cfg(feature = "tui")] +extern crate cursive; + +pub mod client; +pub mod mining; +pub mod stats; +pub mod types; + +#[cfg(feature = "tui")] +pub mod tui; + +use config::GlobalConfig; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; +use std::thread; + +use util::{init_logger, LOGGER}; + +// include build information +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + +pub fn info_strings() -> (String, String, String) { + ( + format!( + "This is Mugle-Miner version {}{}, built for {} by {}.", + built_info::PKG_VERSION, + built_info::GIT_VERSION.map_or_else(|| "".to_owned(), |v| format!(" (git {})", v)), + built_info::TARGET, + built_info::RUSTC_VERSION + ), + format!( + "Built with profile \"{}\", features \"{}\" on {}.", + built_info::PROFILE, + built_info::FEATURES_STR, + built_info::BUILT_TIME_UTC + ), + format!("Dependencies:\n {}", built_info::DEPENDENCIES_STR), + ) +} + +fn log_build_info() { + let (basic_info, detailed_info, deps) = info_strings(); + info!(LOGGER, "{}", basic_info); + debug!(LOGGER, "{}", detailed_info); + trace!(LOGGER, "{}", deps); +} + +#[cfg(feature = "tui")] +mod with_tui { + use stats; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::{mpsc, Arc, RwLock}; + use std::thread; + use tui::ui; + use types; + + pub fn start_tui( + s: Arc>, + client_tx: mpsc::Sender, + miner_tx: mpsc::Sender, + stop: Arc, + ) { + // Run the UI controller.. here for now for simplicity to access + // everything it might need + println!("Starting Mugle Miner in UI mode..."); + println!("Waiting for solvers to shutdown..."); + let _ = thread::Builder::new() + .name("ui".to_string()) + .spawn(move || { + let mut controller = ui::Controller::new().unwrap_or_else(|e| { + panic!("Error loading UI controller: {}", e); + }); + controller.run(s.clone()); + // Shut down everything else on tui exit + let _ = client_tx.send(types::ClientMessage::Shutdown); + let _ = miner_tx.send(types::MinerMessage::Shutdown); + stop.store(true, Ordering::Relaxed); + }); + } +} + +fn main() { + // Init configuration + let mut global_config = GlobalConfig::new(None).unwrap_or_else(|e| { + panic!("Error parsing config file: {}", e); + }); + println!( + "Starting Mugle-Miner from config file at: {}", + global_config.config_file_path.unwrap().to_str().unwrap() + ); + // Init logging + let mut log_conf = global_config + .members + .as_mut() + .unwrap() + .logging + .clone() + .unwrap(); + + let mining_config = global_config.members.as_mut().unwrap().mining.clone(); + + if cfg!(feature = "tui") && mining_config.run_tui { + log_conf.log_to_stdout = false; + log_conf.tui_running = Some(true); + } + + init_logger(Some(log_conf)); + + log_build_info(); + let stats = Arc::new(RwLock::new(stats::Stats::default())); + + let mut mc = + mining::Controller::new(mining_config.clone(), stats.clone()).unwrap_or_else(|e| { + panic!("Error loading mining controller: {}", e); + }); + let cc = client::Controller::new( + &mining_config.stratum_server_addr, + mining_config.stratum_server_login.clone(), + mining_config.stratum_server_password.clone(), + mining_config.stratum_server_tls_enabled, + mc.tx.clone(), + stats.clone(), + ) + .unwrap_or_else(|e| { + panic!("Error loading stratum client controller: {:?}", e); + }); + let tui_stopped = Arc::new(AtomicBool::new(false)); + let miner_stopped = Arc::new(AtomicBool::new(false)); + let client_stopped = Arc::new(AtomicBool::new(false)); + + // Load plugin configuration and start solvers first, + // so we can exit pre-tui if something is obviously wrong + debug!(LOGGER, "Starting solvers"); + let result = config::read_configs( + mining_config.miner_plugin_dir.clone(), + mining_config.miner_plugin_config.clone(), + ); + let mut miner = match result { + Ok(cfgs) => cuckoo::CuckooMiner::new(cfgs), + Err(e) => { + println!("Error loading plugins. Please check logs for further info."); + println!("Error details:"); + println!("{:?}", e); + println!("Exiting"); + return; + } + }; + if let Err(e) = miner.start_solvers() { + println!("Error starting plugins. Please check logs for further info."); + println!("Error details:"); + println!("{:?}", e); + println!("Exiting"); + return; + } + + if mining_config.run_tui { + #[cfg(feature = "tui")] + with_tui::start_tui(stats, cc.tx.clone(), mc.tx.clone(), tui_stopped.clone()); + + #[cfg(not(feature = "tui"))] + warn!(LOGGER, "Mugle-miner was built with TUI support disabled!"); + } else { + tui_stopped.store(true, Ordering::Relaxed); + } + + mc.set_client_tx(cc.tx.clone()); + + let miner_stopped_internal = miner_stopped.clone(); + let _ = thread::Builder::new() + .name("mining_controller".to_string()) + .spawn(move || { + if let Err(e) = mc.run(miner) { + error!( + LOGGER, + "Error loading plugins. Please check logs for further info: {:?}", e + ); + return; + } + miner_stopped_internal.store(true, Ordering::Relaxed); + }); + + let client_stopped_internal = client_stopped.clone(); + let _ = thread::Builder::new() + .name("client_controller".to_string()) + .spawn(move || { + cc.run(); + client_stopped_internal.store(true, Ordering::Relaxed); + }); + + loop { + if miner_stopped.load(Ordering::Relaxed) + && client_stopped.load(Ordering::Relaxed) + && tui_stopped.load(Ordering::Relaxed) + { + thread::sleep(std::time::Duration::from_millis(100)); + break; + } + thread::sleep(std::time::Duration::from_millis(100)); + } +} diff --git a/src/bin/stats.rs b/src/bin/stats.rs new file mode 100644 index 0000000..52fcf99 --- /dev/null +++ b/src/bin/stats.rs @@ -0,0 +1,131 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Miner stats collection types, to be used by tests, logging or GUI/TUI +//! to collect information about mining status + +/// Struct to return relevant information about the mining process +/// back to interested callers (such as the TUI) +use plugin; + +#[derive(Clone)] +pub struct SolutionStats { + /// total solutions found + pub num_solutions_found: u32, + /// total shares accepted + pub num_shares_accepted: u32, + /// total solutions rejected + pub num_rejected: u32, + /// total solutions staled + pub num_staled: u32, + /// total blocks found + pub num_blocks_found: u32, +} + +impl Default for SolutionStats { + fn default() -> SolutionStats { + SolutionStats { + num_solutions_found: 0, + num_shares_accepted: 0, + num_rejected: 0, + num_staled: 0, + num_blocks_found: 0, + } + } +} + +#[derive(Clone)] +pub struct MiningStats { + /// combined graphs per second + combined_gps: Vec, + /// what block height we're mining at + pub block_height: u64, + /// current target for share difficulty we're working on + pub target_difficulty: u64, + /// solution statistics + pub solution_stats: SolutionStats, + /// Individual device status from Cuckoo-Miner + pub device_stats: Vec, +} + +impl Default for MiningStats { + fn default() -> MiningStats { + MiningStats { + combined_gps: vec![], + block_height: 0, + target_difficulty: 0, + solution_stats: SolutionStats::default(), + device_stats: vec![], + } + } +} + +impl MiningStats { + pub fn add_combined_gps(&mut self, val: f64) { + self.combined_gps.insert(0, val); + self.combined_gps.truncate(50); + } + + pub fn combined_gps(&self) -> f64 { + if self.combined_gps.is_empty() { + 0.0 + } else { + let sum: f64 = self.combined_gps.iter().sum(); + sum / (self.combined_gps.len() as f64) + } + } +} + +#[derive(Clone)] +pub struct ClientStats { + /// Server we're connected to + pub server_url: String, + /// whether we're connected + pub connected: bool, + /// Connection status + pub connection_status: String, + /// Last message sent to server + pub last_message_sent: String, + /// Last response/command received from server + pub last_message_received: String, +} + +impl Default for ClientStats { + fn default() -> ClientStats { + ClientStats { + server_url: "".to_string(), + connected: false, + connection_status: "Connection Status: Starting".to_string(), + last_message_sent: "Last Message Sent: None".to_string(), + last_message_received: "Last Message Received: None".to_string(), + } + } +} + +#[derive(Clone)] +pub struct Stats { + /// Client/networking stats + pub client_stats: ClientStats, + /// Mining stats + pub mining_stats: MiningStats, +} + +impl Default for Stats { + fn default() -> Stats { + Stats { + client_stats: ClientStats::default(), + mining_stats: MiningStats::default(), + } + } +} diff --git a/src/bin/tui/constants.rs b/src/bin/tui/constants.rs new file mode 100644 index 0000000..5b28a97 --- /dev/null +++ b/src/bin/tui/constants.rs @@ -0,0 +1,31 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Identifiers for various TUI elements, because they may be referenced +//! from a few different places + +/// Mining View +pub const VIEW_MINING: &str = "mining_view"; +/// Mining status +pub const TABLE_MINING_STATUS: &str = "mining_status_table"; + +// Mining View +/// Version view +pub const VIEW_VERSION: &str = "version_view"; + +// Menu and root elements +/// Main menu +pub const MAIN_MENU: &str = "main_menu"; +/// root stack +pub const ROOT_STACK: &str = "root_stack"; diff --git a/src/bin/tui/menu.rs b/src/bin/tui/menu.rs new file mode 100644 index 0000000..b6f5e39 --- /dev/null +++ b/src/bin/tui/menu.rs @@ -0,0 +1,70 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Main Menu definition + +use cursive::align::HAlign; +use cursive::direction::Orientation; +use cursive::event::Key; +use cursive::view::Identifiable; +use cursive::view::View; +use cursive::views::{ + LinearLayout, OnEventView, ResizedView, SelectView, StackView, TextView, ViewRef, +}; +use cursive::Cursive; + +use tui::constants::*; + +/// Create menu +pub fn create() -> Box { + let mut main_menu = SelectView::new().h_align(HAlign::Left).with_name(MAIN_MENU); + main_menu.get_mut().add_item("Mining", VIEW_MINING); + main_menu.get_mut().add_item("Version Info", VIEW_VERSION); + let change_view = |s: &mut Cursive, v: &&str| { + if *v == "" { + return; + } + + let _ = s.call_on_name(ROOT_STACK, |sv: &mut StackView| { + let pos = sv.find_layer_from_name(v).unwrap(); + sv.move_to_front(pos); + }); + }; + + main_menu.get_mut().set_on_select(change_view); + let main_menu = OnEventView::new(main_menu) + .on_pre_event('j', move |c| { + let mut s: ViewRef> = c.find_name(MAIN_MENU).unwrap(); + s.select_down(1)(c); + }) + .on_pre_event('k', move |c| { + let mut s: ViewRef> = c.find_name(MAIN_MENU).unwrap(); + s.select_up(1)(c); + }) + .on_pre_event(Key::Tab, move |c| { + let mut s: ViewRef> = c.find_name(MAIN_MENU).unwrap(); + if s.selected_id().unwrap() == s.len() - 1 { + s.set_selection(0)(c); + } else { + s.select_down(1)(c); + } + }); + let main_menu = LinearLayout::new(Orientation::Vertical) + .child(ResizedView::with_full_height(main_menu)) + .child(TextView::new("------------------")) + .child(TextView::new("Tab/Arrow : Cycle ")) + .child(TextView::new("Enter : Select")) + .child(TextView::new("Q : Quit ")); + Box::new(main_menu) +} diff --git a/src/bin/tui/mining.rs b/src/bin/tui/mining.rs new file mode 100644 index 0000000..acf5dfa --- /dev/null +++ b/src/bin/tui/mining.rs @@ -0,0 +1,247 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mining status view definition + +use std::cmp::Ordering; +use std::sync::{Arc, RwLock}; + +use cursive::direction::Orientation; +use cursive::traits::*; +use cursive::view::View; +use cursive::views::{Dialog, LinearLayout, ResizedView, StackView, TextView}; +use cursive::Cursive; + +use tui::constants::*; +use tui::types::*; + +use plugin::SolverStats; +use stats; +use tui::table::{TableView, TableViewItem}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum MiningDeviceColumn { + Plugin, + DeviceId, + DeviceName, + EdgeBits, + ErrorStatus, + LastGraphTime, + GraphsPerSecond, +} + +impl MiningDeviceColumn { + fn _as_str(&self) -> &str { + match *self { + MiningDeviceColumn::Plugin => "Plugin", + MiningDeviceColumn::DeviceId => "Device ID", + MiningDeviceColumn::DeviceName => "Name", + MiningDeviceColumn::EdgeBits => "Graph Size", + MiningDeviceColumn::ErrorStatus => "Status", + MiningDeviceColumn::LastGraphTime => "Last Graph Time", + MiningDeviceColumn::GraphsPerSecond => "GPS", + } + } +} + +impl TableViewItem for SolverStats { + fn to_column(&self, column: MiningDeviceColumn) -> String { + let last_solution_time_secs = self.last_solution_time as f64 / 1_000_000_000.0; + match column { + MiningDeviceColumn::Plugin => self.get_plugin_name(), + MiningDeviceColumn::DeviceId => format!("{}", self.device_id), + MiningDeviceColumn::DeviceName => self.get_device_name(), + MiningDeviceColumn::EdgeBits => format!("{}", self.edge_bits), + MiningDeviceColumn::ErrorStatus => { + if self.has_errored { + String::from("Errored") + } else { + String::from("OK") + } + } + MiningDeviceColumn::LastGraphTime => format!("{}s", last_solution_time_secs), + MiningDeviceColumn::GraphsPerSecond => { + format!("{:.*}", 4, 1.0 / last_solution_time_secs) + } + } + } + + fn cmp(&self, other: &Self, column: MiningDeviceColumn) -> Ordering + where + Self: Sized, + { + let last_solution_time_secs_self = self.last_solution_time as f64 / 1_000_000_000.0; + let gps_self = 1.0 / last_solution_time_secs_self; + let last_solution_time_secs_other = other.last_solution_time as f64 / 1_000_000_000.0; + let gps_other = 1.0 / last_solution_time_secs_other; + match column { + MiningDeviceColumn::Plugin => self.plugin_name.cmp(&other.plugin_name), + MiningDeviceColumn::DeviceId => self.device_id.cmp(&other.device_id), + MiningDeviceColumn::DeviceName => self.device_name.cmp(&other.device_name), + MiningDeviceColumn::EdgeBits => self.edge_bits.cmp(&other.edge_bits), + MiningDeviceColumn::ErrorStatus => self.has_errored.cmp(&other.has_errored), + MiningDeviceColumn::LastGraphTime => { + self.last_solution_time.cmp(&other.last_solution_time) + } + MiningDeviceColumn::GraphsPerSecond => gps_self.partial_cmp(&gps_other).unwrap(), + } + } +} + +/// Mining status view +pub struct TUIMiningView; + +impl TUIStatusListener for TUIMiningView { + /// Create the mining view + fn create() -> Box { + let table_view = TableView::::new() + .column(MiningDeviceColumn::Plugin, "Plugin", |c| { + c.width_percent(20) + }) + .column(MiningDeviceColumn::DeviceId, "Device ID", |c| { + c.width_percent(5) + }) + .column(MiningDeviceColumn::DeviceName, "Device Name", |c| { + c.width_percent(20) + }) + .column(MiningDeviceColumn::EdgeBits, "Size", |c| c.width_percent(5)) + .column(MiningDeviceColumn::ErrorStatus, "Status", |c| { + c.width_percent(8) + }) + .column(MiningDeviceColumn::LastGraphTime, "Graph Time", |c| { + c.width_percent(10) + }) + .column(MiningDeviceColumn::GraphsPerSecond, "GPS", |c| { + c.width_percent(10) + }); + + let status_view = LinearLayout::new(Orientation::Vertical) + .child(LinearLayout::new(Orientation::Horizontal).child( + TextView::new("Connection Status: Starting...").with_name("mining_server_status"), + )) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new("Mining Status: ").with_name("mining_status")), + ) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new(" ").with_name("network_info")), + ) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new(" ").with_name("mining_statistics")), + ) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new("Last Message Sent: ").with_name("last_message_sent")), + ) + .child(LinearLayout::new(Orientation::Horizontal).child( + TextView::new("Last Message Received: ").with_name("last_message_received"), + )); + + let mining_device_view = LinearLayout::new(Orientation::Vertical) + .child(status_view) + .child(ResizedView::with_full_screen( + Dialog::around(table_view.with_name(TABLE_MINING_STATUS).min_size((50, 20))) + .title("Mining Devices"), + )) + .with_name("mining_device_view"); + + let view_stack = StackView::new() + .layer(mining_device_view) + .with_name("mining_stack_view"); + + let mining_view = LinearLayout::new(Orientation::Vertical).child(view_stack); + + Box::new(mining_view.with_name(VIEW_MINING)) + } + + /// update + fn update(c: &mut Cursive, stats: Arc>) { + let (client_stats, mining_stats) = { + let stats = stats.read().unwrap(); + (stats.client_stats.clone(), stats.mining_stats.clone()) + }; + + c.call_on_name("mining_server_status", |t: &mut TextView| { + t.set_content(client_stats.connection_status.clone()); + }); + + let (basic_mining_status, basic_network_info) = { + if client_stats.connected { + if mining_stats.combined_gps() == 0.0 { + ( + "Mining Status: Starting miner and awaiting first graph time..." + .to_string(), + " ".to_string(), + ) + } else { + ( + format!( + "Mining Status: Mining at height {} at {:.*} GPS", + mining_stats.block_height, + 4, + mining_stats.combined_gps() + ), + format!( + "Cucka*oo* - Target Share Difficulty {}", + mining_stats.target_difficulty.to_string() + ), + ) + } + } else { + ( + "Mining Status: Waiting for server".to_string(), + " ".to_string(), + ) + } + }; + + // device + c.call_on_name("mining_status", |t: &mut TextView| { + t.set_content(basic_mining_status); + }); + c.call_on_name("network_info", |t: &mut TextView| { + t.set_content(basic_network_info); + }); + + c.call_on_name("last_message_sent", |t: &mut TextView| { + t.set_content(client_stats.last_message_sent.clone()); + }); + c.call_on_name("last_message_received", |t: &mut TextView| { + t.set_content(client_stats.last_message_received.clone()); + }); + + if mining_stats.solution_stats.num_solutions_found > 0 { + let sol_stat = format!( + "Solutions found: {}. Accepted: {}, Rejected: {}, Stale: {}, Blocks found: {}", + mining_stats.solution_stats.num_solutions_found, + mining_stats.solution_stats.num_shares_accepted, + mining_stats.solution_stats.num_rejected, + mining_stats.solution_stats.num_staled, + mining_stats.solution_stats.num_blocks_found, + ); + c.call_on_name("mining_statistics", |t: &mut TextView| { + t.set_content(sol_stat); + }); + } + + let _ = c.call_on_name( + TABLE_MINING_STATUS, + |t: &mut TableView| { + t.set_items(mining_stats.device_stats); + }, + ); + } +} diff --git a/src/bin/tui/mod.rs b/src/bin/tui/mod.rs new file mode 100644 index 0000000..6da9788 --- /dev/null +++ b/src/bin/tui/mod.rs @@ -0,0 +1,29 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mugle Miner TUI + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +pub mod constants; +pub mod menu; +pub mod mining; +pub mod table; +pub mod types; +pub mod ui; +pub mod version; diff --git a/src/bin/tui/table.rs b/src/bin/tui/table.rs new file mode 100644 index 0000000..ba3eafd --- /dev/null +++ b/src/bin/tui/table.rs @@ -0,0 +1,994 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2015-2017 Ivo Wetzel +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Adapted from https://github.com/behnam/rust-cursive-table-view +//! A basic table view implementation for [cursive](https://crates.io/crates/cursive). + +#![deny( + missing_docs, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unsafe_code, + unused_import_braces, + unused_qualifications +)] + +// Crate Dependencies --------------------------------------------------------- +extern crate cursive; + +// STD Dependencies ----------------------------------------------------------- +use std::cmp::{self, Ordering}; +use std::collections::HashMap; +use std::hash::Hash; +use std::rc::Rc; + +// External Dependencies ------------------------------------------------------ +use cursive::align::HAlign; +use cursive::direction::Direction; +use cursive::event::{Callback, Event, EventResult, Key}; +use cursive::theme::ColorStyle; +use cursive::theme::PaletteColor::*; +use cursive::vec::Vec2; +use cursive::view::{ScrollBase, View}; +use cursive::With; +use cursive::{Cursive, Printer}; + +/// A trait for displaying and sorting items inside a +/// [`TableView`](struct.TableView.html). +pub trait TableViewItem: Clone + Sized +where + H: Eq + Hash + Copy + Clone + 'static, +{ + /// Method returning a string representation of the item for the + /// specified column from type `H`. + fn to_column(&self, column: H) -> String; + + /// Method comparing two items via their specified column from type `H`. + fn cmp(&self, other: &Self, column: H) -> Ordering + where + Self: Sized; +} + +/// View to select an item among a list, supporting multiple columns for sorting. +/// +/// # Examples +/// +/// ```rust +/// # extern crate cursive; +/// # extern crate cursive_table_view; +/// # use std::cmp::Ordering; +/// # use cursive_table_view::{TableView, TableViewItem}; +/// # use cursive::align::HAlign; +/// # fn main() { +/// // Provide a type for the table's columns +/// #[derive(Copy, Clone, PartialEq, Eq, Hash)] +/// enum BasicColumn { +/// Name, +/// Count, +/// Rate +/// } +/// +/// // Define the item type +/// #[derive(Clone, Debug)] +/// struct Foo { +/// name: String, +/// count: usize, +/// rate: usize +/// } +/// +/// impl TableViewItem for Foo { +/// +/// fn to_column(&self, column: BasicColumn) -> String { +/// match column { +/// BasicColumn::Name => self.name.to_string(), +/// BasicColumn::Count => format!("{}", self.count), +/// BasicColumn::Rate => format!("{}", self.rate) +/// } +/// } +/// +/// fn cmp(&self, other: &Self, column: BasicColumn) -> Ordering where Self: Sized { +/// match column { +/// BasicColumn::Name => self.name.cmp(&other.name), +/// BasicColumn::Count => self.count.cmp(&other.count), +/// BasicColumn::Rate => self.rate.cmp(&other.rate) +/// } +/// } +/// +/// } +/// +/// // Configure the actual table +/// let table = TableView::::new() +/// .column(BasicColumn::Name, "Name", |c| c.width(20)) +/// .column(BasicColumn::Count, "Count", |c| c.align(HAlign::Center)) +/// .column(BasicColumn::Rate, "Rate", |c| { +/// c.ordering(Ordering::Greater).align(HAlign::Right).width(20) +/// }) +/// .default_column(BasicColumn::Name); +/// # } +/// ``` +pub struct TableView, H: Eq + Hash + Copy + Clone + 'static> { + enabled: bool, + scrollbase: ScrollBase, + last_size: Vec2, + + column_select: bool, + columns: Vec>, + column_indicies: HashMap, + + focus: usize, + items: Vec, + rows_to_items: Vec, + + on_sort: Option>, + // TODO Pass drawing offsets into the handlers so a popup menu + // can be created easily? + on_submit: Option>, + on_select: Option>, +} + +impl, H: Eq + Hash + Copy + Clone + 'static> TableView { + /// Creates a new empty `TableView` without any columns. + /// + /// A TableView should be accompanied by a enum of type `H` representing + /// the table columns. + pub fn new() -> Self { + Self { + enabled: true, + scrollbase: ScrollBase::new(), + last_size: Vec2::new(0, 0), + + column_select: false, + columns: Vec::new(), + column_indicies: HashMap::new(), + + focus: 0, + items: Vec::new(), + rows_to_items: Vec::new(), + + on_sort: None, + on_submit: None, + on_select: None, + } + } + + /// Adds a column for the specified table colum from type `H` along with + /// a title for its visual display. + /// + /// The provided callback can be used to further configure the + /// created [`TableColumn`](struct.TableColumn.html). + pub fn column, C: FnOnce(TableColumn) -> TableColumn>( + mut self, + column: H, + title: S, + callback: C, + ) -> Self { + self.column_indicies.insert(column, self.columns.len()); + self.columns + .push(callback(TableColumn::new(column, title.into()))); + + // Make the first colum the default one + if self.columns.len() == 1 { + self.default_column(column) + } else { + self + } + } + + /// Sets the initially active column of the table. + pub fn default_column(mut self, column: H) -> Self { + if self.column_indicies.contains_key(&column) { + for c in &mut self.columns { + c.selected = c.column == column; + if c.selected { + c.order = c.default_order; + } else { + c.order = Ordering::Equal; + } + } + } + self + } + + /// Sorts the table using the specified table `column` and the passed + /// `order`. + pub fn sort_by(&mut self, column: H, order: Ordering) { + if self.column_indicies.contains_key(&column) { + for c in &mut self.columns { + c.selected = c.column == column; + if c.selected { + c.order = order; + } else { + c.order = Ordering::Equal; + } + } + } + + self.sort_items(column, order); + } + + /// Sorts the table using the currently active column and its + /// ordering. + pub fn sort(&mut self) { + if let Some((column, order)) = self.order() { + self.sort_items(column, order); + } + } + + /// Returns the currently active column that is used for sorting + /// along with its ordering. + /// + /// Might return `None` if there are currently no items in the table + /// and it has not been sorted yet. + pub fn order(&self) -> Option<(H, Ordering)> { + for c in &self.columns { + if c.order != Ordering::Equal { + return Some((c.column, c.order)); + } + } + None + } + + /// Disables this view. + /// + /// A disabled view cannot be selected. + pub fn disable(&mut self) { + self.enabled = false; + } + + /// Re-enables this view. + pub fn enable(&mut self) { + self.enabled = true; + } + + /// Enable or disable this view. + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + /// Returns `true` if this view is enabled. + pub fn is_enabled(&self) -> bool { + self.enabled + } + + /// Sets a callback to be used when a selected column is sorted by + /// pressing ``. + /// + /// # Example + /// + /// ```norun + /// table.set_on_sort(|siv: &mut Cursive, column: BasicColumn, order: Ordering| { + /// + /// }); + /// ``` + pub fn set_on_sort(&mut self, cb: F) + where + F: Fn(&mut Cursive, H, Ordering) + 'static, + { + self.on_sort = Some(Rc::new(move |s, h, o| cb(s, h, o))); + } + + /// Sets a callback to be used when a selected column is sorted by + /// pressing ``. + /// + /// Chainable variant. + /// + /// # Example + /// + /// ```norun + /// table.on_sort(|siv: &mut Cursive, column: BasicColumn, order: Ordering| { + /// + /// }); + /// ``` + pub fn on_sort(self, cb: F) -> Self + where + F: Fn(&mut Cursive, H, Ordering) + 'static, + { + self.with(|t| t.set_on_sort(cb)) + } + + /// Sets a callback to be used when `` is pressed while an item + /// is selected. + /// + /// Both the currently selected row and the index of the corresponding item + /// within the underlying storage vector will be given to the callback. + /// + /// # Example + /// + /// ```norun + /// table.set_on_submit(|siv: &mut Cursive, row: usize, index: usize| { + /// + /// }); + /// ``` + pub fn set_on_submit(&mut self, cb: F) + where + F: Fn(&mut Cursive, usize, usize) + 'static, + { + self.on_submit = Some(Rc::new(move |s, row, index| cb(s, row, index))); + } + + /// Sets a callback to be used when `` is pressed while an item + /// is selected. + /// + /// Both the currently selected row and the index of the corresponding item + /// within the underlying storage vector will be given to the callback. + /// + /// Chainable variant. + /// + /// # Example + /// + /// ```norun + /// table.on_submit(|siv: &mut Cursive, row: usize, index: usize| { + /// + /// }); + /// ``` + pub fn on_submit(self, cb: F) -> Self + where + F: Fn(&mut Cursive, usize, usize) + 'static, + { + self.with(|t| t.set_on_submit(cb)) + } + + /// Sets a callback to be used when an item is selected. + /// + /// Both the currently selected row and the index of the corresponding item + /// within the underlying storage vector will be given to the callback. + /// + /// # Example + /// + /// ```norun + /// table.set_on_select(|siv: &mut Cursive, row: usize, index: usize| { + /// + /// }); + /// ``` + pub fn set_on_select(&mut self, cb: F) + where + F: Fn(&mut Cursive, usize, usize) + 'static, + { + self.on_select = Some(Rc::new(move |s, row, index| cb(s, row, index))); + } + + /// Sets a callback to be used when an item is selected. + /// + /// Both the currently selected row and the index of the corresponding item + /// within the underlying storage vector will be given to the callback. + /// + /// Chainable variant. + /// + /// # Example + /// + /// ```norun + /// table.on_select(|siv: &mut Cursive, row: usize, index: usize| { + /// + /// }); + /// ``` + pub fn on_select(self, cb: F) -> Self + where + F: Fn(&mut Cursive, usize, usize) + 'static, + { + self.with(|t| t.set_on_select(cb)) + } + + /// Removes all items from this view. + pub fn clear(&mut self) { + self.items.clear(); + self.rows_to_items.clear(); + self.focus = 0; + } + + /// Returns the number of items in this table. + pub fn len(&self) -> usize { + self.items.len() + } + + /// Returns `true` if this table has no items. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns the index of the currently selected table row. + pub fn row(&self) -> Option { + if self.items.is_empty() { + None + } else { + Some(self.focus) + } + } + + /// Selects the row at the specified index. + pub fn set_selected_row(&mut self, row_index: usize) { + self.focus = row_index; + self.scrollbase.scroll_to(row_index); + } + + /// Selects the row at the specified index. + /// + /// Chainable variant. + pub fn selected_row(self, row_index: usize) -> Self { + self.with(|t| t.set_selected_row(row_index)) + } + + /// Sets the contained items of the table. + /// + /// The currently active sort order is preserved and will be applied to all + /// items. + pub fn set_items(&mut self, items: Vec) { + self.items = items; + self.rows_to_items = Vec::with_capacity(self.items.len()); + + for i in 0..self.items.len() { + self.rows_to_items.push(i); + } + + if let Some((column, order)) = self.order() { + self.sort_by(column, order); + } + + self.scrollbase + .set_heights(self.last_size.y.saturating_sub(2), self.rows_to_items.len()); + + self.set_selected_row(0); + } + + /// Sets the contained items of the table. + /// + /// The order of the items will be preserved even when the table is sorted. + /// + /// Chainable variant. + pub fn items(self, items: Vec) -> Self { + self.with(|t| t.set_items(items)) + } + + /// Returns a immmutable reference to the item at the specified index + /// within the underlying storage vector. + pub fn borrow_item(&mut self, index: usize) -> Option<&T> { + self.items.get(index) + } + + /// Returns a mutable reference to the item at the specified index within + /// the underlying storage vector. + pub fn borrow_item_mut(&mut self, index: usize) -> Option<&mut T> { + self.items.get_mut(index) + } + + /// Returns a immmutable reference to the items contained within the table. + pub fn borrow_items(&mut self) -> &Vec { + &self.items + } + + /// Returns a mutable reference to the items contained within the table. + /// + /// Can be used to modify the items in place. + pub fn borrow_items_mut(&mut self) -> &mut Vec { + &mut self.items + } + + /// Returns the index of the currently selected item within the underlying + /// storage vector. + pub fn item(&self) -> Option { + if self.items.is_empty() { + None + } else { + Some(self.rows_to_items[self.focus]) + } + } + + /// Selects the item at the specified index within the underlying storage + /// vector. + pub fn set_selected_item(&mut self, item_index: usize) { + // TODO optimize the performance for very large item lists + if item_index < self.items.len() { + for (row, item) in self.rows_to_items.iter().enumerate() { + if *item == item_index { + self.focus = row; + self.scrollbase.scroll_to(row); + break; + } + } + } + } + + /// Selects the item at the specified index within the underlying storage + /// vector. + /// + /// Chainable variant. + pub fn selected_item(self, item_index: usize) -> Self { + self.with(|t| t.set_selected_item(item_index)) + } + + /// Inserts a new item into the table. + /// + /// The currently active sort order is preserved and will be applied to the + /// newly inserted item. + pub fn insert_item(&mut self, item: T) { + self.items.push(item); + self.rows_to_items.push(self.items.len()); + + self.scrollbase + .set_heights(self.last_size.y.saturating_sub(2), self.rows_to_items.len()); + + if let Some((column, order)) = self.order() { + self.sort_by(column, order); + } + } + + /// Removes the item at the specified index within the underlying storage + /// vector and returns it. + pub fn remove_item(&mut self, item_index: usize) -> Option { + if item_index < self.items.len() { + // Move the selection if the currently selected item gets removed + if let Some(selected_index) = self.item() { + if selected_index == item_index { + self.focus_up(1); + } + } + + // Remove the sorted reference to the item + self.rows_to_items.retain(|i| *i != item_index); + + // Adjust remaining references + for ref_index in &mut self.rows_to_items { + if *ref_index > item_index { + *ref_index -= 1; + } + } + + // Update scroll height to prevent out of index drawing + self.scrollbase + .set_heights(self.last_size.y.saturating_sub(2), self.rows_to_items.len()); + + // Remove actual item from the underlying storage + Some(self.items.remove(item_index)) + } else { + None + } + } + + /// Removes all items from the underlying storage and returns them. + pub fn take_items(&mut self) -> Vec { + self.scrollbase + .set_heights(self.last_size.y.saturating_sub(2), 0); + self.set_selected_row(0); + self.rows_to_items.clear(); + self.items.drain(0..).collect() + } +} + +impl, H: Eq + Hash + Copy + Clone + 'static> TableView { + fn draw_columns)>( + &self, + printer: &Printer, + sep: &str, + callback: C, + ) { + let mut column_offset = 0; + let column_count = self.columns.len(); + for (index, column) in self.columns.iter().enumerate() { + let printer = &printer.offset((column_offset, 0)).focused(true); + + callback(printer, column); + + if index < column_count - 1 { + printer.print((column.width + 1, 0), sep); + } + + column_offset += column.width + 3; + } + } + + fn sort_items(&mut self, column: H, order: Ordering) { + if !self.is_empty() { + let old_item = self.item().unwrap(); + + let mut rows_to_items = self.rows_to_items.clone(); + rows_to_items.sort_by(|a, b| { + if order == Ordering::Less { + self.items[*a].cmp(&self.items[*b], column) + } else { + self.items[*b].cmp(&self.items[*a], column) + } + }); + self.rows_to_items = rows_to_items; + + self.set_selected_item(old_item); + } + } + + fn draw_item(&self, printer: &Printer, i: usize) { + self.draw_columns(printer, "┆ ", |printer, column| { + let value = self.items[self.rows_to_items[i]].to_column(column.column); + column.draw_row(printer, value.as_str()); + }); + } + + fn focus_up(&mut self, n: usize) { + self.focus -= cmp::min(self.focus, n); + } + + fn focus_down(&mut self, n: usize) { + self.focus = cmp::min(self.focus + n, self.items.len() - 1); + } + + fn active_column(&self) -> usize { + self.columns.iter().position(|c| c.selected).unwrap_or(0) + } + + fn column_cancel(&mut self) { + self.column_select = false; + for column in &mut self.columns { + column.selected = column.order != Ordering::Equal; + } + } + + fn column_next(&mut self) -> bool { + let column = self.active_column(); + if column < self.columns.len() - 1 { + self.columns[column].selected = false; + self.columns[column + 1].selected = true; + true + } else { + false + } + } + + fn column_prev(&mut self) -> bool { + let column = self.active_column(); + if column > 0 { + self.columns[column].selected = false; + self.columns[column - 1].selected = true; + true + } else { + false + } + } + + fn column_select(&mut self) { + let next = self.active_column(); + let column = self.columns[next].column; + let current = self + .columns + .iter() + .position(|c| c.order != Ordering::Equal) + .unwrap_or(0); + + let order = if current != next { + self.columns[next].default_order + } else if self.columns[current].order == Ordering::Less { + Ordering::Greater + } else { + Ordering::Less + }; + + self.sort_by(column, order); + } +} + +impl + 'static, H: Eq + Hash + Copy + Clone + 'static> View + for TableView +{ + fn draw(&self, printer: &Printer) { + self.draw_columns(printer, "╷ ", |printer, column| { + let color = if column.order != Ordering::Equal || column.selected { + if self.column_select && column.selected && self.enabled && printer.focused { + Highlight + } else { + HighlightInactive + } + } else { + Primary + }; + + printer.with_color(ColorStyle::from(color), |printer| { + column.draw_header(printer); + }); + }); + + self.draw_columns( + &printer.offset((0, 1)).focused(true), + "┴─", + |printer, column| { + printer.print_hline((0, 0), column.width + 1, "─"); + }, + ); + + let printer = &printer.offset((0, 2)).focused(true); + self.scrollbase.draw(printer, |printer, i| { + let color = if i == self.focus { + if !self.column_select && self.enabled && printer.focused { + Highlight + } else { + HighlightInactive + } + } else { + Primary + }; + + printer.with_color(ColorStyle::from(color), |printer| { + self.draw_item(printer, i); + }); + }); + } + + fn layout(&mut self, size: Vec2) { + if size == self.last_size { + return; + } + + let item_count = self.items.len(); + let column_count = self.columns.len(); + + // Split up all columns into sized / unsized groups + let (mut sized, mut usized): (Vec<&mut TableColumn>, Vec<&mut TableColumn>) = self + .columns + .iter_mut() + .partition(|c| c.requested_width.is_some()); + + // Subtract one for the seperators between our columns (that's column_count - 1) + let mut available_width = size.x.saturating_sub(column_count.saturating_sub(1) * 3); + + // Reduce the with in case we are displaying a scrollbar + if size.y.saturating_sub(1) < item_count { + available_width = available_width.saturating_sub(2); + } + + // Calculate widths for all requested columns + let mut remaining_width = available_width; + for column in &mut sized { + column.width = match *column.requested_width.as_ref().unwrap() { + TableColumnWidth::Percent(width) => cmp::min( + (size.x as f32 / 100.0 * width as f32).ceil() as usize, + remaining_width, + ), + TableColumnWidth::Absolute(width) => width, + }; + remaining_width = remaining_width.saturating_sub(column.width); + } + + // Spread the remaining with across the unsized columns + let remaining_columns = usized.len(); + for column in &mut usized { + column.width = (remaining_width as f32 / remaining_columns as f32).floor() as usize; + } + + self.scrollbase + .set_heights(size.y.saturating_sub(2), item_count); + self.last_size = size; + } + + fn take_focus(&mut self, _: Direction) -> bool { + self.enabled && !self.items.is_empty() + } + + fn on_event(&mut self, event: Event) -> EventResult { + if !self.enabled { + return EventResult::Ignored; + } + + let last_focus = self.focus; + match event { + Event::Key(Key::Right) => { + if self.column_select { + if !self.column_next() { + return EventResult::Ignored; + } + } else { + self.column_select = true; + } + } + Event::Key(Key::Left) => { + if self.column_select { + if !self.column_prev() { + return EventResult::Ignored; + } + } else { + self.column_select = true; + } + } + Event::Key(Key::Up) if self.focus > 0 || self.column_select => { + if self.column_select { + self.column_cancel(); + } else { + self.focus_up(1); + } + } + Event::Key(Key::Down) if self.focus + 1 < self.items.len() || self.column_select => { + if self.column_select { + self.column_cancel(); + } else { + self.focus_down(1); + } + } + Event::Key(Key::PageUp) => { + self.column_cancel(); + self.focus_up(10); + } + Event::Key(Key::PageDown) => { + self.column_cancel(); + self.focus_down(10); + } + Event::Key(Key::Home) => { + self.column_cancel(); + self.focus = 0; + } + Event::Key(Key::End) => { + self.column_cancel(); + self.focus = self.items.len() - 1; + } + Event::Key(Key::Enter) => { + if self.column_select { + self.column_select(); + + if self.on_sort.is_some() { + let c = &self.columns[self.active_column()]; + let column = c.column; + let order = c.order; + + let cb = self.on_sort.clone().unwrap(); + return EventResult::Consumed(Some(Callback::from_fn(move |s| { + cb(s, column, order) + }))); + } + } else if !self.is_empty() && self.on_submit.is_some() { + let cb = self.on_submit.clone().unwrap(); + let row = self.row().unwrap(); + let index = self.item().unwrap(); + return EventResult::Consumed(Some(Callback::from_fn(move |s| { + cb(s, row, index) + }))); + } + } + _ => return EventResult::Ignored, + } + + let focus = self.focus; + self.scrollbase.scroll_to(focus); + + if self.column_select { + EventResult::Consumed(None) + } else if !self.is_empty() && last_focus != focus { + let row = self.row().unwrap(); + let index = self.item().unwrap(); + EventResult::Consumed( + self.on_select + .clone() + .map(|cb| Callback::from_fn(move |s| cb(s, row, index))), + ) + } else { + EventResult::Ignored + } + } +} + +/// A type used for the construction of columns in a +/// [`TableView`](struct.TableView.html). +pub struct TableColumn { + column: H, + title: String, + selected: bool, + alignment: HAlign, + order: Ordering, + width: usize, + default_order: Ordering, + requested_width: Option, +} + +enum TableColumnWidth { + Percent(usize), + Absolute(usize), +} + +impl TableColumn { + /// Sets the default ordering of the column. + pub fn ordering(mut self, order: Ordering) -> Self { + self.default_order = order; + self + } + + /// Sets the horizontal text alignment of the column. + pub fn align(mut self, alignment: HAlign) -> Self { + self.alignment = alignment; + self + } + + /// Sets how many characters of width this column will try to occupy. + pub fn width(mut self, width: usize) -> Self { + self.requested_width = Some(TableColumnWidth::Absolute(width)); + self + } + + /// Sets what percentage of the width of the entire table this column will + /// try to occupy. + pub fn width_percent(mut self, width: usize) -> Self { + self.requested_width = Some(TableColumnWidth::Percent(width)); + self + } + + fn new(column: H, title: String) -> Self { + Self { + column, + title, + selected: false, + alignment: HAlign::Left, + order: Ordering::Equal, + width: 0, + default_order: Ordering::Less, + requested_width: None, + } + } + + fn draw_header(&self, printer: &Printer) { + let order = match self.order { + Ordering::Less => "^", + Ordering::Greater => "v", + Ordering::Equal => " ", + }; + + let header = match self.alignment { + HAlign::Left => format!( + "{: format!( + "{:>width$} [{}]", + self.title, + order, + width = self.width.saturating_sub(4) + ), + HAlign::Center => format!( + "{:^width$} [{}]", + self.title, + order, + width = self.width.saturating_sub(4) + ), + }; + printer.print((0, 0), header.as_str()); + } + + fn draw_row(&self, printer: &Printer, value: &str) { + let value = match self.alignment { + HAlign::Left => format!("{: format!("{:>width$} ", value, width = self.width), + HAlign::Center => format!("{:^width$} ", value, width = self.width), + }; + printer.print((0, 0), value.as_str()); + } +} diff --git a/src/bin/tui/types.rs b/src/bin/tui/types.rs new file mode 100644 index 0000000..ff21e0b --- /dev/null +++ b/src/bin/tui/types.rs @@ -0,0 +1,38 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types specific to the UI module + +use std::sync::{Arc, RwLock}; + +use cursive::view::View; +use cursive::Cursive; +use stats::Stats; + +/// Main message struct to communicate between the UI and +/// the main process +pub enum UIMessage { + /// Update mining status + UpdateStatus(Arc>), +} + +/// Trait for a UI element that recieves status update messages +/// and updates itself + +pub trait TUIStatusListener { + /// create the view, to return to the main UI controller + fn create() -> Box; + /// Update according to status update contents + fn update(c: &mut Cursive, stats: Arc>); +} diff --git a/src/bin/tui/ui.rs b/src/bin/tui/ui.rs new file mode 100644 index 0000000..a6c9903 --- /dev/null +++ b/src/bin/tui/ui.rs @@ -0,0 +1,186 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Basic TUI to better output the overall system status and status +//! of various subsystems + +use std::sync::{mpsc, Arc, RwLock}; +use std::{self, thread}; +use time; + +use cursive::direction::Orientation; +use cursive::theme::BaseColor::*; +use cursive::theme::Color::*; +use cursive::theme::PaletteColor::*; +use cursive::theme::{BaseColor, BorderStyle, Color, Theme}; +use cursive::traits::*; +use cursive::utils::markup::StyledString; +use cursive::views::{BoxedView, LinearLayout, Panel, StackView, TextView}; +use cursive::Cursive; + +use tui::constants::*; +use tui::types::*; +use tui::{menu, mining, version}; + +use stats; + +use built_info; + +/// Main UI +pub struct UI { + cursive: Cursive, + ui_rx: mpsc::Receiver, + ui_tx: mpsc::Sender, + controller_tx: mpsc::Sender, +} + +fn modify_theme(theme: &mut Theme) { + theme.shadow = false; + theme.borders = BorderStyle::Simple; + theme.palette[Background] = Dark(Black); + theme.palette[Shadow] = Dark(Black); + theme.palette[View] = Dark(Black); + theme.palette[Primary] = Dark(White); + theme.palette[Highlight] = Dark(Cyan); + theme.palette[HighlightInactive] = Dark(Blue); + // also secondary, tertiary, TitlePrimary, TitleSecondary +} + +impl UI { + /// Create a new UI + pub fn new(controller_tx: mpsc::Sender) -> UI { + let (ui_tx, ui_rx) = mpsc::channel::(); + let mut mugle_ui = UI { + cursive: Cursive::default(), + ui_tx, + ui_rx, + controller_tx, + }; + + // Create UI objects, etc + let mining_view = mining::TUIMiningView::create(); + let version_view = version::TUIVersionView::create(); + + let main_menu = menu::create(); + + let root_stack = StackView::new() + .layer(version_view) + .layer(mining_view) + .with_name(ROOT_STACK); + + let mut title_string = StyledString::new(); + title_string.append(StyledString::styled( + format!("Mugle Miner Version {}", built_info::PKG_VERSION), + Color::Dark(BaseColor::Yellow), + )); + + let main_layer = LinearLayout::new(Orientation::Vertical) + .child(Panel::new(TextView::new(title_string))) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(Panel::new(BoxedView::new(main_menu))) + .child(Panel::new(root_stack)), + ); + + //set theme + let mut theme = mugle_ui.cursive.current_theme().clone(); + modify_theme(&mut theme); + mugle_ui.cursive.set_theme(theme); + mugle_ui.cursive.add_layer(main_layer); + + // Configure a callback (shutdown, for the first test) + let controller_tx_clone = mugle_ui.controller_tx.clone(); + mugle_ui.cursive.add_global_callback('q', move |_| { + controller_tx_clone + .send(ControllerMessage::Shutdown) + .unwrap(); + }); + mugle_ui.cursive.set_fps(4); + mugle_ui + } + + /// Step the UI by calling into Cursive's step function, then + /// processing any UI messages + pub fn step(&mut self) -> bool { + if !self.cursive.is_running() { + return false; + } + + // Process any pending UI messages + while let Some(message) = self.ui_rx.try_iter().next() { + match message { + UIMessage::UpdateStatus(update) => { + mining::TUIMiningView::update(&mut self.cursive, update.clone()); + version::TUIVersionView::update(&mut self.cursive, update.clone()); + } + } + } + + // Step the UI + self.cursive.step(); + true + } + + /// Stop the UI + pub fn stop(&mut self) { + self.cursive.quit(); + } +} + +/// Controller message + +pub struct Controller { + rx: mpsc::Receiver, + ui: UI, +} + +/// Controller Message +pub enum ControllerMessage { + /// Shutdown + Shutdown, +} + +impl Controller { + /// Create a new controller + pub fn new() -> Result { + let (tx, rx) = mpsc::channel::(); + Ok(Controller { + rx, + ui: UI::new(tx), + }) + } + /// Run the controller + pub fn run(&mut self, stats: Arc>) { + let stat_update_interval = 1; + let mut next_stat_update = time::get_time().sec + stat_update_interval; + while self.ui.step() { + if let Some(message) = self.rx.try_iter().next() { + match message { + ControllerMessage::Shutdown => { + self.ui.stop(); + return; + } + } + } + if time::get_time().sec > next_stat_update { + self.ui + .ui_tx + .send(UIMessage::UpdateStatus(stats.clone())) + .unwrap(); + next_stat_update = time::get_time().sec + stat_update_interval; + } + thread::sleep(std::time::Duration::from_millis(100)); + } + } +} diff --git a/src/bin/tui/version.rs b/src/bin/tui/version.rs new file mode 100644 index 0000000..b497b85 --- /dev/null +++ b/src/bin/tui/version.rs @@ -0,0 +1,49 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Version and build info + +use std::sync::{Arc, RwLock}; + +use cursive::direction::Orientation; +use cursive::traits::*; +use cursive::view::View; +use cursive::views::{LinearLayout, ResizedView, TextView}; +use cursive::Cursive; + +use tui::constants::*; +use tui::types::*; + +use info_strings; +use stats::Stats; + +/// Version view +pub struct TUIVersionView; + +impl TUIStatusListener for TUIVersionView { + /// Create basic status view + fn create() -> Box { + let (basic_info, detailed_info, _) = info_strings(); + let basic_status_view = ResizedView::with_full_screen( + LinearLayout::new(Orientation::Vertical) + .child(TextView::new(basic_info)) + .child(TextView::new(" ")) + .child(TextView::new(detailed_info)), + ); + Box::new(basic_status_view.with_name(VIEW_VERSION)) + } + + /// update + fn update(_c: &mut Cursive, _stats: Arc>) {} +} diff --git a/src/bin/types.rs b/src/bin/types.rs new file mode 100644 index 0000000..ad3cdcf --- /dev/null +++ b/src/bin/types.rs @@ -0,0 +1,90 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde_json::Value; + +/// Types used for stratum + +#[derive(Serialize, Deserialize, Debug)] +pub struct JobTemplate { + pub height: u64, + pub job_id: u64, + pub difficulty: u64, + pub pre_pow: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcRequest { + pub id: String, + pub jsonrpc: String, + pub method: String, + pub params: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcResponse { + pub id: String, + pub method: String, + pub jsonrpc: String, + pub result: Option, + pub error: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RpcError { + pub code: i32, + pub message: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct LoginParams { + pub login: String, + pub pass: String, + pub agent: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SubmitParams { + pub height: u64, + pub job_id: u64, + pub edge_bits: u32, + pub nonce: u64, + pub pow: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WorkerStatus { + pub id: String, + pub height: u64, + pub difficulty: u64, + pub accepted: u64, + pub rejected: u64, + pub stale: u64, +} + +/// Types used for internal communication from stratum client to miner +#[derive(Serialize, Deserialize, Debug)] +pub enum MinerMessage { + // Height, difficulty, pre_pow + ReceivedJob(u64, u64, u64, String), + StopJob, + Shutdown, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum ClientMessage { + // height, job_id, edge_bits, nonce, pow + FoundSolution(u64, u64, u32, u64, Vec), + Shutdown, +} diff --git a/src/build/build.rs b/src/build/build.rs new file mode 100644 index 0000000..64adcfd --- /dev/null +++ b/src/build/build.rs @@ -0,0 +1,26 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Build hooks to spit out version+build time info + +extern crate built; + +fn main() { + let mut opts = built::Options::default(); + opts.set_dependencies(true); + let manifest_location = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let dst = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("built.rs"); + built::write_built_file_with_opts(&opts, manifest_location.as_ref(), &dst) + .expect("Failed to acquire build-time information"); +} diff --git a/util/Cargo.toml b/util/Cargo.toml new file mode 100644 index 0000000..0745994 --- /dev/null +++ b/util/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mugle_miner_util" +version = "4.0.0" +authors = ["Mugle Developers "] +description = "Utilities for the mugle miner" +repository = "https://github.com/mugleproject/mugle-miner" +license = "Apache-2.0" +workspace = ".." + +[dependencies] +backtrace = "0.3" +byteorder = "1" +lazy_static = "1" +rand = "0.7" +serde = "1" +serde_derive = "1" +slog = { version = "2", features = ["max_level_trace", "release_max_level_trace"] } +slog-term = "2" +slog-async = "2" diff --git a/util/src/hex.rs b/util/src/hex.rs new file mode 100644 index 0000000..d6c376f --- /dev/null +++ b/util/src/hex.rs @@ -0,0 +1,73 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Implements hex-encoding from bytes to string and decoding of strings +/// to bytes. Given that rustc-serialize is deprecated and serde doesn't +/// provide easy hex encoding, hex is a bit in limbo right now in Rust- +/// land. It's simple enough that we can just have our own. +use std::fmt::Write; +use std::num; + +/// Encode the provided bytes into a hex string +pub fn to_hex(bytes: Vec) -> String { + let mut s = String::new(); + for byte in bytes { + write!(&mut s, "{:02x}", byte).expect("Unable to write"); + } + s +} + +/// Decode a hex string into bytes. +pub fn from_hex(hex_str: String) -> Result, num::ParseIntError> { + let hex_trim = if &hex_str[..2] == "0x" { + hex_str[2..].to_owned() + } else { + hex_str + }; + split_n(&hex_trim.trim()[..], 2) + .iter() + .map(|b| u8::from_str_radix(b, 16)) + .collect::, _>>() +} + +fn split_n(s: &str, n: usize) -> Vec<&str> { + (0..(s.len() - n + 1) / 2 + 1) + .map(|i| &s[2 * i..2 * i + n]) + .collect() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_to_hex() { + assert_eq!(to_hex(vec![0, 0, 0, 0]), "00000000"); + assert_eq!(to_hex(vec![10, 11, 12, 13]), "0a0b0c0d"); + assert_eq!(to_hex(vec![0, 0, 0, 255]), "000000ff"); + } + + #[test] + fn test_from_hex() { + assert_eq!(from_hex("00000000".to_string()).unwrap(), vec![0, 0, 0, 0]); + assert_eq!( + from_hex("0a0b0c0d".to_string()).unwrap(), + vec![10, 11, 12, 13] + ); + assert_eq!( + from_hex("000000ff".to_string()).unwrap(), + vec![0, 0, 0, 255] + ); + } +} diff --git a/util/src/lib.rs b/util/src/lib.rs new file mode 100644 index 0000000..01e086d --- /dev/null +++ b/util/src/lib.rs @@ -0,0 +1,51 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Logging, as well as various low-level utilities that factor Rust +//! patterns that are frequent within the mugle codebase. + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +extern crate backtrace; +extern crate byteorder; +extern crate rand; +#[macro_use] +extern crate slog; +extern crate slog_async; +extern crate slog_term; + +#[macro_use] +extern crate lazy_static; + +extern crate serde; +#[macro_use] +extern crate serde_derive; + +// Logging related +pub mod logger; +pub use logger::{init_logger, init_test_logger, LOGGER}; + +pub mod types; +pub use types::{LogLevel, LoggingConfig}; + +// other utils +#[allow(unused_imports)] +use std::ops::Deref; + +mod hex; +pub use hex::*; diff --git a/util/src/logger.rs b/util/src/logger.rs new file mode 100644 index 0000000..e43b80b --- /dev/null +++ b/util/src/logger.rs @@ -0,0 +1,158 @@ +// Copyright 2020 The Mugle Developers +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Logging wrapper to be used throughout all crates in the workspace +use slog::{Discard, Drain, Duplicate, Level, LevelFilter, Logger}; +use slog_async; +use slog_term; +use std::fs::OpenOptions; +use std::ops::Deref; +use std::sync::Mutex; + +use backtrace::Backtrace; +use std::{panic, thread}; + +use types::{LogLevel, LoggingConfig}; + +fn convert_log_level(in_level: &LogLevel) -> Level { + match *in_level { + LogLevel::Info => Level::Info, + LogLevel::Critical => Level::Critical, + LogLevel::Warning => Level::Warning, + LogLevel::Debug => Level::Debug, + LogLevel::Trace => Level::Trace, + LogLevel::Error => Level::Error, + } +} + +lazy_static! { + /// Flag to observe whether logging was explicitly initialised (don't output otherwise) + static ref WAS_INIT: Mutex = Mutex::new(false); + /// Flag to observe whether tui is running, and we therefore don't want to attempt to write + /// panics to stdout + static ref TUI_RUNNING: Mutex = Mutex::new(false); + /// Static Logging configuration, should only be set once, before first logging call + static ref LOGGING_CONFIG: Mutex = Mutex::new(LoggingConfig::default()); + + /// And a static reference to the logger itself, accessible from all crates + pub static ref LOGGER: Logger = { + let was_init = *WAS_INIT.lock().unwrap(); + let config = LOGGING_CONFIG.lock().unwrap(); + let slog_level_stdout = convert_log_level(&config.stdout_log_level); + let slog_level_file = convert_log_level(&config.file_log_level); + if config.tui_running.is_some() && config.tui_running.unwrap() { + let mut tui_running_ref = TUI_RUNNING.lock().unwrap(); + *tui_running_ref = true; + } + + //Terminal output drain + let terminal_decorator = slog_term::TermDecorator::new().build(); + let terminal_drain = slog_term::FullFormat::new(terminal_decorator).build().fuse(); + let terminal_drain = LevelFilter::new(terminal_drain, slog_level_stdout).fuse(); + let mut terminal_drain = slog_async::Async::new(terminal_drain).build().fuse(); + if !config.log_to_stdout || !was_init { + terminal_drain = slog_async::Async::new(Discard{}).build().fuse(); + } + + //File drain + let mut file_drain_final = slog_async::Async::new(Discard{}).build().fuse(); + if config.log_to_file && was_init { + let file = OpenOptions::new() + .create(true) + .write(true) + .append(config.log_file_append) + .truncate(!config.log_file_append) + .open(&config.log_file_path) + .unwrap(); + + let file_decorator = slog_term::PlainDecorator::new(file); + let file_drain = slog_term::FullFormat::new(file_decorator).build().fuse(); + let file_drain = LevelFilter::new(file_drain, slog_level_file).fuse(); + file_drain_final = slog_async::Async::new(file_drain).build().fuse(); + } + + //Compose file and terminal drains + let composite_drain = Duplicate::new(terminal_drain, file_drain_final).fuse(); + + Logger::root(composite_drain, o!()) + }; +} + +/// Initialises the logger with the given configuration +pub fn init_logger(config: Option) { + if let Some(c) = config { + let mut config_ref = LOGGING_CONFIG.lock().unwrap(); + *config_ref = c; + // Logger configuration successfully injected into LOGGING_CONFIG... + let mut was_init_ref = WAS_INIT.lock().unwrap(); + *was_init_ref = true; + // .. allow logging, having ensured that paths etc are immutable + } + send_panic_to_log(); +} + +/// Initializes the logger for unit and integration tests +pub fn init_test_logger() { + let mut was_init_ref = WAS_INIT.lock().unwrap(); + if *was_init_ref.deref() { + return; + } + let mut config_ref = LOGGING_CONFIG.lock().unwrap(); + *config_ref = LoggingConfig::default(); + *was_init_ref = true; + send_panic_to_log(); +} + +/// hook to send panics to logs as well as stderr +fn send_panic_to_log() { + panic::set_hook(Box::new(|info| { + let backtrace = Backtrace::new(); + + let thread = thread::current(); + let thread = thread.name().unwrap_or("unnamed"); + + let msg = match info.payload().downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload().downcast_ref::() { + Some(s) => &**s, + None => "Box", + }, + }; + + match info.location() { + Some(location) => { + error!( + LOGGER, + "\nthread '{}' panicked at '{}': {}:{}{:?}\n\n", + thread, + msg, + location.file(), + location.line(), + backtrace + ); + } + None => error!( + LOGGER, + "thread '{}' panicked at '{}'{:?}", thread, msg, backtrace + ), + } + //also print to stderr + let tui_running = *TUI_RUNNING.lock().unwrap(); + if !tui_running { + eprintln!( + "Thread '{}' panicked with message:\n\"{}\"\nSee mugle.log for further details.", + thread, msg + ); + } + })); +} diff --git a/util/src/types.rs b/util/src/types.rs new file mode 100644 index 0000000..7f5a2b2 --- /dev/null +++ b/util/src/types.rs @@ -0,0 +1,65 @@ +// Copyright 2020 The Mugle Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Logging configuration types + +/// Log level types, as slog's don't implement serialize +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LogLevel { + /// Critical + Critical, + /// Error + Error, + /// Warning + Warning, + /// Info + Info, + /// Debug + Debug, + /// Trace + Trace, +} + +/// Logging config +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LoggingConfig { + /// whether to log to stdout + pub log_to_stdout: bool, + /// logging level for stdout + pub stdout_log_level: LogLevel, + /// whether to log to file + pub log_to_file: bool, + /// log file level + pub file_log_level: LogLevel, + /// Log file path + pub log_file_path: String, + /// Whether to append to log or replace + pub log_file_append: bool, + /// Whether the tui is running (optional) + pub tui_running: Option, +} + +impl Default for LoggingConfig { + fn default() -> LoggingConfig { + LoggingConfig { + log_to_stdout: true, + stdout_log_level: LogLevel::Debug, + log_to_file: false, + file_log_level: LogLevel::Trace, + log_file_path: String::from("mugle.log"), + log_file_append: false, + tui_running: None, + } + } +}