forked from MentalCollatz/odo-miner
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2e41a9b
commit 511c973
Showing
7 changed files
with
709 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
*.[oa] | ||
*.db_info | ||
*.pyc | ||
__pycache__ | ||
src/verilog/odo_gen | ||
src/pool/fakepool | ||
src/projects/*/build_files/* | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# Copyright (C) 2019 MentalCollatz | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
import argparse | ||
from base64 import b64encode | ||
import os | ||
import platform | ||
import sys | ||
|
||
from template import Script | ||
|
||
DEFAULT_LISTEN_PORT = 17064 | ||
|
||
MAINNET_RPC_PORT = 14022 | ||
TESTNET_RPC_PORT = 18332 | ||
|
||
MAINNET_ADDR_FORMAT = {"bech32_hrp": "dgb", "prefix_pubkey": 30, "prefix_script": 63 } | ||
TESTNET_ADDR_FORMAT = {"bech32_hrp": "dgbt", "prefix_pubkey": 126, "prefix_script": 140 } | ||
|
||
def data_dir(): | ||
if platform.system() == "Windows": | ||
return os.path.join(os.environ["APPDATA"], "DigiByte") | ||
elif platform.system() == "Darwin": | ||
return os.path.expanduser("~/Library/Application Support/DigiByte/") | ||
else: | ||
return os.path.expanduser("~/.digibyte/") | ||
|
||
# base64 encode that works the same in both Python 2 and 3 | ||
def b64encode_helper(s): | ||
res = b64encode(s.encode()) | ||
if type(res) == bytes: | ||
res = res.decode() | ||
return res | ||
|
||
params = {} | ||
|
||
def init(argv): | ||
parser = argparse.ArgumentParser(description="Solo-mining pool.") | ||
parser.add_argument("-t", "--testnet", help="use testnet params", action="store_true") | ||
parser.add_argument("-H", "--host", help="rpc host", dest="rpc_host", default="localhost") | ||
parser.add_argument("-p", "--port", help="rpc port", dest="rpc_port", type=int) | ||
parser.add_argument("--user", help="rpc user (discouraged, --auth is preferred)") | ||
parser.add_argument("--password", help="rpc password (discouraged, --auth is preferred)") | ||
parser.add_argument("-a", "--auth", help="rpc authorization file", type=argparse.FileType("r")) | ||
parser.add_argument("-l", "--listen", help="port to listen for miners on", dest="listen_port", default=DEFAULT_LISTEN_PORT, type=int) | ||
parser.add_argument("-r", "--remote", help="allow remote miners to connect", action="store_true") | ||
parser.add_argument("--coinbase", help="coinbase string", type=str, default="/odo-miner-solo/") | ||
parser.add_argument("address", help="address to mine to", type=str) | ||
args = parser.parse_args(argv[1:]) | ||
|
||
global params | ||
params = {key: getattr(args, key) for key in ["rpc_host", "listen_port", "testnet"]} | ||
|
||
addr_format = TESTNET_ADDR_FORMAT if args.testnet else MAINNET_ADDR_FORMAT | ||
cbscript = Script.from_address(args.address, **addr_format) | ||
if cbscript is None: | ||
other_addr_format = MAINNET_ADDR_FORMAT if args.testnet else TESTNET_ADDR_FORMAT | ||
cbscript = Script.from_address(args.address, **other_addr_format) | ||
if cbscript is not None: | ||
if args.testnet: | ||
parser.error("mainnet address specified with --testnet") | ||
else: | ||
parser.error("testnet address specified without --testnet") | ||
else: | ||
parser.error("invalid address") | ||
params["cbscript"] = cbscript.data | ||
params["cbstring"] = args.coinbase | ||
|
||
if args.user and args.password: | ||
if args.auth: | ||
parser.error("argument --auth is not allowed with arguments --user and --password") | ||
if ':' in args.user: | ||
parser.error("user may not contain `:`") | ||
rpc_auth = args.user + ':' + args.password | ||
elif args.user or args.password: | ||
parser.error("--user and --password must both be present or neither present") | ||
elif args.auth: | ||
rpc_auth = args.auth.read() | ||
else: | ||
cookie = data_dir() | ||
if args.testnet: | ||
cookie = os.path.join(cookie, "testnet3") | ||
cookie = os.path.join(cookie, ".cookie") | ||
try: | ||
with open(cookie, "r") as f: | ||
# Note: if the user restarts the server, they will need to | ||
# restart the pool also | ||
rpc_auth = f.read() | ||
except IOError as e: | ||
parser.error("Unable to read default auth file `%s`, please specify auth file or user and password.") | ||
params["rpc_auth"] = "Basic " + b64encode_helper(rpc_auth) | ||
|
||
if args.rpc_port: | ||
rpc_port = args.rpc_port | ||
else: | ||
rpc_port = TESTNET_RPC_PORT if args.testnet else MAINNET_RPC_PORT | ||
params["rpc_port"] = rpc_port | ||
params["rpc_url"] = "http://%s:%d" % (params["rpc_host"], params["rpc_port"]) | ||
|
||
params["bind_addr"] = "" if args.remote else "localhost" | ||
|
||
def get(key): | ||
global params | ||
if not params: | ||
init(sys.argv) | ||
return params[key] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
#!/usr/bin/env python | ||
|
||
# Copyright (C) 2019 MentalCollatz | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
import socket | ||
import threading | ||
import time | ||
|
||
import config | ||
import rpc | ||
from template import BlockTemplate | ||
|
||
def get_templates(callback): | ||
longpollid = None | ||
last_errno = None | ||
while True: | ||
try: | ||
template = rpc.get_block_template(longpollid) | ||
if "coinbaseaux" not in template: | ||
template["coinbaseaux"] = {} | ||
template["coinbaseaux"]["cbstring"] = config.get("cbstring") | ||
callback(template) | ||
longpollid = template["longpollid"] | ||
if last_errno != 0: | ||
print("%s: successfully acquired template" % time.asctime()) | ||
last_errno = 0 | ||
except (rpc.RpcError, socket.error) as e: | ||
if last_errno == 0: | ||
callback(None) | ||
if e.errno != last_errno: | ||
last_errno = e.errno | ||
print("%s: %s (errno %d)" % (time.asctime(), e.strerror, e.errno)) | ||
time.sleep(1) | ||
|
||
class Manager(threading.Thread): | ||
def __init__(self, cbscript): | ||
threading.Thread.__init__(self) | ||
self.cbscript = cbscript | ||
self.template = None | ||
self.extra_nonce = 0 | ||
self.miners = [] | ||
self.cond = threading.Condition() | ||
|
||
def add_miner(self, miner): | ||
with self.cond: | ||
self.miners.append(miner) | ||
self.cond.notify() | ||
|
||
def remove_miner(self, miner): | ||
with self.cond: | ||
self.miners.remove(miner) | ||
|
||
def push_template(self, template): | ||
with self.cond: | ||
if template is None: | ||
self.template = None | ||
else: | ||
self.template = BlockTemplate(template, self.cbscript) | ||
self.extra_nonce = 0 | ||
for miner in self.miners: | ||
miner.next_refresh = 0 | ||
self.cond.notify() | ||
|
||
def run(self): | ||
while True: | ||
with self.cond: | ||
now = time.time() | ||
next_refresh = now + 1000 | ||
for miner in self.miners: | ||
if miner.next_refresh < now: | ||
miner.push_work(self.template, self.extra_nonce) | ||
self.extra_nonce += 1 | ||
next_refresh = min(next_refresh, miner.next_refresh) | ||
wait_time = max(0, next_refresh - time.time()) | ||
self.cond.wait(wait_time) | ||
|
||
class Miner(threading.Thread): | ||
def __init__(self, conn, manager): | ||
threading.Thread.__init__(self) | ||
self.conn = conn | ||
self.manager = manager | ||
self.lock = threading.Lock() | ||
self.conn_lock = threading.Lock() | ||
self.work_items = [] | ||
self.next_refresh = 0 | ||
self.refresh_interval = 10 | ||
manager.add_miner(self) | ||
self.start() | ||
|
||
def push_work(self, template, extra_nonce): | ||
if template is None: | ||
workstr = "work %s %s %d" % ("0"*64, "0"*64, 0) | ||
else: | ||
work = template.get_work(extra_nonce) | ||
workstr = "work %s %s %d" % (work, template.target, template.odo_key) | ||
with self.lock: | ||
if template is None: | ||
self.work_items = [] | ||
else: | ||
self.work_items.insert(0, (work, template, extra_nonce)) | ||
if len(self.work_items) > 2: | ||
self.work_items.pop() | ||
self.next_refresh = time.time() + self.refresh_interval | ||
try: | ||
self.send(workstr) | ||
except socket.error as e: | ||
# let the other thread clean it up | ||
pass | ||
|
||
def send(self, s): | ||
with self.conn_lock: | ||
self.conn.sendall((s + "\n").encode()) | ||
|
||
def submit(self, work): | ||
with self.lock: | ||
for work_item in self.work_items: | ||
if work_item[0][0:152] == work[0:152]: | ||
template = work_item[1] | ||
extra_nonce = work_item[2] | ||
submit_data = work + template.get_data(extra_nonce) | ||
break | ||
else: | ||
return "stale" | ||
try: | ||
return rpc.submit_work(submit_data) | ||
except (rpc.RpcError, socket.error) as e: | ||
print("failed to submit: %s (errno %d)" % (e.strerror, e.errno)); | ||
return "error" | ||
|
||
def run(self): | ||
while True: | ||
try: | ||
data = self.conn.makefile().readline().rstrip() | ||
if not data: | ||
break | ||
parts = data.split() | ||
command, args = parts[0], parts[1:] | ||
if command == "submit" and len(args) == 1: | ||
result = self.submit(*args) | ||
self.send("result %s" % result) | ||
else: | ||
print("unknown command: %s" % data) | ||
except socket.error as e: | ||
break | ||
self.manager.remove_miner(self) | ||
self.conn.close() | ||
|
||
if __name__ == "__main__": | ||
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
listener.bind((config.get("bind_addr"), config.get("listen_port"))) | ||
listener.listen(10) | ||
|
||
manager = Manager(config.get("cbscript")) | ||
manager.start() | ||
|
||
callback = lambda t: manager.push_template(t) | ||
threading.Thread(target=get_templates, args=(callback,)).start() | ||
|
||
while True: | ||
conn, addr = listener.accept() | ||
Miner(conn, manager) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Copyright (C) 2019 MentalCollatz | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
import requests | ||
import json | ||
|
||
import config | ||
|
||
class RpcError(Exception): | ||
def __init__(self, **kwargs): | ||
self.strerror = kwargs["message"] | ||
self.errno = kwargs["code"] | ||
|
||
def json_request(method, *params): | ||
jdata = {"method": method, "params": params} | ||
headers = {"Content-Type": "application/json", "Authorization": config.get("rpc_auth")} | ||
response = requests.post(config.get("rpc_url"), headers=headers, json=jdata) | ||
|
||
try: | ||
data = response.json() | ||
except ValueError as e: | ||
if response.status_code != requests.codes.ok: | ||
raise RpcError(code=response.status_code, message="HTTP status code") | ||
raise RpcError(code=500, message=str(e)) | ||
|
||
if data["error"] is None: | ||
return data["result"] | ||
raise RpcError(**data["error"]) | ||
|
||
def get_block_template(longpollid): | ||
params = {"rules":["segwit"]} | ||
algo = "odo" | ||
if longpollid is not None: | ||
params["longpollid"] = longpollid | ||
return json_request("getblocktemplate", params, algo) | ||
|
||
def submit_work(submit_data): | ||
return json_request("submitblock", submit_data) or "accepted" | ||
|
Oops, something went wrong.