├── .gitignore ├── version.py ├── README.md ├── docker-build.sh ├── docker-run.sh ├── docker-dev-build.sh ├── morpavsolver ├── morpavsolver.so └── __init__.py ├── res ├── gui_rc.qrc └── miner.svg ├── docker-dev-run.sh ├── run.sh ├── Dockerfile-dev ├── Dockerfile ├── LICENSE ├── gui.py ├── tests.py └── epycyzm.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | 3 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | VERSION = 'slush/epycyzm/16.10' 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # epycyzm 2 | Experimental Python CPU (yet) Zcash miner 3 | -------------------------------------------------------------------------------- /docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -t epycyzm . 4 | 5 | -------------------------------------------------------------------------------- /docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run --name epycyzm --rm -ti epycyzm "$@" 4 | 5 | -------------------------------------------------------------------------------- /docker-dev-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -f Dockerfile-dev -t epycyzm-dev . 4 | 5 | -------------------------------------------------------------------------------- /morpavsolver/morpavsolver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slush0/epycyzm/HEAD/morpavsolver/morpavsolver.so -------------------------------------------------------------------------------- /res/gui_rc.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | miner.svg 4 | 5 | 6 | -------------------------------------------------------------------------------- /docker-dev-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run --name epycyzm-dev --rm -ti -v `pwd`:/epycyzm epycyzm-dev "$@" 4 | 5 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Example usage. You can try it for a hour as a donation :-) 4 | nice -n9 ./epycyzm.py "$@" 5 | -------------------------------------------------------------------------------- /Dockerfile-dev: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | 3 | MAINTAINER Ondrej Sika 4 | 5 | RUN pip install cffi 6 | 7 | WORKDIR /epycyzm 8 | 9 | ENTRYPOINT ["./run.sh"] 10 | CMD ["stratum+tcp://slush:x@zec.slushpool.com:4444"] 11 | 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5 2 | 3 | MAINTAINER Ondrej Sika 4 | 5 | RUN apt-get update 6 | RUN apt-get install -y git 7 | RUN git clone https://github.com/slush0/epycyzm.git 8 | RUN pip install cffi 9 | 10 | WORKDIR /epycyzm 11 | ENTRYPOINT ["./run.sh"] 12 | CMD ["stratum+tcp://slush:x@zec.slushpool.com:4444"] 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 slush0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /morpavsolver/__init__.py: -------------------------------------------------------------------------------- 1 | # https://github.com/morpav/zceq_solver--bin 2 | 3 | from cffi import FFI 4 | import os.path 5 | import inspect 6 | 7 | ffi = None 8 | library = None 9 | 10 | library_header = """ 11 | typedef struct { 12 | char data[1344]; 13 | } Solution; 14 | 15 | typedef struct { 16 | unsigned int data[512]; 17 | } ExpandedSolution; 18 | 19 | typedef struct HeaderAndNonce { 20 | char data[140]; 21 | } HeaderAndNonce; 22 | 23 | typedef struct ZcEquihashSolverT ZcEquihashSolver; 24 | 25 | ZcEquihashSolver* CreateSolver(void); 26 | 27 | void DestroySolver(ZcEquihashSolver* solver); 28 | 29 | int FindSolutions(ZcEquihashSolver* solver, HeaderAndNonce* inputs, 30 | Solution solutions[], int max_solutions); 31 | 32 | int ValidateSolution(ZcEquihashSolver* solver, HeaderAndNonce* inputs, Solution* solutions); 33 | 34 | void RunBenchmark(long long nonce_start, int iterations); 35 | 36 | bool ExpandedToMinimal(Solution* minimal, ExpandedSolution* expanded); 37 | 38 | bool MinimalToExpanded(ExpandedSolution* expanded, Solution* minimal); 39 | 40 | """ 41 | 42 | def load_library(path=None): 43 | global library, ffi 44 | assert library is None 45 | 46 | ffi = FFI() 47 | ffi.cdef(library_header) 48 | 49 | if path is None: 50 | path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'morpavsolver.so') 51 | library = ffi.dlopen(path) 52 | assert library is not None 53 | 54 | 55 | class Solver: 56 | def __init__(self): 57 | self.solver_ = self.header_ = self.solutions_ = self.solution_to_check_ = None 58 | self._ensure_library() 59 | assert library and ffi 60 | self.solver_ = library.CreateSolver() 61 | self.header_ = ffi.new("HeaderAndNonce*") 62 | self.solutions_ = ffi.new("Solution[16]") 63 | self.solution_to_check_ = ffi.new("Solution*") 64 | self.expanded_tmp_ = ffi.new("ExpandedSolution*") 65 | 66 | def __del__(self): 67 | # Free the underlying resources on destruction 68 | library.DestroySolver(self.solver_); 69 | self.solver_ = None 70 | # cffi's cdata are collected automatically 71 | self.header_ = self.solutions_ = self.solution_to_check_ = None 72 | 73 | def _ensure_library(self): 74 | # Try to load library from standard 75 | if (library is None): 76 | load_library() 77 | 78 | def run_benchmark(self, iterations=10, nonce_start=0): 79 | library.RunBenchmark(nonce_start, iterations) 80 | 81 | def find_solutions(self, block_header): 82 | assert len(block_header) == 140 83 | self.header_.data = block_header 84 | return library.FindSolutions(self.solver_, self.header_, self.solutions_, 16); 85 | 86 | def get_solution(self, num): 87 | assert(num >= 0 and num < 16) 88 | return bytes(ffi.buffer(self.solutions_[num].data)) 89 | 90 | def validate_solution(self, block_header, solution): 91 | assert len(block_header) == 140 92 | assert len(solution) == 1344 93 | self.solution_to_check_.data = solution 94 | return library.ValidateSolution(self.solver_, self.header_, self.solution_to_check_); 95 | 96 | def list_to_minimal(self, expanded): 97 | if isinstance(expanded, (list, tuple)): 98 | assert len(expanded) == 512 99 | minimal = ffi.new("Solution*") 100 | tmp = self.expanded_tmp_ 101 | for i, idx in enumerate(expanded): 102 | tmp.data[i] = idx 103 | expanded = tmp 104 | 105 | res = library.ExpandedToMinimal(minimal, expanded) 106 | assert res 107 | return minimal 108 | 109 | def minimal_to_list(self, minimal): 110 | tmp = self.expanded_tmp_ 111 | res = library.MinimalToExpanded(tmp, minimal) 112 | assert res 113 | result = [tmp.data[i] for i in range(512)] 114 | return result 115 | 116 | __all__ = ['Solver', 'load_library'] 117 | -------------------------------------------------------------------------------- /res/miner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 14 | 17 | 22 | 25 | 28 | 30 | 33 | 36 | 40 | 43 | 48 | 51 | 53 | 55 | 56 | 57 | 58 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | from PySide import QtCore, QtGui 2 | 3 | import gui_rc 4 | 5 | class Window(QtGui.QDialog): 6 | def __init__(self): 7 | super(Window, self).__init__() 8 | 9 | self.createIconGroupBox() 10 | self.createMessageGroupBox() 11 | 12 | self.iconLabel.setMinimumWidth(self.durationLabel.sizeHint().width()) 13 | 14 | self.createActions() 15 | self.createTrayIcon() 16 | 17 | self.showMessageButton.clicked.connect(self.showMessage) 18 | self.showIconCheckBox.toggled.connect(self.trayIcon.setVisible) 19 | self.iconComboBox.currentIndexChanged[int].connect(self.setIcon) 20 | self.trayIcon.messageClicked.connect(self.messageClicked) 21 | self.trayIcon.activated.connect(self.iconActivated) 22 | 23 | mainLayout = QtGui.QVBoxLayout() 24 | mainLayout.addWidget(self.iconGroupBox) 25 | mainLayout.addWidget(self.messageGroupBox) 26 | self.setLayout(mainLayout) 27 | 28 | self.iconComboBox.setCurrentIndex(1) 29 | self.trayIcon.show() 30 | 31 | self.setWindowTitle("Systray") 32 | self.resize(400, 300) 33 | 34 | def setVisible(self, visible): 35 | self.minimizeAction.setEnabled(visible) 36 | self.maximizeAction.setEnabled(not self.isMaximized()) 37 | self.restoreAction.setEnabled(self.isMaximized() or not visible) 38 | super(Window, self).setVisible(visible) 39 | 40 | def closeEvent(self, event): 41 | if self.trayIcon.isVisible(): 42 | QtGui.QMessageBox.information(self, "Systray", 43 | "The program will keep running in the system tray. To " 44 | "terminate the program, choose Quit in the " 45 | "context menu of the system tray entry.") 46 | self.hide() 47 | event.ignore() 48 | 49 | def setIcon(self, index): 50 | icon = self.iconComboBox.itemIcon(index) 51 | self.trayIcon.setIcon(icon) 52 | self.setWindowIcon(icon) 53 | 54 | self.trayIcon.setToolTip(self.iconComboBox.itemText(index)) 55 | 56 | def iconActivated(self, reason): 57 | if reason in (QtGui.QSystemTrayIcon.Trigger, QtGui.QSystemTrayIcon.DoubleClick): 58 | self.iconComboBox.setCurrentIndex( 59 | (self.iconComboBox.currentIndex() + 1) 60 | % self.iconComboBox.count()) 61 | elif reason == QtGui.QSystemTrayIcon.MiddleClick: 62 | self.showMessage() 63 | 64 | def showMessage(self): 65 | icon = QtGui.QSystemTrayIcon.MessageIcon( 66 | self.typeComboBox.itemData(self.typeComboBox.currentIndex())) 67 | self.trayIcon.showMessage(self.titleEdit.text(), 68 | self.bodyEdit.toPlainText(), icon, 69 | self.durationSpinBox.value() * 1000) 70 | 71 | def messageClicked(self): 72 | QtGui.QMessageBox.information(None, "Systray", 73 | "Sorry, I already gave what help I could.\nMaybe you should " 74 | "try asking a human?") 75 | 76 | def createIconGroupBox(self): 77 | self.iconGroupBox = QtGui.QGroupBox("Tray Icon") 78 | 79 | self.iconLabel = QtGui.QLabel("Icon:") 80 | 81 | self.iconComboBox = QtGui.QComboBox() 82 | self.iconComboBox.addItem(QtGui.QIcon(':/icons/miner.svg'), "Miner") 83 | self.iconComboBox.addItem(QtGui.QIcon(':/images/heart.svg'), "Heart") 84 | self.iconComboBox.addItem(QtGui.QIcon(':/images/trash.svg'), "Trash") 85 | 86 | self.showIconCheckBox = QtGui.QCheckBox("Show icon") 87 | self.showIconCheckBox.setChecked(True) 88 | 89 | iconLayout = QtGui.QHBoxLayout() 90 | iconLayout.addWidget(self.iconLabel) 91 | iconLayout.addWidget(self.iconComboBox) 92 | iconLayout.addStretch() 93 | iconLayout.addWidget(self.showIconCheckBox) 94 | self.iconGroupBox.setLayout(iconLayout) 95 | 96 | def createMessageGroupBox(self): 97 | self.messageGroupBox = QtGui.QGroupBox("Balloon Message") 98 | 99 | typeLabel = QtGui.QLabel("Type:") 100 | 101 | self.typeComboBox = QtGui.QComboBox() 102 | self.typeComboBox.addItem("None", QtGui.QSystemTrayIcon.NoIcon) 103 | self.typeComboBox.addItem(self.style().standardIcon( 104 | QtGui.QStyle.SP_MessageBoxInformation), "Information", 105 | QtGui.QSystemTrayIcon.Information) 106 | self.typeComboBox.addItem(self.style().standardIcon( 107 | QtGui.QStyle.SP_MessageBoxWarning), "Warning", 108 | QtGui.QSystemTrayIcon.Warning) 109 | self.typeComboBox.addItem(self.style().standardIcon( 110 | QtGui.QStyle.SP_MessageBoxCritical), "Critical", 111 | QtGui.QSystemTrayIcon.Critical) 112 | self.typeComboBox.setCurrentIndex(1) 113 | 114 | self.durationLabel = QtGui.QLabel("Duration:") 115 | 116 | self.durationSpinBox = QtGui.QSpinBox() 117 | self.durationSpinBox.setRange(5, 60) 118 | self.durationSpinBox.setSuffix(" s") 119 | self.durationSpinBox.setValue(15) 120 | 121 | durationWarningLabel = QtGui.QLabel("(some systems might ignore this " 122 | "hint)") 123 | durationWarningLabel.setIndent(10) 124 | 125 | titleLabel = QtGui.QLabel("Title:") 126 | 127 | self.titleEdit = QtGui.QLineEdit("Cannot connect to network") 128 | 129 | bodyLabel = QtGui.QLabel("Body:") 130 | 131 | self.bodyEdit = QtGui.QTextEdit() 132 | self.bodyEdit.setPlainText("Don't believe me. Honestly, I don't have " 133 | "a clue.\nClick this balloon for details.") 134 | 135 | self.showMessageButton = QtGui.QPushButton("Show Message") 136 | self.showMessageButton.setDefault(True) 137 | 138 | messageLayout = QtGui.QGridLayout() 139 | messageLayout.addWidget(typeLabel, 0, 0) 140 | messageLayout.addWidget(self.typeComboBox, 0, 1, 1, 2) 141 | messageLayout.addWidget(self.durationLabel, 1, 0) 142 | messageLayout.addWidget(self.durationSpinBox, 1, 1) 143 | messageLayout.addWidget(durationWarningLabel, 1, 2, 1, 3) 144 | messageLayout.addWidget(titleLabel, 2, 0) 145 | messageLayout.addWidget(self.titleEdit, 2, 1, 1, 4) 146 | messageLayout.addWidget(bodyLabel, 3, 0) 147 | messageLayout.addWidget(self.bodyEdit, 3, 1, 2, 4) 148 | messageLayout.addWidget(self.showMessageButton, 5, 4) 149 | messageLayout.setColumnStretch(3, 1) 150 | messageLayout.setRowStretch(4, 1) 151 | self.messageGroupBox.setLayout(messageLayout) 152 | 153 | def createActions(self): 154 | self.minimizeAction = QtGui.QAction("Mi&nimize", self, 155 | triggered=self.hide) 156 | 157 | self.maximizeAction = QtGui.QAction("Ma&ximize", self, 158 | triggered=self.showMaximized) 159 | 160 | self.restoreAction = QtGui.QAction("&Restore", self, 161 | triggered=self.showNormal) 162 | 163 | self.quitAction = QtGui.QAction("&Quit", self, 164 | triggered=QtGui.qApp.quit) 165 | 166 | def createTrayIcon(self): 167 | self.trayIconMenu = QtGui.QMenu(self) 168 | self.trayIconMenu.addAction(self.minimizeAction) 169 | self.trayIconMenu.addAction(self.maximizeAction) 170 | self.trayIconMenu.addAction(self.restoreAction) 171 | self.trayIconMenu.addSeparator() 172 | self.trayIconMenu.addAction(self.quitAction) 173 | 174 | self.trayIcon = QtGui.QSystemTrayIcon(self) 175 | self.trayIcon.setContextMenu(self.trayIconMenu) 176 | 177 | 178 | if __name__ == '__main__': 179 | 180 | import sys 181 | 182 | app = QtGui.QApplication(sys.argv) 183 | 184 | if not QtGui.QSystemTrayIcon.isSystemTrayAvailable(): 185 | QtGui.QMessageBox.critical(None, "Systray", 186 | "I couldn't detect any system tray on this system.") 187 | sys.exit(1) 188 | 189 | QtGui.QApplication.setQuitOnLastWindowClosed(False) 190 | 191 | window = Window() 192 | window.show() 193 | sys.exit(app.exec_()) 194 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pyzceqsolver import Solver 4 | import binascii 5 | from hashlib import sha256 6 | 7 | def main(): 8 | vectors = ( 9 | { 10 | 'height': 100, # testnet rc4 11 | 'hash': b'004a4bcb42c9fe51332ea01c9bf9eeff7f9bd6957711862c5471041fd94ef95b', 12 | 'block': '04000000ad6c8c34c3d72887c5eab6276564c349d60df2863495ff22f4079488813bf0018e20f8505a31f69381c0fbeabe3f24b99b6109e0e66eaea14cf9bb1ac2e3f88c0000000000000000000000000000000000000000000000000000000000000000bb341258a81802200600dfe244233d83bec8985f6c5b5e5e248d9b19204451df5dc2f4b7f87c0000fd400500b31c3ae78b552cfbd1a6df3ef175e610c63980aa3fdeee24be6727b5d40645c91570118e5faf1c0a0611572ee7f0c8246398bc62efce1ff899c14efc93dd57804abd80da0ac2e71617b00bfb29fee324fda66805ffbe283b8e6ef3b4fd43ba66309a95cb01d2e572135406763cc4d573738741859975bf756b739340120d15d8cbaed4b6790e2e01b5d3e8cd09e1d8f77bb73d1be7ab4021fec317ea26ce9b570f61d7749fc9e208cebfba2ced40e3762d927b412f6612733adf35bc347cf619dc20ab5df05497f85bc3ecaba113be5b9a292b74d9938c3e08a972b353f6eba4c950d959a0024b846d8642d466e7045049b0b95d7d1b77745fd90f0c6a664ecc8c202bf7b590e2c0cacc4d1a307d652f314617fc4e954bfd32fb54f891f0fee5e38af8e1b4238112016e5bf3f5ba21b4567dfc431146686ed13d26400adf810a34f6eaeea3b196ae45354f408bac700113fd95f6cb0b81ea6d128023d968a987129ed11a18442d4aa20955f7cb8ae2bba777a7f57049789cbf262ddc0d539a3da7ecb46575e765ada657a85eb94744b202c0d52cd335a19f845c1f4c08e54d50f52502061edfcac89d5df7c43383783aa903c24f94ba400726d03250d3d8ffc52986b2df3d5a5ba4ff3e7ce0510a9d7bc6384f101f3f072238fcae7b00b45dbf31250b91e0de30d6ab7367a0c8a56ef9909251145cdd1f01418921b2913080a14f909292f6d3d59be5717d7209b853ffbb48c0cd233db173ef770ace1f64f5422c033495709d54f3335d22b0872813261d4d40970b981a40bdb93b3525abb4f3224fcfe994121d041e39d6021564b9fc111fbe9def83311ea56c28d13d4a48ca1509bbe745e750b58fc0f25832dd091d29598d3e59123f357df04c20813414618b5eeaef9d004dcd6f9a1f238f8fe9651a3ba21ad6a723d9c9b5c6186f4ad80105ae08495f391b196c219e0194eff2a6bbf83a3a2123434e499b8f4d6230735634d5410544dd70e3f80571df6fb199d5637640d6dc637c4b91fd929abe5a229ee47fc0d7e493baef99280e68656aee6ff9cb64097f6b313885b5f7cfcfad4e8afeec1fa7355df5b60b26da0b2f4b464ba9c616ae3f3ccf9208a7178ae01c0d0c985ba6313f77dc448963a6d6c2f7e57fb4f125e389d66699467564468aebe4fcd0f2d0b0fedd2a013dbec635892ca584f612b404aac832408d388c55051928bd38211009a1b9b2ed6b3fbc3da21ed515411710b8f5d0537c8f5d70a3265f685c16a1ac36485924a16bded44b7a948e20026c77cb1c6e0bad9739da0424b2c49b958da3f2ef357c2e403c4d9ed496f1dc1fb785394c4e9493881f5281b9e798a2e73d7dff4504994b8e3e8a331f0c55d1ca0e955a2cb933da91d3435d7ff68d255f957b08663abf7dc27a2380d8ee5f024e8ccb8d1952d8f13a71e063a128ae743cb5f7180e700e9e25615a2fc293f2b148c7cb419f21fe203311db1de3cddbf3fbbc04a2286073a8ce36199409c4355aee2ed4e8e9096991b6fc2dfd302e706194477620be22f410d2c7d5c09786e034da708f00e8fe12c1335521ea9f21b13ffcc3e41c3f370de9aff0ba2f94248843d7f662fe776b984607b5b626ca05c8de5bd7258b8befc31b406b1b6293cdbdd663ce0fdbdd33a80d2307eb30231ad58a4c4364082b6ef65573bbc2f232bba1d24b16d9c96fc143610927f3e1457b99edf10ee592ed028456fff761016f18411ffb65af9c5e7d1ae88e84c8bd614df64c6275ddc46f19c4c3fac8d515b4b9e114237f15cbebf22be62e0ef91c976b021317004a29c3e6210dbb37d1842153840dafb777dd93285d09a8f38e6b5de0b06294ba19c6f94ba3f9335431f73dc3cd9126d1f417f597663d05bdc9f93034ae0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0401640103ffffffff02404b4c0000000000232103bc707cd02a0bcc498afceb1748822482ef8e179d79d8dd672913f61c16484069acd01213000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000', 13 | 'sol_id': 0 14 | }, 15 | { 16 | 'height': 1000, # testnet rc4 17 | 'hash': b'00000012d0323e496c5c2ded0fc47c555894d29a1e092fb3af2df72e121078d9', 18 | 'block': '04000000708fcb43f55789d24439addb18cc94413e649e672e2099202514e58558000000150b1446c8ff33834e45152d7bbeb493f1f99c6d22e36968a6013e9d14e0d6c3000000000000000000000000000000000000000000000000000000000000000024101358ed7d011e5103000000000000010000000000000000000000000000000000000000000000fd4005001f028d4d071ba70f8229286178d9cfaee47f35654a259eb59464db29993977cff84bcdbe25de324c1213f7725beec79ba33588828f34ce5de8ada96fcf4668043b86c822783f960e98abbe66747e652735517a04b4e48058c4a887e8f0e090f6b6e434ef4bced9c42d28b9ebf4d6fef1179d130c283c8d29508aea939e0f6c4ae530e684ef5a4926787e5025eeed15d80d1a286eb4cca08bdc126c398610d65f7ab596f31b175f029f1c75a0da30e90f81d29ca1bf8d2b49751f41db1468bd4413f21c4b9b3df24f04ea72ad0a7ff8dc710768865127061d4c5d2b023d7d45dbf223913a66d8142554f49d2b5197c161528899f100c2d42bf99850059d28afecd2070df65606a64e75f979e2a3fe49d40935f57526d727a5a327c1048762892e26057d93372b4d7e0a8b34120beab0b4b0383be61e0bb31c0a504306567188b45785d17f96ce9d47ea42b2a63ef520004e4b8cbe049d3aa08de85206ce4eeef733199eec36b97dfa3c9723d8ba39b37e1c63e5a1bbeb2ed36f0d507b5307a7f54fdf61c2c812fc2440b25d735acd0e92cc98867a1455d56e711b5e9f95d8fbe9518a940265fd68b24a83fdcac041b0e52afb89912aee621b278464c7039b7ad5e4b12a93fb6a67b731161eea7707e3ab80c105fd82e23453a09a4fc5e64ff4fb568635c72c550c60b7732b1e751a19d2449d4e24192abb036057dd6940ef4b8e1e368458fcd6c9f82ed2c13116b13dd509990e57173d72625b5ddae0a4f3f7c4cf09bc4934de8cb165ccd68429ebe6e161ab13b6523432fc666857ef516583c393a81cb88dd268d33d8df505deb3ec0944174cb2c072e8a22c5dad61205d16ad13cac8c71d506a77e10fb264162c69870f9a1b9f8f0b763af0c24770eeca5412503c22b9b27db2f7d72b4b68345d4bd9a077becd76f2244ca63e69f39dc3ec00d12dc10554b5558fbdf03e0f385a49ea16162f857ee24e142c2485273177c83f13c952838aa55e0a4803c8a03e3d727e1d9e1e508dceaeaa52041b72e43a0595ea3e7b5b374f966fa2bc781df1671cf87d6c810b67d504b08dd2a2da977117277297c7163f9fc50e1cbf627c3f6ebf31f0be0203ede6e390cc3f5da85826ae0cbdd70f706eeedbe402b524ea766df3dd17d5525a1c4542ec3733915dd55964b3bd1a2c1b9bae8f01e8833ccac22fdb300533b569acd63eb59356e5f02669bb962419940334cde96f5cea833f3a105ee7790270f31522c61c99e6af43435d49225cf510bd1021104a1161b4cb539f7923c2ec7828909d9a528d3d890ba4b39c6fce151e773652e5381f81e116e377c4c72246a478a05551b3f1f5e24bf0bc49a4f55873808310acf716048a00f51e3e5348d8f8875db5ca6e50832a405bf5ee0e50870810d78c9f6055e331c77cdcad01c0cd7e3721a865caf1d5e16f7eab9dfa2ebfbb254bc32ff760762331c8219612a7b907ee752b975a890576f26cd7100e994a89f0968551f4e9bcd8f89d560f7b8ba9c3255d9738fe53f283ed8bc2f3e01991bb057ea7e8aad6351d38798512414a8f6dc9539f92050de436ab08087b2735c9819c917e8894955bccaf640dfe5f5bbd0ef60b1226a45c41f6070199479c3a09529f1360b23243d195e3565488fdd8a2eae8df0fda0479a1bab0203dedc1c31455857915c6723a55fa0c056270495a022f3479d8b31f4873119e5f75b6201c055d64ccb63bc021fa9f732384c7013e88921a847f0c2c6fa6986fc64dbae0955fe87be9a19282da6faa16d4ff39b60939b133d0f2d6e3dcad08bf24488e71599786a11656cc496643666e0f54b1675a30befc0817778a92964901428155b982e5d24402ef175f22343309dd4c1d9cd5c53da42aa3f67fc88f26191f598a0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0602e80302d700ffffffff0280f0fa0200000000232103ad63e41526f301370b3eac4f243efce6e1cc9f5907189360666f82312fe6d8f0ac20bcbe000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000', 19 | 'sol_id': 2 20 | }, 21 | { 22 | 'height': 70, # mainnet 23 | 'hash': b'0000c3e45b6183f5d1938d7293c0517ccbacbd53abd79eb8b8b5c9a06e50a66a', 24 | 'block': '04000000d88b45abc52c84d1345b6344b04a53abf92f05577b08c88d709fc110bb480200deb767c7c9cfdec27967faf5d0c09026da0b835b58a3acef9e39d7552db5e2a800000000000000000000000000000000000000000000000000000000000000001f7c135822b5021f1bb2b37300000000000000000000000000000000000000000000000000000002fd400500673cdd059b980150fe526e7fb7750e3e25b515f933ffaacb765a893f7652445bc073ad054b186fe718057ce0c584969247b2ca23609a5e87711b3c54d5fc2e021edb07b43e1df03fa2e557c47e55b9b139a445099751e11d48df60804cc57159fe23fd7bc6b932b0295719cf859aa6333054b98fda77e5f296f7fdb9161561ad757dc8489377d2f53c0c2e73de2c3234a7e7880014c7f42e87c9873daa964c7c92a2d2ab56afa004c3f58abfdfbbd524bcf17f6b76e5f6310b5e80bd0b7811ffc997d767eaa5c230225d35b5c612b1cd0d09a6bda04d82dff59bf8d1f99fa64acd735a94ac1a4cb63d3ff9e71feb3b1575e186ff461996b21c14190fe73d9f3c1d42b9fbf69cc179ed42b739257e17291c7c4f0afa0e922ff0d45766a26edb1e461c5b1b7212d913438462470d924b27ec23ed592b64acdd9d90137aedd39ebd3425f047d55597d72042af1e1fd330031bff47e6c30cbcef6cb034d9e5094e657fd8d9210901e7f697f0f251a9dfe3b64c7ac56a082c7176aa04eb47fbbc55a535aac61314f57ad5f1d3f77c1b7f42a2bc31b7e24f49184215a7a5757fe369f8db9cd203dfc8f714c2313dfd2c249d01f369ba3f507ce8dc0d1b35796b2d8bb7ae5314838c671819b45e30995a06727c7bdfe4fe65d2fb1135afe882e8e04b580701419cb6a09a516a8f6c8b14a67df037d55e4b70758605c1e46338939eb69d71132ab12ce489bae6b1f509283aae0a3f69590da10c02adb4e5d48d8735ec804c06eb76971f838485e222e1fd06a287114511da40210a2620e6505ca8ab0b2037c4526b793a897e7887aa0bac48e44261ce49cbebc3d69d55ad23210199619f45ecc6a8bda0365316ab99cfa77945632ff1ba645e23f56d3bacb17317c340e4b886f2036d476593c16c4cecc56d87d37bc73984e609805b12bdedd3722e1f0091e0d120ca9db30062d3c444dea97aaa8f5bc4981d2ea15785d0946ad7162270e7747e7195087416ff0641c164a457c57ef316653530c6e8eb22feb986611d554c7bbbee6c19c01069964556741efd8c5cbc8701ca23f86198cd6ccc1c923f6363fcd95aff5e395407d0c2630795e63986c17de72e7c72cf7bf95e755923789e18f1685059eeb57355dea7dd12cee439471c3637733bbe9e99b71d7e64e962e71c4e41beb5f2810175dd7f8feff25fc8da638e8b31f7717048929cd84bdadb13ee5b28f3f610b4cdafc9fbdd71e87858da07cb84dd7cdf3cb9e6d63767bdeffb3a795fffe17d18fe85e010cf741d3e2787369055040669c6d5befc061a5a337d09b78f556510aeecf2b821b4af35a8105ab79d0486732f25fef1b7035366f9ca8fd599ae3e065d3f42ec869321391b723bc51d701d7a7333cca5334813429f9d19c5dbae635fedcfaf093840d6bd2f0188e74cf68e4a52c96913039faa2b3df946fee04e0805987f089647058668c82e92442c5bbb8b3f9f050d5fda8b64e40e23899425f4cfed659de5055e6c023b389a37151962f5dc7473b61e29649dcc03b079240253e1660c41bcc5ca884210b829671596c438ce101049dfc6189fe97d01aa69084ffc47c6fb50ddf0a11f6406ab34e02dc17b2033955ef207e7aaee5dbb31490d2d0ef1ad184dce13980559ed5b3b5d573cf03309772cd3cd642363985860df34674ff0479575d24f1056df7a4a53c1339d0481c8e918a394ff0a1c8cd80a051746678f0e6ad1e5c18a5ba55e793c5e5ffae9231d6b5bf75429ad928a25296cd93b3e7455555ba60b1f6e50118aa6ba9433929d564e067e14f7bcad730d274a677f11a3e8f23172e9aea102829ea678f2560e9cd2d91c5da62bdf5ef138ffba584db72b2f0d04232f8df8c3115813e786052deb55f26dce457e9ff70101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03014600ffffffff02e0673500000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acf8590d000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000', 25 | 'sol_id': 1, 26 | } 27 | ) 28 | 29 | s = Solver() 30 | 31 | for v in vectors: 32 | print("Testing block %s" % v['hash']) 33 | header = binascii.unhexlify(v['block'])[:140] 34 | solution = binascii.unhexlify(v['block'])[143:143+1344] 35 | sol_cnt = s.find_solutions(header) 36 | 37 | for i in range(sol_cnt): 38 | sol = s.get_solution(i) 39 | if i == v['sol_id']: 40 | if sol == solution: 41 | print("Computed solution PASSED") 42 | else: 43 | raise Exception("Solutions are different!") 44 | elif sol == solution: 45 | raise Exception("Something is seriously broken") 46 | 47 | # Recalculate block hash of given block 48 | tohash = binascii.unhexlify(v['block'])[:143+1344] 49 | print(binascii.hexlify(tohash)) 50 | hash = sha256(sha256(tohash).digest()).digest() 51 | print("Block data fits block hash:", binascii.hexlify(hash[::-1]) == v['hash']) 52 | 53 | if __name__ == '__main__': 54 | main() 55 | -------------------------------------------------------------------------------- /epycyzm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Experimental Python CPU (yet) Zcash miner 4 | 5 | Inspired by m0mchil's poclbm (thanks!), but heavily rewritten with asyncio. 6 | 7 | Miner icon: 8 | http://www.flaticon.com/free-icon/miner_206873 9 | 10 | (c) 2016 slush 11 | MIT license 12 | ''' 13 | 14 | import re 15 | import os 16 | import json 17 | import time 18 | import struct 19 | import asyncio 20 | import binascii 21 | import itertools 22 | import traceback 23 | import threading 24 | import multiprocessing 25 | from hashlib import sha256 26 | from optparse import OptionGroup, OptionParser 27 | from concurrent.futures._base import TimeoutError 28 | from asyncio import coroutine, coroutines, futures 29 | 30 | try: 31 | import PySide 32 | gui_enabled = True 33 | except ImportError: 34 | gui_enabled = False 35 | 36 | from morpavsolver import Solver 37 | from version import VERSION 38 | 39 | class Server(object): 40 | @classmethod 41 | def from_url(cls, url): 42 | # Parses proto://user:password@zec.suprnova.cc:1234#tagname 43 | s = cls() 44 | x = re.match(r'^(.*\:\/\/)?((?P.*?)(\:(?P.*?))?\@)?(?P.*?)(\:(?P\d+))?(\#(?P.*?))?$', url) 45 | s.username = x.group('username') or '' 46 | s.password = x.group('password') or '' 47 | s.host = x.group('host') 48 | s.port = int(x.group('port') or s.port) 49 | s.tag = x.group('tag') or s.host # set host if tag not present 50 | return s 51 | 52 | def __repr__(self): 53 | return str(self.__dict__) 54 | 55 | 56 | class ServerSwitcher(object): 57 | def __init__(self, loop, servers, solvers): 58 | self.loop = loop 59 | self.servers = servers 60 | self.solvers = solvers 61 | 62 | @coroutine 63 | def run(self): 64 | for server in itertools.cycle(self.servers): 65 | try: 66 | client = StratumClient(self.loop, server, self.solvers) 67 | yield from client.connect() 68 | except KeyboardInterrupt: 69 | print("Closing...") 70 | self.solvers.stop() 71 | break 72 | except: 73 | traceback.print_exc() 74 | 75 | print("Server connection closed, trying again...") 76 | yield from asyncio.sleep(5) 77 | 78 | class StratumNotifier(object): 79 | def __init__(self, reader, on_notify): 80 | self.waiters = {} 81 | self.on_notify = on_notify 82 | self.reader = reader 83 | self.task = None 84 | 85 | def run(self): 86 | # self.task = asyncio.ensure_future(self.observe()) 87 | self.task = asyncio.async(self.observe()) 88 | return self.task 89 | 90 | @coroutine 91 | def observe(self): 92 | try: 93 | while True: 94 | data = yield from self.reader.readline() 95 | if data == b'': 96 | raise Exception("Server closed connection.") 97 | 98 | try: 99 | msg = json.loads(data.decode()) 100 | except: 101 | raise Exception("Recieved corrupted data from server: %s" % data) 102 | 103 | if msg['id'] == None: 104 | # It is notification 105 | yield from self.on_notify(msg) 106 | else: 107 | # It is response of our call 108 | self.waiters[int(msg['id'])].set_result(msg) 109 | 110 | except Exception as e: 111 | # Do not try to recover from errors, let ServerSwitcher handle this 112 | traceback.print_exc() 113 | raise 114 | 115 | @coroutine 116 | def wait_for(self, msg_id): 117 | f = asyncio.Future() 118 | self.waiters[msg_id] = f 119 | return (yield from asyncio.wait_for(f, 10)) 120 | 121 | class Job(object): 122 | @classmethod 123 | def from_params(cls, params): 124 | j = cls() 125 | j.job_id = params[0] 126 | j.version = binascii.unhexlify(params[1]) 127 | j.prev_hash = binascii.unhexlify(params[2]) 128 | j.merkle_root = binascii.unhexlify(params[3]) 129 | j.reserved = binascii.unhexlify(params[4]) 130 | j.ntime = binascii.unhexlify(params[5]) 131 | j.nbits = binascii.unhexlify(params[6]) 132 | j.clean_job = bool(params[7]) 133 | 134 | assert (len(j.version) == 4) 135 | assert (len(j.prev_hash) == 32) 136 | assert (len(j.merkle_root) == 32) 137 | assert (len(j.reserved) == 32) 138 | assert (len(j.ntime) == 4) 139 | assert (len(j.nbits) == 4) 140 | 141 | return j 142 | 143 | def set_target(self, target): 144 | self.target = target 145 | 146 | def build_header(self, nonce): 147 | assert(len(nonce) == 32) 148 | 149 | header = self.version + self.prev_hash + self.merkle_root + self.reserved + self.ntime + self.nbits + nonce 150 | assert(len(header) == 140) 151 | return header 152 | 153 | @classmethod 154 | def is_valid(cls, header, solution, target): 155 | assert (len(header) == 140) 156 | assert (len(solution) == 1344 + 3) 157 | 158 | hash = sha256(sha256(header + solution).digest()).digest() 159 | print("hash %064x" % int.from_bytes(hash, 'little')) 160 | 161 | return int.from_bytes(hash, 'little') < target 162 | 163 | def __repr__(self): 164 | return str(self.__dict__) 165 | 166 | class CpuSolver(threading.Thread): 167 | def __init__(self, loop, counter): 168 | super(CpuSolver, self).__init__() 169 | self._stop = False 170 | self.loop = loop 171 | self.counter = counter 172 | 173 | self.job = None 174 | self.nonce1 = None 175 | self.nonce2_int = 0 176 | self.on_share = None 177 | 178 | def stop(self): 179 | raise Exception("FIXME") 180 | self._stop = True 181 | 182 | def set_nonce(self, nonce1): 183 | self.nonce1 = nonce1 184 | 185 | def new_job(self, job, solver_nonce, on_share): 186 | self.job = job 187 | self.solver_nonce = solver_nonce 188 | self.on_share = on_share 189 | 190 | def increase_nonce(self): 191 | if self.nonce2_int > 2**62: 192 | self.nonce2_int = 0 193 | 194 | self.nonce2_int += 1 195 | return struct.pack('>q', self.nonce2_int) 196 | 197 | def run(self): 198 | print("Starting CPU solver") 199 | s = Solver() 200 | 201 | while self.job == None or self.nonce1 == None: 202 | time.sleep(2) 203 | print(".", end='', flush=True) 204 | 205 | while not self._stop: 206 | nonce2 = self.increase_nonce() 207 | nonce2 = nonce2.rjust(32 - len(self.nonce1) - len(self.solver_nonce), b'\0') 208 | 209 | header = self.job.build_header(self.nonce1 + self.solver_nonce + nonce2) 210 | 211 | sol_cnt = s.find_solutions(header) 212 | self.counter(sol_cnt) # Increase counter for stats 213 | 214 | for i in range(sol_cnt): 215 | solution = b'\xfd\x40\x05' + s.get_solution(i) 216 | 217 | if self.job.is_valid(header, solution, self.job.target): 218 | print("FOUND VALID SOLUTION!") 219 | # asyncio.run_coroutine_threadsafe(self.on_share(self.job, self.solver_nonce + nonce2, solution), self.loop) 220 | asyncio.async(self.on_share(self.job, self.solver_nonce + nonce2, solution), loop=self.loop) 221 | 222 | class SolverPool(object): 223 | def __init__(self, loop, gpus=0, cpus=0): 224 | self.solvers = [] 225 | self.time_start = time.time() 226 | self.solutions = 0 227 | 228 | for i in range(cpus): 229 | s = CpuSolver(loop, self.inc_solutions) 230 | s.start() 231 | self.solvers.append(s) 232 | 233 | def inc_solutions(self, i): 234 | self.solutions += i 235 | print("%.02f H/s" % (self.solutions / (time.time() - self.time_start))) 236 | 237 | def set_nonce(self, nonce1): 238 | for i, s in enumerate(self.solvers): 239 | s.set_nonce(nonce1) 240 | 241 | def new_job(self, job, on_share): 242 | for i, s in enumerate(self.solvers): 243 | s.new_job(job, 244 | # Generate unique nonce1 for each solver 245 | struct.pack('>B', i), 246 | on_share) 247 | 248 | def stop(self): 249 | for s in self.solvers: 250 | s.stop() 251 | 252 | # Stratum protocol specification: https://github.com/zcash/zips/pull/78 253 | class StratumClient(object): 254 | def __init__(self, loop, server, solvers): 255 | self.loop = loop 256 | self.server = server 257 | self.solvers = solvers 258 | self.msg_id = 0 # counter of stratum messages 259 | 260 | self.writer = None 261 | self.notifier = None 262 | 263 | @coroutine 264 | def connect(self): 265 | print("Connecting to", self.server) 266 | asyncio.open_connection() 267 | reader, self.writer = yield from asyncio.open_connection(self.server.host, self.server.port, loop=self.loop) 268 | 269 | # Observe and route incoming message 270 | self.notifier = StratumNotifier(reader, self.on_notify) 271 | self.notifier.run() 272 | 273 | yield from self.subscribe() 274 | yield from self.authorize() 275 | 276 | while True: 277 | yield from asyncio.sleep(1) 278 | 279 | if self.notifier.task.done(): 280 | # Notifier failed or wanted to stop procesing 281 | # Let ServerSwitcher catch this and round-robin connection 282 | raise self.notifier.task.exception() or Exception("StratumNotifier failed, restarting.") 283 | 284 | def new_id(self): 285 | self.msg_id += 1 286 | return self.msg_id 287 | 288 | def close(self): 289 | print('Close the socket') 290 | self.writer.close() 291 | 292 | @coroutine 293 | def on_notify(self, msg): 294 | if msg['method'] == 'mining.notify': 295 | print("Giving new job to solvers") 296 | j = Job.from_params(msg['params']) 297 | j.set_target(self.target) 298 | self.solvers.new_job(j, self.submit) 299 | return 300 | 301 | if msg['method'] == 'mining.set_target': 302 | print("Received set.target") 303 | self.target = int.from_bytes(binascii.unhexlify(msg['params'][0]), 'big') 304 | return 305 | 306 | print("Received unknown notification", msg) 307 | 308 | @coroutine 309 | def authorize(self): 310 | ret = yield from self.call('mining.authorize', self.server.username, self.server.password) 311 | if ret['result'] != True: 312 | raise Exception("Authorization failed: %s" % ret['error']) 313 | print("Successfully authorized as %s" % self.server.username) 314 | 315 | @coroutine 316 | def subscribe(self): 317 | ret = yield from self.call('mining.subscribe', VERSION, None, self.server.host, self.server.port) 318 | nonce1 = binascii.unhexlify(ret['result'][1]) 319 | print("Successfully subscribed for jobs") 320 | self.solvers.set_nonce(nonce1) 321 | return nonce1 322 | 323 | @coroutine 324 | def submit(self, job, nonce2, solution): 325 | t = time.time() 326 | 327 | ret = yield from self.call('mining.submit', 328 | self.server.username, 329 | job.job_id, 330 | binascii.hexlify(job.ntime).decode('utf-8'), 331 | binascii.hexlify(nonce2).decode('utf-8'), 332 | binascii.hexlify(solution).decode('utf-8')) 333 | if ret['result'] == True: 334 | print("Share ACCEPTED in %.02fs" % (time.time() - t)) 335 | else: 336 | print("Share REJECTED in %.02fs" % (time.time() - t)) 337 | 338 | @coroutine 339 | def call(self, method, *params): 340 | msg_id = self.new_id() 341 | msg = {"id": msg_id, 342 | "method": method, 343 | "params": params} 344 | 345 | data = "%s\n" % json.dumps(msg) 346 | print('< %s' % data[:200] + (data[200:] and "...\n"), end='') 347 | self.writer.write(data.encode()) 348 | 349 | try: 350 | #r = asyncio.ensure_future(self.notifier.wait_for(msg_id)) 351 | r = asyncio.async(self.notifier.wait_for(msg_id)) 352 | yield from asyncio.wait([r, self.notifier.task], timeout=30, return_when=asyncio.FIRST_COMPLETED) 353 | 354 | if self.notifier.task.done(): 355 | raise self.notifier.task.exception() 356 | 357 | data = r.result() 358 | log = '> %s' % data 359 | print(log[:100] + (log[100:] and '...')) 360 | 361 | except TimeoutError: 362 | raise Exception("Request to server timed out.") 363 | 364 | return data 365 | 366 | def main(): 367 | usage = "usage: %prog [OPTION]... SERVER[#tag]...\n" \ 368 | "SERVER is one or more [stratum+tcp://]user:pass@host:port (required)\n" \ 369 | "[#tag] is a per SERVER user friendly name displayed in stats (optional)\n" \ 370 | "Example usage: %prog stratum+tcp://slush.miner1:password@zcash.slushpool.com:4444" 371 | 372 | parser = OptionParser(version=VERSION, usage=usage) 373 | parser.add_option('-g', '--disable-gui', dest='nogui', action='store_true', help='Disable graphical interface, use console only') 374 | parser.add_option('-c', '--cpu', dest='cpu', default=0, help='How many CPU solvers to start (-1=disabled, 0=auto)', type='int') 375 | parser.add_option('-n', '--nice', dest='nice', default=0, help="Niceness of the process (Linux only)", type='int') 376 | 377 | #parser.add_option('--verbose', dest='verbose', action='store_true', help='verbose output, suitable for redirection to log file') 378 | #parser.add_option('-q', '--quiet', dest='quiet', action='store_true', help='suppress all output except hash rate display') 379 | #parser.add_option('--proxy', dest='proxy', default='', help='specify as [[socks4|socks5|http://]user:pass@]host:port (default proto is socks5)') 380 | #parser.add_option('--no-ocl', dest='no_ocl', action='store_true', help="don't use OpenCL") 381 | #parser.add_option('-d', '--device', dest='device', default=[], help='comma separated device IDs, by default will use all (for OpenCL - only GPU devices)') 382 | 383 | #group = OptionGroup(parser, "Miner Options") 384 | #group.add_option('-r', '--rate', dest='rate', default=1, help='hash rate display interval in seconds, default=1 (60 with --verbose)', type='float') 385 | #group.add_option('-e', '--estimate', dest='estimate', default=900, help='estimated rate time window in seconds, default 900 (15 minutes)', type='int') 386 | #group.add_option('-t', '--tolerance', dest='tolerance', default=2, help='use fallback pool only after N consecutive connection errors, default 2', type='int') 387 | #group.add_option('-b', '--failback', dest='failback', default=60, help='attempt to fail back to the primary pool after N seconds, default 60', type='int') 388 | #group.add_option('--cutoff-temp', dest='cutoff_temp',default=[], help='AMD GPUs only. For GPUs requires github.com/mjmvisser/adl3. Comma separated temperatures at which to skip kernel execution, in C, default=95') 389 | #group.add_option('--cutoff-interval', dest='cutoff_interval',default=[], help='how long to not execute calculations if CUTOFF_TEMP is reached, in seconds, default=0.01') 390 | #group.add_option('--no-server-failbacks', dest='nsf', action='store_true', help='disable using failback hosts provided by server') 391 | #parser.add_option_group(group) 392 | 393 | #group = OptionGroup(parser, 394 | # "OpenCL Options", 395 | # "Every option except 'platform' and 'vectors' can be specified as a comma separated list. " 396 | # "If there aren't enough entries specified, the last available is used. " 397 | # "Use --vv to specify per-device vectors usage." 398 | #) 399 | #group.add_option('-p', '--platform', dest='platform', default=-1, help='use platform by id', type='int') 400 | #group.add_option('-w', '--worksize', dest='worksize', default=[], help='work group size, default is maximum returned by OpenCL') 401 | #group.add_option('-f', '--frames', dest='frames', default=[], help='will try to bring single kernel execution to 1/frames seconds, default=30, increase this for less desktop lag') 402 | #group.add_option('-s', '--sleep', dest='frameSleep', default=[], help='sleep per frame in seconds, default 0') 403 | #group.add_option('--vv', dest='vectors', default=[], help='use vectors, default false') 404 | #group.add_option('-v', '--vectors', dest='old_vectors',action='store_true', help='use vectors') 405 | #parser.add_option_group(group) 406 | #options.rate = max(options.rate, 60) if options.verbose else max(options.rate, 0.1) 407 | #options.max_update_time = 60 408 | #options.device = tokenize(options.device, 'device', []) 409 | #options.cutoff_temp = tokenize(options.cutoff_temp, 'cutoff_temp', [95], float) 410 | #options.cutoff_interval = tokenize(options.cutoff_interval, 'cutoff_interval', [0.01], float) 411 | 412 | (options, options.servers) = parser.parse_args() 413 | options.version = VERSION 414 | 415 | servers = [ Server.from_url(s) for s in options.servers] 416 | if len(servers) == 0: 417 | parser.print_usage() 418 | return 419 | 420 | if options.nice is not 0: 421 | print("Setting proces niceness to %d" % os.nice(options.nice)) 422 | 423 | if options.cpu == -1: 424 | cpus = 0 425 | elif options.cpu == 0: 426 | cpus = multiprocessing.cpu_count() 427 | else: 428 | cpus = options.cpu 429 | 430 | global gui_enabled 431 | if options.nogui: 432 | gui_enabled = False 433 | elif not gui_enabled: 434 | print("GUI disabled, please install PySide/Qt first.") 435 | 436 | print("Using %d CPU solver instances" % cpus) 437 | print(servers) 438 | 439 | loop = asyncio.get_event_loop() 440 | 441 | solvers = SolverPool(loop, gpus=0, cpus=cpus) 442 | switcher = ServerSwitcher(loop, servers, solvers) 443 | loop.run_until_complete(switcher.run()) 444 | 445 | loop.close() 446 | 447 | if __name__ == '__main__': 448 | main() 449 | --------------------------------------------------------------------------------