/*
* DiabloMiner - OpenCL miner for Bitcoin
* Copyright (C) 2010, 2011, 2012 Patrick McFarland <diablod3@gmail.com>
*
* 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/>.
*/
package com.diablominer.DiabloMiner;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.URL;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Formatter;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import com.diablominer.DiabloMiner.DeviceState.DeviceState;
import com.diablominer.DiabloMiner.DeviceState.GPUHardwareType;
import com.diablominer.DiabloMiner.NetworkState.JSONRPCNetworkState;
import com.diablominer.DiabloMiner.NetworkState.NetworkState;
public class DiabloMiner {
public final static long TWO32 = 4294967295L;
public final static long TIME_OFFSET = 7500;
NetworkState networkStateHead = null;
NetworkState networkStateTail = null;
Proxy proxy = null;
int workLifetime = 5000;
boolean debug = false;
boolean debugtimer = false;
double GPUTargetFPS = 30.0;
double GPUTargetFPSBasis;
int GPUForceWorkSize = 0;
Integer GPUVectors[] = null;
boolean GPUNoArray = false;
boolean GPUDebugSource = false;
String source;
AtomicBoolean running = new AtomicBoolean(true);
List<Thread> threads = new ArrayList<Thread>();
long startTime;
AtomicLong blocks = new AtomicLong(0);
AtomicLong attempts = new AtomicLong(0);
AtomicLong rejects = new AtomicLong(0);
AtomicLong hwErrors = new AtomicLong(0);
Set<String> enabledDevices = null;
AtomicLong hashCount = new AtomicLong(0);
final static String CLEAR = " ";
public static void main(String[] args) throws Exception {
DiabloMiner diabloMiner = new DiabloMiner();
try {
diabloMiner.execute(args);
} catch(DiabloMinerFatalException e) {
System.exit(-1);
}
}
void execute(String[] args) throws Exception {
threads.add(Thread.currentThread());
Options options = new Options();
options.addOption("u", "user", true, "bitcoin host username");
options.addOption("p", "pass", true, "bitcoin host password");
options.addOption("o", "host", true, "bitcoin host IP");
options.addOption("r", "port", true, "bitcoin host port");
options.addOption("l", "url", true, "bitcoin host url");
options.addOption("x", "proxy", true, "optional proxy settings IP:PORT<:username:password>");
options.addOption("g", "worklifetime", true, "maximum work lifetime in seconds");
options.addOption("d", "debug", false, "enable debug output");
options.addOption("dt", "debugtimer", false, "run for 1 minute and quit");
options.addOption("D", "devices", true, "devices to enable, default all");
options.addOption("f", "fps", true, "target GPU execution timing");
options.addOption("na", "noarray", false, "turn GPU kernel array off");
options.addOption("v", "vectors", true, "vector size in GPU kernel");
options.addOption("w", "worksize", true, "override GPU worksize");
options.addOption("ds", "ksource", false, "output GPU kernel source and quit");
options.addOption("h", "help", false, "this help");
PosixParser parser = new PosixParser();
CommandLine line = null;
try {
line = parser.parse(options, args);
if(line.hasOption("help")) {
throw new ParseException("");
}
} catch(ParseException e) {
System.out.println(e.getLocalizedMessage() + "\n");
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("DiabloMiner -u myuser -p mypassword [args]\n", "", options, "\nRemember to set rpcuser and rpcpassword in your ~/.bitcoin/bitcoin.conf " + "before starting bitcoind or bitcoin --daemon");
return;
}
String splitUrl[] = null;
String splitUser[] = null;
String splitPass[] = null;
String splitHost[] = null;
String splitPort[] = null;
if(line.hasOption("url"))
splitUrl = line.getOptionValue("url").split(",");
if(line.hasOption("user"))
splitUser = line.getOptionValue("user").split(",");
if(line.hasOption("pass"))
splitPass = line.getOptionValue("pass").split(",");
if(line.hasOption("host"))
splitHost = line.getOptionValue("host").split(",");
if(line.hasOption("port"))
splitPort = line.getOptionValue("port").split(",");
int networkStatesCount = 0;
if(splitUrl != null)
networkStatesCount = splitUrl.length;
if(splitUser != null)
networkStatesCount = Math.max(splitUser.length, networkStatesCount);
if(splitPass != null)
networkStatesCount = Math.max(splitPass.length, networkStatesCount);
if(splitHost != null)
networkStatesCount = Math.max(splitHost.length, networkStatesCount);
if(splitPort != null)
networkStatesCount = Math.max(splitPort.length, networkStatesCount);
if(networkStatesCount == 0) {
error("You forgot to give any bitcoin connection info, please add either -l, or -u -p -o and -r");
System.exit(-1);
}
int j = 0;
for(int i = 0; j < networkStatesCount; i++, j++) {
String protocol = "http";
String host = "localhost";
int port = 8332;
String path = "/";
String user = "diablominer";
String pass = "diablominer";
byte hostChain = 0;
if(splitUrl != null && splitUrl.length > i) {
String[] usernameFix = splitUrl[i].split("@", 3);
if(usernameFix.length > 2)
splitUrl[i] = usernameFix[0] + "+++++" + usernameFix[1] + "@" + usernameFix[2];
URL url = new URL(splitUrl[i]);
if(url.getProtocol() != null && url.getProtocol().length() > 1)
protocol = url.getProtocol();
if(url.getHost() != null && url.getHost().length() > 1)
host = url.getHost();
if(url.getPort() != -1)
port = url.getPort();
if(url.getPath() != null && url.getPath().length() > 1)
path = url.getPath();
if(url.getUserInfo() != null && url.getUserInfo().length() > 1) {
String[] userPassSplit = url.getUserInfo().split(":");
user = userPassSplit[0].replace("+++++", "@");
if(userPassSplit.length > 1 && userPassSplit[1].length() > 1)
pass = userPassSplit[1];
}
}
if(splitUser != null && splitUser.length > i)
user = splitUser[i];
if(splitPass != null && splitPass.length > i)
pass = splitPass[i];
if(splitHost != null && splitHost.length > i)
host = splitHost[i];
if(splitPort != null && splitPort.length > i)
port = Integer.parseInt(splitPort[i]);
NetworkState networkState;
try {
networkState = new JSONRPCNetworkState(this, new URL(protocol, host, port, path), user, pass, hostChain);
} catch(MalformedURLException e) {
throw new DiabloMinerFatalException(this, "Malformed connection paramaters");
}
if(networkStateHead == null) {
networkStateHead = networkStateTail = networkState;
} else {
networkStateTail.setNetworkStateNext(networkState);
networkStateTail = networkState;
}
}
networkStateTail.setNetworkStateNext(networkStateHead);
if(line.hasOption("proxy")) {
final String[] proxySettings = line.getOptionValue("proxy").split(":");
if(proxySettings.length >= 2) {
proxy = new Proxy(Type.HTTP, new InetSocketAddress(proxySettings[0], Integer.valueOf(proxySettings[1])));
}
if(proxySettings.length >= 3) {
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(proxySettings[2], proxySettings[3].toCharArray());
}
});
}
}
if(line.hasOption("worklifetime"))
workLifetime = Integer.parseInt(line.getOptionValue("worklifetime")) * 1000;
if(line.hasOption("debug"))
debug = true;
if(line.hasOption("debugtimer"))
debugtimer = true;
if(line.hasOption("devices")) {
String devices[] = line.getOptionValue("devices").split(",");
enabledDevices = new HashSet<String>();
for(String s : devices) {
enabledDevices.add(s);
if(Integer.parseInt(s) == 0) {
error("Do not use 0 with -D, devices start at 1");
System.exit(-1);
}
}
}
if(line.hasOption("fps")) {
GPUTargetFPS = Float.parseFloat(line.getOptionValue("fps"));
if(GPUTargetFPS < 0.1) {
error("--fps argument is too low, adjusting to 0.1");
GPUTargetFPS = 0.1;
}
}
if(line.hasOption("noarray"))
GPUNoArray = true;
if(line.hasOption("worksize"))
GPUForceWorkSize = Integer.parseInt(line.getOptionValue("worksize"));
if(line.hasOption("vectors")) {
String tempVectors[] = line.getOptionValue("vectors").split(",");
GPUVectors = new Integer[tempVectors.length];
try {
for(int i = 0; i < GPUVectors.length; i++) {
GPUVectors[i] = Integer.parseInt(tempVectors[i]);
if(GPUVectors[i] > 16) {
error("DiabloMiner now uses comma-seperated vector layouts, use those instead");
System.exit(-1);
} else if(GPUVectors[i] != 1 && GPUVectors[i] != 2 && GPUVectors[i] != 3 && GPUVectors[i] != 4 && GPUVectors[i] != 8 && GPUVectors[i] != 16) {
error(GPUVectors[i] + " is not a vector length of 1, 2, 3, 4, 8, or 16");
System.exit(-1);
}
}
Arrays.sort(GPUVectors, Collections.reverseOrder());
} catch(NumberFormatException e) {
error("Cannot parse --vector argument(s)");
System.exit(-1);
}
} else {
GPUVectors = new Integer[1];
GPUVectors[0] = 1;
}
if(line.hasOption("ds"))
GPUDebugSource = true;
info("Started");
StringBuilder list = new StringBuilder(networkStateHead.getQueryUrl().toString());
NetworkState networkState = networkStateHead.getNetworkStateNext();
while(networkState != networkStateHead) {
list.append(", " + networkState.getQueryUrl());
networkState = networkState.getNetworkStateNext();
}
info("Connecting to: " + list);
long previousHashCount = 0;
double previousAdjustedHashCount = 0.0;
long previousAdjustedStartTime = startTime = (now()) - 1;
StringBuilder hashMeter = new StringBuilder(80);
Formatter hashMeterFormatter = new Formatter(hashMeter);
int deviceCount = 0;
List<List<? extends DeviceState>> allDeviceStates = new ArrayList<List<? extends DeviceState>>();
List<? extends DeviceState> GPUDeviceStates = new GPUHardwareType(this).getDeviceStates();
deviceCount += GPUDeviceStates.size();
allDeviceStates.add(GPUDeviceStates);
while(running.get()) {
for(List<? extends DeviceState> deviceStates : allDeviceStates) {
for(DeviceState deviceState : deviceStates) {
deviceState.checkDevice();
}
}
long now = now();
long currentHashCount = hashCount.get();
double adjustedHashCount = (double) (currentHashCount - previousHashCount) / (double) (now - previousAdjustedStartTime);
double hashLongCount = (double) currentHashCount / (double) (now - startTime) / 1000.0;
if(now - startTime > TIME_OFFSET * 2) {
double averageHashCount = (adjustedHashCount + previousAdjustedHashCount) / 2.0 / 1000.0;
hashMeter.setLength(0);
if(!debug) {
hashMeterFormatter.format("\rmhash: %.1f/%.1f | accept: %d | reject: %d | hw error: %d", averageHashCount, hashLongCount, blocks.get(), rejects.get(), hwErrors.get());
} else {
hashMeterFormatter.format("\rmh: %.1f/%.1f | a/r/hwe: %d/%d/%d | gh: ", averageHashCount, hashLongCount, blocks.get(), rejects.get(), hwErrors.get());
double basisAverage = 0.0;
for(List<? extends DeviceState> deviceStates : allDeviceStates) {
for(DeviceState deviceState : deviceStates) {
hashMeterFormatter.format("%.1f ", deviceState.getDeviceHashCount() / 1000.0 / 1000.0 / 1000.0);
basisAverage += deviceState.getBasis();
}
}
basisAverage = 1000 / (basisAverage / deviceCount);
hashMeterFormatter.format("| fps: %.1f", basisAverage);
}
System.out.print(hashMeter);
} else {
System.out.print("\rWaiting...");
}
if(now() - TIME_OFFSET * 2 > previousAdjustedStartTime) {
previousHashCount = currentHashCount;
previousAdjustedHashCount = adjustedHashCount;
previousAdjustedStartTime = now - 1;
}
if(debugtimer && now() > startTime + 60 * 1000) {
System.out.print("\n");
info("Debug timer is up, quitting...");
System.exit(0);
}
try {
if(now - startTime > TIME_OFFSET)
Thread.sleep(1000);
else
Thread.sleep(1);
} catch(InterruptedException e) {
}
}
hashMeterFormatter.close();
}
public static int rot(int x, int y) {
return (x >>> y) | (x << (32 - y));
}
public static void sharound(int out[], int na, int nb, int nc, int nd, int ne, int nf, int ng, int nh, int x, int K) {
int a = out[na];
int b = out[nb];
int c = out[nc];
int d = out[nd];
int e = out[ne];
int f = out[nf];
int g = out[ng];
int h = out[nh];
int t1 = h + (rot(e, 6) ^ rot(e, 11) ^ rot(e, 25)) + ((e & f) ^ ((~e) & g)) + K + x;
int t2 = (rot(a, 2) ^ rot(a, 13) ^ rot(a, 22)) + ((a & b) ^ (a & c) ^ (b & c));
out[nd] = d + t1;
out[nh] = t1 + t2;
}
public static String dateTime() {
return "[" + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date()) + "]";
}
public void info(String msg) {
System.out.println("\r" + CLEAR + "\r" + dateTime() + " " + msg);
threads.get(0).interrupt();
}
public void debug(String msg) {
if(debug) {
System.out.println("\r" + CLEAR + "\r" + dateTime() + " DEBUG: " + msg);
threads.get(0).interrupt();
}
}
public void error(String msg) {
System.err.println("\r" + CLEAR + "\r" + dateTime() + " ERROR: " + msg);
threads.get(0).interrupt();
}
public void addThread(Thread thread) {
threads.add(thread);
}
public long incrementBlocks() {
return blocks.incrementAndGet();
}
public long incrementAttempts() {
return attempts.incrementAndGet();
}
public long incrementRejects() {
return rejects.incrementAndGet();
}
public long incrementHWErrors() {
return hwErrors.incrementAndGet();
}
public long addAndGetHashCount(long delta) {
return hashCount.addAndGet(delta);
}
public static long now() {
return System.nanoTime() / 1000000;
}
public void halt() {
running.set(false);
for(int i = 0; i < threads.size(); i++) {
Thread thread = threads.get(i);
if(thread != Thread.currentThread())
thread.interrupt();
}
}
public boolean getDebug() {
return debug;
}
public Set<String> getEnabledDevices() {
return enabledDevices;
}
public NetworkState getNetworkStateHead() {
return networkStateHead;
}
public Proxy getProxy() {
return proxy;
}
public int getWorkLifetime() {
return workLifetime;
}
public boolean getRunning() {
return running.get();
}
public double getGPUTargetFPS() {
return GPUTargetFPS;
}
public int getGPUForceWorkSize() {
return GPUForceWorkSize;
}
public Integer[] getGPUVectors() {
return GPUVectors;
}
public boolean getGPUNoArray() {
return GPUNoArray;
}
public boolean getGPUDebugSource() {
return GPUDebugSource;
}
}