363 lines
9.9 KiB
C++
363 lines
9.9 KiB
C++
/*
|
|
This file is part of progminer.
|
|
|
|
progminer 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.
|
|
|
|
progminer 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 progminer. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
CPUMiner simulates mining devices but does NOT real mine!
|
|
USE FOR DEVELOPMENT ONLY !
|
|
*/
|
|
|
|
#if defined(__linux__)
|
|
#if !defined(_GNU_SOURCE)
|
|
#define _GNU_SOURCE /* we need sched_setaffinity() */
|
|
#endif
|
|
#include <error.h>
|
|
#include <sched.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <libethcore/Farm.h>
|
|
#include <ethash/ethash.hpp>
|
|
#include <ethash/progpow.hpp>
|
|
|
|
#include <boost/version.hpp>
|
|
|
|
#if 0
|
|
#include <boost/fiber/numa/pin_thread.hpp>
|
|
#include <boost/fiber/numa/topology.hpp>
|
|
#endif
|
|
|
|
#include "CPUMiner.h"
|
|
|
|
|
|
/* Sanity check for defined OS */
|
|
#if defined(__APPLE__) || defined(__MACOSX)
|
|
/* MACOSX */
|
|
#elif defined(__linux__)
|
|
/* linux */
|
|
#elif defined(_WIN32)
|
|
/* windows */
|
|
#else
|
|
#error "Invalid OS configuration"
|
|
#endif
|
|
|
|
|
|
using namespace std;
|
|
using namespace dev;
|
|
using namespace eth;
|
|
|
|
|
|
/* ################## OS-specific functions ################## */
|
|
|
|
/*
|
|
* returns physically available memory (no swap)
|
|
*/
|
|
static size_t getTotalPhysAvailableMemory()
|
|
{
|
|
#if defined(__APPLE__) || defined(__MACOSX)
|
|
#error "TODO: Function CPUMiner getTotalPhysAvailableMemory() on MAXOSX not implemented"
|
|
#elif defined(__linux__)
|
|
long pages = sysconf(_SC_AVPHYS_PAGES);
|
|
if (pages == -1L)
|
|
{
|
|
cwarn << "Error in func " << __FUNCTION__ << " at sysconf(_SC_AVPHYS_PAGES) \""
|
|
<< strerror(errno) << "\"\n";
|
|
return 0;
|
|
}
|
|
|
|
long page_size = sysconf(_SC_PAGESIZE);
|
|
if (page_size == -1L)
|
|
{
|
|
cwarn << "Error in func " << __FUNCTION__ << " at sysconf(_SC_PAGESIZE) \""
|
|
<< strerror(errno) << "\"\n";
|
|
return 0;
|
|
}
|
|
|
|
return (size_t)pages * (size_t)page_size;
|
|
#else
|
|
MEMORYSTATUSEX memInfo;
|
|
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
|
|
if (GlobalMemoryStatusEx(&memInfo) == 0)
|
|
{
|
|
// Handle Errorcode (GetLastError) ??
|
|
return 0;
|
|
}
|
|
return memInfo.ullAvailPhys;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* return numbers of available CPUs
|
|
*/
|
|
unsigned CPUMiner::getNumDevices()
|
|
{
|
|
#if 0
|
|
static unsigned cpus = 0;
|
|
|
|
if (cpus == 0)
|
|
{
|
|
std::vector< boost::fibers::numa::node > topo = boost::fibers::numa::topology();
|
|
for (auto n : topo) {
|
|
cpus += n.logical_cpus.size();
|
|
}
|
|
}
|
|
return cpus;
|
|
#elif defined(__APPLE__) || defined(__MACOSX)
|
|
#error "TODO: Function CPUMiner::getNumDevices() on MAXOSX not implemented"
|
|
#elif defined(__linux__)
|
|
long cpus_available;
|
|
cpus_available = sysconf(_SC_NPROCESSORS_ONLN);
|
|
if (cpus_available == -1L)
|
|
{
|
|
cwarn << "Error in func " << __FUNCTION__ << " at sysconf(_SC_NPROCESSORS_ONLN) \""
|
|
<< strerror(errno) << "\"\n";
|
|
return 0;
|
|
}
|
|
return cpus_available;
|
|
#else
|
|
SYSTEM_INFO sysinfo;
|
|
GetSystemInfo(&sysinfo);
|
|
return sysinfo.dwNumberOfProcessors;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* ######################## CPU Miner ######################## */
|
|
|
|
struct CPUChannel : public LogChannel
|
|
{
|
|
static const char* name() { return EthOrange "cp"; }
|
|
static const int verbosity = 2;
|
|
};
|
|
#define cpulog clog(CPUChannel)
|
|
|
|
|
|
CPUMiner::CPUMiner(unsigned _index, CPSettings _settings, DeviceDescriptor& _device)
|
|
: Miner("cpu-", _index), m_settings(_settings)
|
|
{
|
|
m_deviceDescriptor = _device;
|
|
}
|
|
|
|
|
|
CPUMiner::~CPUMiner()
|
|
{
|
|
// DEV_BUILD_LOG_PROGRAMFLOW(cpulog, "cp-" << m_index << " CPUMiner::~CPUMiner() begin");
|
|
stopWorking();
|
|
kick_miner();
|
|
// DEV_BUILD_LOG_PROGRAMFLOW(cpulog, "cp-" << m_index << " CPUMiner::~CPUMiner() end");
|
|
}
|
|
|
|
|
|
/*
|
|
* Bind the current thread to a spcific CPU
|
|
*/
|
|
bool CPUMiner::initDevice()
|
|
{
|
|
// DEV_BUILD_LOG_PROGRAMFLOW(cpulog, "cp-" << m_index << " CPUMiner::initDevice begin");
|
|
|
|
cpulog << "Using CPU: " << m_deviceDescriptor.cpCpuNumer << " " << m_deviceDescriptor.cuName
|
|
<< " Memory : " << dev::getFormattedMemory((double)m_deviceDescriptor.totalMemory);
|
|
|
|
#if defined(__APPLE__) || defined(__MACOSX)
|
|
#error "TODO: Function CPUMiner::initDevice() on MAXOSX not implemented"
|
|
#elif defined(__linux__)
|
|
cpu_set_t cpuset;
|
|
int err;
|
|
|
|
CPU_ZERO(&cpuset);
|
|
CPU_SET(m_deviceDescriptor.cpCpuNumer, &cpuset);
|
|
|
|
err = sched_setaffinity(0, sizeof(cpuset), &cpuset);
|
|
if (err != 0)
|
|
{
|
|
cwarn << "Error in func " << __FUNCTION__ << " at sched_setaffinity() \"" << strerror(errno)
|
|
<< "\"\n";
|
|
cwarn << "cp-" << m_index << "could not bind thread to cpu" << m_deviceDescriptor.cpCpuNumer
|
|
<< "\n";
|
|
}
|
|
#else
|
|
DWORD_PTR dwThreadAffinityMask = 1i64 << m_deviceDescriptor.cpCpuNumer;
|
|
DWORD_PTR previous_mask;
|
|
previous_mask = SetThreadAffinityMask(GetCurrentThread(), dwThreadAffinityMask);
|
|
if (previous_mask == NULL)
|
|
{
|
|
cwarn << "cp-" << m_index << "could not bind thread to cpu" << m_deviceDescriptor.cpCpuNumer
|
|
<< "\n";
|
|
// Handle Errorcode (GetLastError) ??
|
|
}
|
|
#endif
|
|
// DEV_BUILD_LOG_PROGRAMFLOW(cpulog, "cp-" << m_index << " CPUMiner::initDevice end");
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* A new epoch was receifed with last work package (called from Miner::initEpoch())
|
|
*
|
|
* If we get here it means epoch has changed so it's not necessary
|
|
* to check again dag sizes. They're changed for sure
|
|
* We've all related infos in m_epochContext (.dagSize, .dagNumItems, .lightSize, .lightNumItems)
|
|
*/
|
|
bool CPUMiner::initEpoch_internal()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
Miner should stop working on the current block
|
|
This happens if a
|
|
* new work arrived or
|
|
* miner should stop (eg exit progminer) or
|
|
* miner should pause
|
|
*/
|
|
void CPUMiner::kick_miner()
|
|
{
|
|
m_new_work.store(true, std::memory_order_relaxed);
|
|
m_new_work_signal.notify_one();
|
|
}
|
|
|
|
|
|
void CPUMiner::search(const dev::eth::WorkPackage& w)
|
|
{
|
|
constexpr size_t blocksize = 30;
|
|
|
|
const auto& context = ethash::get_global_epoch_context_full(w.epoch);
|
|
const auto header = ethash::hash256_from_bytes(w.header.data());
|
|
const auto boundary = ethash::hash256_from_bytes(w.boundary.data());
|
|
auto nonce = w.startNonce;
|
|
|
|
while (true)
|
|
{
|
|
if (m_new_work.load(std::memory_order_relaxed)) // new work arrived ?
|
|
{
|
|
m_new_work.store(false, std::memory_order_relaxed);
|
|
break;
|
|
}
|
|
|
|
if (shouldStop())
|
|
break;
|
|
|
|
|
|
//auto r = ethash::search(context, header, boundary, nonce, blocksize);
|
|
auto r = progpow::search(context, w.block, header, boundary, nonce, blocksize);
|
|
if (r.solution_found)
|
|
{
|
|
h256 mix{reinterpret_cast<byte*>(r.mix_hash.bytes), h256::ConstructFromPointer};
|
|
auto sol = Solution{r.nonce, mix, w, std::chrono::steady_clock::now(), m_index};
|
|
|
|
cpulog << EthWhite << "Job: " << w.header.abridged()
|
|
<< " Sol: " << toHex(sol.nonce, HexPrefix::Add) << EthReset;
|
|
Farm::f().submitProof(sol);
|
|
}
|
|
nonce += blocksize;
|
|
|
|
// Update the hash rate
|
|
updateHashRate(blocksize, 1);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* The main work loop of a Worker thread
|
|
*/
|
|
void CPUMiner::workLoop()
|
|
{
|
|
// DEV_BUILD_LOG_PROGRAMFLOW(cpulog, "cp-" << m_index << " CPUMiner::workLoop() begin");
|
|
|
|
WorkPackage current;
|
|
current.header = h256();
|
|
|
|
if (!initDevice())
|
|
return;
|
|
|
|
while (!shouldStop())
|
|
{
|
|
// Wait for work or 3 seconds (whichever the first)
|
|
const WorkPackage w = work();
|
|
if (!w)
|
|
{
|
|
boost::system_time const timeout =
|
|
boost::get_system_time() + boost::posix_time::seconds(3);
|
|
boost::mutex::scoped_lock l(x_work);
|
|
m_new_work_signal.timed_wait(l, timeout);
|
|
continue;
|
|
}
|
|
|
|
if (w.algo == "ethash")
|
|
{
|
|
// Epoch change ?
|
|
if (current.epoch != w.epoch)
|
|
{
|
|
if (!initEpoch())
|
|
break; // This will simply exit the thread
|
|
|
|
// As DAG generation takes a while we need to
|
|
// ensure we're on latest job, not on the one
|
|
// which triggered the epoch change
|
|
current = w;
|
|
continue;
|
|
}
|
|
|
|
// Persist most recent job.
|
|
// Job's differences should be handled at higher level
|
|
current = w;
|
|
|
|
// Start searching
|
|
search(w);
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error("Algo : " + w.algo + " not yet implemented");
|
|
}
|
|
}
|
|
|
|
// DEV_BUILD_LOG_PROGRAMFLOW(cpulog, "cp-" << m_index << " CPUMiner::workLoop() end");
|
|
}
|
|
|
|
|
|
void CPUMiner::enumDevices(std::map<string, DeviceDescriptor>& _DevicesCollection)
|
|
{
|
|
unsigned numDevices = getNumDevices();
|
|
|
|
for (unsigned i = 0; i < numDevices; i++)
|
|
{
|
|
string uniqueId;
|
|
ostringstream s;
|
|
DeviceDescriptor deviceDescriptor;
|
|
|
|
s << "cpu-" << i;
|
|
uniqueId = s.str();
|
|
if (_DevicesCollection.find(uniqueId) != _DevicesCollection.end())
|
|
deviceDescriptor = _DevicesCollection[uniqueId];
|
|
else
|
|
deviceDescriptor = DeviceDescriptor();
|
|
|
|
s.str("");
|
|
s.clear();
|
|
s << "ethash::eval()/boost " << (BOOST_VERSION / 100000) << "."
|
|
<< (BOOST_VERSION / 100 % 1000) << "." << (BOOST_VERSION % 100);
|
|
deviceDescriptor.name = s.str();
|
|
deviceDescriptor.uniqueId = uniqueId;
|
|
deviceDescriptor.type = DeviceTypeEnum::Cpu;
|
|
deviceDescriptor.totalMemory = getTotalPhysAvailableMemory();
|
|
|
|
deviceDescriptor.cpCpuNumer = i;
|
|
|
|
_DevicesCollection[uniqueId] = deviceDescriptor;
|
|
}
|
|
}
|