diff --git a/ripple2010.vcxproj b/ripple2010.vcxproj index 374a65bd62..7867ca8f90 100644 --- a/ripple2010.vcxproj +++ b/ripple2010.vcxproj @@ -122,6 +122,7 @@ + diff --git a/ripple2010.vcxproj.filters b/ripple2010.vcxproj.filters index 9e03588ae8..5650c27636 100644 --- a/ripple2010.vcxproj.filters +++ b/ripple2010.vcxproj.filters @@ -345,6 +345,9 @@ Source Files + + Source Files + diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index aa5dc56618..7505a4c24e 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -60,6 +60,7 @@ void HashedObjectStore::waitWrite() void HashedObjectStore::bulkWrite() { + LoadEvent::pointer event = theApp->getJobQueue().getLoadEvent(jtDISK); while (1) { std::vector< boost::shared_ptr > set; diff --git a/src/cpp/ripple/JobQueue.cpp b/src/cpp/ripple/JobQueue.cpp index cbb2bee83f..a72e8ddae0 100644 --- a/src/cpp/ripple/JobQueue.cpp +++ b/src/cpp/ripple/JobQueue.cpp @@ -1,6 +1,5 @@ #include "JobQueue.h" -#include #include #include #include @@ -21,6 +20,9 @@ const char* Job::toString(JobType t) case jtPROPOSAL_t: return "trustedProposal"; case jtADMIN: return "administration"; case jtDEATH: return "jobOfDeath"; + case jtCLIENT: return "clientCommand"; + case jtPEER: return "peerCommand"; + case jtDISK: return "diskAccess"; default: assert(false); return "unknown"; } } @@ -68,7 +70,7 @@ void JobQueue::addJob(JobType type, const boost::function& jobFunc) boost::mutex::scoped_lock sl(mJobLock); assert(mThreadCount != 0); // do not add jobs to a queue with no threads - mJobSet.insert(Job(type, ++mLastJob, jobFunc)); + mJobSet.insert(Job(type, ++mLastJob, mJobLoads[type], jobFunc)); ++mJobCounts[type]; mJobCond.notify_one(); } @@ -108,6 +110,43 @@ std::vector< std::pair > JobQueue::getJobCounts() return ret; } +Json::Value JobQueue::getJson(int) +{ + Json::Value ret(Json::objectValue); + boost::mutex::scoped_lock sl(mJobLock); + + ret["threads"] = mThreadCount; + + Json::Value priorities = Json::arrayValue; + for (int i = 0; i < NUM_JOB_TYPES; ++i) + { + uint64 count, latencyAvg, latencyPeak, jobCount; + mJobLoads[i].getCountAndLatency(count, latencyAvg, latencyPeak); + std::map::iterator it = mJobCounts.find(static_cast(i)); + if (it == mJobCounts.end()) + jobCount = 0; + else + jobCount = it->second; + if ((count != 0) || (jobCount != 0) || (latencyPeak != 0)) + { + Json::Value pri(Json::objectValue); + pri["job_type"] = Job::toString(static_cast(i)); + if (jobCount != 0) + pri["waiting"] = static_cast(jobCount); + if (count != 0) + pri["per_second"] = static_cast(count); + if (latencyPeak != 0) + pri["peak_latency"] = static_cast(latencyPeak); + if (latencyAvg != 0) + pri["avg_latency"] = static_cast(latencyAvg); + priorities.append(pri); + } + } + ret["job_types"] = priorities; + + return ret; +} + void JobQueue::shutdown() { // shut down the job queue without completing pending jobs cLog(lsINFO) << "Job queue shutting down"; diff --git a/src/cpp/ripple/JobQueue.h b/src/cpp/ripple/JobQueue.h index bdd23bc690..339804d206 100644 --- a/src/cpp/ripple/JobQueue.h +++ b/src/cpp/ripple/JobQueue.h @@ -8,26 +8,36 @@ #include #include #include +#include + +#include "../json/value.h" #include "types.h" +#include "LoadMonitor.h" // Note that this queue should only be used for CPU-bound jobs // It is primarily intended for signature checking enum JobType { // must be in priority order, low to high - jtINVALID, - jtVALIDATION_ut, // A validation from an untrusted source - jtCLIENTOP_ut, // A client operation from a non-local/untrusted source - jtTRANSACTION, // A transaction received from the network - jtPROPOSAL_ut, // A proposal from an untrusted source - jtCLIENTOP_t, // A client operation from a trusted source - jtVALIDATION_t, // A validation from a trusted source - jtTRANSACTION_l, // A local transaction - jtPROPOSAL_t, // A proposal from a trusted source - jtADMIN, // An administrative operation - jtDEATH, // job of death, used internally + jtINVALID = -1, + jtVALIDATION_ut = 0, // A validation from an untrusted source + jtCLIENTOP_ut = 1, // A client operation from a non-local/untrusted source + jtTRANSACTION = 2, // A transaction received from the network + jtPROPOSAL_ut = 3, // A proposal from an untrusted source + jtCLIENTOP_t = 4, // A client operation from a trusted source + jtVALIDATION_t = 5, // A validation from a trusted source + jtTRANSACTION_l = 6, // A local transaction + jtPROPOSAL_t = 7, // A proposal from a trusted source + jtADMIN = 8, // An administrative operation + jtDEATH = 9, // job of death, used internally + +// special types not dispatched by the job pool + jtCLIENT = 10, + jtPEER = 11, + jtDISK = 12, }; +#define NUM_JOB_TYPES 16 class Job { @@ -35,13 +45,18 @@ protected: JobType mType; uint64 mJobIndex; boost::function mJob; + LoadEvent::pointer mLoadMonitor; public: - Job() : mType(jtINVALID), mJobIndex(0) { ; } - Job(JobType type, uint64 index) : mType(type), mJobIndex(index) { ; } - Job(JobType type, uint64 index, const boost::function& job) - : mType(type), mJobIndex(index), mJob(job) { ; } + Job() : mType(jtINVALID), mJobIndex(0) { ; } + + Job(JobType type, uint64 index) : mType(type), mJobIndex(index) + { ; } + + Job(JobType type, uint64 index, LoadMonitor& lm, const boost::function& job) + : mType(type), mJobIndex(index), mJob(job) + { mLoadMonitor = boost::make_shared(boost::ref(lm), true, 1); } JobType getType() const { return mType; } void doJob(void) { mJob(*this); } @@ -57,14 +72,15 @@ public: class JobQueue { protected: - boost::mutex mJobLock; - boost::condition_variable mJobCond; + boost::mutex mJobLock; + boost::condition_variable mJobCond; - uint64 mLastJob; - std::set mJobSet; - std::map mJobCounts; - int mThreadCount; - bool mShuttingDown; + uint64 mLastJob; + std::set mJobSet; + std::map mJobCounts; + LoadMonitor mJobLoads[NUM_JOB_TYPES]; + int mThreadCount; + bool mShuttingDown; void threadEntry(void); @@ -81,6 +97,11 @@ public: void shutdown(); void setThreadCount(int c = 0); + + LoadEvent::pointer getLoadEvent(JobType t) + { return boost::make_shared(boost::ref(mJobLoads[t]), true, 1); } + + Json::Value getJson(int c = 0); }; #endif diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 03e0a48d1b..8f6d87e798 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -341,6 +341,7 @@ uint256 Ledger::getHash() void Ledger::saveAcceptedLedger(bool fromConsensus) { // can be called in a different thread + LoadEvent::pointer event = theApp->getJobQueue().getLoadEvent(jtDISK); cLog(lsTRACE) << "saveAcceptedLedger " << (fromConsensus ? "fromConsensus " : "fromAcquire ") << getLedgerSeq(); static boost::format ledgerExists("SELECT LedgerSeq FROM Ledgers where LedgerSeq = %d;"); static boost::format deleteLedger("DELETE FROM Ledgers WHERE LedgerSeq = %d;"); diff --git a/src/cpp/ripple/LoadMonitor.cpp b/src/cpp/ripple/LoadMonitor.cpp new file mode 100644 index 0000000000..2011035aec --- /dev/null +++ b/src/cpp/ripple/LoadMonitor.cpp @@ -0,0 +1,90 @@ +#include "LoadMonitor.h" + +void LoadMonitor::update() +{ // call with the mutex + time_t now = time(NULL); + + if (now == mLastUpdate) // current + return; + + if ((now < mLastUpdate) || (now > (mLastUpdate + 8))) + { // way out of date + mCounts = 0; + mLatencyEvents = 0; + mLatencyMSAvg = 0; + mLatencyMSPeak = 0; + mLastUpdate = now; + return; + } + + do + { // do exponential decay + ++mLastUpdate; + mCounts -= ((mCounts + 3) / 4); + mLatencyEvents -= ((mLatencyEvents + 3) / 4); + mLatencyMSAvg -= (mLatencyMSAvg / 4); + mLatencyMSPeak -= (mLatencyMSPeak / 4); + } while (mLastUpdate < now); +} + +void LoadMonitor::addCount(int counts) +{ + boost::mutex::scoped_lock sl(mLock); + + update(); + mCounts += counts; +} + +void LoadMonitor::addLatency(int latency) +{ + if (latency == 1) + latency = 0; + boost::mutex::scoped_lock sl(mLock); + + update(); + + ++mLatencyEvents; + mLatencyMSAvg += latency; + mLatencyMSPeak += latency; + + int lp = mLatencyEvents * latency * 4; + if (mLatencyMSPeak < lp) + mLatencyMSPeak = lp; +} + +void LoadMonitor::addCountAndLatency(int counts, int latency) +{ + if (latency == 1) + latency = 0; + boost::mutex::scoped_lock sl(mLock); + + update(); + mCounts += counts; + ++mLatencyEvents; + mLatencyMSAvg += latency; + mLatencyMSPeak += latency; + + int lp = mLatencyEvents * latency * 4; + if (mLatencyMSPeak < lp) + mLatencyMSPeak = lp; +} + +void LoadMonitor::getCountAndLatency(uint64& count, uint64& latencyAvg, uint64& latencyPeak) +{ + boost::mutex::scoped_lock sl(mLock); + + update(); + + count = mCounts / 4; + + if (mLatencyEvents == 0) + { + latencyAvg = 0; + latencyPeak = 0; + } + else + { + latencyAvg = mLatencyMSAvg / (mLatencyEvents * 4); + latencyPeak = mLatencyMSPeak / (mLatencyEvents * 4); + } +} diff --git a/src/cpp/ripple/LoadMonitor.h b/src/cpp/ripple/LoadMonitor.h new file mode 100644 index 0000000000..08c2afbf21 --- /dev/null +++ b/src/cpp/ripple/LoadMonitor.h @@ -0,0 +1,76 @@ +#ifndef LOADMONITOR__H_ +#define LOADMONITOR__H_ + +#include + +#include +#include + +#include "types.h" + +// Monitors load levels and response times + +class LoadMonitor +{ +protected: + uint64 mCounts; + uint64 mLatencyEvents; + uint64 mLatencyMSAvg; + uint64 mLatencyMSPeak; + time_t mLastUpdate; + boost::mutex mLock; + + void update(); + +public: + LoadMonitor() : mCounts(0), mLatencyEvents(0), mLatencyMSAvg(0), mLatencyMSPeak(0) + { mLastUpdate = time(NULL); } + + void addCount(int counts); + void addLatency(int latency); + void addCountAndLatency(int counts, int latency); + + void getCountAndLatency(uint64& count, uint64& latencyAvg, uint64& latencyPeak); +}; + +class LoadEvent +{ +public: + typedef boost::shared_ptr pointer; + +protected: + LoadMonitor& mMonitor; + bool mRunning; + int mCount; + boost::posix_time::ptime mStartTime; + +public: + LoadEvent(LoadMonitor& monitor, bool shouldStart, int count) : mMonitor(monitor), mRunning(false), mCount(count) + { + mStartTime = boost::posix_time::microsec_clock::universal_time(); + if (shouldStart) + start(); + } + + ~LoadEvent() + { + if (mRunning) + stop(); + } + + void start() + { // okay to call if already started + mRunning = true; + mStartTime = boost::posix_time::microsec_clock::universal_time(); + } + + void stop() + { + assert(mRunning); + mRunning = false; + mMonitor.addCountAndLatency(mCount, + (boost::posix_time::microsec_clock::universal_time() - mStartTime).total_milliseconds()); + } +}; + +#endif diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 89a5d77e76..ed4847f9bd 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -957,22 +957,7 @@ Json::Value NetworkOPs::getServerInfo() if (mConsensus) info["consensus"] = mConsensus->getJson(); - typedef std::pair jt_int_pair; - bool anyJobs = false; - Json::Value jobs = Json::arrayValue; - std::vector< std::pair > jobCounts = theApp->getJobQueue().getJobCounts(); - BOOST_FOREACH(jt_int_pair& it, jobCounts) - { - if (it.second != 0) - { - Json::Value o = Json::objectValue; - o[Job::toString(it.first)] = it.second; - jobs.append(o); - anyJobs = true; - } - } - if (anyJobs) - info["jobs"] = jobs; + info["load"] = theApp->getJobQueue().getJson(); return info; } diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp index 7577341cb6..e2e6bceed4 100644 --- a/src/cpp/ripple/PaymentTransactor.cpp +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -4,12 +4,15 @@ #define RIPPLE_PATHS_MAX 3 -// TODO: only have the higher fee if the account doesn't in fact exist +// only have the higher fee if the account doesn't in fact exist void PaymentTransactor::calculateFee() { if (mTxn.getFlags() & tfCreateAccount) { - mFeeDue = theConfig.FEE_ACCOUNT_CREATE; + const uint160 uDstAccountID = mTxn.getFieldAccount160(sfDestination); + SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if(!sleDst) mFeeDue = theConfig.FEE_ACCOUNT_CREATE; + else Transactor::calculateFee(); }else Transactor::calculateFee(); } @@ -146,7 +149,13 @@ TER PaymentTransactor::doApply() else { mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount); - sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); + // re-arm the password change fee if we can and need to + if ( (sleDst->getFlags() & lsfPasswordSpent) && + (saDstAmount > theConfig.FEE_DEFAULT) ) + { + sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount-theConfig.FEE_DEFAULT); + sleDst->clearFlag(lsfPasswordSpent); + }else sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); terResult = tesSUCCESS; } diff --git a/src/cpp/ripple/Peer.cpp b/src/cpp/ripple/Peer.cpp index f364df838b..76e396b3e3 100644 --- a/src/cpp/ripple/Peer.cpp +++ b/src/cpp/ripple/Peer.cpp @@ -373,8 +373,11 @@ void Peer::processReadBuffer() // std::cerr << "Peer::processReadBuffer: " << mIpPort.first << " " << mIpPort.second << std::endl; - // If connected and get a mtHELLO or if not connected and get a non-mtHELLO, wrong message was sent. + LoadEvent::pointer event = theApp->getJobQueue().getLoadEvent(jtPEER); + boost::recursive_mutex::scoped_lock sl(theApp->getMasterLock()); + + // If connected and get a mtHELLO or if not connected and get a non-mtHELLO, wrong message was sent. if (mHelloed == (type == ripple::mtHELLO)) { cLog(lsWARNING) << "Wrong message type: " << type; diff --git a/src/cpp/ripple/ProofOfWork.cpp b/src/cpp/ripple/ProofOfWork.cpp index 044794a888..7243620597 100644 --- a/src/cpp/ripple/ProofOfWork.cpp +++ b/src/cpp/ripple/ProofOfWork.cpp @@ -214,46 +214,51 @@ struct PowEntry int iterations; }; -PowEntry PowEntries[32] = -{ - // FIXME: These targets are too low and iteration counts too low - // These get too difficulty before they become sufficently RAM intensive - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 256 }, // Hashes:5242880 KB=8 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 512 }, // Hashes:5242880 KB=16 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 512 }, // Hashes:10485760 KB=16 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1024 }, // Hashes:10485760 KB=32 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1024 }, // Hashes:20971520 KB=32 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2048 }, // Hashes:20971520 KB=64 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2048 }, // Hashes:41943040 KB=64 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 4096 }, // Hashes:41943040 KB=128 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 4096 }, // Hashes:83886080 KB=128 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 8192 }, // Hashes:83886080 KB=256 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 8192 }, // Hashes:167772160 KB=256 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16384 }, // Hashes:167772160 KB=512 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 32768 }, // Hashes:335544320 MB=1 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 32768 }, // Hashes:671088640 MB=1 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // Hashes:671088640 MB=2 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // Hashes:1342177280 MB=2 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072 }, // Hashes:1342177280 MB=4 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072 }, // Hashes:2684354560 MB=4 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144 }, // Hashes:2684354560 MB=8 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:5368709120 MB=16 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:10737418240 MB=16 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:10737418240 MB=32 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:21474836480 MB=32 - { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:21474836480 MB=64 - { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:42949672960 MB=64 - { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:85899345920 MB=16 - { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:171798691840 MB=16 - { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:171798691840 MB=32 - { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:343597383680 MB=32 - { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:343597383680 MB=64 - { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:687194767360 MB=64 +PowEntry PowEntries[31] = +{ // 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 }; void ProofOfWorkGenerator::setDifficulty(int i) { - assert((i >= 0) && (i <= 31)); + assert((i >= 0) && (i <= 30)); time_t now = time(NULL); boost::mutex::scoped_lock sl(mLock); diff --git a/src/cpp/ripple/RegularKeySetTransactor.cpp b/src/cpp/ripple/RegularKeySetTransactor.cpp index eb89bed283..f74b8f5a94 100644 --- a/src/cpp/ripple/RegularKeySetTransactor.cpp +++ b/src/cpp/ripple/RegularKeySetTransactor.cpp @@ -4,43 +4,30 @@ SETUP_LOG(); -// TODO: -TER RegularKeySetTransactor::checkSig() -{ - // Transaction's signing public key must be for the source account. - // To prove the master private key made this transaction. - if (mSigningPubKey.getAccountID() != mTxnAccountID) - { - // Signing Pub Key must be for Source Account ID. - cLog(lsWARNING) << "sourceAccountID: " << mSigningPubKey.humanAccountID(); - cLog(lsWARNING) << "txn accountID: " << mTxn.getSourceAccount().humanAccountID(); - return temBAD_SET_ID; - } - return tesSUCCESS; -} - -// TODO: this should be default fee if flag isn't set void RegularKeySetTransactor::calculateFee() { - mFeeDue = 0; + Transactor::calculateFee(); + + if ( !(mTxnAccount->getFlags() & lsfPasswordSpent) && + (mSigningPubKey.getAccountID() == mTxnAccountID)) + { // flag is armed and they signed with the right account + + mSourceBalance = mTxnAccount->getFieldAmount(sfBalance); + if(mSourceBalance < mFeeDue) mFeeDue = 0; + } } -// TODO: change to take a fee if there is one there TER RegularKeySetTransactor::doApply() { std::cerr << "doRegularKeySet>" << std::endl; - if (mTxnAccount->getFlags() & lsfPasswordSpent) + if(mFeeDue.isZero()) { - std::cerr << "doRegularKeySet: Delay transaction: Funds already spent." << std::endl; - - return terFUNDS_SPENT; + mTxnAccount->setFlag(lsfPasswordSpent); } - mTxnAccount->setFlag(lsfPasswordSpent); - uint160 uAuthKeyID=mTxn.getFieldAccount160(sfRegularKey); mTxnAccount->setFieldAccount(sfRegularKey, uAuthKeyID); diff --git a/src/cpp/ripple/RegularKeySetTransactor.h b/src/cpp/ripple/RegularKeySetTransactor.h index a6df0b3569..35d744c8f0 100644 --- a/src/cpp/ripple/RegularKeySetTransactor.h +++ b/src/cpp/ripple/RegularKeySetTransactor.h @@ -6,6 +6,5 @@ class RegularKeySetTransactor : public Transactor public: RegularKeySetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} TER checkFee(); - TER checkSig(); TER doApply(); }; diff --git a/src/cpp/ripple/SerializedTypes.cpp b/src/cpp/ripple/SerializedTypes.cpp index 61f526eeb0..b9daae2fe5 100644 --- a/src/cpp/ripple/SerializedTypes.cpp +++ b/src/cpp/ripple/SerializedTypes.cpp @@ -395,7 +395,7 @@ STPathSet* STPathSet::construct(SerializerIterator& s, SField::ref name) if (bIssuer) uIssuerID = s.get160(); - path.push_back(STPathElement(uAccountID, uCurrency, uIssuerID)); + path.push_back(STPathElement(uAccountID, uCurrency, uIssuerID, bCurrency)); } } while(1); } diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index acb6c2c4b3..cab64a9508 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -289,6 +289,7 @@ void ValidationCollection::condWrite() void ValidationCollection::doWrite() { + LoadEvent::pointer event = theApp->getJobQueue().getLoadEvent(jtDISK); static boost::format insVal("INSERT INTO LedgerValidations " "(LedgerHash,NodePubKey,Flags,SignTime,Signature) VALUES ('%s','%s','%u','%u',%s);"); diff --git a/src/cpp/ripple/WSHandler.h b/src/cpp/ripple/WSHandler.h index 43c9a9ce66..306647c5c5 100644 --- a/src/cpp/ripple/WSHandler.h +++ b/src/cpp/ripple/WSHandler.h @@ -1,6 +1,8 @@ #ifndef __WSHANDLER__ #define __WSHANDLER__ +#include "Application.h" + class WSConnection; // A single instance of this object is made. @@ -87,10 +89,11 @@ public: void on_message(connection_ptr cpClient, message_ptr mpMessage) { + LoadEvent::pointer event = theApp->getJobQueue().getLoadEvent(jtCLIENT); Json::Value jvRequest; Json::Reader jrReader; - cLog(lsDEBUG) << "Ws:: Receiving '" << mpMessage->get_payload() << "'"; + cLog(lsDEBUG) << "Ws:: Receiving '" << mpMessage->get_payload() << "'"; if (mpMessage->get_opcode() != websocketpp::frame::opcode::TEXT) { diff --git a/src/js/amount.js b/src/js/amount.js index e75f248378..5f5f58e37c 100644 --- a/src/js/amount.js +++ b/src/js/amount.js @@ -353,6 +353,12 @@ Amount.prototype.issuer = function() { return this._issuer; }; +Amount.prototype.to_number = function(allow_nan) { + var s = this.to_text(allow_nan); + + return ('string' === typeof s) ? Number(s) : s; +} + // Convert only value to JSON wire format. Amount.prototype.to_text = function(allow_nan) { if (isNaN(this._value)) { diff --git a/src/js/remote.js b/src/js/remote.js index 0f990fe950..c7040e2396 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -806,13 +806,15 @@ Remote.prototype.ledger_accept = function () { // Return a request to refresh the account balance. Remote.prototype.request_account_balance = function (account, current) { - return (this.request_ledger_entry('account_root')) + var request = this.request_ledger_entry('account_root'); + + return request .account_root(account) .ledger_choose(current) .on('success', function (message) { - // If the caller also waits for 'success', they might run before this. - request.emit('account_balance', message.node.Balance); - }); + // If the caller also waits for 'success', they might run before this. + request.emit('account_balance', Amount.from_json(message.node.Balance)); + }); }; // Return the next account sequence if possible. diff --git a/test/offer-test.js b/test/offer-test.js index fc42aac2e1..ef83dd5901 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -400,7 +400,7 @@ buster.testCase("Offer tests", { testutils.verify_balances(self.remote, { - "alice" : [ "0/USD/mtgox", "500" ], + "alice" : [ "0/USD/mtgox", String(10000+500-2*(Remote.fees['default'].to_number())) ], "bob" : "100/USD/mtgox", }, callback); @@ -482,7 +482,7 @@ buster.testCase("Offer tests", { testutils.verify_balances(self.remote, { - "alice" : [ "160/USD/mtgox", "200" ], // XXX Verfiying XRP balance needs to account for fees. + "alice" : [ "160/USD/mtgox", String(10000+200-2*(Remote.fees['default'].to_number())) ], "bob" : "40/USD/mtgox", }, callback); @@ -524,7 +524,7 @@ buster.testCase("Offer tests", { testutils.verify_balances(self.remote, { - "alice" : [ "100/USD/mtgox", "500" ], // XXX Verfiying XRP balance needs to account for fees. + "alice" : [ "100/USD/mtgox", String(10000+200+300-4*(Remote.fees['default'].to_number())) ], "bob" : "100/USD/mtgox", }, callback); @@ -534,8 +534,13 @@ buster.testCase("Offer tests", { done(); }); }, +}); - "ripple cross currency payment" : +buster.testCase("Offer cross currency", { + 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "ripple cross currency payment - start with XRP" : // alice --> [XRP --> carol --> USD/mtgox] --> bob function (done) { @@ -616,5 +621,192 @@ buster.testCase("Offer tests", { done(); }); }, + + "ripple cross currency payment - end with XRP" : + // alice --> [USD/mtgox --> carol --> XRP] --> bob + + function (done) { + var self = this; + var seq; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "carol" : "2000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("carol", "50/USD/mtgox", "500") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send XRP to bob converting from USD/mtgox."; + + self.remote.transaction() + .payment("alice", "bob", "250") + .send_max("333/USD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : "475/USD/mtgox", + "bob" : "10250", + "carol" : "25/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Verify offer partially consumed."; + + testutils.verify_offer(self.remote, "carol", seq, "250", "25/USD/mtgox", callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "// ripple cross currency bridged payment" : + // alice --> [USD/mtgox --> carol --> XRP] --> [XRP --> dan --> EUR/bitstamp] --> bob + + function (done) { + var self = this; + var seq_carol; + var seq_dan; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "carol", "dan", "bitstamp", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "bob" : "1000/EUR/bitstamp", + "carol" : "1000/USD/mtgox", + "dan" : "1000/EUR/bitstamp" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "400/EUR/dan", + "mtgox" : "500/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Create offer carol."; + + self.remote.transaction() + .offer_create("carol", "50/USD/mtgox", "500") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq_carol = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Create offer dan."; + + self.remote.transaction() + .offer_create("dan", "500", "50/EUR/bitstamp") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq_dan = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send EUR/bitstamp to bob converting from USD/mtgox."; + + self.remote.transaction() + .payment("alice", "bob", "30/EUR/bitstamp") + .send_max("333/USD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, +// function (callback) { +// self.what = "Verify balances."; +// +// testutils.verify_balances(self.remote, +// { +// "alice" : "470/USD/mtgox", +// "bob" : "30/EUR/bitstamp", +// "carol" : "30/USD/mtgox", +// "dan" : "370/EUR/bitstamp", +// }, +// callback); +// }, +// function (callback) { +// self.what = "Verify carol offer partially consumed."; +// +// testutils.verify_offer(self.remote, "carol", seq_carol, "250", "25/USD/mtgox", callback); +// }, +// function (callback) { +// self.what = "Verify dan offer partially consumed."; +// +// testutils.verify_offer(self.remote, "dan", seq_dan, "250", "25/USD/mtgox", callback); +// }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, }); // vim:sw=2:sts=2:ts=8 diff --git a/test/send-test.js b/test/send-test.js index 043855714c..21d261c7a7 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -15,13 +15,14 @@ var serverDelay = 1500; buster.testRunner.timeout = 5000; + /* -buster.testCase("Simple", { +buster.testCase("Fee Changes", { 'setUp' : testutils.build_setup({no_server: true}), // 'tearDown' : testutils.build_teardown(), - "simple." : - function (done) { buster.assert(1); + "varying the fee for Payment" : + function (done) { this.remote.transaction() .payment('root', 'alice', "10000") @@ -36,8 +37,8 @@ buster.testCase("Simple", { }).submit(); } - }); */ - + }); + */ buster.testCase("Sending", { 'setUp' : testutils.build_setup(), 'tearDown' : testutils.build_teardown(), diff --git a/test/testutils.js b/test/testutils.js index 8a840bd5f0..bea65eb8a1 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -265,14 +265,22 @@ var transfer_rate = function (remote, src, billionths, callback) { var verify_balance = function (remote, src, amount_json, callback) { assert(4 === arguments.length); - var amount = Amount.from_json(amount_json); + var amount_req = Amount.from_json(amount_json); - if (amount.is_native()) { - // XXX Not implemented. - callback(); + if (amount_req.is_native()) { + remote.request_account_balance(src, 'CURRENT') + .once('account_balance', function (amount_act) { + if (!amount_act.equals(amount_req)) + console.log("verify_balance: failed: %s / %s", + amount_act.to_text_full(), + amount_req.to_text_full()); + + callback(!amount_act.equals(amount_req)); + }) + .request(); } else { - remote.request_ripple_balance(src, amount.issuer().to_json(), amount.currency().to_json(), 'CURRENT') + remote.request_ripple_balance(src, amount_req.issuer().to_json(), amount_req.currency().to_json(), 'CURRENT') .once('ripple_state', function (m) { // console.log("BALANCE: %s", JSON.stringify(m)); // console.log("account_balance: %s", m.account_balance.to_text_full()); @@ -282,11 +290,11 @@ var verify_balance = function (remote, src, amount_json, callback) { var account_balance = Amount.from_json(m.account_balance); - if (!account_balance.equals(amount)) { - console.log("verify_balance: failed: %s vs %s is %s: %s", src, account_balance.to_text_full(), amount.to_text_full(), account_balance.not_equals_why(amount)); + if (!account_balance.equals(amount_req)) { + console.log("verify_balance: failed: %s vs %s is %s: %s", src, account_balance.to_text_full(), amount_req.to_text_full(), account_balance.not_equals_why(amount_req)); } - callback(!account_balance.equals(amount)); + callback(!account_balance.equals(amount_req)); }) .request(); }