diff --git a/.gitignore b/.gitignore index 6cf9a27..4e58383 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ nimcache/ nimblecache/ htmldocs/ -compile* \ No newline at end of file +compile* +ducominer.exe +ducominer \ No newline at end of file diff --git a/ducominer.nim b/ducominer.nim index d87a940..1ec577b 100644 --- a/ducominer.nim +++ b/ducominer.nim @@ -1,42 +1,104 @@ -import hashlib/rhash/sha1 -import net, httpclient -import json -import strutils, strformat -import threadpool -import os - -proc recvAll(s: Socket): string = # Function for receiving an arbitrary amount of data from the socket - var res = "" - res = res & s.recv(1, timeout=45000) - while s.hasDataBuffered(): - res = res & s.recv(1, timeout=45000) - return res +import std / [ + net, + strutils, strformat, strscans, + threadpool, atomics, + os, times, + json, httpclient +] + +import nimcrypto/sha + +const + monitorInterval = 7500 + +var + startTime: Time + acceptedCnt, rejectedCnt, hashesCnt, currentDifficulty: Atomic[int] -proc mine(username: string, pool_ip: string, pool_port: Port, difficulty: string, miner_name: string) {.thread.} = # Mining functions executed in multiple threads - var soc: Socket = newSocket() # Creating a new TCP socket - soc.connect(pool_ip, pool_port) # Connecting to the mining server - discard soc.recv(3, timeout=45000) # Receiving the server version and voiding it +# Function for receiving an arbitrary amount of data from the socket +proc recvAll(s: Socket): string = + result = s.recv(1, timeout=30000) + while s.hasDataBuffered(): + result &= s.recv(1, timeout=30000) - echo fmt"Thread #{getThreadId()} connected to {pool_ip}:{pool_port}" +proc minerThread(username: string, pool_ip: string, pool_port: Port, difficulty: string, miner_name: string) {.thread.} = # Mining functions executed in multiple threads + # Connecting to the server and discarding the server verison + var soc: Socket = newSocket() + soc.connect(pool_ip, pool_port) + discard soc.recvAll() - var job: seq[string] - var feedback: string + # echo fmt"Thread #{getThreadId()} connected to {pool_ip}:{pool_port}" - while true: # An infinite loop of requesting and solving jobs - if difficulty == "NORMAL": # Checking if the difficulty is set to "NORMAL" and sending a job request to the server + while true: + # Checking if the difficulty is set to "NORMAL" and sending a job request to the server + if difficulty == "NORMAL": soc.send(fmt"JOB,{username}") else: soc.send(fmt"JOB,{username},{difficulty}") - job = soc.recvAll().split(",") # Receiving a job from the server that is comma-separated - for result in 0..100 * parseInt(job[2]): # A loop for solving the job - if $count[RHASH_SHA1](job[0] & $(result)) == job[1]: # Checking if the hashes of the job matches our hash - soc.send($(result) & ",," & miner_name) # Sending the result to the server - feedback = soc.recvAll() # Receiving feedback from the server + + # Receiving and parsing the job from the server + var job = soc.recvAll() + var + prefix, target: string + diff: int + if not scanf(job, "$+,$+,$i", prefix, target, diff): + quit("Error: couldn't parse job from the server!") + target = target.toUpper() + currentDifficulty.store(diff) + + # Initialize the sha1 context and add prefix + var ctx: sha1 + ctx.init() + ctx.update(prefix) + + # A loop for solving the job + for res in 0 .. 100 * diff: + # Copy the initialized context and add the value + var ctxCopy = ctx + ctxCopy.update($res) + + # Checking if the hash of the job matches our hash + if $ctxCopy.finish() == target: + hashesCnt.atomicInc(res) + soc.send(fmt"{$res},,{miner_name}") + + # Receiving and checking the feedback from the server + let feedback = soc.recvAll() if feedback == "GOOD": # Checking the server feedback - echo fmt"Accepted share {result} with a difficulty of {parseInt(job[2])}" + acceptedCnt.atomicInc() elif feedback == "BAD": - echo fmt"Rejected share {result} with a difficulty of {parseInt(job[2])}" - break # Breaking from the loop, as the job was solved + rejectedCnt.atomicInc() + + # Breaking from the loop, because the job was solved + break + + +proc monitorThread() {.thread.} = + startTime = getTime() + echo fmt"Statistics update interval: {monitorInterval / 1000} seconds" + while true: + sleep(monitorInterval) + # Get time diff in milliseconds + let mils = (getTime() - startTime).inMilliseconds.float + + # Calculate amount of hashes per second + let hashesSec = (hashesCnt.load().float / mils) * 1000 + let khsec = hashesSec / 1000 + let mhsec = khsec / 1000 + let toShow = if mhsec >= 1: + mhsec.formatFloat(ffDecimal, 2) & " MH/s" + elif khsec >= 1: + khsec.formatFloat(ffDecimal, 2) & " KH/s" + else: + hashesSec.formatFloat(ffDecimal, 2) & " H/s" + + startTime = getTime() + let strTime = startTime.format("HH:mm:ss") + echo fmt"{strTime} Hashrate: {toShow}, Accepted: {acceptedCnt.load()}, Rejected: {rejectedCnt.load()}, Difficulty: {currentDifficulty.load()}" + + # Resetting hash count + hashesCnt.store(0) + var config: JsonNode if paramCount() < 1: @@ -54,16 +116,22 @@ else: let client: HttpClient = newHttpClient() # Creating a new HTTP client -var pool_address: string = client.getContent(config["ip_url"].getStr()) # Making a request to the URL specified in the config for getting mining server details +var pool_address: seq[string] = client.getContent(config["ip_url"].getStr()).split("\n") # Making a request to the URL specified in the config for getting mining server details + +var pool_ip: string = pool_address[0] # Parsing the server IP +var pool_port: Port = Port(parseInt(pool_address[1])) # Parsing the server port -var pool_ip: string = pool_address.split("\n")[0] # Parsing the server IP -var pool_port: Port = Port(parseInt(pool_address.split("\n")[1])) # Parsing the server port +var username = config["username"].getStr() +var difficulty = config["difficulty"].getStr() +var miner_name = config["miner_name"].getStr() +var thread_count = config["thread_count"].getInt() -var username = config["username"].getStr(default = "5Q") -var difficulty = config["difficulty"].getStr(default = "NORMAL") -var miner_name = config["miner_name"].getStr(default = "DUCOMiner-Nim") -var thread_count = config["thread_count"].getInt(default = 16) +# Starting mining threads and the monitor thread +for i in 0 ..< thread_count: + spawn minerThread(username, pool_ip, pool_port, difficulty, miner_name) + sleep(300) +echo "Started all mining threads" +spawn monitorThread() -for i in countup(0, thread_count - 1): # A loop that spawns new threads executing the mine() function - spawn mine(username, pool_ip, pool_port, difficulty, miner_name) -sync() # Synchronizing the threads so the program doesn't exit until Ctrl+C is pressed or an exception is raised \ No newline at end of file +# Synchronizing the threads so the program doesn't exit until Ctrl+C is pressed or an exception is raised +sync() \ No newline at end of file diff --git a/ducominer.nim.cfg b/ducominer.nim.cfg index 1b4da38..4eeecc3 100644 --- a/ducominer.nim.cfg +++ b/ducominer.nim.cfg @@ -1,2 +1,2 @@ -d:ssl ---threads:on +--threads:on \ No newline at end of file diff --git a/ducominer.nimble b/ducominer.nimble index 86c3150..da84ae5 100644 --- a/ducominer.nimble +++ b/ducominer.nimble @@ -1,5 +1,5 @@ # Package Information -version = "1.0.0" +version = "1.2.0" author = "its5Q" description = "A multithreaded miner for DuinoCoin written in Nim." license = "MIT" @@ -7,4 +7,4 @@ license = "MIT" bin = @["ducominer"] # Dependencies -requires "hashlib" \ No newline at end of file +requires "nimcrypto" \ No newline at end of file