diff --git a/js/amount.js b/js/amount.js
index d2da47a05..a9e6e839d 100644
--- a/js/amount.js
+++ b/js/amount.js
@@ -436,7 +436,7 @@ Amount.prototype.parse_native = function(j) {
var m;
if ('string' === typeof j)
- m = j.match(/^(-?)(\d+)(\.\d{1,6})?$/);
+ m = j.match(/^(-?)(\d+)(\.\d{0,6})?$/);
if (m) {
if (undefined === m[3]) {
@@ -445,7 +445,7 @@ Amount.prototype.parse_native = function(j) {
this.value = new BigInteger(m[2]);
}
else {
- // Decimal notation
+ // Float notation
var int_part = (new BigInteger(m[2])).multiply(exports.consts.bi_xns_unit);
var fraction_part = (new BigInteger(m[3])).multiply(new BigInteger(String(Math.pow(10, 1+exports.consts.xns_precision-m[3].length))));
diff --git a/js/remote.js b/js/remote.js
index 9fa97054a..8313088f0 100644
--- a/js/remote.js
+++ b/js/remote.js
@@ -216,10 +216,10 @@ Remote.flags = {
// XXX This needs to be determined from the network.
Remote.fees = {
- 'default' : Amount.from_json("100"),
- 'account_create' : Amount.from_json("1000"),
+ 'default' : Amount.from_json("10"),
+ 'account_create' : Amount.from_json("1000000000"),
'nickname_create' : Amount.from_json("1000"),
- 'offer' : Amount.from_json("100"),
+ 'offer' : Amount.from_json("10"),
};
// Set the emited state: 'online' or 'offline'
diff --git a/newcoin.vcxproj b/newcoin.vcxproj
index 0723eb907..a2dd3bbb6 100644
--- a/newcoin.vcxproj
+++ b/newcoin.vcxproj
@@ -107,6 +107,7 @@
+
@@ -275,8 +276,8 @@
Document
- d:/code/protoc-2.4.1-win32/protoc -I=..\newcoin --cpp_out=D:\code\newcoin\obj\ ..\newcoin/src/ripple.proto
- D:\code\newcoin\obj\src\ripple.pb.h
+ /code/protoc-2.4.1-win32/protoc -I=..\newcoin --cpp_out=\code\newcoin\obj\ ..\newcoin/src/ripple.proto
+ \code\newcoin\obj\src\ripple.pb.h
diff --git a/src/Amount.cpp b/src/Amount.cpp
index bfcd5e27a..cacfe72a1 100644
--- a/src/Amount.cpp
+++ b/src/Amount.cpp
@@ -780,7 +780,6 @@ STAmount operator+(const STAmount& v1, const STAmount& v2)
if (v1.mIsNative)
return STAmount(v1.getFName(), v1.getSNValue() + v2.getSNValue());
-
int ov1 = v1.mOffset, ov2 = v2.mOffset;
int64 vv1 = static_cast(v1.mValue), vv2 = static_cast(v2.mValue);
if (v1.mIsNegative) vv1 = -vv1;
diff --git a/src/Application.cpp b/src/Application.cpp
index 975b0411f..7c459fc12 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -46,6 +46,7 @@ Application::Application() :
{
RAND_bytes(mNonce256.begin(), mNonce256.size());
RAND_bytes(reinterpret_cast(&mNonceST), sizeof(mNonceST));
+ mJobQueue.setThreadCount();
}
extern const char *RpcDBInit[], *TxnDBInit[], *LedgerDBInit[], *WalletDBInit[], *HashNodeDBInit[], *NetNodeDBInit[];
@@ -54,6 +55,7 @@ extern int RpcDBCount, TxnDBCount, LedgerDBCount, WalletDBCount, HashNodeDBCount
void Application::stop()
{
mIOService.stop();
+ mJobQueue.shutdown();
mHashedObjectStore.bulkWrite();
mValidations.flush();
mAuxService.stop();
@@ -70,9 +72,10 @@ void Application::run()
{
assert(mTxnDB == NULL);
if (!theConfig.DEBUG_LOGFILE.empty())
- { // Let DEBUG messages go to the file but only WARNING or higher to regular output
+ { // Let DEBUG messages go to the file but only WARNING or higher to regular output (unless verbose)
Log::setLogFile(theConfig.DEBUG_LOGFILE);
- LogPartition::setSeverity(lsDEBUG);
+ if (Log::getMinSeverity() > lsDEBUG)
+ LogPartition::setSeverity(lsDEBUG);
}
boost::thread auxThread(boost::bind(&boost::asio::io_service::run, &mAuxService));
diff --git a/src/Application.h b/src/Application.h
index 9a37333fb..ebb078492 100644
--- a/src/Application.h
+++ b/src/Application.h
@@ -18,6 +18,7 @@
#include "Suppression.h"
#include "SNTPClient.h"
#include "../database/database.h"
+#include "JobQueue.h"
class RPCDoor;
@@ -53,6 +54,7 @@ class Application
SuppressionTable mSuppressions;
HashedObjectStore mHashedObjectStore;
SNTPClient mSNTPClient;
+ JobQueue mJobQueue;
DatabaseCon *mRpcDB, *mTxnDB, *mLedgerDB, *mWalletDB, *mHashNodeDB, *mNetNodeDB;
@@ -90,6 +92,7 @@ public:
NodeCache& getTempNodeCache() { return mTempNodeCache; }
HashedObjectStore& getHashedObjectStore() { return mHashedObjectStore; }
ValidationCollection& getValidations() { return mValidations; }
+ JobQueue& getJobQueue() { return mJobQueue; }
bool isNew(const uint256& s) { return mSuppressions.addSuppression(s); }
bool isNew(const uint160& s) { return mSuppressions.addSuppression(s); }
bool running() { return mTxnDB != NULL; }
diff --git a/src/Config.cpp b/src/Config.cpp
index 91a3acbe8..2faed71d3 100644
--- a/src/Config.cpp
+++ b/src/Config.cpp
@@ -35,9 +35,9 @@
#define SECTION_VALIDATORS "validators"
#define SECTION_VALIDATORS_SITE "validators_site"
-// Fees are in XNB.
-#define DEFAULT_FEE_DEFAULT 100
-#define DEFAULT_FEE_ACCOUNT_CREATE 1000
+// Fees are in XNS.
+#define DEFAULT_FEE_DEFAULT 10
+#define DEFAULT_FEE_ACCOUNT_CREATE 1000*SYSTEM_CURRENCY_PARTS
#define DEFAULT_FEE_NICKNAME_CREATE 1000
#define DEFAULT_FEE_OFFER DEFAULT_FEE_DEFAULT
#define DEFAULT_FEE_OPERATION 1
diff --git a/src/ConnectionPool.cpp b/src/ConnectionPool.cpp
index c9e93f3f7..a6e1a9924 100644
--- a/src/ConnectionPool.cpp
+++ b/src/ConnectionPool.cpp
@@ -330,6 +330,13 @@ Json::Value ConnectionPool::getPeersJson()
return ret;
}
+int ConnectionPool::getPeerCount()
+{
+ boost::mutex::scoped_lock sl(mPeerLock);
+
+ return mConnectedMap.size();
+}
+
std::vector ConnectionPool::getPeerVector()
{
std::vector ret;
diff --git a/src/ConnectionPool.h b/src/ConnectionPool.h
index 569a1e458..1b909f626 100644
--- a/src/ConnectionPool.h
+++ b/src/ConnectionPool.h
@@ -83,6 +83,7 @@ public:
// As client failed connect and be accepted.
void peerClosed(Peer::ref peer, const std::string& strIp, int iPort);
+ int getPeerCount();
Json::Value getPeersJson();
std::vector getPeerVector();
diff --git a/src/FieldNames.cpp b/src/FieldNames.cpp
index 2f84ec7ab..67695731d 100644
--- a/src/FieldNames.cpp
+++ b/src/FieldNames.cpp
@@ -26,6 +26,31 @@ SField sfIndex(STI_HASH256, 258, "index");
#undef FIELD
#undef TYPE
+static int initFields()
+{
+ sfHighQualityIn.setMeta(SFM_CHANGE); sfHighQualityOut.setMeta(SFM_CHANGE);
+ sfLowQualityIn.setMeta(SFM_CHANGE); sfLowQualityOut.setMeta(SFM_CHANGE);
+
+ sfLowLimit.setMeta(SFM_ALWAYS); sfHighLimit.setMeta(SFM_ALWAYS);
+ sfTakerPays.setMeta(SFM_ALWAYS); sfTakerGets.setMeta(SFM_ALWAYS);
+ sfQualityIn.setMeta(SFM_ALWAYS); sfQualityOut.setMeta(SFM_ALWAYS);
+
+ sfBalance.setMeta(SFM_ALWAYS);
+
+ sfPublicKey.setMeta(SFM_CHANGE); sfMessageKey.setMeta(SFM_CHANGE);
+ sfSigningPubKey.setMeta(SFM_CHANGE); sfAuthorizedKey.setMeta(SFM_CHANGE);
+ sfSigningAccounts.setMeta(SFM_CHANGE);
+
+ sfWalletLocator.setMeta(SFM_CHANGE);
+ sfNickname.setMeta(SFM_CHANGE);
+ sfAmount.setMeta(SFM_ALWAYS);
+ sfDomain.setMeta(SFM_CHANGE);
+ sfOwner.setMeta(SFM_ALWAYS);
+
+ return 0;
+}
+static const int f = initFields();
+
SField::SField(SerializedTypeID tid, int fv) : fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv)
{ // call with the map mutex
diff --git a/src/FieldNames.h b/src/FieldNames.h
index 3a09f67a4..cdb11ad6b 100644
--- a/src/FieldNames.h
+++ b/src/FieldNames.h
@@ -33,6 +33,14 @@ enum SOE_Flags
SOE_OPTIONAL = 1, // optional
};
+enum SF_Meta
+{
+ SFM_NEVER = 0,
+ SFM_CHANGE = 1,
+ SFM_DELETE = 2,
+ SFM_ALWAYS = 3
+};
+
class SField
{
public:
@@ -51,16 +59,17 @@ public:
const SerializedTypeID fieldType; // STI_*
const int fieldValue; // Code number for protocol
std::string fieldName;
+ SF_Meta fieldMeta;
SField(int fc, SerializedTypeID tid, int fv, const char* fn) :
- fieldCode(fc), fieldType(tid), fieldValue(fv), fieldName(fn)
+ fieldCode(fc), fieldType(tid), fieldValue(fv), fieldName(fn), fieldMeta(SFM_NEVER)
{
boost::mutex::scoped_lock sl(mapMutex);
codeToField[fieldCode] = this;
}
SField(SerializedTypeID tid, int fv, const char *fn) :
- fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv), fieldName(fn)
+ fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv), fieldName(fn), fieldMeta(SFM_NEVER)
{
boost::mutex::scoped_lock sl(mapMutex);
codeToField[fieldCode] = this;
@@ -84,6 +93,11 @@ public:
bool isBinary() const { return fieldValue < 256; }
bool isDiscardable() const { return fieldValue > 256; }
+ SF_Meta getMeta() const { return fieldMeta; }
+ bool shouldMetaDel() const { return (fieldMeta == SFM_DELETE) || (fieldMeta == SFM_ALWAYS); }
+ bool shouldMetaMod() const { return (fieldMeta == SFM_CHANGE) || (fieldMeta == SFM_ALWAYS); }
+ void setMeta(SF_Meta m) { fieldMeta = m; }
+
bool operator==(const SField& f) const { return fieldCode == f.fieldCode; }
bool operator!=(const SField& f) const { return fieldCode != f.fieldCode; }
diff --git a/src/HashedObject.cpp b/src/HashedObject.cpp
index 34874e991..997be4c23 100644
--- a/src/HashedObject.cpp
+++ b/src/HashedObject.cpp
@@ -19,7 +19,7 @@ HashedObjectStore::HashedObjectStore(int cacheSize, int cacheAge) :
bool HashedObjectStore::store(HashedObjectType type, uint32 index,
const std::vector& data, const uint256& hash)
-{ // return: false=already in cache, true = added to cache
+{ // return: false = already in cache, true = added to cache
assert(hash == Serializer::getSHA512Half(data));
if (!theApp->getHashNodeDB())
{
@@ -59,15 +59,15 @@ void HashedObjectStore::waitWrite()
void HashedObjectStore::bulkWrite()
{
- std::vector< boost::shared_ptr > set;
while (1)
{
- set.clear();
+ std::vector< boost::shared_ptr > set;
set.reserve(128);
{
boost::unique_lock sl(mWriteMutex);
mWriteSet.swap(set);
+ assert(mWriteSet.empty());
if (set.empty())
{
mWritePending = false;
diff --git a/src/JobQueue.cpp b/src/JobQueue.cpp
new file mode 100644
index 000000000..cbb2bee83
--- /dev/null
+++ b/src/JobQueue.cpp
@@ -0,0 +1,181 @@
+#include "JobQueue.h"
+
+#include
+#include
+#include
+#include
+
+#include "Log.h"
+
+SETUP_LOG();
+
+const char* Job::toString(JobType t)
+{
+ switch(t)
+ {
+ case jtINVALID: return "invalid";
+ case jtVALIDATION_ut: return "untrustedValidation";
+ case jtTRANSACTION: return "transaction";
+ case jtPROPOSAL_ut: return "untrustedProposal";
+ case jtVALIDATION_t: return "trustedValidation";
+ case jtPROPOSAL_t: return "trustedProposal";
+ case jtADMIN: return "administration";
+ case jtDEATH: return "jobOfDeath";
+ default: assert(false); return "unknown";
+ }
+}
+
+bool Job::operator<(const Job& j) const
+{ // These comparison operators make the jobs sort in priority order in the job set
+ if (mType < j.mType)
+ return true;
+ if (mType > j.mType)
+ return false;
+ return mJobIndex < j.mJobIndex;
+}
+
+bool Job::operator<=(const Job& j) const
+{
+ if (mType < j.mType)
+ return true;
+ if (mType > j.mType)
+ return false;
+ return mJobIndex <= j.mJobIndex;
+}
+
+bool Job::operator>(const Job& j) const
+{
+ if (mType < j.mType)
+ return false;
+ if (mType > j.mType)
+ return true;
+ return mJobIndex > j.mJobIndex;
+}
+
+bool Job::operator>=(const Job& j) const
+{
+ if (mType < j.mType)
+ return false;
+ if (mType > j.mType)
+ return true;
+ return mJobIndex >= j.mJobIndex;
+}
+
+void JobQueue::addJob(JobType type, const boost::function& jobFunc)
+{
+ assert(type != jtINVALID);
+
+ 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));
+ ++mJobCounts[type];
+ mJobCond.notify_one();
+}
+
+int JobQueue::getJobCount(JobType t)
+{
+ boost::mutex::scoped_lock sl(mJobLock);
+
+ std::map::iterator c = mJobCounts.find(t);
+ return (c == mJobCounts.end()) ? 0 : c->second;
+}
+
+int JobQueue::getJobCountGE(JobType t)
+{ // return the number of jobs at this priority level or greater
+ int ret = 0;
+
+ boost::mutex::scoped_lock sl(mJobLock);
+
+ typedef std::pair jt_int_pair;
+ BOOST_FOREACH(const jt_int_pair& it, mJobCounts)
+ if (it.first >= t)
+ ret += it.second;
+ return ret;
+}
+
+std::vector< std::pair > JobQueue::getJobCounts()
+{ // return all jobs at all priority levels
+ std::vector< std::pair > ret;
+
+ boost::mutex::scoped_lock sl(mJobLock);
+ ret.reserve(mJobCounts.size());
+
+ typedef std::pair jt_int_pair;
+ BOOST_FOREACH(const jt_int_pair& it, mJobCounts)
+ ret.push_back(it);
+
+ return ret;
+}
+
+void JobQueue::shutdown()
+{ // shut down the job queue without completing pending jobs
+ cLog(lsINFO) << "Job queue shutting down";
+ boost::mutex::scoped_lock sl(mJobLock);
+ mShuttingDown = true;
+ mJobCond.notify_all();
+ while (mThreadCount != 0)
+ mJobCond.wait(sl);
+}
+
+void JobQueue::setThreadCount(int c)
+{ // set the number of thread serving the job queue to precisely this number
+ if (c == 0)
+ {
+ c = boost::thread::hardware_concurrency();
+ if (c < 2)
+ c = 2;
+ cLog(lsINFO) << "Auto-tuning to " << c << " validation/transaction/proposal threads";
+ }
+
+ boost::mutex::scoped_lock sl(mJobLock);
+
+ while (mJobCounts[jtDEATH] != 0)
+ mJobCond.wait(sl);
+
+ while (mThreadCount < c)
+ {
+ ++mThreadCount;
+ boost::thread t(boost::bind(&JobQueue::threadEntry, this));
+ t.detach();
+ }
+ while (mThreadCount > c)
+ {
+ if (mJobCounts[jtDEATH] != 0)
+ mJobCond.wait(sl);
+ else
+ {
+ mJobSet.insert(Job(jtDEATH, 0));
+ ++mJobCounts[jtDEATH];
+ }
+ }
+ mJobCond.notify_one(); // in case we sucked up someone else's signal
+}
+
+void JobQueue::threadEntry()
+{ // do jobs until asked to stop
+ boost::mutex::scoped_lock sl(mJobLock);
+ while (1)
+ {
+ while (mJobSet.empty() && !mShuttingDown)
+ mJobCond.wait(sl);
+
+ if (mShuttingDown)
+ break;
+
+ std::set::iterator it = mJobSet.begin();
+ Job job(*it);
+ mJobSet.erase(it);
+ --mJobCounts[job.getType()];
+
+ if (job.getType() == jtDEATH)
+ break;
+
+ sl.unlock();
+ cLog(lsDEBUG) << "Doing " << Job::toString(job.getType()) << " job";
+ job.doJob();
+ sl.lock();
+ }
+ --mThreadCount;
+ mJobCond.notify_all();
+}
diff --git a/src/JobQueue.h b/src/JobQueue.h
new file mode 100644
index 000000000..ddcfa1017
--- /dev/null
+++ b/src/JobQueue.h
@@ -0,0 +1,83 @@
+#ifndef JOB_QUEUE__H
+#define JOB_QUEUE__H
+
+#include