//------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. */ //============================================================================== class ProofOfWorkFactoryImp : public ProofOfWorkFactory , public LeakChecked { public: typedef boost::bimap< boost::bimaps::multiset_of, boost::bimaps::unordered_set_of > powMap_t; typedef powMap_t::value_type powMap_vt; //-------------------------------------------------------------------------- ProofOfWorkFactoryImp () : mLock (this, "PoWFactory", __FILE__, __LINE__) , mValidTime (180) { setDifficulty (1); RandomNumbers::getInstance ().fillBytes (mSecret.begin (), mSecret.size ()); } //-------------------------------------------------------------------------- enum { numPowEntries = kMaxDifficulty + 1 }; struct PowEntry { const char* target; int iterations; }; typedef std::vector PowEntries; static PowEntries const& getPowEntries () { struct StaticPowEntries { StaticPowEntries () { // VFALCO TODO Make this array know its own size. // PowEntry entries [numPowEntries] = { // target iterations hashes memory { "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // 1451874, 2 MB { "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 2177811, 3 MB { "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 3538944, 3 MB { "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 4355623, 6 MB { "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072}, // 4718592, 4 MB { "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 5807497, 8 MB { "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 7077888, 6 MB { "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 9437184, 8 MB { "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 14155776, 12MB { "03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 28311552, 12MB { "00CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 92919965, 8 MB { "00CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 139379948, 12MB { "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 150994944, 8 MB { "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 226492416, 12MB { "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 49152 }, // 278759896, 1.5MB { "003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 301989888, 8 MB { "003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 452984832, 12MB { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 905969664, 3 MB { "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 1115039586, 6 MB { "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 1486719448 8 MB { "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 2230079172 12MB { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 2415919104, 8 MB { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 3623878656, 12MB { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 7247757312, 12MB { "0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 23787511177, 8 MB { "0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 35681266766, 12MB { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072}, // 38654705664, 4 MB { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 38654705664, 8 MB { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 57982058496, 6 MB { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 57982058496, 12MB { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 77309411328, 8 MB }; data.reserve (numPowEntries); for (int i = 0; i < numPowEntries; ++i) data.push_back (entries [i]); } std::vector data; }; static StaticPowEntries list; return list.data; } //-------------------------------------------------------------------------- static int getPowEntry (uint256 const& target, int iterations) { PowEntries const& entries (getPowEntries ()); for (int i = 0; i < numPowEntries; ++i) { if (entries [i].iterations == iterations) { uint256 t; t.SetHex (entries [i].target); if (t == target) return i; } } return -1; } //-------------------------------------------------------------------------- ProofOfWork getProof () { // challenge - target - iterations - time - validator static boost::format f ("%s-%s-%d-%d"); int now = static_cast (time (NULL) / 4); uint256 challenge; RandomNumbers::getInstance ().fillBytes (challenge.begin (), challenge.size ()); ScopedLockType sl (mLock, __FILE__, __LINE__); std::string s = boost::str (boost::format (f) % challenge.GetHex () % mTarget.GetHex () % mIterations % now); std::string c = mSecret.GetHex () + s; s += "-" + Serializer::getSHA512Half (c).GetHex (); return ProofOfWork (s, mIterations, challenge, mTarget); } //-------------------------------------------------------------------------- PowResult checkProof (const std::string& token, uint256 const& solution) { // VFALCO COmmented this out because Dave said it wasn't used // and also we dont have the lexicalCast from a vector of strings to a time_t // challenge - target - iterations - time - validator std::vector fields; boost::split (fields, token, boost::algorithm::is_any_of ("-")); if (fields.size () != 5) { WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " is corrupt"; return powCORRUPT; } std::string v = mSecret.GetHex () + fields[0] + "-" + fields[1] + "-" + fields[2] + "-" + fields[3]; if (fields[4] != Serializer::getSHA512Half (v).GetHex ()) { WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has a bad token"; return powCORRUPT; } uint256 challenge, target; challenge.SetHex (fields[0]); target.SetHex (fields[1]); time_t t; #if 0 // Broken with lexicalCast<> changes t = lexicalCast (fields[3]); #else t = static_cast (lexicalCast (fields [3])); #endif time_t now = time (NULL); int iterations = lexicalCast (fields[2]); { ScopedLockType sl (mLock, __FILE__, __LINE__); if ((t * 4) > (now + mValidTime)) { WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has expired"; return powEXPIRED; } if (((iterations != mIterations) || (target != mTarget)) && getPowEntry (target, iterations) < (mPowEntry - 2)) { // difficulty has increased more than two times since PoW requested WriteLog (lsINFO, ProofOfWork) << "Difficulty has increased since PoW requested"; return powTOOEASY; } } ProofOfWork pow (token, iterations, challenge, target); if (!pow.checkSolution (solution)) { WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has a bad nonce"; return powBADNONCE; } { ScopedLockType sl (mLock, __FILE__, __LINE__); if (!mSolvedChallenges.insert (powMap_vt (now, challenge)).second) { WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has been reused"; return powREUSED; } } return powOK; } //-------------------------------------------------------------------------- void sweep () { time_t expire = time (NULL) - mValidTime; ScopedLockType sl (mLock, __FILE__, __LINE__); do { powMap_t::left_map::iterator it = mSolvedChallenges.left.begin (); if (it == mSolvedChallenges.left.end ()) return; if (it->first >= expire) return; mSolvedChallenges.left.erase (it); } while (1); } //-------------------------------------------------------------------------- void loadHigh () { time_t now = time (NULL); ScopedLockType sl (mLock, __FILE__, __LINE__); if (mLastDifficultyChange == now) return; if (mPowEntry == 30) return; ++mPowEntry; mLastDifficultyChange = now; } //-------------------------------------------------------------------------- void loadLow () { time_t now = time (NULL); ScopedLockType sl (mLock, __FILE__, __LINE__); if (mLastDifficultyChange == now) return; if (mPowEntry == 0) return; --mPowEntry; mLastDifficultyChange = now; } //-------------------------------------------------------------------------- void setDifficulty (int i) { assert ((i >= 0) && (i <= kMaxDifficulty)); time_t now = time (NULL); ScopedLockType sl (mLock, __FILE__, __LINE__); mPowEntry = i; PowEntries const& entries (getPowEntries ()); mIterations = entries [i].iterations; mTarget.SetHex (entries [i].target); mLastDifficultyChange = now; } //-------------------------------------------------------------------------- uint64 getDifficulty () { return ProofOfWork::getDifficulty (mTarget, mIterations); } uint256 const& getSecret () const { return mSecret; } void setSecret (uint256 const& secret) { mSecret = secret; } private: typedef RippleMutex LockType; typedef LockType::ScopedLockType ScopedLockType; LockType mLock; uint256 mSecret; int mIterations; uint256 mTarget; time_t mLastDifficultyChange; int mValidTime; int mPowEntry; powMap_t mSolvedChallenges; }; //------------------------------------------------------------------------------ ProofOfWorkFactory* ProofOfWorkFactory::New () { ScopedPointer object (new ProofOfWorkFactoryImp); return object.release (); } //------------------------------------------------------------------------------ class ProofOfWorkTests : public UnitTest { public: ProofOfWorkTests () : UnitTest ("ProofOfWork", "ripple", runManual) { } void runTest () { using namespace ripple; ProofOfWorkFactoryImp gen; ProofOfWork pow = gen.getProof (); String s; s << "solve difficulty " << String (pow.getDifficulty ()); beginTestCase ("solve"); uint256 solution = pow.solve (16777216); expect (! solution.isZero (), "Should be solved"); expect (pow.checkSolution (solution), "Should be checked"); // Why is this emitted? //WriteLog (lsDEBUG, ProofOfWork) << "A bad nonce error is expected"; PowResult r = gen.checkProof (pow.getToken (), uint256 ()); expect (r == powBADNONCE, "Should show bad nonce for empty solution"); expect (gen.checkProof (pow.getToken (), solution) == powOK, "Solution should check with issuer"); //WriteLog (lsDEBUG, ProofOfWork) << "A reused nonce error is expected"; expect (gen.checkProof (pow.getToken (), solution) == powREUSED, "Reuse solution should be detected"); #ifdef SOLVE_POWS for (int i = 0; i < 12; ++i) { gen.setDifficulty (i); ProofOfWork pow = gen.getProof (); WriteLog (lsINFO, ProofOfWork) << "Level: " << i << ", Estimated difficulty: " << pow.getDifficulty (); uint256 solution = pow.solve (131072); if (solution.isZero ()) { //WriteLog (lsINFO, ProofOfWork) << "Giving up"; } else { //WriteLog (lsINFO, ProofOfWork) << "Solution found"; if (gen.checkProof (pow.getToken (), solution) != powOK) { //WriteLog (lsFATAL, ProofOfWork) << "Solution fails"; } } } #endif } }; static ProofOfWorkTests proofOfWorkTests;