diff --git a/libethcore/Common.cpp b/libethcore/Common.cpp index 572ade3e2..f0e749aaa 100644 --- a/libethcore/Common.cpp +++ b/libethcore/Common.cpp @@ -22,7 +22,6 @@ #include "Common.h" #include #include -#include "Ethasher.h" #include "Exceptions.h" using namespace std; using namespace dev; @@ -33,7 +32,6 @@ namespace dev namespace eth { -const unsigned c_ethashVersion = c_ethashRevision; const unsigned c_protocolVersion = 60; const unsigned c_minorProtocolVersion = 0; const unsigned c_databaseBaseVersion = 9; diff --git a/libethcore/Common.h b/libethcore/Common.h index ec826d2d1..b5291648b 100644 --- a/libethcore/Common.h +++ b/libethcore/Common.h @@ -96,5 +96,42 @@ enum class ImportResult BadChain }; +class Signal; + +class Handler +{ +public: + Handler() = default; + Handler(Handler const&) = delete; + ~Handler() { reset(); } + + Handler& operator=(Handler const& _h) = delete; + + void reset(); + +private: + Handler(unsigned _i, Signal* _s): m_i(_i), m_s(_s) {} + + unsigned m_i = 0; + Signal* m_s = nullptr; +}; + +/// Super-duper signal mechanism. TODO: replace with somthing a bit heavier weight. +class Signal +{ +public: + using Callback = std::function; + using Callbacks = std::vector; + + Handler add(Callback const& _h) { auto n = m_onReady.size() ? m_onReady.rbegin()->first + 1 : 0; m_onReady[n] = _h; return Handler(n, this); } + + void operator()() { for (auto const& f: m_fire) f.second(); } + +private: + std::map m_fire; +}; + +inline void Handler::reset() { if (m_s) m_s->m_fire->erase(m_i); m_s = nullptr; } + } } diff --git a/libethcore/Ethash.cpp b/libethcore/Ethash.cpp new file mode 100644 index 000000000..82e349b4c --- /dev/null +++ b/libethcore/Ethash.cpp @@ -0,0 +1,241 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file Ethash.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Ethash.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if ETH_ETHASHCL || !ETH_TRUE +#include +#endif +#include "BlockInfo.h" +#include "EthashAux.h" +using namespace std; +using namespace std::chrono; + +namespace dev +{ +namespace eth +{ + +const Ethash::WorkPackage Ethash::NullWorkPackage; + +std::string Ethash::name() +{ + return "Ethash"; +} + +unsigned Ethash::revision() +{ + return ETHASH_REVISION; +} + +void Ethash::prep(BlockInfo const& _header) +{ + if (_header.number % ETHASH_EPOCH_LENGTH == 1) + EthashAux::full(bi); +} + +bool Ethash::preVerify(BlockInfo const& _header) +{ + if (_header.number >= ETHASH_EPOCH_LENGTH * 2048) + return false; + + h256 boundary = u256((bigint(1) << 256) / _header.difficulty); + + return ethash_quick_check_difficulty( + _header.headerHash(WithoutNonce).data(), + (uint64_t)(u64)_header.nonce, + _header.mixHash.data(), + boundary.data()); +} + +bool Ethash::verify(BlockInfo const& _header) +{ + bool pre = preVerify(_header); +#if !ETH_DEBUG + if (!pre) + return false; +#endif + + h256 boundary = u256((bigint(1) << 256) / _header.difficulty); + auto result = eval(_header); + bool slow = result.value <= boundary && result.mixHash == _header.mixHash; + +#if ETH_DEBUG || !ETH_TRUE + if (!pre && slow) + { + cwarn << "WARNING: evaluated result gives true whereas ethash_quick_check_difficulty gives false."; + cwarn << "headerHash:" << _header.headerHash(WithoutNonce); + cwarn << "nonce:" << _header.nonce; + cwarn << "mixHash:" << _header.mixHash; + cwarn << "difficulty:" << _header.difficulty; + cwarn << "boundary:" << boundary; + cwarn << "result.value:" << result.value; + cwarn << "result.mixHash:" << result.mixHash; + } +#endif + + return slow; +} + +void Ethash::CPUMiner::workLoop() +{ + auto tid = std::this_thread::get_id(); + static std::mt19937_64 s_eng((time(0) + *reinterpret_cast(m_last.data()) + std::hash()(tid))); + + uint64_t tryNonce = Nonce::random(s_eng); + ethash_return_value ethashReturn; + + auto p = Ethash::params(m_work.seedHash); + void const* dagPointer = Ethash::full(m_work.headerHash).data(); + uint8_t const* headerHashPointer = m_work.headerHash.data(); + h256 boundary = m_work.boundary(); + unsigned hashCount = 0; + for (; !shouldStop(); tryNonce++, hashCount++) + { + ethash_compute_full(ðashReturn, dagPointer, &p, headerHashPointer, tryNonce); + h256 value = h256(ethashReturn.result, h256::ConstructFromPointer); + if (value <= boundary && submitProof(Solution{value, h256(ethashReturn.mix_hash, h256::ConstructFromPointer)})) + break; + } +} + +#if ETH_ETHASHCL || !ETH_TRUE + +namespace dev { namespace eth { + +class EthashCLHook: public ethash_cl_miner::search_hook +{ +public: + EthashCLHook(Ethash::GPUMiner* _owner): m_owner(_owner) {} + + void abort() + { + Guard l(x_all); + if (m_aborted) + return; +// cdebug << "Attempting to abort"; + m_abort = true; + for (unsigned timeout = 0; timeout < 100 && !m_aborted; ++timeout) + std::this_thread::sleep_for(chrono::milliseconds(30)); + if (!m_aborted) + cwarn << "Couldn't abort. Abandoning OpenCL process."; + m_aborted = m_abort = false; + } + + uint64_t fetchTotal() { Guard l(x_all); auto ret = m_total; m_total = 0; return ret; } + +protected: + virtual bool found(uint64_t const* _nonces, uint32_t _count) override + { +// cdebug << "Found nonces: " << vector(_nonces, _nonces + _count); + for (uint32_t i = 0; i < _count; ++i) + { + if (m_owner->found(_nonces[i])) + { + m_aborted = true; + return true; + } + } + return false; + } + + virtual bool searched(uint64_t _startNonce, uint32_t _count) override + { + Guard l(x_all); +// cdebug << "Searched" << _count << "from" << _startNonce; + m_total += _count; + m_last = _startNonce + _count; + if (m_abort) + { + m_aborted = true; + return true; + } + return false; + } + +private: + Mutex x_all; + uint64_t m_total; + uint64_t m_last; + bool m_abort = false; + bool m_aborted = true; + Ethash::GPUMiner* m_owner = nullptr; +}; + +} } + +Ethash::GPUMiner::GPUMiner(ConstructionInfo const& _ci): + Miner(_ci), + m_hook(new EthashCLHook(this)) +{ +} + +void Ethash::GPUMiner::report(uint64_t _nonce) +{ + Nonce n = (Nonce)(u64)_nonce; + Result r = Ethash::eval(m_work.seedHash, m_work.headerHash, n); + if (r.value < m_work.boundary) + return submitProof(Solution{n, r.mixHash}); + return false; +} + +void Ethash::GPUMiner::kickOff(WorkPackage const& _work) +{ + if (!m_miner || m_minerSeed != _work.seedHash) + { + if (m_miner) + m_hook->abort(); + m_miner.reset(new ethash_cl_miner); + auto p = Ethash::params(_work.seedHash); + auto cb = [&](void* d) { EthashAux::readFull(_work.seedHash, bytesRef((byte*)d, p.full_size)); }; + m_miner->init(p, cb, 32); + } + if (m_lastWork.headerHash != _work.headerHash) + { + m_hook->abort(); + uint64_t upper64OfBoundary = (uint64_t)(u64)((u256)_work.boundary >> 192); + m_miner->search(_work.headerHash, upper64OfBoundary, *m_hook); + } + m_work = _work; +} + +void Ethash::GPUMiner::pause() +{ + m_hook->abort(); +} + +#endif + +} +} diff --git a/libethcore/Ethash.h b/libethcore/Ethash.h new file mode 100644 index 000000000..bdd6bf6c5 --- /dev/null +++ b/libethcore/Ethash.h @@ -0,0 +1,128 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file Ethash.h + * @author Gav Wood + * @date 2014 + * + * A proof of work algorithm. + */ + +#pragma once + +#include +#include +#include +#include "Common.h" +#include "BlockInfo.h" +#include "Miner.h" + +class ethash_cl_miner; + +namespace dev +{ +namespace eth +{ + +class Ethash +{ +public: + struct Solution + { + Nonce nonce; + h256 mixHash; + }; + + struct Result + { + h256 value; + h256 mixHash; + }; + + struct WorkPackage + { + h256 boundary; + h256 headerHash; ///< When h256() means "pause until notified a new work package is available". + h256 seedHash; + }; + + static const WorkPackage NullWorkPackage; + + static std::string name(); + static unsigned revision(); + static bool verify(BlockInfo const& _header); + static bool preVerify(BlockInfo const& _header); + static void assignResult(Solution const& _r, BlockInfo& _header) { _header.nonce = _r.nonce; _header.mixHash = _r.mixHash; } + static void prep(BlockInfo const& _header); + + class CPUMiner: public Miner, Worker + { + public: + CPUMiner(ConstructionInfo const& _ci): Miner(_ci), Worker("miner" + toString(index())) {} + + static unsigned instances() { return thread::hardware_concurrency(); } + + protected: + void kickOff(WorkPackage const& _work) override + { + stopWorking(); + m_work = _work; + startWorking(); + } + + void pause() override { stopWorking(); } + + private: + void workLoop() override; + + WorkPackage m_work; + MineInfo m_info; + }; + +#if ETH_ETHASHCL || !ETH_TRUE + class EthashCLHook; + class GPUMiner: public Miner + { + friend class EthashCLHook; + + public: + GPUMiner(ConstructionInfo const& _ci); + + static unsigned instances() { return 1; } + + protected: + void kickOff(WorkPackage const& _work) override; + void pause() override; + + private: + void report(uint64_t _nonce); + + std::unique_ptr m_hook; + std::unique_ptr m_miner; + h256 m_minerSeed; + WorkPackage m_lastWork; ///< Work loaded into m_miner. + MineInfo m_info; + }; +#else + using GPUMiner = CPUMiner; +#endif +}; + +using ProofOfWork = Ethash; +using Solution = Ethash::Solution; + +} +} diff --git a/libethcore/Ethasher.cpp b/libethcore/EthashAux.cpp similarity index 60% rename from libethcore/Ethasher.cpp rename to libethcore/EthashAux.cpp index 2118952eb..a6143e264 100644 --- a/libethcore/Ethasher.cpp +++ b/libethcore/EthashAux.cpp @@ -14,11 +14,13 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file Ethasher.cpp +/** @file EthashAux.cpp * @author Gav Wood * @date 2014 */ +#include "EthashAux.h" + #include #include #include @@ -33,7 +35,6 @@ #include #include #include "BlockInfo.h" -#include "Ethasher.h" using namespace std; using namespace chrono; using namespace dev; @@ -41,15 +42,50 @@ using namespace eth; #define ETH_IGNORE_EXCEPTIONS(X) try { X; } catch (...) {} -Ethasher* dev::eth::Ethasher::s_this = nullptr; +EthashAux* dev::eth::EthashAux::s_this = nullptr; -Ethasher::~Ethasher() +EthashAux::~EthashAux() { while (!m_lights.empty()) killCache(m_lights.begin()->first); } -void Ethasher::killCache(h256 const& _s) +ethash_params EthashAux::params(BlockInfo const& _header) +{ + return params((unsigned)_header.number); +} + +ethash_params EthashAux::params(unsigned _n) +{ + ethash_params p; + p.cache_size = ethash_get_cachesize(_n); + p.full_size = ethash_get_datasize(_n); + return p; +} + +ethash_params EthashAux::params(h256 const& _seedHash) +{ + RecursiveGuard l(get()->x_this); + unsigned epoch = 0; + try + { + epoch = get()->m_seedHashes.at(_seedHash); + } + catch (...) + { + for (h256 h; h != _seedHash && epoch < 2048; ++epoch, h = h256(h)) {} + if (epoch == 2048) + { + std::ostringstream error; + error << "apparent block number for " << _seedHash.abridged() << " is too high; max is " << (ETHASH_EPOCH_LENGTH * 2048); + throw std::invalid_argument(error.str()); + } + get()->m_seedHashes[_seedHash] = epoch; + } + return params(epoch * ETHASH_EPOCH_LENGTH); +} + +void EthashAux::killCache(h256 const& _s) { RecursiveGuard l(x_this); if (m_lights.count(_s)) @@ -59,12 +95,12 @@ void Ethasher::killCache(h256 const& _s) } } -void const* Ethasher::light(BlockInfo const& _header) +void const* EthashAux::light(BlockInfo const& _header) { return light(_header.seedHash()); } -void const* Ethasher::light(h256 const& _seedHash) +void const* EthashAux::light(h256 const& _seedHash) { RecursiveGuard l(x_this); if (!m_lights.count(_header.seedHash())) @@ -75,12 +111,12 @@ void const* Ethasher::light(h256 const& _seedHash) return m_lights[_seedHash]; } -bytesConstRef Ethasher::full(BlockInfo const& _header, bytesRef _dest) +bytesConstRef EthashAux::full(BlockInfo const& _header, bytesRef _dest) { return full(_header.seedHash(), _dest); } -bytesConstRef Ethasher::full(h256 const& _seedHash, bytesRef _dest) +bytesConstRef EthashAux::full(h256 const& _seedHash, bytesRef _dest) { RecursiveGuard l(x_this); if (m_fulls.count(_seedHash) && _dest) @@ -102,9 +138,9 @@ bytesConstRef Ethasher::full(h256 const& _seedHash, bytesRef _dest) boost::filesystem::create_directories(getDataDir("ethash")); } catch (...) {} - auto info = rlpList(c_ethashRevision, _seedHash); + auto info = rlpList(revision(), _seedHash); std::string oldMemoFile = getDataDir("ethash") + "/full"; - std::string memoFile = getDataDir("ethash") + "/full-R" + toString(c_ethashRevision) + "-" + toHex(_seedHash.ref().cropped(0, 8)); + std::string memoFile = getDataDir("ethash") + "/full-R" + toString(ETHASH_REVISION) + "-" + toHex(_seedHash.ref().cropped(0, 8)); if (boost::filesystem::exists(oldMemoFile) && contents(oldMemoFile + ".info") == info) { // memofile valid - rename. @@ -136,91 +172,19 @@ bytesConstRef Ethasher::full(h256 const& _seedHash, bytesRef _dest) return m_fulls[_seedHash]; } -ethash_params Ethasher::params(BlockInfo const& _header) -{ - return params((unsigned)_header.number); -} - -ethash_params Ethasher::params(unsigned _n) -{ - ethash_params p; - p.cache_size = ethash_get_cachesize(_n); - p.full_size = ethash_get_datasize(_n); - return p; -} - -ethash_params Ethasher::params(h256 const& _seedHash) -{ - unsigned epoch = 0; - try - { - epoch = m_seedHashes.at(_seedHash); - } - catch (...) - { - for (h256 h; h != _seedHash && epoch < 2048; ++epoch, h = h256(h)) {} - if (epoch == 2048) - { - std::ostringstream error; - error << "apparent block number for " << _seedHash.abridged() << " is too high; max is " << (c_ethashEpochLength * 2048); - throw std::invalid_argument(error.str()); - } - m_seedHashes[_seedHash] = epoch; - } - return params(epoch * c_ethashEpochLength); -} - -bool Ethasher::verify(BlockInfo const& _header) -{ - if (_header.number >= c_ethashEpochLength * 2048) - return false; - - h256 boundary = u256((bigint(1) << 256) / _header.difficulty); - - bool quick = ethash_quick_check_difficulty( - _header.headerHash(WithoutNonce).data(), - (uint64_t)(u64)_header.nonce, - _header.mixHash.data(), - boundary.data()); - -#if !ETH_DEBUG - if (!quick) - return false; -#endif - - auto result = eval(_header); - bool slow = result.value <= boundary && result.mixHash == _header.mixHash; - -#if ETH_DEBUG - if (!quick && slow) - { - cwarn << "WARNING: evaluated result gives true whereas ethash_quick_check_difficulty gives false."; - cwarn << "headerHash:" << _header.headerHash(WithoutNonce); - cwarn << "nonce:" << _header.nonce; - cwarn << "mixHash:" << _header.mixHash; - cwarn << "difficulty:" << _header.difficulty; - cwarn << "boundary:" << boundary; - cwarn << "result.value:" << result.value; - cwarn << "result.mixHash:" << result.mixHash; - } -#endif - - return slow; -} - -Ethasher::Result Ethasher::eval(BlockInfo const& _header, Nonce const& _nonce) +Ethash::Result EthashAux::eval(BlockInfo const& _header, Nonce const& _nonce) { return eval(_header.seedHash(), _header.headerHash(WithoutNonce), _nonce); } -Ethasher::Result Ethasher::eval(h256 const& _seedHash, h256 const& _headerHash, Nonce const& _nonce) +Ethash::Result EthashAux::eval(h256 const& _seedHash, h256 const& _headerHash, Nonce const& _nonce) { - auto p = Ethasher::params(_header); + auto p = EthashAux::params(_header); ethash_return_value r; - if (Ethasher::get()->m_fulls.count(_seedHash)) - ethash_compute_full(&r, Ethasher::get()->full(_seedHash).data(), &p, _headerHash.data(), (uint64_t)(u64)_nonce); + if (EthashAux::get()->m_fulls.count(_seedHash)) + ethash_compute_full(&r, EthashAux::get()->full(_seedHash).data(), &p, _headerHash.data(), (uint64_t)(u64)_nonce); else - ethash_compute_light(&r, Ethasher::get()->light(_seedHash), &p, _headerHash.data(), (uint64_t)(u64)_nonce); -// cdebug << "Ethasher::eval sha3(cache):" << sha3(Ethasher::get()->cache(_header)) << "hh:" << _header.headerHash(WithoutNonce) << "nonce:" << _nonce << " => " << h256(r.result, h256::ConstructFromPointer); + ethash_compute_light(&r, EthashAux::get()->light(_seedHash), &p, _headerHash.data(), (uint64_t)(u64)_nonce); +// cdebug << "EthashAux::eval sha3(cache):" << sha3(EthashAux::get()->cache(_header)) << "hh:" << _header.headerHash(WithoutNonce) << "nonce:" << _nonce << " => " << h256(r.result, h256::ConstructFromPointer); return Result{h256(r.result, h256::ConstructFromPointer), h256(r.mix_hash, h256::ConstructFromPointer)}; } diff --git a/libethcore/EthashAux.h b/libethcore/EthashAux.h new file mode 100644 index 000000000..bfd01a594 --- /dev/null +++ b/libethcore/EthashAux.h @@ -0,0 +1,63 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file EthashAux.cpp + * @author Gav Wood + * @date 2014 + */ + +#include +#include "Ethash.h" + +namespace dev +{ +namespace eth{ + +class EthashAux +{ + EthashAux() {} + ~EthashAux(); + + static EthashAux* get() { if (!s_this) s_this = new EthashAux(); return s_this; } + + using LightType = void const*; + using FullType = void const*; + + static ethash_params params(BlockInfo const& _header); + static ethash_params params(h256 const& _seedHash); + static ethash_params params(unsigned _n); + static LightType light(BlockInfo const& _header); + static LightType light(h256 const& _header); + static bytesConstRef full(BlockInfo const& _header, bytesRef _dest = bytesRef()); + static bytesConstRef full(h256 const& _header, bytesRef _dest = bytesRef()); + + static Ethash::Result eval(BlockInfo const& _header) { return eval(_header, _header.nonce); } + static Ethash::Result eval(BlockInfo const& _header, Nonce const& _nonce); + static Ethash::Result eval(h256 const& _seedHash, h256 const& _headerHash, Nonce const& _nonce); + +private: + void killCache(h256 const& _s); + + static Ethash* s_this; + RecursiveMutex x_this; + + std::map m_lights; + std::map m_fulls; + std::map m_seedHashes; +}; + +} +} diff --git a/libethcore/Ethasher.h b/libethcore/Ethasher.h deleted file mode 100644 index f57c2f4f6..000000000 --- a/libethcore/Ethasher.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum 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. - - cpp-ethereum 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 cpp-ethereum. If not, see . -*/ -/** @file Ethasher.h - * @author Gav Wood - * @date 2014 - * - * ProofOfWork algorithm. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include // TODO: REMOVE once everything merged into this class and an opaque API can be provided. -static const unsigned c_ethashRevision = ETHASH_REVISION; -static const unsigned c_ethashEpochLength = ETHASH_EPOCH_LENGTH; -#include "Common.h" -#include "BlockInfo.h" - -namespace dev -{ -namespace eth -{ - -class Ethasher -{ -public: - Ethasher() {} - ~Ethasher(); - - static Ethasher* get() { if (!s_this) s_this = new Ethasher(); return s_this; } - - using LightType = void const*; - using FullType = void const*; - - LightType light(BlockInfo const& _header); - LightType light(h256 const& _header); - bytesConstRef full(BlockInfo const& _header, bytesRef _dest = bytesRef()); - bytesConstRef full(h256 const& _header, bytesRef _dest = bytesRef()); - - static ethash_params params(BlockInfo const& _header); - static ethash_params params(h256 const& _seedHash); - static ethash_params params(unsigned _n); - - struct Result - { - h256 value; - h256 mixHash; - }; - - static Result eval(BlockInfo const& _header) { return eval(_header, _header.nonce); } - static Result eval(BlockInfo const& _header, Nonce const& _nonce); - static Result eval(h256 const& _seedHash, h256 const& _headerHash, Nonce const& _nonce); - static bool verify(BlockInfo const& _header); - - class Miner - { - public: - Miner(BlockInfo const& _header): - m_headerHash(_header.headerHash(WithoutNonce)), - m_params(Ethasher::params(_header)), - m_datasetPointer(Ethasher::get()->full(_header).data()) - {} - - inline h256 mine(uint64_t _nonce) - { - ethash_compute_full(&m_ethashReturn, m_datasetPointer, &m_params, m_headerHash.data(), _nonce); -// cdebug << "Ethasher::mine hh:" << m_headerHash << "nonce:" << (Nonce)(u64)_nonce << " => " << h256(m_ethashReturn.result, h256::ConstructFromPointer); - return h256(m_ethashReturn.result, h256::ConstructFromPointer); - } - - inline h256 lastMixHash() const - { - return h256(m_ethashReturn.mix_hash, h256::ConstructFromPointer); - } - - private: - ethash_return_value m_ethashReturn; - h256 m_headerHash; - ethash_params m_params; - void const* m_datasetPointer; - }; - -private: - void killCache(h256 const& _s); - - static Ethasher* s_this; - RecursiveMutex x_this; - std::map m_lights; - std::map m_fulls; - std::map m_seedHashes; -}; - -} -} diff --git a/libethcore/Miner.cpp b/libethcore/Miner.cpp index d6f15866f..e69de29bb 100644 --- a/libethcore/Miner.cpp +++ b/libethcore/Miner.cpp @@ -1,12 +0,0 @@ -#include "Miner.h" - -Miner::Miner() -{ - -} - -Miner::~Miner() -{ - -} - diff --git a/libethcore/Miner.h b/libethcore/Miner.h index a7f17e565..e7157e660 100644 --- a/libethcore/Miner.h +++ b/libethcore/Miner.h @@ -15,9 +15,8 @@ along with cpp-ethereum. If not, see . */ /** @file Miner.h - * @author Alex Leverington * @author Gav Wood - * @date 2014 + * @date 2015 */ #pragma once @@ -28,7 +27,6 @@ #include #include #include -#include "State.h" namespace dev { @@ -36,15 +34,17 @@ namespace dev namespace eth { -struct WorkPackage +struct MineInfo { - h256 boundary; - h256 headerHash; ///< When h256() means "pause until notified a new work package is available". - h256 seedHash; + MineInfo() = default; + MineInfo(bool _completed): completed(_completed) {} + void combine(MineInfo const& _m) { requirement = std::max(requirement, _m.requirement); best = std::min(best, _m.best); hashes += _m.hashes; completed = completed || _m.completed; } + double requirement = 0; + double best = 1e99; + unsigned hashes = 0; + bool completed = false; }; -static const WorkPackage NullWorkPackage; - /** * @brief Describes the progress of a mining operation. */ @@ -58,30 +58,40 @@ struct MiningProgress unsigned ms = 0; ///< Total number of milliseconds of mining thus far. }; +template class Miner; + /** * @brief Class for hosting one or more Miners. * @warning Must be implemented in a threadsafe manner since it will be called from multiple * miner threads. */ -class FarmFace +template class FarmFace { public: + using WorkPackage = typename PoW::WorkPackage; + using Solution = typename PoW::Solution; + using Miner = Miner; + /** * @brief Called from a Miner to note a WorkPackage has a solution. * @param _p The solution. * @param _wp The WorkPackage that the Solution is for. + * @param _finder The miner that found it. * @return true iff the solution was good (implying that mining should be . */ - virtual bool submitProof(ProofOfWork::Solution const& _p, WorkPackage const& _wp) = 0; + virtual bool submitProof(Solution const& _p, WorkPackage const& _wp, Miner* _finder) = 0; }; /** * @brief A miner - a member and adoptee of the Farm. */ -class Miner +template class Miner { public: - using ConstructionInfo = std::pair; + using ConstructionInfo = std::pair*, unsigned>; + using WorkPackage = typename PoW::WorkPackage; + using Solution = typename PoW::Solution; + using FarmFace = FarmFace; Miner(ConstructionInfo const& _ci): m_farm(_ci.first), @@ -124,7 +134,7 @@ protected: * @param _s The solution. * @return true if the solution was correct and that the miner should pause. */ - bool submitProof(ProofOfWork::Solution const& _s) + bool submitProof(Solution const& _s) { if (m_farm) { diff --git a/libethcore/ProofOfWork.cpp b/libethcore/ProofOfWork.cpp index 97488ee35..ec910f7f2 100644 --- a/libethcore/ProofOfWork.cpp +++ b/libethcore/ProofOfWork.cpp @@ -19,224 +19,6 @@ * @date 2014 */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if ETH_ETHASHCL || !ETH_TRUE -#include -#endif -#include "BlockInfo.h" -#include "Ethasher.h" #include "ProofOfWork.h" using namespace std; -using namespace std::chrono; - -namespace dev -{ -namespace eth -{ - -void Ethash::CPUMiner::workLoop() -{ - Solution solution; - - class Miner - { - public: - Miner(BlockInfo const& _header): - m_headerHash(_header.headerHash(WithoutNonce)), - m_params(Ethasher::params(_header)), - m_datasetPointer(Ethasher::get()->full(_header).data()) - {} - - inline h256 mine(uint64_t _nonce) - { - ethash_compute_full(&m_ethashReturn, m_datasetPointer, &m_params, m_headerHash.data(), _nonce); -// cdebug << "Ethasher::mine hh:" << m_headerHash << "nonce:" << (Nonce)(u64)_nonce << " => " << h256(m_ethashReturn.result, h256::ConstructFromPointer); - return h256(m_ethashReturn.result, h256::ConstructFromPointer); - } - - inline h256 lastMixHash() const - { - return h256(m_ethashReturn.mix_hash, h256::ConstructFromPointer); - } - - private: - ethash_return_value m_ethashReturn; - h256 m_headerHash; - ethash_params m_params; - void const* m_datasetPointer; - }; - - Ethasher::Miner m(_header); - - std::pair ret; - auto tid = std::this_thread::get_id(); - static std::mt19937_64 s_eng((time(0) + *reinterpret_cast(m_last.data()) + std::hash()(tid))); - uint64_t tryNonce = (uint64_t)(u64)(m_last = Nonce::random(s_eng)); - - h256 boundary = _header.boundary(); - ret.first.requirement = log2((double)(u256)boundary); - - // 2^ 0 32 64 128 256 - // [--------*-------------------------] - // - // evaluate until we run out of time - auto startTime = std::chrono::steady_clock::now(); - double best = 1e99; // high enough to be effectively infinity :) - Solution result; - unsigned hashCount = 0; - for (; !shouldStop(); tryNonce++, hashCount++) - { - h256 val(m.mine(tryNonce)); - best = std::min(best, log2((double)(u256)val)); - if (val <= boundary) - { - if (submitProof(solution)) - return; - } - } - ret.first.hashes = hashCount; - ret.first.best = best; - ret.second = result; - - return; -} - -#if ETH_ETHASHCL || !ETH_TRUE - -/* -class ethash_cl_miner -{ -public: - struct search_hook - { - // reports progress, return true to abort - virtual bool found(uint64_t const* nonces, uint32_t count) = 0; - virtual bool searched(uint64_t start_nonce, uint32_t count) = 0; - }; - - ethash_cl_miner(); - - bool init(ethash_params const& params, const uint8_t seed[32], unsigned workgroup_size = 64); - - void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); - void search(uint8_t const* header, uint64_t target, search_hook& hook); -}; -*/ - -namespace dev { namespace eth { -class EthashCLHook: public ethash_cl_miner::search_hook -{ -public: - EthashCLHook(Ethash::GPUMiner* _owner): m_owner(_owner) {} - - void abort() - { - Guard l(x_all); - if (m_aborted) - return; -// cdebug << "Attempting to abort"; - m_abort = true; - for (unsigned timeout = 0; timeout < 100 && !m_aborted; ++timeout) - std::this_thread::sleep_for(chrono::milliseconds(30)); - if (!m_aborted) - cwarn << "Couldn't abort. Abandoning OpenCL process."; - m_aborted = m_abort = false; - } - - uint64_t fetchTotal() { Guard l(x_all); auto ret = m_total; m_total = 0; return ret; } - -protected: - virtual bool found(uint64_t const* _nonces, uint32_t _count) override - { -// cdebug << "Found nonces: " << vector(_nonces, _nonces + _count); - for (uint32_t i = 0; i < _count; ++i) - { - if (m_owner->found(_nonces[i])) - { - m_aborted = true; - return true; - } - } - return false; - } - - virtual bool searched(uint64_t _startNonce, uint32_t _count) override - { - Guard l(x_all); -// cdebug << "Searched" << _count << "from" << _startNonce; - m_total += _count; - m_last = _startNonce + _count; - if (m_abort) - { - m_aborted = true; - return true; - } - return false; - } - -private: - Mutex x_all; - uint64_t m_total; - uint64_t m_last; - bool m_abort = false; - bool m_aborted = true; - Ethash::GPUMiner* m_owner = nullptr; -}; - -} } - -Ethash::GPUMiner::GPUMiner(ConstructionInfo const& _ci): - Miner(_ci), - m_hook(new EthashCLHook(this)) -{ -} - -void Ethash::GPUMiner::report(uint64_t _nonce) -{ - Nonce n = (Nonce)(u64)_nonce; - Ethasher::Result r = Ethasher::eval(m_work.seedHash, m_work.headerHash, n); - if (r.value < m_work.boundary) - return submitProof(Solution{n, r.mixHash}); - return false; -} - -void Ethash::GPUMiner::kickOff(WorkPackage const& _work) -{ - if (!m_miner || m_minerSeed != _work.seedHash) - { - if (m_miner) - m_hook->abort(); - m_miner.reset(new ethash_cl_miner); - auto p = Ethasher::params(_work.seedHash); - auto cb = [&](void* d) { Ethasher::get()->readFull(_work.seedHash, bytesRef((byte*)d, p.full_size)); }; - m_miner->init(p, cb, 32); - } - if (m_lastWork.headerHash != _work.headerHash) - { - m_hook->abort(); - uint64_t upper64OfBoundary = (uint64_t)(u64)((u256)_work.boundary >> 192); - m_miner->search(_work.headerHash, upper64OfBoundary, *m_hook); - } - m_work = _work; -} - -void Ethash::GPUMiner::pause() -{ - m_hook->abort(); -} - -#endif - -} -} +using namespace dev; diff --git a/libethcore/ProofOfWork.h b/libethcore/ProofOfWork.h index f66bc77c9..764207aef 100644 --- a/libethcore/ProofOfWork.h +++ b/libethcore/ProofOfWork.h @@ -18,112 +18,29 @@ * @author Gav Wood * @date 2014 * - * ProofOfWork algorithm. Or not. + * Determines the PoW algorithm. */ #pragma once -#include -#include -#include -#include -#include "Common.h" -#include "BlockInfo.h" -#include "Miner.h" - -#define FAKE_DAGGER 1 - -class ethash_cl_miner; +#include "Ethash.h" namespace dev { namespace eth { -struct MineInfo -{ - MineInfo() = default; - MineInfo(bool _completed): completed(_completed) {} - void combine(MineInfo const& _m) { requirement = std::max(requirement, _m.requirement); best = std::min(best, _m.best); hashes += _m.hashes; completed = completed || _m.completed; } - double requirement = 0; - double best = 1e99; - unsigned hashes = 0; - bool completed = false; -}; - -class Ethash -{ - -public: - -struct Solution -{ - Nonce nonce; - h256 mixHash; -}; - -static bool verify(BlockInfo const& _header); -static void assignResult(Solution const& _r, BlockInfo& _header) { _header.nonce = _r.nonce; _header.mixHash = _r.mixHash; } - -class CPUMiner: public Miner, Worker -{ -public: - CPUMiner(ConstructionInfo const& _ci): Miner(_ci), Worker("miner" + toString(index())) {} - - static unsigned instances() { return thread::hardware_concurrency(); } - -protected: - void kickOff(WorkPackage const& _work) override - { - stopWorking(); - m_work = _work; - startWorking(); - } - - void pause() override { stopWorking(); } - -private: - void workLoop() override; - - WorkPackage m_work; - MineInfo m_info; -}; - -#if ETH_ETHASHCL || !ETH_TRUE - -class EthashCLHook; - -class GPUMiner: public Miner -{ - friend class EthashCLHook; - -public: - GPUMiner(ConstructionInfo const& _ci); - - static unsigned instances() { return 1; } - -protected: - void kickOff(WorkPackage const& _work) override; - void pause() override; - -private: - void report(uint64_t _nonce); - - std::unique_ptr m_hook; - std::unique_ptr m_miner; - h256 m_minerSeed; - WorkPackage m_lastWork; ///< Work loaded into m_miner. - MineInfo m_info; -}; - -#else - -using GPUMiner = CPUMiner; - -#endif - -}; - +/** + * The proof of work algorithm base type. + * + * Must implement a basic templated interface, including: + * typename Result + * typename Solution + * typename CPUMiner + * typename GPUMiner + * void assignResult(BlockInfo&, Result) + * and a few others. TODO + */ using ProofOfWork = Ethash; } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index bf2bce4fe..0ff0a3628 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -231,8 +231,7 @@ void BlockChain::rebuild(std::string const& _path, std::function(h256(u256(d)), m_blockHashes, x_blockHashes, NullBlockHash, oldExtrasDB).value); BlockInfo bi(b); - if (bi.number % c_ethashEpochLength == 1) - Ethasher::get()->full(bi); + ProofOfWork::prep(bi); if (bi.parentHash != lastHash) { @@ -307,14 +306,8 @@ tuple BlockChain::sync(BlockQueue& _bq, OverlayDB const& _st try { auto r = import(block, _stateDB); - bool isOld = true; - for (auto const& h: r.first) - if (h == r.second) - isOld = false; - else if (isOld) - dead.push_back(h); - else - fresh.push_back(h); + fresh += r.first; + dead += r.second; } catch (UnknownParent) { @@ -334,7 +327,7 @@ tuple BlockChain::sync(BlockQueue& _bq, OverlayDB const& _st return make_tuple(fresh, dead, _bq.doneDrain(badBlocks)); } -pair BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB, Aversion _force) noexcept +ImportRoute BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB, Aversion _force) noexcept { try { @@ -347,7 +340,7 @@ pair BlockChain::attemptImport(bytes const& _block, OverlayDB const } } -pair BlockChain::import(bytes const& _block, OverlayDB const& _db, Aversion _force) +ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Aversion _force) { //@tidy This is a behemoth of a method - could do to be split into a few smaller ones. @@ -626,7 +619,17 @@ pair BlockChain::import(bytes const& _block, OverlayDB const& _db, cnote << "checkBest:" << checkBest; #endif - return make_pair(route, common); + h256s fresh; + h256s dead; + bool isOld = true; + for (auto const& h: route) + if (h == common) + isOld = false; + else if (isOld) + dead.push_back(h); + else + fresh.push_back(h); + return make_pair(fresh, dead); } void BlockChain::clearBlockBlooms(unsigned _begin, unsigned _end) diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 765e00b03..cdff566fb 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -68,6 +68,7 @@ ldb::Slice toSlice(h256 const& _h, unsigned _sub = 0); using BlocksHash = std::map; using TransactionHashes = h256s; using UncleHashes = h256s; +using ImportRoute = std::pair; enum { ExtraDetails = 0, @@ -108,11 +109,11 @@ public: /// Attempt to import the given block directly into the CanonBlockChain and sync with the state DB. /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. - std::pair attemptImport(bytes const& _block, OverlayDB const& _stateDB, Aversion _force = Aversion::AvoidOldBlocks) noexcept; + ImportRoute attemptImport(bytes const& _block, OverlayDB const& _stateDB, Aversion _force = Aversion::AvoidOldBlocks) noexcept; /// Import block into disk-backed DB /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. - std::pair import(bytes const& _block, OverlayDB const& _stateDB, Aversion _force = Aversion::AvoidOldBlocks); + ImportRoute import(bytes const& _block, OverlayDB const& _stateDB, Aversion _force = Aversion::AvoidOldBlocks); /// Returns true if the given block is known (though not necessarily a part of the canon chain). bool isKnown(h256 const& _hash) const; diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 6f8c64827..e9dd99cd1 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -102,6 +102,7 @@ ImportResult BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) m_readySet.insert(h); noteReadyWithoutWriteGuard(h); + m_onReady(); return ImportResult::Success; } } diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index ce0582db2..f5cdf7ab5 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -83,6 +83,8 @@ public: /// Get some infomration on the current status. BlockQueueStatus status() const { ReadGuard l(m_lock); return BlockQueueStatus{m_ready.size(), m_future.size(), m_unknown.size(), m_knownBad.size()}; } + template Handler onReady(T const& _t) { return m_onReady.add(_t); } + private: void noteReadyWithoutWriteGuard(h256 _b); void notePresentWithoutWriteGuard(bytesConstRef _block); @@ -95,6 +97,7 @@ private: std::multimap> m_unknown; ///< For transactions that have an unknown parent; we map their parent hash to the block stuff, and insert once the block appears. std::multimap m_future; ///< Set of blocks that are not yet valid. std::set m_knownBad; ///< Set of blocks that we know will never be valid. + Signal m_onReady; ///< Called when a subsequent call to import transactions will return a non-empty container. Be nice and exit fast. }; } diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index e7c91c03f..15a7c9bec 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -117,7 +117,7 @@ void BasicGasPricer::update(BlockChain const& _bc) } } -Client::Client(p2p::Host* _extNet, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId, int _miners): +Client::Client(p2p::Host* _extNet, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId): Worker("eth"), m_vc(_dbPath), m_bc(_dbPath, max(m_vc.action(), _forceAction), [](unsigned d, unsigned t){ cerr << "REVISING BLOCKCHAIN: Processed " << d << " of " << t << "...\r"; }), @@ -126,14 +126,14 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, WithExisting _for m_preMine(m_stateDB, BaseState::CanonGenesis), m_postMine(m_stateDB) { + m_tqReady = m_tq->onReady([=](){ this->onTransactionQueueReady(); }); // TODO: should read m_tq->onReady(thisThread, syncTransactionQueue); + m_bqReady = m_bq->onReady([=](){ this->onBlockQueueReady(); }); // TODO: should read m_bq->onReady(thisThread, syncBlockQueue); + m_farm->onSolutionFound([=](ProofOfWork::Solution const& s){ return this->submitWork(s); }); + m_gp->update(m_bc); m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId)); - if (_miners > -1) - setMiningThreads(_miners); - else - setMiningThreads(); if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); @@ -142,7 +142,7 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, WithExisting _for startWorking(); } -Client::Client(p2p::Host* _extNet, std::shared_ptr _gp, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId, int _miners): +Client::Client(p2p::Host* _extNet, std::shared_ptr _gp, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId): Worker("eth"), m_vc(_dbPath), m_bc(_dbPath, max(m_vc.action(), _forceAction), [](unsigned d, unsigned t){ cerr << "REVISING BLOCKCHAIN: Processed " << d << " of " << t << "...\r"; }), @@ -151,14 +151,14 @@ Client::Client(p2p::Host* _extNet, std::shared_ptr _gp, std::string c m_preMine(m_stateDB), m_postMine(m_stateDB) { + m_tq->onReady([=](){ this->onTransactionQueueReady(); }); + m_bq->onReady([=](){ this->onBlockQueueReady(); }); + m_farm->onSolutionFound([=](ProofOfWork::Solution const& s){ return this->submitWork(s); }); + m_gp->update(m_bc); m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId)); - if (_miners > -1) - setMiningThreads(_miners); - else - setMiningThreads(); if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); @@ -229,8 +229,6 @@ void Client::killChain() doWork(); - setMiningThreads(0); - startWorking(); if (wasMining) startMining(); @@ -271,26 +269,6 @@ static string filtersToString(T const& _fs) return ret.str(); } -void Client::noteChanged(h256Set const& _filters) -{ - Guard l(x_filtersWatches); - if (_filters.size()) - cnote << "noteChanged(" << filtersToString(_filters) << ")"; - // accrue all changes left in each filter into the watches. - for (auto& w: m_watches) - if (_filters.count(w.second.id)) - { - cwatch << "!!!" << w.first << (m_filters.count(w.second.id) ? w.second.id.abridged() : w.second.id == PendingChangedFilter ? "pending" : w.second.id == ChainChangedFilter ? "chain" : "???"); - if (m_filters.count(w.second.id)) // Normal filtering watch - w.second.changes += m_filters.at(w.second.id).changes; - else // Special ('pending'/'latest') watch - w.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0)); - } - // clear the filters now. - for (auto& i: m_filters) - i.second.changes.clear(); -} - void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _transactionHash) { Guard l(x_filtersWatches); @@ -342,22 +320,6 @@ void Client::setForceMining(bool _enable) m.noteStateChange(); } -void Client::setMiningThreads(unsigned _threads) -{ - stopMining(); - auto t = _threads ? _threads : thread::hardware_concurrency(); -#if ETH_ETHASHCL || !ETH_TRUE - if (m_turboMining) - t = 1; -#endif - WriteGuard l(x_localMiners); - m_localMiners.clear(); - m_localMiners.resize(t); - unsigned i = 0; - for (auto& m: m_localMiners) - m.setup(this, i++); -} - MineProgress Client::miningProgress() const { MineProgress ret; @@ -452,160 +414,161 @@ pair Client::getWork() return make_pair(m_remoteMiner.workHash(), m_remoteMiner.difficulty()); } -bool Client::submitWork(ProofOfWork::Solution const& _proof) +bool Client::submitWork(ProofOfWork::Solution const& _solution) { - Guard l(x_remoteMiner); - return m_remoteMiner.submitWork(_proof); + bytes newBlock; + { + WriteGuard l(x_stateDB); + if (!m_postMine.completeMine(_solution)) + return false; + newBlock = m_postMine.blockData(); + } + + ImportRoute ir = m_bc.attemptImport(newBlock, m_stateDB); + if (!ir.first.empty()) + onChainChanged(ir); + return true; } -void Client::doWork() +void Client::syncBlockQueue() { - // TODO: Use condition variable rather than polling. - - bool stillGotWork = false; - - cworkin << "WORK"; - h256Set changeds; - - auto maintainMiner = [&](OldMiner& m) - { - if (m.isComplete()) - { - // TODO: enable a short-circuit option since we mined it. will need to get the end state from the miner. - auto lm = dynamic_cast(&m); - h256s hs; - h256 c; - if (false && lm && !m_verifyOwnBlocks) - { - // TODO: implement - //m_bc.attemptImport(m_blockData(), m_stateDB, lm->state()); - // TODO: derive hs from lm->state() - } - else - { - cwork << "CHAIN <== postSTATE"; - WriteGuard l(x_stateDB); - tie(hs, c) = m_bc.attemptImport(m.blockData(), m_stateDB); - } - if (hs.size()) - { - for (auto const& h: hs) - if (h != c) - appendFromNewBlock(h, changeds); - changeds.insert(ChainChangedFilter); - } - for (auto& m: m_localMiners) - m.noteStateChange(); - } - }; - { - ReadGuard l(x_localMiners); - for (auto& m: m_localMiners) - maintainMiner(m); - } - { - Guard l(x_remoteMiner); - maintainMiner(m_remoteMiner); - } + ImportResult ir; - // Synchronise state to block chain. - // This should remove any transactions on our queue that are included within our state. - // It also guarantees that the state reflects the longest (valid!) chain on the block chain. - // This might mean reverting to an earlier state and replaying some blocks, or, (worst-case: - // if there are no checkpoints before our fork) reverting to the genesis block and replaying - // all blocks. - // Resynchronise state with block chain & trans - bool resyncStateNeeded = false; { WriteGuard l(x_stateDB); cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; x_stateDB.unlock(); - h256s fresh; - h256s dead; - bool sgw; - tie(fresh, dead, sgw) = m_bc.sync(m_bq, db, 100); - - // insert transactions that we are declaring the dead part of the chain - for (auto const& h: dead) - { - clog(ClientNote) << "Dead block:" << h.abridged(); - for (auto const& t: m_bc.transactions(h)) - { - clog(ClientNote) << "Resubmitting transaction " << Transaction(t, CheckTransaction::None); - m_tq.import(t); - } - } - // remove transactions from m_tq nicely rather than relying on out of date nonce later on. - for (auto const& h: fresh) - { - clog(ClientChat) << "Live block:" << h.abridged(); - for (auto const& th: m_bc.transactionHashes(h)) - { - clog(ClientNote) << "Safely dropping transaction " << th.abridged(); - m_tq.drop(th); - } - } + tie(ir.first, ir.second, m_syncBlockQueue) = m_bc.sync(m_bq, db, 100); - stillGotWork = stillGotWork | sgw; - if (!fresh.empty()) - { - for (auto i: fresh) - appendFromNewBlock(i, changeds); - changeds.insert(ChainChangedFilter); - } x_stateDB.lock(); if (fresh.size()) m_stateDB = db; + } + + if (!ir.first.empty()) + onChainChanged(ir); + return true; +} + +void Client::syncTransactionQueue() +{ + // returns TransactionReceipts, once for each transaction. + cwork << "postSTATE <== TQ"; + TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq, *m_gp); + if (newPendingReceipts.size()) + { + for (size_t i = 0; i < newPendingReceipts.size(); i++) + appendFromNewPending(newPendingReceipts[i], changeds, m_postMine.pending()[i].sha3()); - cwork << "preSTATE <== CHAIN"; - if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) + changeds.insert(PendingChangedFilter); + + if (isMining()) + cnote << "Additional transaction ready: Restarting mining operation."; + resyncStateNeeded = true; + if (auto h = m_host.lock()) + h->noteNewTransactions(); + } +} + +void Client::onChainChanged(ImportRoute const& _ir) +{ + // insert transactions that we are declaring the dead part of the chain + for (auto const& h: _ir.second) + { + clog(ClientNote) << "Dead block:" << h.abridged(); + for (auto const& t: m_bc.transactions(h)) { - if (isMining()) - cnote << "New block on chain: Restarting mining operation."; - m_postMine = m_preMine; - resyncStateNeeded = true; - changeds.insert(PendingChangedFilter); - // TODO: Move transactions pending from m_postMine back to transaction queue. + clog(ClientNote) << "Resubmitting transaction " << Transaction(t, CheckTransaction::None); + m_tq.import(t); } + } - // returns TransactionReceipts, once for each transaction. - cwork << "postSTATE <== TQ"; - TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq, *m_gp); - if (newPendingReceipts.size()) + // remove transactions from m_tq nicely rather than relying on out of date nonce later on. + for (auto const& h: _ir.first) + { + clog(ClientChat) << "Live block:" << h.abridged(); + for (auto const& th: m_bc.transactionHashes(h)) { - for (size_t i = 0; i < newPendingReceipts.size(); i++) - appendFromNewPending(newPendingReceipts[i], changeds, m_postMine.pending()[i].sha3()); - - changeds.insert(PendingChangedFilter); - - if (isMining()) - cnote << "Additional transaction ready: Restarting mining operation."; - resyncStateNeeded = true; - if (auto h = m_host.lock()) - h->noteNewTransactions(); + clog(ClientNote) << "Safely dropping transaction " << th.abridged(); + m_tq.drop(th); } } - if (!changeds.empty()) - if (auto h = m_host.lock()) - h->noteNewBlocks(); + if (auto h = m_host.lock()) + h->noteNewBlocks(); + + h256Set changeds; + for (auto const& h: _ir.first) + if (h != _ir.second) + appendFromNewBlock(h, changeds); + changeds.insert(ChainChangedFilter); + noteChanged(changeds); + + // RESTART MINING - if (resyncStateNeeded) + // LOCKS NEEDED? + Guard l(x_stateDB); + cwork << "preSTATE <== CHAIN"; + if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { - ReadGuard l(x_localMiners); - for (auto& m: m_localMiners) - m.noteStateChange(); + if (isMining()) + cnote << "New block on chain: Restarting mining operation."; + m_postMine = m_preMine; + resyncStateNeeded = true; + changeds.insert(PendingChangedFilter); + + m_postMine.commitToMine(m_bc); + m_farm.setWork(m_postMine.info()); } +} - cwork << "noteChanged" << changeds.size() << "items"; - noteChanged(changeds); - cworkout << "WORK"; +void Client::noteChanged(h256Set const& _filters) +{ + Guard l(x_filtersWatches); + if (_filters.size()) + cnote << "noteChanged(" << filtersToString(_filters) << ")"; + // accrue all changes left in each filter into the watches. + for (auto& w: m_watches) + if (_filters.count(w.second.id)) + { + cwatch << "!!!" << w.first << (m_filters.count(w.second.id) ? w.second.id.abridged() : w.second.id == PendingChangedFilter ? "pending" : w.second.id == ChainChangedFilter ? "chain" : "???"); + if (m_filters.count(w.second.id)) // Normal filtering watch + w.second.changes += m_filters.at(w.second.id).changes; + else // Special ('pending'/'latest') watch + w.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0)); + } + // clear the filters now. + for (auto& i: m_filters) + i.second.changes.clear(); +} + +void Client::doWork() +{ + // TODO: Use condition variable rather than this rubbish. + + Guard l(x_fakeSignalSystemState); + + if (m_syncTransactionQueue) + { + m_syncTransactionQueue = false; + syncTransactionQueue(); + } + + if (m_syncBlockQueue) + { + m_syncBlockQueue = false; + syncBlockQueue(); + } + + checkWatchGarbage(); - if (!stillGotWork) - this_thread::sleep_for(chrono::milliseconds(100)); + this_thread::sleep_for(chrono::milliseconds(20)); +} +void Client::checkWatchGarbage() +{ if (chrono::system_clock::now() - m_lastGarbageCollection > chrono::seconds(5)) { // watches garbage collection diff --git a/libethereum/Client.h b/libethereum/Client.h index 9af501f74..8eac2e577 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -42,6 +42,7 @@ #include "CommonNet.h" #include "Miner.h" #include "ABI.h" +#include "Farm.h" #include "ClientBase.h" namespace dev @@ -122,7 +123,7 @@ struct ClientDetail: public LogChannel { static const char* name() { return " C /** * @brief Main API hub for interfacing with Ethereum. */ -class Client: public MinerHost, public ClientBase, Worker +class Client: public ClientBase, Worker { friend class OldMiner; @@ -132,8 +133,7 @@ public: p2p::Host* _host, std::string const& _dbPath = std::string(), WithExisting _forceAction = WithExisting::Trust, - u256 _networkId = 0, - int _miners = -1 + u256 _networkId = 0 ); explicit Client( @@ -141,8 +141,7 @@ public: std::shared_ptr _gpForAdoption, // pass it in with new. std::string const& _dbPath = std::string(), WithExisting _forceAction = WithExisting::Trust, - u256 _networkId = 0, - int _miners = -1 + u256 _networkId = 0 ); /// Destructor. @@ -191,31 +190,31 @@ public: /// Are we allowed to GPU mine? bool turboMining() const { return m_turboMining; } /// Enable/disable GPU mining. - void setTurboMining(bool _enable = true) { bool was = isMining(); stopMining(); m_turboMining = _enable; setMiningThreads(0); if (was) startMining(); } + void setTurboMining(bool _enable = true) { m_turboMining = _enable; if (isMining()) startMining(); } - /// Stops mining and sets the number of mining threads (0 for automatic). - void setMiningThreads(unsigned _threads = 0) override; - /// Get the effective number of mining threads. - unsigned miningThreads() const override { ReadGuard l(x_localMiners); return m_localMiners.size(); } /// Start mining. /// NOT thread-safe - call it & stopMining only from a single thread - void startMining() override { startWorking(); { ReadGuard l(x_localMiners); for (auto& m: m_localMiners) m.start(); } } + void startMining() override { if (m_turboMining) m_farm.startGPU(); else m_farm.startCPU(); } /// Stop mining. /// NOT thread-safe - void stopMining() override { { ReadGuard l(x_localMiners); for (auto& m: m_localMiners) m.stop(); } } - /// Are we mining now? - bool isMining() const override { { ReadGuard l(x_localMiners); if (!m_localMiners.empty() && m_localMiners[0].isRunning()) return true; } return false; } + void stopMining() override { m_farm.stop(); } /// Are we mining now? + bool isMining() const override { return m_farm.isMining(); } + /// The hashrate... uint64_t hashrate() const override; /// Check the progress of the mining. - MineProgress miningProgress() const override; + MiningProgress miningProgress() const override; /// Get and clear the mining history. std::list miningHistory(); /// Update to the latest transactions and get hash of the current block to be mined minus the /// nonce (the 'work hash') and the difficulty to be met. virtual std::pair getWork() override; - /// Submit the proof for the proof-of-work. + + /** @brief Submit the proof for the proof-of-work. + * @param _s A valid solution. + * @return true if the solution was indeed valid and accepted. + */ virtual bool submitWork(ProofOfWork::Solution const& _proof) override; // Debug stuff: @@ -261,10 +260,23 @@ private: /// Called when Worker is exiting. virtual void doneWorking(); - /// Overrides for being a mining host. - virtual void setupState(State& _s); - virtual bool turbo() const { return m_turboMining; } - virtual bool force() const { return m_forceMining; } + /// Magically called when the chain has changed. An import route is provided. + /// Called by either submitWork() or in our main thread through syncBlockQueue(). + void onChainChanged(ImportRoute const& _ir); + + /// Signal handler for when the block queue needs processing. + void syncBlockQueue(); + + /// Signal handler for when the block queue needs processing. + void syncTransactionQueue(); + + /// Magically called when m_tq needs syncing. Be nice and don't block. + void onTransactionQueueReady() { Guard l(x_fakeSignalSystemState); m_syncTransactionQueue = true; } + + /// Magically called when m_tq needs syncing. Be nice and don't block. + void onBlockQueueReady() { Guard l(x_fakeSignalSystemState); m_syncBlockQueue = true; } + + void checkWatchGarbage(); VersionChecker m_vc; ///< Dummy object to check & update the protocol version. CanonBlockChain m_bc; ///< Maintains block database. @@ -278,17 +290,24 @@ private: std::weak_ptr m_host; ///< Our Ethereum Host. Don't do anything if we can't lock. + Farm m_farm; ///< Our mining farm. mutable Mutex x_remoteMiner; ///< The remote miner lock. RemoteMiner m_remoteMiner; ///< The remote miner. - std::vector m_localMiners; ///< The in-process miners. - mutable SharedMutex x_localMiners; ///< The in-process miners lock. - bool m_paranoia = false; ///< Should we be paranoid about our state? + Handler m_tqReady; + Handler m_bqReady; + bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping. bool m_forceMining = false; ///< Mine even when there are no transactions pending? - bool m_verifyOwnBlocks = true; ///< Should be verify blocks that we mined? + bool m_paranoia = false; ///< Should we be paranoid about our state? mutable std::chrono::system_clock::time_point m_lastGarbageCollection; + ///< When did we last both doing GC on the watches? + + // TODO!!!!!! REPLACE WITH A PROPER X-THREAD ASIO SIGNAL SYSTEM (could just be condition variables) + mutable Mutex x_fakeSignalSystemState; + bool m_syncTransactionQueue = false; + bool m_syncBlockQueue = false; }; } diff --git a/libethereum/ClientBase.h b/libethereum/ClientBase.h index ae6d27578..4b3cc5002 100644 --- a/libethereum/ClientBase.h +++ b/libethereum/ClientBase.h @@ -142,8 +142,6 @@ public: /// TODO: consider moving it to a separate interface - virtual void setMiningThreads(unsigned _threads) override { (void)_threads; BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::setMiningThreads")); } - virtual unsigned miningThreads() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningThreads")); } virtual void startMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::startMining")); } virtual void stopMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::stopMining")); } virtual bool isMining() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::isMining")); } diff --git a/libethereum/Farm.cpp b/libethereum/Farm.cpp index 639e4efcf..e69de29bb 100644 --- a/libethereum/Farm.cpp +++ b/libethereum/Farm.cpp @@ -1,12 +0,0 @@ -#include "Farm.h" - -Farm::Farm() -{ - -} - -Farm::~Farm() -{ - -} - diff --git a/libethereum/Farm.h b/libethereum/Farm.h index a49038f0d..55d1aa8df 100644 --- a/libethereum/Farm.h +++ b/libethereum/Farm.h @@ -14,10 +14,9 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file Miner.h - * @author Alex Leverington +/** @file Farm.h * @author Gav Wood - * @date 2014 + * @date 2015 */ #pragma once @@ -28,7 +27,8 @@ #include #include #include -#include "Miner.h" +#include +#include namespace dev { @@ -41,8 +41,8 @@ namespace eth * Miners ask for work, then submit proofs * @threadsafe */ -template -class Farm: public FarmFace +template +class Farm: public FarmFace { public: /** @@ -65,13 +65,13 @@ public: * @brief (Re)start miners for CPU only. * @returns true if started properly. */ - bool startCPU() { return start(); } + bool startCPU() { return start(); } /** * @brief (Re)start miners for GPU only. * @returns true if started properly. */ - bool startGPU() { start(); } + bool startGPU() { start(); } /** * @brief Stop all mining activities. @@ -82,20 +82,24 @@ public: m_miners.clear(); } + bool isMining() const + { + ReadGuard l(x_miners); + return !m_miners.empty(); + } + /** * @brief Get information on the progress of mining this work package. * @return The progress with mining so far. */ - MineProgress const& mineProgress() const { ReadGuard l(x_progress); return m_progress; } + MiningProgress const& miningProgress() const { ReadGuard l(x_progress); return m_progress; } -protected: - // TO BE REIMPLEMENTED BY THE SUBCLASS /** * @brief Provides a valid header based upon that received previously with setWork(). * @param _bi The now-valid header. * @return true if the header was good and that the Farm should pause until more work is submitted. */ - virtual bool submitHeader(BlockInfo const& _bi) = 0; + void onSolutionFound(function _handler) { m_onSolutionFound = _handler; } private: /** @@ -104,16 +108,15 @@ private: * @param _wp The WorkPackage that the Solution is for. * @return true iff the solution was good (implying that mining should be . */ - bool submitProof(ProofOfWork::Solution const& _p, WorkPackage const& _wp, NewMiner* _m) override + bool submitProof(Solution const& _s, WorkPackage const& _wp, Miner* _m) override { if (_wp.headerHash != m_work.headerHash) return false; - ProofOfWork::assignResult(_p, m_header); - if (submitHeader(m_header)) + if (m_onSolutionFound && m_onSolutionFound(_s)) { ReadGuard l(x_miners); - for (std::shared_ptr const& m: m_miners) + for (std::shared_ptr const& m: m_miners) if (m.get() != _m) m->pause(); m_work.headerHash = h256(); @@ -139,14 +142,16 @@ private: } mutable SharedMutex x_miners; - std::vector> m_miners; + std::vector> m_miners; mutable SharedMutex x_progress; - MineProgress m_progress; + MiningProgress m_progress; mutable SharedMutex x_work; WorkPackage m_work; BlockInfo m_header; + + function m_onSolutionFound; }; } diff --git a/libethereum/State.cpp b/libethereum/State.cpp index e77565837..75618eb5f 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -856,20 +856,6 @@ void State::commitToMine(BlockChain const& _bc) m_committedToMine = true; } -bool State::completeMine(ProofOfWork::Solution const& _nonce) -{ - ProofOfWork::assignResult(_nonce, m_currentBlock); - -// if (!m_pow.verify(m_currentBlock)) -// return false; - - cnote << "Completed" << m_currentBlock.headerHash(WithoutNonce).abridged() << m_currentBlock.nonce.abridged() << m_currentBlock.difficulty << ProofOfWork::verify(m_currentBlock); - - completeMine(); - - return true; -} - void State::completeMine() { cdebug << "Completing mine!"; diff --git a/libethereum/State.h b/libethereum/State.h index b327378a1..36a505481 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include "TransactionQueue.h" @@ -162,30 +163,19 @@ public: /// Pass in a solution to the proof-of-work. /// @returns true iff the given nonce is a proof-of-work for this State's block. - bool completeMine(ProofOfWork::Solution const& _result); - - /// Attempt to find valid nonce for block that this state represents. - /// This function is thread-safe. You can safely have other interactions with this object while it is happening. - /// @param _msTimeout Timeout before return in milliseconds. - /// @returns Information on the mining. - template MineInfo mine(ProofOfWork* _pow) + template + bool completeMine(typename PoW::Solution const& _result) { - // Update difficulty according to timestamp. - m_currentBlock.difficulty = m_currentBlock.calculateDifficulty(m_previousBlock); + PoW::assignResult(_result, m_currentBlock); - MineInfo ret; - typename ProofOfWork::Solution r; - std::tie(ret, r) = _pow->mine(m_currentBlock, _pow->defaultTimeout(), true); + // if (!m_pow.verify(m_currentBlock)) + // return false; - if (!ret.completed) - m_currentBytes.clear(); - else - { - ProofOfWork::assignResult(r, m_currentBlock); - cnote << "Completed" << m_currentBlock.headerHash(WithoutNonce).abridged() << m_currentBlock.nonce.abridged() << m_currentBlock.difficulty << ProofOfWork::verify(m_currentBlock); - } + cnote << "Completed" << m_currentBlock.headerHash(WithoutNonce).abridged() << m_currentBlock.nonce.abridged() << m_currentBlock.difficulty << PoW::verify(m_currentBlock); - return ret; + completeMine(); + + return true; } /** Commit to DB and build the final block if the previous call to mine()'s result is completion. @@ -369,7 +359,6 @@ private: /// Debugging only. Good for checking the Trie is in shape. void paranoia(std::string const& _when, bool _enforceRefs = false) const; - OverlayDB m_db; ///< Our overlay for the state tree. SecureTrieDB m_state; ///< Our state tree, as an OverlayDB DB. Transactions m_transactions; ///< The current list of transactions that we've included in the state. diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 7c72f53e8..506de2d2f 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -53,6 +53,7 @@ ImportResult TransactionQueue::import(bytesConstRef _transactionRLP, ImportCallb if (_cb) m_callbacks[h] = _cb; ctxq << "Queued vaguely legit-looking transaction" << h.abridged(); + m_onReady(); } catch (Exception const& _e) { diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index ad093b4e5..e18f47e80 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -26,7 +26,7 @@ #include #include #include -#include "libethcore/Common.h" +#include #include "Transaction.h" namespace dev @@ -60,13 +60,15 @@ public: void noteGood(std::pair const& _t); void clear() { WriteGuard l(m_lock); m_known.clear(); m_current.clear(); m_unknown.clear(); } + template Handler onReady(T const& _t) { return m_onReady.add(_t); } private: mutable boost::shared_mutex m_lock; ///< General lock. std::set m_known; ///< Hashes of transactions in both sets. std::map m_current; ///< Map of SHA3(tx) to tx. std::multimap> m_unknown; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. - std::map> m_callbacks; ///< Called once + std::map> m_callbacks; ///< Called once. + Signal m_onReady; ///< Called when a subsequent call to import transactions will return a non-empty container. Be nice and exit fast. }; }