Files
mines/zano/libpoolprotocols/PoolManager.cpp

526 lines
16 KiB
C++

#include <chrono>
#include "PoolManager.h"
using namespace std;
using namespace dev;
using namespace eth;
PoolManager* PoolManager::m_this = nullptr;
PoolManager::PoolManager(PoolSettings _settings)
: m_Settings(std::move(_settings)),
m_io_strand(g_io_service),
m_failovertimer(g_io_service),
m_submithrtimer(g_io_service)
{
m_this = this;
m_currentWp.header = h256();
Farm::f().onMinerRestart([&]() {
cnote << "Restart miners...";
if (Farm::f().isMining())
{
cnote << "Shutting down miners...";
Farm::f().stop();
}
cnote << "Spinning up miners...";
Farm::f().start();
});
Farm::f().onSolutionFound([&](const Solution& sol) {
// Solution should passthrough only if client is
// properly connected. Otherwise we'll have the bad behavior
// to log nonce submission but receive no response
if (p_client && p_client->isConnected())
{
p_client->submitSolution(sol);
}
else
{
cnote << string(EthOrange "Solution 0x") + toHex(sol.nonce)
<< " wasted. Waiting for connection...";
}
return false;
});
}
void PoolManager::setClientHandlers()
{
p_client->onConnected([&]() {
{
// If HostName is already an IP address no need to append the
// effective ip address.
if (p_client->getConnection()->HostNameType() == dev::UriHostNameType::Dns ||
p_client->getConnection()->HostNameType() == dev::UriHostNameType::Basic)
{
string ep = p_client->ActiveEndPoint();
if (!ep.empty())
m_selectedHost = p_client->getConnection()->Host() + ep;
}
cnote << "Established connection to " << m_selectedHost;
// Reset current WorkPackage
m_currentWp.job.clear();
m_currentWp.header = h256();
// Shuffle if needed
if (Farm::f().get_ergodicity() == 1U)
Farm::f().shuffle();
// Rough implementation to return to primary pool
// after specified amount of time
if (m_activeConnectionIdx != 0 && m_Settings.poolFailoverTimeout)
{
m_failovertimer.expires_from_now(
boost::posix_time::minutes(m_Settings.poolFailoverTimeout));
m_failovertimer.async_wait(m_io_strand.wrap(boost::bind(
&PoolManager::failovertimer_elapsed, this, boost::asio::placeholders::error)));
}
else
{
m_failovertimer.cancel();
}
}
if (!Farm::f().isMining())
{
cnote << "Spinning up miners...";
Farm::f().start();
}
else if (Farm::f().paused())
{
cnote << "Resume mining ...";
Farm::f().resume();
}
// Activate timing for HR submission
if (m_Settings.reportHashrate)
{
m_submithrtimer.expires_from_now(boost::posix_time::seconds(m_Settings.hashRateInterval));
m_submithrtimer.async_wait(m_io_strand.wrap(boost::bind(
&PoolManager::submithrtimer_elapsed, this, boost::asio::placeholders::error)));
}
// Signal async operations have completed
m_async_pending.store(false, std::memory_order_relaxed);
});
p_client->onDisconnected([&]() {
cnote << "Disconnected from " << m_selectedHost;
// Clear current connection
p_client->unsetConnection();
m_currentWp.header = h256();
// Stop timing actors
m_failovertimer.cancel();
m_submithrtimer.cancel();
if (m_stopping.load(std::memory_order_relaxed))
{
if (Farm::f().isMining())
{
cnote << "Shutting down miners...";
Farm::f().stop();
}
m_running.store(false, std::memory_order_relaxed);
}
else
{
// Signal we will reconnect async
m_async_pending.store(true, std::memory_order_relaxed);
// Suspend mining and submit new connection request
cnote << "No connection. Suspend mining ...";
Farm::f().pause();
g_io_service.post(m_io_strand.wrap(boost::bind(&PoolManager::rotateConnect, this)));
}
});
p_client->onWorkReceived([&](WorkPackage const& wp) {
// Should not happen !
if (!wp)
return;
int _currentEpoch = m_currentWp.epoch;
bool newEpoch = (_currentEpoch == -1);
// In EthereumStratum/2.0.0 epoch number is set in session
if (!newEpoch)
{
if (p_client->getConnection()->StratumMode() == 3)
newEpoch = (wp.epoch != m_currentWp.epoch);
else
newEpoch = (wp.seed != m_currentWp.seed);
}
bool newDiff = (wp.boundary != m_currentWp.boundary);
m_currentWp = wp;
if (newEpoch)
{
m_epochChanges.fetch_add(1, std::memory_order_relaxed);
// If epoch is valued in workpackage take it
if (wp.epoch == -1)
{
if (m_currentWp.block > 0)
m_currentWp.epoch = m_currentWp.block / 30000;
else
m_currentWp.epoch = ethash::find_epoch_number(
ethash::hash256_from_bytes(m_currentWp.seed.data()));
}
}
else
{
m_currentWp.epoch = _currentEpoch;
}
if (newDiff || newEpoch)
showMiningAt();
cnote << "Job: " EthWhite << m_currentWp.header.abridged()
<< (m_currentWp.block != -1 ? (" block " + to_string(m_currentWp.block)) : "")
<< EthReset << " " << m_selectedHost;
Farm::f().setWork(m_currentWp);
});
p_client->onSolutionAccepted(
[&](std::chrono::milliseconds const& _responseDelay, unsigned const& _minerIdx, bool _asStale) {
std::stringstream ss;
ss << std::setw(4) << std::setfill(' ') << _responseDelay.count() << " ms. "
<< m_selectedHost;
cnote << EthLime "**Accepted" << (_asStale ? " stale": "") << EthReset << ss.str();
Farm::f().accountSolution(_minerIdx, SolutionAccountingEnum::Accepted);
});
p_client->onSolutionRejected(
[&](std::chrono::milliseconds const& _responseDelay, unsigned const& _minerIdx) {
std::stringstream ss;
ss << std::setw(4) << std::setfill(' ') << _responseDelay.count() << " ms. "
<< m_selectedHost;
cwarn << EthRed "**Rejected" EthReset << ss.str();
Farm::f().accountSolution(_minerIdx, SolutionAccountingEnum::Rejected);
});
}
void PoolManager::stop()
{
if (m_running.load(std::memory_order_relaxed))
{
m_async_pending.store(true, std::memory_order_relaxed);
m_stopping.store(true, std::memory_order_relaxed);
if (p_client && p_client->isConnected())
{
p_client->disconnect();
// Wait for async operations to complete
while (m_running.load(std::memory_order_relaxed))
this_thread::sleep_for(chrono::milliseconds(500));
p_client = nullptr;
}
else
{
// Stop timing actors
m_failovertimer.cancel();
m_submithrtimer.cancel();
if (Farm::f().isMining())
{
cnote << "Shutting down miners...";
Farm::f().stop();
}
}
}
}
void PoolManager::addConnection(std::string _connstring)
{
m_Settings.connections.push_back(std::shared_ptr<URI>(new URI(_connstring)));
}
void PoolManager::addConnection(std::shared_ptr<URI> _uri)
{
m_Settings.connections.push_back(_uri);
}
/*
* Remove a connection
* Returns: 0 on success
* -1 failure (out of bounds)
* -2 failure (active connection should be deleted)
*/
void PoolManager::removeConnection(unsigned int idx)
{
// Are there any outstanding operations ?
if (m_async_pending.load(std::memory_order_relaxed))
throw std::runtime_error("Outstanding operations. Retry ...");
// Check bounds
if (idx >= m_Settings.connections.size())
throw std::runtime_error("Index out-of bounds.");
// Can't delete active connection
if (idx == m_activeConnectionIdx)
throw std::runtime_error("Can't remove active connection");
// Remove the selected connection
m_Settings.connections.erase(m_Settings.connections.begin() + idx);
if (m_activeConnectionIdx > idx)
m_activeConnectionIdx--;
}
void PoolManager::setActiveConnectionCommon(unsigned int idx)
{
// Are there any outstanding operations ?
bool ex = false;
if (!m_async_pending.compare_exchange_weak(ex, true, std::memory_order_relaxed))
throw std::runtime_error("Outstanding operations. Retry ...");
if (idx != m_activeConnectionIdx)
{
m_connectionSwitches.fetch_add(1, std::memory_order_relaxed);
m_activeConnectionIdx = idx;
m_connectionAttempt = 0;
p_client->disconnect();
}
else
{
// Release the flag immediately
m_async_pending.store(false, std::memory_order_relaxed);
}
}
/*
* Sets the active connection
* Returns: 0 on success, -1 on failure (out of bounds)
*/
void PoolManager::setActiveConnection(unsigned int idx)
{
// Sets the active connection to the requested index
if (idx >= m_Settings.connections.size())
throw std::runtime_error("Index out-of bounds.");
setActiveConnectionCommon(idx);
}
void PoolManager::setActiveConnection(std::string& _connstring)
{
bool found = false;
for (size_t idx = 0; idx < m_Settings.connections.size(); idx++)
if (boost::iequals(m_Settings.connections[idx]->str(), _connstring))
{
setActiveConnectionCommon(idx);
break;
}
if (!found)
throw std::runtime_error("Not found.");
}
std::shared_ptr<URI> PoolManager::getActiveConnection()
{
try
{
return m_Settings.connections.at(m_activeConnectionIdx);
}
catch (const std::exception&)
{
return nullptr;
}
}
Json::Value PoolManager::getConnectionsJson()
{
// Returns the list of configured connections
Json::Value jRes;
for (size_t i = 0; i < m_Settings.connections.size(); i++)
{
Json::Value JConn;
JConn["index"] = (unsigned)i;
JConn["active"] = (i == m_activeConnectionIdx ? true : false);
JConn["uri"] = m_Settings.connections[i]->str();
jRes.append(JConn);
}
return jRes;
}
void PoolManager::start()
{
m_running.store(true, std::memory_order_relaxed);
m_async_pending.store(true, std::memory_order_relaxed);
m_connectionSwitches.fetch_add(1, std::memory_order_relaxed);
g_io_service.post(m_io_strand.wrap(boost::bind(&PoolManager::rotateConnect, this)));
}
void PoolManager::rotateConnect()
{
if (p_client && p_client->isConnected())
return;
// Check we're within bounds
if (m_activeConnectionIdx >= m_Settings.connections.size())
m_activeConnectionIdx = 0;
// If this connection is marked Unrecoverable then discard it
if (m_Settings.connections.at(m_activeConnectionIdx)->IsUnrecoverable())
{
m_Settings.connections.erase(m_Settings.connections.begin() + m_activeConnectionIdx);
m_connectionAttempt = 0;
if (m_activeConnectionIdx >= m_Settings.connections.size())
m_activeConnectionIdx = 0;
m_connectionSwitches.fetch_add(1, std::memory_order_relaxed);
}
else if (m_connectionAttempt >= m_Settings.connectionMaxRetries)
{
// If this is the only connection we can't rotate
// forever
if (m_Settings.connections.size() == 1)
{
m_Settings.connections.erase(m_Settings.connections.begin() + m_activeConnectionIdx);
}
// Rotate connections if above max attempts threshold
else
{
m_connectionAttempt = 0;
m_activeConnectionIdx++;
if (m_activeConnectionIdx >= m_Settings.connections.size())
m_activeConnectionIdx = 0;
m_connectionSwitches.fetch_add(1, std::memory_order_relaxed);
}
}
if (!m_Settings.connections.empty() && m_Settings.connections.at(m_activeConnectionIdx)->Host() != "exit")
{
if (p_client)
p_client = nullptr;
if (m_Settings.connections.at(m_activeConnectionIdx)->Family() == ProtocolFamily::GETWORK)
p_client =
std::unique_ptr<PoolClient>(new EthGetworkClient(m_Settings.noWorkTimeout, m_Settings.getWorkPollInterval));
if (m_Settings.connections.at(m_activeConnectionIdx)->Family() == ProtocolFamily::STRATUM)
p_client = std::unique_ptr<PoolClient>(
new EthStratumClient(m_Settings.noWorkTimeout, m_Settings.noResponseTimeout));
if (m_Settings.connections.at(m_activeConnectionIdx)->Family() == ProtocolFamily::SIMULATION)
p_client = std::unique_ptr<PoolClient>(
new SimulateClient(m_Settings.benchmarkBlock, m_Settings.benchmarkDiff));
if (p_client)
setClientHandlers();
// Count connectionAttempts
m_connectionAttempt++;
// Invoke connections
m_selectedHost = m_Settings.connections.at(m_activeConnectionIdx)->Host() + ":" +
to_string(m_Settings.connections.at(m_activeConnectionIdx)->Port());
p_client->setConnection(m_Settings.connections.at(m_activeConnectionIdx));
cnote << "Selected pool " << m_selectedHost;
p_client->connect();
}
else
{
if (m_Settings.connections.empty())
cnote << "No more connections to try. Exiting...";
else
cnote << "'exit' failover just got hit. Exiting...";
// Stop mining if applicable
if (Farm::f().isMining())
{
cnote << "Shutting down miners...";
Farm::f().stop();
}
m_running.store(false, std::memory_order_relaxed);
raise(SIGTERM);
}
}
void PoolManager::showMiningAt()
{
// Should not happen
if (!m_currentWp)
return;
double d = dev::getHashesToTarget(m_currentWp.boundary.hex(HexPrefix::Add));
cnote << "Epoch : " EthWhite << m_currentWp.epoch << EthReset << " Difficulty : " EthWhite
<< dev::getFormattedHashes(d) << EthReset;
}
void PoolManager::failovertimer_elapsed(const boost::system::error_code& ec)
{
if (!ec)
{
if (m_running.load(std::memory_order_relaxed))
{
if (m_activeConnectionIdx != 0)
{
m_activeConnectionIdx = 0;
m_connectionAttempt = 0;
m_connectionSwitches.fetch_add(1, std::memory_order_relaxed);
cnote << "Failover timeout reached, retrying connection to primary pool";
p_client->disconnect();
}
}
}
}
void PoolManager::submithrtimer_elapsed(const boost::system::error_code& ec)
{
if (!ec)
{
if (m_running.load(std::memory_order_relaxed))
{
if (p_client && p_client->isConnected())
p_client->submitHashrate((uint32_t)Farm::f().HashRate(), m_Settings.hashRateId);
// Resubmit actor
m_submithrtimer.expires_from_now(boost::posix_time::seconds(m_Settings.hashRateInterval));
m_submithrtimer.async_wait(m_io_strand.wrap(boost::bind(
&PoolManager::submithrtimer_elapsed, this, boost::asio::placeholders::error)));
}
}
}
int PoolManager::getCurrentEpoch()
{
return m_currentWp.epoch;
}
double PoolManager::getCurrentDifficulty()
{
if (!m_currentWp)
return 0.0;
return dev::getHashesToTarget(m_currentWp.boundary.hex(HexPrefix::Add));
}
unsigned PoolManager::getConnectionSwitches()
{
return m_connectionSwitches.load(std::memory_order_relaxed);
}
unsigned PoolManager::getEpochChanges()
{
return m_epochChanges.load(std::memory_order_relaxed);
}