From 2dbabb11592163b6711efdb4a923c91b5a5b2197 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 20 Jul 2016 19:06:49 +1200 Subject: [PATCH 1/5] Make Equihash solvers cancellable The miner only cancels the solver when chainActive.Tip() changes. Closes #1055 --- src/crypto/equihash.cpp | 51 ++++++++++++++++++++++++++++--------- src/crypto/equihash.h | 50 +++++++++++++++++++++++------------- src/miner.cpp | 8 +++++- src/rpcmining.cpp | 2 +- src/test/equihash_tests.cpp | 4 +-- src/test/miner_tests.cpp | 2 +- src/zcbenchmarks.cpp | 2 +- 7 files changed, 83 insertions(+), 36 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index e8a8fcb22..0e66e4886 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -21,6 +21,8 @@ #include +EhSolverCancelledException solver_cancelled; + template int Equihash::InitialiseState(eh_HashState& base_state) { @@ -191,7 +193,7 @@ eh_trunc* TruncatedStepRow::GetTruncatedIndices(size_t len, size_t lenInd } template -std::set> Equihash::BasicSolve(const eh_HashState& base_state) +std::set> Equihash::BasicSolve(const eh_HashState& base_state, const std::function cancelled) { eh_index init_size { 1 << (CollisionBitLength + 1) }; @@ -203,6 +205,8 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba X.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { X.emplace_back(N, base_state, i); + // Slow down checking to prevent segfaults (??) + if (i % 10000 == 0 && cancelled()) throw solver_cancelled; } // 3) Repeat step 2 until 2n/(k+1) bits remain @@ -211,6 +215,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba // 2a) Sort the list LogPrint("pow", "- Sorting list\n"); std::sort(X.begin(), X.end(), CompareSR(CollisionByteLength)); + if (cancelled()) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; @@ -240,6 +245,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba } i += j; + if (cancelled()) throw solver_cancelled; } // 2e) Handle edge case where final table entry has no collision @@ -259,6 +265,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba hashLen -= CollisionByteLength; lenIndices *= 2; + if (cancelled()) throw solver_cancelled; } // k+1) Find a collision on last 2n(k+1) bits @@ -286,6 +293,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba } i += j; + if (cancelled()) throw solver_cancelled; } } else LogPrint("pow", "- List is empty\n"); @@ -346,7 +354,7 @@ void CollideBranches(std::vector>& X, const size_t hlen, cons } template -std::set> Equihash::OptimisedSolve(const eh_HashState& base_state) +std::set> Equihash::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled) { eh_index init_size { 1 << (CollisionBitLength + 1) }; @@ -366,6 +374,8 @@ std::set> Equihash::OptimisedSolve(const eh_HashState Xt.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { Xt.emplace_back(N, base_state, i, CollisionBitLength + 1); + // Slow down checking to prevent segfaults (??) + if (i % 10000 == 0 && cancelled()) throw solver_cancelled; } // 3) Repeat step 2 until 2n/(k+1) bits remain @@ -374,6 +384,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // 2a) Sort the list LogPrint("pow", "- Sorting list\n"); std::sort(Xt.begin(), Xt.end(), CompareSR(CollisionByteLength)); + if (cancelled()) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; @@ -402,6 +413,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } i += j; + if (cancelled()) throw solver_cancelled; } // 2e) Handle edge case where final table entry has no collision @@ -421,6 +433,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState hashLen -= CollisionByteLength; lenIndices *= 2; + if (cancelled()) throw solver_cancelled; } // k+1) Find a collision on last 2n(k+1) bits @@ -428,6 +441,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState if (Xt.size() > 1) { LogPrint("pow", "- Sorting list\n"); std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen)); + if (cancelled()) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; while (i < Xt.size() - 1) { @@ -445,6 +459,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } i += j; + if (cancelled()) break; } } else LogPrint("pow", "- List is empty\n"); @@ -458,6 +473,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState std::set> solns; eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) }; int invalidCount = 0; + if (cancelled()) goto cancelsolver; for (eh_trunc* partialSoln : partialSolns) { size_t hashLen; size_t lenIndices; @@ -472,6 +488,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (eh_index j = 0; j < recreate_size; j++) { eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) }; icv.emplace_back(N, base_state, newIndex); + if (cancelled()) goto cancelsolver; } boost::optional>> ic = icv; @@ -487,6 +504,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState ic->reserve(ic->size() + X[r]->size()); ic->insert(ic->end(), X[r]->begin(), X[r]->end()); std::sort(ic->begin(), ic->end(), CompareSR(hashLen)); + if (cancelled()) goto cancelsolver; size_t lti = rti-(1<> Equihash::OptimisedSolve(const eh_HashState X.push_back(ic); break; } + if (cancelled()) goto cancelsolver; } + if (cancelled()) goto cancelsolver; } // We are at the top of the tree @@ -517,17 +537,24 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (FullStepRow row : *X[K]) { solns.insert(row.GetIndices(hashLen, lenIndices)); } - goto deletesolution; + if (cancelled()) goto cancelsolver; + continue; invalidsolution: invalidCount++; - -deletesolution: - delete[] partialSoln; } LogPrint("pow", "- Number of invalid solutions found: %d\n", invalidCount); + for (eh_trunc* partialSoln : partialSolns) { + delete[] partialSoln; + } return solns; + +cancelsolver: + for (eh_trunc* partialSoln : partialSolns) { + delete[] partialSoln; + } + throw solver_cancelled; } template @@ -577,18 +604,18 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector< // Explicit instantiations for Equihash<96,3> template int Equihash<96,3>::InitialiseState(eh_HashState& base_state); -template std::set> Equihash<96,3>::BasicSolve(const eh_HashState& base_state); -template std::set> Equihash<96,3>::OptimisedSolve(const eh_HashState& base_state); +template std::set> Equihash<96,3>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<96,3>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); template bool Equihash<96,3>::IsValidSolution(const eh_HashState& base_state, std::vector soln); // Explicit instantiations for Equihash<96,5> template int Equihash<96,5>::InitialiseState(eh_HashState& base_state); -template std::set> Equihash<96,5>::BasicSolve(const eh_HashState& base_state); -template std::set> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state); +template std::set> Equihash<96,5>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector soln); // Explicit instantiations for Equihash<48,5> template int Equihash<48,5>::InitialiseState(eh_HashState& base_state); -template std::set> Equihash<48,5>::BasicSolve(const eh_HashState& base_state); -template std::set> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state); +template std::set> Equihash<48,5>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector soln); diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 49be6dfad..e87c693a2 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -12,6 +12,8 @@ #include "sodium.h" #include +#include +#include #include #include @@ -108,6 +110,14 @@ public: eh_trunc* GetTruncatedIndices(size_t len, size_t lenIndices) const; }; +class EhSolverCancelledException : public std::exception +{ + virtual const char* what() const throw() + { + return "Equihash solver was cancelled"; + } +}; + inline constexpr const size_t max(const size_t A, const size_t B) { return A > B ? A : B; } template @@ -130,8 +140,8 @@ public: Equihash() { } int InitialiseState(eh_HashState& base_state); - std::set> BasicSolve(const eh_HashState& base_state); - std::set> OptimisedSolve(const eh_HashState& base_state); + std::set> BasicSolve(const eh_HashState& base_state, const std::function cancelled); + std::set> OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); bool IsValidSolution(const eh_HashState& base_state, std::vector soln); }; @@ -152,27 +162,31 @@ static Equihash<48,5> Eh48_5; throw std::invalid_argument("Unsupported Equihash parameters"); \ } -#define EhBasicSolve(n, k, base_state, solns) \ - if (n == 96 && k == 3) { \ - solns = Eh96_3.BasicSolve(base_state); \ - } else if (n == 96 && k == 5) { \ - solns = Eh96_5.BasicSolve(base_state); \ - } else if (n == 48 && k == 5) { \ - solns = Eh48_5.BasicSolve(base_state); \ - } else { \ +#define EhBasicSolve(n, k, base_state, solns, cancelled) \ + if (n == 96 && k == 3) { \ + solns = Eh96_3.BasicSolve(base_state, cancelled); \ + } else if (n == 96 && k == 5) { \ + solns = Eh96_5.BasicSolve(base_state, cancelled); \ + } else if (n == 48 && k == 5) { \ + solns = Eh48_5.BasicSolve(base_state, cancelled); \ + } else { \ throw std::invalid_argument("Unsupported Equihash parameters"); \ } +#define EhBasicSolveUncancellable(n, k, base_state, solns) \ + EhBasicSolve(n, k, base_state, solns, [] { return false; }) -#define EhOptimisedSolve(n, k, base_state, solns) \ - if (n == 96 && k == 3) { \ - solns = Eh96_3.OptimisedSolve(base_state); \ - } else if (n == 96 && k == 5) { \ - solns = Eh96_5.OptimisedSolve(base_state); \ - } else if (n == 48 && k == 5) { \ - solns = Eh48_5.OptimisedSolve(base_state); \ - } else { \ +#define EhOptimisedSolve(n, k, base_state, solns, cancelled) \ + if (n == 96 && k == 3) { \ + solns = Eh96_3.OptimisedSolve(base_state, cancelled); \ + } else if (n == 96 && k == 5) { \ + solns = Eh96_5.OptimisedSolve(base_state, cancelled); \ + } else if (n == 48 && k == 5) { \ + solns = Eh48_5.OptimisedSolve(base_state, cancelled); \ + } else { \ throw std::invalid_argument("Unsupported Equihash parameters"); \ } +#define EhOptimisedSolveUncancellable(n, k, base_state, solns) \ + EhOptimisedSolve(n, k, base_state, solns, [] { return false; }) #define EhIsValidSolution(n, k, base_state, soln, ret) \ if (n == 96 && k == 3) { \ diff --git a/src/miner.cpp b/src/miner.cpp index 95ec6df85..5065977cb 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -21,6 +21,7 @@ #ifdef ENABLE_WALLET #include "crypto/equihash.h" #include "wallet/wallet.h" +#include #endif #include "sodium.h" @@ -519,7 +520,12 @@ void static BitcoinMiner(CWallet *pwallet) LogPrint("pow", "Running Equihash solver with nNonce = %s\n", pblock->nNonce.ToString()); std::set> solns; - EhOptimisedSolve(n, k, curr_state, solns); + try { + std::function cancelled = [pindexPrev] { return pindexPrev != chainActive.Tip(); }; + EhOptimisedSolve(n, k, curr_state, solns, cancelled); + } catch (EhSolverCancelledException&) { + LogPrint("pow", "Equihash solver cancelled\n"); + } LogPrint("pow", "Solutions: %d\n", solns.size()); // Write the solution to the hash and compute the result. diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 82bbe0c12..95b32df96 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -189,7 +189,7 @@ Value generate(const Array& params, bool fHelp) // (x_1, x_2, ...) = A(I, V, n, k) std::set> solns; - EhBasicSolve(n, k, curr_state, solns); + EhBasicSolveUncancellable(n, k, curr_state, solns); for (auto soln : solns) { bool isValid; diff --git a/src/test/equihash_tests.cpp b/src/test/equihash_tests.cpp index dbe757a7a..cf4d98c31 100644 --- a/src/test/equihash_tests.cpp +++ b/src/test/equihash_tests.cpp @@ -50,7 +50,7 @@ void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, c // First test the basic solver std::set> ret; - EhBasicSolve(n, k, state, ret); + EhBasicSolveUncancellable(n, k, state, ret); BOOST_TEST_MESSAGE("[Basic] Number of solutions: " << ret.size()); std::stringstream strm; PrintSolutions(strm, ret); @@ -59,7 +59,7 @@ void TestEquihashSolvers(unsigned int n, unsigned int k, const std::string &I, c // The optimised solver should have the exact same result std::set> retOpt; - EhOptimisedSolve(n, k, state, retOpt); + EhOptimisedSolveUncancellable(n, k, state, retOpt); BOOST_TEST_MESSAGE("[Optimised] Number of solutions: " << retOpt.size()); strm.str(""); PrintSolutions(strm, retOpt); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index b0a0bead4..adc606aa7 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -206,7 +206,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // (x_1, x_2, ...) = A(I, V, n, k) std::set> solns; - EhOptimisedSolve(n, k, curr_state, solns); + EhOptimisedSolveUncancellable(n, k, curr_state, solns); bool ret; for (auto soln : solns) { diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index abe631f8b..5e90c55b5 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -118,7 +118,7 @@ double benchmark_solve_equihash() timer_start(); std::set> solns; - EhOptimisedSolve(n, k, eh_state, solns); + EhOptimisedSolveUncancellable(n, k, eh_state, solns); return timer_stop(); } From 5b4ebcd5e259ea997e3ae516b675a43dc8a81d92 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 21 Jul 2016 16:39:32 +1200 Subject: [PATCH 2/5] Add tests that exercise the cancellation code branches --- src/Makefile.gtest.include | 1 + src/crypto/equihash.cpp | 51 +++++----- src/crypto/equihash.h | 33 +++++-- src/gtest/test_equihash.cpp | 184 ++++++++++++++++++++++++++++++++++++ src/miner.cpp | 4 +- 5 files changed, 238 insertions(+), 35 deletions(-) create mode 100644 src/gtest/test_equihash.cpp diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index 7369f9431..ba07d2c43 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -6,6 +6,7 @@ zcash_gtest_SOURCES = \ gtest/main.cpp \ gtest/test_tautology.cpp \ gtest/test_checktransaction.cpp \ + gtest/test_equihash.cpp \ gtest/test_joinsplit.cpp \ gtest/test_noteencryption.cpp \ gtest/test_merkletree.cpp \ diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 0e66e4886..64011ab59 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -193,7 +193,7 @@ eh_trunc* TruncatedStepRow::GetTruncatedIndices(size_t len, size_t lenInd } template -std::set> Equihash::BasicSolve(const eh_HashState& base_state, const std::function cancelled) +std::set> Equihash::BasicSolve(const eh_HashState& base_state, const std::function cancelled) { eh_index init_size { 1 << (CollisionBitLength + 1) }; @@ -206,7 +206,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba for (eh_index i = 0; i < init_size; i++) { X.emplace_back(N, base_state, i); // Slow down checking to prevent segfaults (??) - if (i % 10000 == 0 && cancelled()) throw solver_cancelled; + if (i % 10000 == 0 && cancelled(ListGeneration)) throw solver_cancelled; } // 3) Repeat step 2 until 2n/(k+1) bits remain @@ -215,7 +215,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba // 2a) Sort the list LogPrint("pow", "- Sorting list\n"); std::sort(X.begin(), X.end(), CompareSR(CollisionByteLength)); - if (cancelled()) throw solver_cancelled; + if (cancelled(ListSorting)) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; @@ -245,7 +245,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba } i += j; - if (cancelled()) throw solver_cancelled; + if (cancelled(ListColliding)) throw solver_cancelled; } // 2e) Handle edge case where final table entry has no collision @@ -265,7 +265,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba hashLen -= CollisionByteLength; lenIndices *= 2; - if (cancelled()) throw solver_cancelled; + if (cancelled(RoundEnd)) throw solver_cancelled; } // k+1) Find a collision on last 2n(k+1) bits @@ -274,6 +274,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba if (X.size() > 1) { LogPrint("pow", "- Sorting list\n"); std::sort(X.begin(), X.end(), CompareSR(hashLen)); + if (cancelled(FinalSorting)) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; while (i < X.size() - 1) { @@ -293,7 +294,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba } i += j; - if (cancelled()) throw solver_cancelled; + if (cancelled(FinalColliding)) throw solver_cancelled; } } else LogPrint("pow", "- List is empty\n"); @@ -354,7 +355,7 @@ void CollideBranches(std::vector>& X, const size_t hlen, cons } template -std::set> Equihash::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled) +std::set> Equihash::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled) { eh_index init_size { 1 << (CollisionBitLength + 1) }; @@ -375,7 +376,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (eh_index i = 0; i < init_size; i++) { Xt.emplace_back(N, base_state, i, CollisionBitLength + 1); // Slow down checking to prevent segfaults (??) - if (i % 10000 == 0 && cancelled()) throw solver_cancelled; + if (i % 10000 == 0 && cancelled(ListGeneration)) throw solver_cancelled; } // 3) Repeat step 2 until 2n/(k+1) bits remain @@ -384,7 +385,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // 2a) Sort the list LogPrint("pow", "- Sorting list\n"); std::sort(Xt.begin(), Xt.end(), CompareSR(CollisionByteLength)); - if (cancelled()) throw solver_cancelled; + if (cancelled(ListSorting)) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; @@ -413,7 +414,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } i += j; - if (cancelled()) throw solver_cancelled; + if (cancelled(ListColliding)) throw solver_cancelled; } // 2e) Handle edge case where final table entry has no collision @@ -433,7 +434,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState hashLen -= CollisionByteLength; lenIndices *= 2; - if (cancelled()) throw solver_cancelled; + if (cancelled(RoundEnd)) throw solver_cancelled; } // k+1) Find a collision on last 2n(k+1) bits @@ -441,7 +442,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState if (Xt.size() > 1) { LogPrint("pow", "- Sorting list\n"); std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen)); - if (cancelled()) throw solver_cancelled; + if (cancelled(FinalSorting)) throw solver_cancelled; LogPrint("pow", "- Finding collisions\n"); int i = 0; while (i < Xt.size() - 1) { @@ -459,7 +460,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } i += j; - if (cancelled()) break; + if (cancelled(FinalColliding)) break; } } else LogPrint("pow", "- List is empty\n"); @@ -473,7 +474,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState std::set> solns; eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) }; int invalidCount = 0; - if (cancelled()) goto cancelsolver; + if (cancelled(StartCulling)) goto cancelsolver; for (eh_trunc* partialSoln : partialSolns) { size_t hashLen; size_t lenIndices; @@ -488,7 +489,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (eh_index j = 0; j < recreate_size; j++) { eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) }; icv.emplace_back(N, base_state, newIndex); - if (cancelled()) goto cancelsolver; + if (cancelled(PartialGeneration)) goto cancelsolver; } boost::optional>> ic = icv; @@ -504,7 +505,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState ic->reserve(ic->size() + X[r]->size()); ic->insert(ic->end(), X[r]->begin(), X[r]->end()); std::sort(ic->begin(), ic->end(), CompareSR(hashLen)); - if (cancelled()) goto cancelsolver; + if (cancelled(PartialSorting)) goto cancelsolver; size_t lti = rti-(1<> Equihash::OptimisedSolve(const eh_HashState X.push_back(ic); break; } - if (cancelled()) goto cancelsolver; + if (cancelled(PartialSubtreeEnd)) goto cancelsolver; } - if (cancelled()) goto cancelsolver; + if (cancelled(PartialIndexEnd)) goto cancelsolver; } // We are at the top of the tree @@ -537,7 +538,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (FullStepRow row : *X[K]) { solns.insert(row.GetIndices(hashLen, lenIndices)); } - if (cancelled()) goto cancelsolver; + if (cancelled(PartialEnd)) goto cancelsolver; continue; invalidsolution: @@ -604,18 +605,18 @@ bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector< // Explicit instantiations for Equihash<96,3> template int Equihash<96,3>::InitialiseState(eh_HashState& base_state); -template std::set> Equihash<96,3>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); -template std::set> Equihash<96,3>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<96,3>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<96,3>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); template bool Equihash<96,3>::IsValidSolution(const eh_HashState& base_state, std::vector soln); // Explicit instantiations for Equihash<96,5> template int Equihash<96,5>::InitialiseState(eh_HashState& base_state); -template std::set> Equihash<96,5>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); -template std::set> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<96,5>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector soln); // Explicit instantiations for Equihash<48,5> template int Equihash<48,5>::InitialiseState(eh_HashState& base_state); -template std::set> Equihash<48,5>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); -template std::set> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<48,5>::BasicSolve(const eh_HashState& base_state, const std::function cancelled); +template std::set> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector soln); diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index e87c693a2..f561b0249 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -110,12 +110,27 @@ public: eh_trunc* GetTruncatedIndices(size_t len, size_t lenIndices) const; }; +enum EhSolverCancelCheck +{ + ListGeneration, + ListSorting, + ListColliding, + RoundEnd, + FinalSorting, + FinalColliding, + StartCulling, + PartialGeneration, + PartialSorting, + PartialSubtreeEnd, + PartialIndexEnd, + PartialEnd +}; + class EhSolverCancelledException : public std::exception { - virtual const char* what() const throw() - { - return "Equihash solver was cancelled"; - } + virtual const char* what() const throw() { + return "Equihash solver was cancelled"; + } }; inline constexpr const size_t max(const size_t A, const size_t B) { return A > B ? A : B; } @@ -140,8 +155,8 @@ public: Equihash() { } int InitialiseState(eh_HashState& base_state); - std::set> BasicSolve(const eh_HashState& base_state, const std::function cancelled); - std::set> OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); + std::set> BasicSolve(const eh_HashState& base_state, const std::function cancelled); + std::set> OptimisedSolve(const eh_HashState& base_state, const std::function cancelled); bool IsValidSolution(const eh_HashState& base_state, std::vector soln); }; @@ -172,8 +187,8 @@ static Equihash<48,5> Eh48_5; } else { \ throw std::invalid_argument("Unsupported Equihash parameters"); \ } -#define EhBasicSolveUncancellable(n, k, base_state, solns) \ - EhBasicSolve(n, k, base_state, solns, [] { return false; }) +#define EhBasicSolveUncancellable(n, k, base_state, solns) \ + EhBasicSolve(n, k, base_state, solns, [](EhSolverCancelCheck pos) { return false; }) #define EhOptimisedSolve(n, k, base_state, solns, cancelled) \ if (n == 96 && k == 3) { \ @@ -186,7 +201,7 @@ static Equihash<48,5> Eh48_5; throw std::invalid_argument("Unsupported Equihash parameters"); \ } #define EhOptimisedSolveUncancellable(n, k, base_state, solns) \ - EhOptimisedSolve(n, k, base_state, solns, [] { return false; }) + EhOptimisedSolve(n, k, base_state, solns, [](EhSolverCancelCheck pos) { return false; }) #define EhIsValidSolution(n, k, base_state, soln, ret) \ if (n == 96 && k == 3) { \ diff --git a/src/gtest/test_equihash.cpp b/src/gtest/test_equihash.cpp new file mode 100644 index 000000000..52636ba7d --- /dev/null +++ b/src/gtest/test_equihash.cpp @@ -0,0 +1,184 @@ +#include +#include + +#include "crypto/equihash.h" + +TEST(equihash_tests, check_basic_solver_cancelled) { + Equihash<48,5> Eh48_5; + crypto_generichash_blake2b_state state; + Eh48_5.InitialiseState(state); + std::set> solns; + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return false; + })); + } + + { + ASSERT_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == ListGeneration; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == ListSorting; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == ListColliding; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == RoundEnd; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == FinalSorting; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == FinalColliding; + }), EhSolverCancelledException); + } + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == StartCulling; + })); + } + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialGeneration; + })); + } + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialSorting; + })); + } + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialSubtreeEnd; + })); + } + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialIndexEnd; + })); + } + + { + ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialEnd; + })); + } +} + +TEST(equihash_tests, check_optimised_solver_cancelled) { + Equihash<48,5> Eh48_5; + crypto_generichash_blake2b_state state; + Eh48_5.InitialiseState(state); + std::set> solns; + + { + ASSERT_NO_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return false; + })); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == ListGeneration; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == ListSorting; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == ListColliding; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == RoundEnd; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == FinalSorting; + }), EhSolverCancelledException); + } + + { + // More state required here, because in OptimisedSolve() the + // FinalColliding cancellation check can't throw because it will leak + // memory, and it can't goto because that steps over initialisations. + bool triggered = false; + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [=](EhSolverCancelCheck pos) mutable { + if (triggered) + return pos == StartCulling; + if (pos == FinalColliding) { + triggered = true; + return true; + } + return false; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == StartCulling; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialGeneration; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialSorting; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialSubtreeEnd; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialIndexEnd; + }), EhSolverCancelledException); + } + + { + ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { + return pos == PartialEnd; + }), EhSolverCancelledException); + } +} diff --git a/src/miner.cpp b/src/miner.cpp index 5065977cb..48de9ab60 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -521,7 +521,9 @@ void static BitcoinMiner(CWallet *pwallet) pblock->nNonce.ToString()); std::set> solns; try { - std::function cancelled = [pindexPrev] { return pindexPrev != chainActive.Tip(); }; + std::function cancelled = [pindexPrev](EhSolverCancelCheck pos) { + return pindexPrev != chainActive.Tip(); + }; EhOptimisedSolve(n, k, curr_state, solns, cancelled); } catch (EhSolverCancelledException&) { LogPrint("pow", "Equihash solver cancelled\n"); From 5a360a5c6afcb00d3e9bb0d90c55e5044bbda817 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 21 Jul 2016 18:04:38 +1200 Subject: [PATCH 3/5] Fix segfault by indirectly monitoring chainActive.Tip(), locking on mutex --- src/crypto/equihash.cpp | 6 ++---- src/miner.cpp | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 64011ab59..0fd2b2910 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -205,8 +205,7 @@ std::set> Equihash::BasicSolve(const eh_HashState& ba X.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { X.emplace_back(N, base_state, i); - // Slow down checking to prevent segfaults (??) - if (i % 10000 == 0 && cancelled(ListGeneration)) throw solver_cancelled; + if (cancelled(ListGeneration)) throw solver_cancelled; } // 3) Repeat step 2 until 2n/(k+1) bits remain @@ -375,8 +374,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState Xt.reserve(init_size); for (eh_index i = 0; i < init_size; i++) { Xt.emplace_back(N, base_state, i, CollisionBitLength + 1); - // Slow down checking to prevent segfaults (??) - if (i % 10000 == 0 && cancelled(ListGeneration)) throw solver_cancelled; + if (cancelled(ListGeneration)) throw solver_cancelled; } // 3) Repeat step 2 until 2n/(k+1) bits remain diff --git a/src/miner.cpp b/src/miner.cpp index 48de9ab60..d110bb425 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -28,6 +28,7 @@ #include #include +#include using namespace std; @@ -455,6 +456,15 @@ void static BitcoinMiner(CWallet *pwallet) unsigned int n = chainparams.EquihashN(); unsigned int k = chainparams.EquihashK(); + std::mutex m_cs; + bool cancelSolver = false; + boost::signals2::connection c = uiInterface.NotifyBlockTip.connect( + [&m_cs, &cancelSolver](const uint256& hashNewTip) mutable { + std::lock_guard lock{m_cs}; + cancelSolver = true; + } + ); + try { while (true) { if (chainparams.MiningRequiresPeers()) { @@ -521,12 +531,15 @@ void static BitcoinMiner(CWallet *pwallet) pblock->nNonce.ToString()); std::set> solns; try { - std::function cancelled = [pindexPrev](EhSolverCancelCheck pos) { - return pindexPrev != chainActive.Tip(); + std::function cancelled = [&m_cs, &cancelSolver](EhSolverCancelCheck pos) { + std::lock_guard lock{m_cs}; + return cancelSolver; }; EhOptimisedSolve(n, k, curr_state, solns, cancelled); } catch (EhSolverCancelledException&) { LogPrint("pow", "Equihash solver cancelled\n"); + std::lock_guard lock{m_cs}; + cancelSolver = false; } LogPrint("pow", "Solutions: %d\n", solns.size()); @@ -542,7 +555,11 @@ void static BitcoinMiner(CWallet *pwallet) SetThreadPriority(THREAD_PRIORITY_NORMAL); LogPrintf("ZcashMiner:\n"); LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", pblock->GetHash().GetHex(), hashTarget.GetHex()); - ProcessBlockFound(pblock, *pwallet, reservekey); + if (ProcessBlockFound(pblock, *pwallet, reservekey)) { + // Ignore chain updates caused by us + std::lock_guard lock{m_cs}; + cancelSolver = false; + } SetThreadPriority(THREAD_PRIORITY_LOWEST); // In regression test mode, stop mining after a block is found. @@ -585,6 +602,7 @@ void static BitcoinMiner(CWallet *pwallet) LogPrintf("ZcashMiner runtime error: %s\n", e.what()); return; } + c.disconnect(); } void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads) From 1655db285d999f23622d087fb80716c0e9743529 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 22 Jul 2016 23:54:14 +1200 Subject: [PATCH 4/5] Move initialisations to simplify cancelled checks --- src/crypto/equihash.cpp | 9 ++++----- src/crypto/equihash.h | 1 - src/gtest/test_equihash.cpp | 24 +----------------------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index 0fd2b2910..e71a9788f 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -357,6 +357,7 @@ template std::set> Equihash::OptimisedSolve(const eh_HashState& base_state, const std::function cancelled) { eh_index init_size { 1 << (CollisionBitLength + 1) }; + eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) }; // First run the algorithm with truncated indices @@ -364,6 +365,8 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // Each element of partialSolns is dynamically allocated in a call to // GetTruncatedIndices(), and freed at the end of this function. std::vector partialSolns; + std::set> solns; + int invalidCount = 0; { // 1) Generate first list @@ -458,7 +461,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } i += j; - if (cancelled(FinalColliding)) break; + if (cancelled(FinalColliding)) goto cancelsolver; } } else LogPrint("pow", "- List is empty\n"); @@ -469,10 +472,6 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // Now for each solution run the algorithm again to recreate the indices LogPrint("pow", "Culling solutions\n"); - std::set> solns; - eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) }; - int invalidCount = 0; - if (cancelled(StartCulling)) goto cancelsolver; for (eh_trunc* partialSoln : partialSolns) { size_t hashLen; size_t lenIndices; diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index f561b0249..044b67de9 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -118,7 +118,6 @@ enum EhSolverCancelCheck RoundEnd, FinalSorting, FinalColliding, - StartCulling, PartialGeneration, PartialSorting, PartialSubtreeEnd, diff --git a/src/gtest/test_equihash.cpp b/src/gtest/test_equihash.cpp index 52636ba7d..7e281990a 100644 --- a/src/gtest/test_equihash.cpp +++ b/src/gtest/test_equihash.cpp @@ -51,12 +51,6 @@ TEST(equihash_tests, check_basic_solver_cancelled) { }), EhSolverCancelledException); } - { - ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { - return pos == StartCulling; - })); - } - { ASSERT_NO_THROW(Eh48_5.BasicSolve(state, [](EhSolverCancelCheck pos) { return pos == PartialGeneration; @@ -130,25 +124,9 @@ TEST(equihash_tests, check_optimised_solver_cancelled) { }), EhSolverCancelledException); } - { - // More state required here, because in OptimisedSolve() the - // FinalColliding cancellation check can't throw because it will leak - // memory, and it can't goto because that steps over initialisations. - bool triggered = false; - ASSERT_THROW(Eh48_5.OptimisedSolve(state, [=](EhSolverCancelCheck pos) mutable { - if (triggered) - return pos == StartCulling; - if (pos == FinalColliding) { - triggered = true; - return true; - } - return false; - }), EhSolverCancelledException); - } - { ASSERT_THROW(Eh48_5.OptimisedSolve(state, [](EhSolverCancelCheck pos) { - return pos == StartCulling; + return pos == FinalColliding; }), EhSolverCancelledException); } From 215b9e139d3f8839bb9b7025d8c7b185eb0de10e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 23 Jul 2016 00:31:47 +1200 Subject: [PATCH 5/5] Use std::shared_ptr to deallocate partialSolns automatically --- src/crypto/equihash.cpp | 37 +++++++++++++------------------------ src/crypto/equihash.h | 3 ++- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/crypto/equihash.cpp b/src/crypto/equihash.cpp index e71a9788f..515ae3302 100644 --- a/src/crypto/equihash.cpp +++ b/src/crypto/equihash.cpp @@ -185,10 +185,10 @@ TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepR } template -eh_trunc* TruncatedStepRow::GetTruncatedIndices(size_t len, size_t lenIndices) const +std::shared_ptr TruncatedStepRow::GetTruncatedIndices(size_t len, size_t lenIndices) const { - eh_trunc* p = new eh_trunc[lenIndices]; - std::copy(hash+len, hash+len+lenIndices, p); + std::shared_ptr p (new eh_trunc[lenIndices]); + std::copy(hash+len, hash+len+lenIndices, p.get()); return p; } @@ -362,9 +362,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // First run the algorithm with truncated indices eh_index soln_size { 1 << K }; - // Each element of partialSolns is dynamically allocated in a call to - // GetTruncatedIndices(), and freed at the end of this function. - std::vector partialSolns; + std::vector> partialSolns; std::set> solns; int invalidCount = 0; { @@ -461,7 +459,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState } i += j; - if (cancelled(FinalColliding)) goto cancelsolver; + if (cancelled(FinalColliding)) throw solver_cancelled; } } else LogPrint("pow", "- List is empty\n"); @@ -472,7 +470,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState // Now for each solution run the algorithm again to recreate the indices LogPrint("pow", "Culling solutions\n"); - for (eh_trunc* partialSoln : partialSolns) { + for (std::shared_ptr partialSoln : partialSolns) { size_t hashLen; size_t lenIndices; std::vector>>> X; @@ -484,9 +482,9 @@ std::set> Equihash::OptimisedSolve(const eh_HashState std::vector> icv; icv.reserve(recreate_size); for (eh_index j = 0; j < recreate_size; j++) { - eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) }; + eh_index newIndex { UntruncateIndex(partialSoln.get()[i], j, CollisionBitLength + 1) }; icv.emplace_back(N, base_state, newIndex); - if (cancelled(PartialGeneration)) goto cancelsolver; + if (cancelled(PartialGeneration)) throw solver_cancelled; } boost::optional>> ic = icv; @@ -502,12 +500,12 @@ std::set> Equihash::OptimisedSolve(const eh_HashState ic->reserve(ic->size() + X[r]->size()); ic->insert(ic->end(), X[r]->begin(), X[r]->end()); std::sort(ic->begin(), ic->end(), CompareSR(hashLen)); - if (cancelled(PartialSorting)) goto cancelsolver; + if (cancelled(PartialSorting)) throw solver_cancelled; size_t lti = rti-(1<size() == 0) @@ -525,9 +523,9 @@ std::set> Equihash::OptimisedSolve(const eh_HashState X.push_back(ic); break; } - if (cancelled(PartialSubtreeEnd)) goto cancelsolver; + if (cancelled(PartialSubtreeEnd)) throw solver_cancelled; } - if (cancelled(PartialIndexEnd)) goto cancelsolver; + if (cancelled(PartialIndexEnd)) throw solver_cancelled; } // We are at the top of the tree @@ -535,7 +533,7 @@ std::set> Equihash::OptimisedSolve(const eh_HashState for (FullStepRow row : *X[K]) { solns.insert(row.GetIndices(hashLen, lenIndices)); } - if (cancelled(PartialEnd)) goto cancelsolver; + if (cancelled(PartialEnd)) throw solver_cancelled; continue; invalidsolution: @@ -543,16 +541,7 @@ invalidsolution: } LogPrint("pow", "- Number of invalid solutions found: %d\n", invalidCount); - for (eh_trunc* partialSoln : partialSolns) { - delete[] partialSoln; - } return solns; - -cancelsolver: - for (eh_trunc* partialSoln : partialSolns) { - delete[] partialSoln; - } - throw solver_cancelled; } template diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 044b67de9..4ba0e1c00 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -107,7 +108,7 @@ public: TruncatedStepRow& operator=(const TruncatedStepRow& a); inline bool IndicesBefore(const TruncatedStepRow& a, size_t len, size_t lenIndices) const { return memcmp(hash+len, a.hash+len, lenIndices) < 0; } - eh_trunc* GetTruncatedIndices(size_t len, size_t lenIndices) const; + std::shared_ptr GetTruncatedIndices(size_t len, size_t lenIndices) const; }; enum EhSolverCancelCheck