diff --git a/rippled-example.cfg b/rippled-example.cfg index 8bd553b9cb..6eb7172bfb 100644 --- a/rippled-example.cfg +++ b/rippled-example.cfg @@ -211,6 +211,11 @@ # Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE # shfArahZT9Q9ckTf3s1psJ7C7qzVN # +# [node_size]: +# Tunes the servers based on the expected load and available memory. Legal +# sizes are "tiny", "small", "medium", "large", and "huge". +# The default is "tiny". +# # [cluster_nodes]: # To extend full trust to other nodes, place their node public keys here. # Generally, you should only do this for nodes under common administration. diff --git a/src/cpp/database/SqliteDatabase.cpp b/src/cpp/database/SqliteDatabase.cpp index e760d69030..addf399b68 100644 --- a/src/cpp/database/SqliteDatabase.cpp +++ b/src/cpp/database/SqliteDatabase.cpp @@ -238,4 +238,120 @@ void SqliteDatabase::runWal() } } +SqliteStatement::SqliteStatement(SqliteDatabase* db, const char *sql) +{ + assert(db); + int j = sqlite3_prepare_v2(db->peekConnection(), sql, strlen(sql) + 1, &statement, NULL); + if (j != SQLITE_OK) + throw j; +} + +SqliteStatement::~SqliteStatement() +{ + sqlite3_finalize(statement); +} + +sqlite3_stmt* SqliteStatement::peekStatement() +{ + return statement; +} + +int SqliteStatement::bind(int position, const void *data, int length) +{ + return sqlite3_bind_blob(statement, position, data, length, SQLITE_TRANSIENT); +} + +int SqliteStatement::bindStatic(int position, const void *data, int length) +{ + return sqlite3_bind_blob(statement, position, data, length, SQLITE_STATIC); +} + +int SqliteStatement::bindStatic(int position, const std::vector& value) +{ + return sqlite3_bind_blob(statement, position, &value.front(), value.size(), SQLITE_STATIC); +} + +int SqliteStatement::bind(int position, uint32 value) +{ + return sqlite3_bind_int64(statement, position, static_cast(value)); +} + +int SqliteStatement::bind(int position, const std::string& value) +{ + return sqlite3_bind_text(statement, position, value.data(), value.size(), SQLITE_TRANSIENT); +} + +int SqliteStatement::bindStatic(int position, const std::string& value) +{ + return sqlite3_bind_text(statement, position, value.data(), value.size(), SQLITE_STATIC); +} + +int SqliteStatement::bind(int position) +{ + return sqlite3_bind_null(statement, position); +} + +int SqliteStatement::size(int column) +{ + return sqlite3_column_bytes(statement, column); +} + +const void* SqliteStatement::peekBlob(int column) +{ + return sqlite3_column_blob(statement, column); +} + +std::vector SqliteStatement::getBlob(int column) +{ + int size = sqlite3_column_bytes(statement, column); + std::vector ret(size); + memcpy(&(ret.front()), sqlite3_column_blob(statement, column), size); + return ret; +} + +std::string SqliteStatement::getString(int column) +{ + return reinterpret_cast(sqlite3_column_text(statement, column)); +} + +const char* SqliteStatement::peekString(int column) +{ + return reinterpret_cast(sqlite3_column_text(statement, column)); +} + +uint32 SqliteStatement::getUInt32(int column) +{ + return static_cast(sqlite3_column_int64(statement, column)); +} + +int64 SqliteStatement::getInt64(int column) +{ + return sqlite3_column_int64(statement, column); +} + +int SqliteStatement::step() +{ + return sqlite3_step(statement); +} + +int SqliteStatement::reset() +{ + return sqlite3_reset(statement); +} + +bool SqliteStatement::isOk(int j) +{ + return j == SQLITE_OK; +} + +bool SqliteStatement::isDone(int j) +{ + return j == SQLITE_DONE; +} + +bool SqliteStatement::isRow(int j) +{ + return j == SQLITE_ROW; +} + // vim:ts=4 diff --git a/src/cpp/database/SqliteDatabase.h b/src/cpp/database/SqliteDatabase.h index 9b99bbf11b..ad0c0eab66 100644 --- a/src/cpp/database/SqliteDatabase.h +++ b/src/cpp/database/SqliteDatabase.h @@ -52,9 +52,53 @@ public: sqlite3* peekConnection() { return mConnection; } virtual bool setupCheckpointing(); + virtual SqliteDatabase* getSqliteDB() { return this; } void runWal(); void doHook(const char *db, int walSize); }; +class SqliteStatement +{ +private: + SqliteStatement(const SqliteStatement&); // no implementation + SqliteStatement& operator=(const SqliteStatement&); // no implementation + +protected: + sqlite3_stmt* statement; + +public: + SqliteStatement(SqliteDatabase* db, const char *statement); + ~SqliteStatement(); + + sqlite3_stmt* peekStatement(); + + int bind(int position, const void *data, int length); + int bindStatic(int position, const void *data, int length); + int bindStatic(int position, const std::vector& value); + + int bind(int position, const std::string& value); + int bindStatic(int position, const std::string& value); + + int bind(int position, uint32 value); + int bind(int position); + + int size(int column); + + const void* peekBlob(int column); + std::vector getBlob(int column); + + std::string getString(int column); + const char* peekString(int column); + uint32 getUInt32(int column); + int64 getInt64(int column); + + int step(); + int reset(); + + bool isOk(int); + bool isDone(int); + bool isRow(int); +}; + // vim:ts=4 diff --git a/src/cpp/database/database.h b/src/cpp/database/database.h index 7f9199529f..aaad8ac488 100644 --- a/src/cpp/database/database.h +++ b/src/cpp/database/database.h @@ -16,6 +16,9 @@ /* this maintains the connection to the database */ + +class SqliteDatabase; + class Database { protected: @@ -83,7 +86,8 @@ public: // float getSingleDBValueFloat(const char* sql); // char* getSingleDBValueStr(const char* sql, std::string& retStr); - virtual bool setupCheckpointing() { return false; } + virtual bool setupCheckpointing() { return false; } + virtual SqliteDatabase* getSqliteDB() { return NULL; } }; #endif diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index 1d300f48ef..a8e86f4c7b 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -1026,6 +1026,7 @@ STAmount STAmount::setRate(uint64 rate) // --> saTakerFunds: Limit for saOfferGets : How much taker really wants. : Driver // --> saOfferPays: Request : this should be reduced as the offer is fullfilled. // --> saOfferGets: Request : this should be reduced as the offer is fullfilled. +// --> saTakerPays: Limit for taker to Pays. // --> saTakerGets: Limit for taker to get. // <-- saTakerPaid: Actual // <-- saTakerGot: Actual @@ -1037,7 +1038,7 @@ bool STAmount::applyOffer( const STAmount& saOfferRate, const STAmount& saOfferFunds, const STAmount& saTakerFunds, const STAmount& saOfferPays, const STAmount& saOfferGets, - const STAmount& saTakerGets, + const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot, STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee) { @@ -1075,21 +1076,27 @@ bool STAmount::applyOffer( else { // Offer has limited funding, limit offer gets and pays by funds available. + saOfferPaysAvailable = saOfferFundsAvailable; - saOfferGetsAvailable = multiply(saOfferFundsAvailable, saOfferRate, saOfferGets); + saOfferGetsAvailable = std::min(saOfferGets, multiply(saOfferPaysAvailable, saOfferRate, saOfferGets)); } cLog(lsINFO) << "applyOffer: saOfferPaysAvailable=" << saOfferFundsAvailable.getFullText(); cLog(lsINFO) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText(); - STAmount saTakerGetsAvailable = saTakerFunds >= saOfferGetsAvailable - ? saTakerGets - : multiply(saTakerFunds, saOfferRate, saTakerGets); // Amount can afford. + STAmount saTakerPaysAvailable = std::min(saTakerPays, saTakerFundsAvailable); + STAmount saTakerPaysMax = std::min(saTakerPaysAvailable, saOfferGetsAvailable); + STAmount saTakerGetsMax = saTakerPaysMax >= saOfferGetsAvailable + ? saOfferPaysAvailable // Potentially take entire offer. Avoid math shenanigans. + : std::min(saOfferPaysAvailable, multiply(saTakerPaysMax, saOfferRate, saTakerGets)); // Taker a portion of offer. - saTakerGot = std::min(saTakerGets, saOfferPaysAvailable); // Limit by wanted and available. + saTakerGot = std::min(saTakerGets, saTakerGetsMax); // Limit by wanted. saTakerPaid = saTakerGot == saOfferPaysAvailable ? saOfferGetsAvailable - : multiply(saTakerGot, saOfferRate, saTakerFunds); + : std::min(saOfferGetsAvailable, multiply(saTakerGot, saOfferRate, saTakerFunds)); + + cLog(lsINFO) << "applyOffer: saTakerGot=" << saTakerGot.getFullText(); + cLog(lsINFO) << "applyOffer: saTakerPaid=" << saTakerPaid.getFullText(); if (uTakerPaysRate == QUALITY_ONE) { @@ -1098,9 +1105,19 @@ bool STAmount::applyOffer( else { // Compute fees in a rounding safe way. - STAmount saTotal = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); - saTakerIssuerFee = (saTotal > saTakerFunds) ? saTakerFunds-saTakerPaid : saTotal-saTakerPaid; + // TakerCost includes transfer fees. + STAmount saTakerCost = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9)); + + STAmount saTransferRate = STAmount(CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9); + cLog(lsINFO) << "applyOffer: saTransferRate=" << saTransferRate.getFullText(); + cLog(lsINFO) << "applyOffer: saTakerCost=" << saTakerCost.getFullText(); + cLog(lsINFO) << "applyOffer: saTakerFunds=" << saTakerFunds.getFullText(); + saTakerIssuerFee = saTakerCost > saTakerFunds + ? saTakerFunds-saTakerPaid // Not enough funds to cover fee, stiff issuer the rounding error. + : saTakerCost-saTakerPaid; + cLog(lsINFO) << "applyOffer: saTakerIssuerFee=" << saTakerIssuerFee.getFullText(); + assert(!saTakerIssuerFee.isNegative()); } if (uOfferPaysRate == QUALITY_ONE) @@ -1110,14 +1127,16 @@ bool STAmount::applyOffer( else { // Compute fees in a rounding safe way. - STAmount saTotal = STAmount::multiply(saTakerGot, STAmount(CURRENCY_ONE, uOfferPaysRate, -9)); + STAmount saOfferCost = STAmount::multiply(saTakerGot, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9)); - saOfferIssuerFee = (saTotal > saOfferFunds) ? saOfferFunds-saTakerGot : saTotal-saTakerGot; + saOfferIssuerFee = saOfferCost > saOfferFunds + ? saOfferFunds-saTakerGot // Not enough funds to cover fee, stiff issuer the rounding error. + : saOfferCost-saTakerGot; } cLog(lsINFO) << "applyOffer: saTakerGot=" << saTakerGot.getFullText(); - return saTakerGot >= saOfferPays; + return saTakerGot >= saOfferPaysAvailable; } STAmount STAmount::getPay(const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed) diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index 58b75e8b48..09e37a2e4f 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -50,7 +50,7 @@ Application::Application() : getRand(mNonce256.begin(), mNonce256.size()); getRand(reinterpret_cast(&mNonceST), sizeof(mNonceST)); mJobQueue.setThreadCount(); - mSweepTimer.expires_from_now(boost::posix_time::seconds(60)); + mSweepTimer.expires_from_now(boost::posix_time::seconds(20)); mSweepTimer.async_wait(boost::bind(&Application::sweep, this)); } @@ -159,6 +159,9 @@ void Application::setup() if (!theConfig.RUN_STANDALONE) getUNL().nodeBootstrap(); + mValidations.tune(theConfig.getSize(siValidationsSize), theConfig.getSize(siValidationsAge)); + mHashedObjectStore.tune(theConfig.getSize(siNodeCacheSize), theConfig.getSize(siNodeCacheAge)); + mLedgerMaster.tune(theConfig.getSize(siLedgerSize), theConfig.getSize(siLedgerAge)); // // Allow peer connections. @@ -294,7 +297,7 @@ void Application::sweep() mTempNodeCache.sweep(); mValidations.sweep(); getMasterLedgerAcquire().sweep(); - mSweepTimer.expires_from_now(boost::posix_time::seconds(60)); + mSweepTimer.expires_from_now(boost::posix_time::seconds(theConfig.getSize(siSweepInterval))); mSweepTimer.async_wait(boost::bind(&Application::sweep, this)); } diff --git a/src/cpp/ripple/Config.cpp b/src/cpp/ripple/Config.cpp index d45d35d65a..7313d87cf9 100644 --- a/src/cpp/ripple/Config.cpp +++ b/src/cpp/ripple/Config.cpp @@ -27,6 +27,7 @@ #define SECTION_IPS "ips" #define SECTION_NETWORK_QUORUM "network_quorum" #define SECTION_NODE_SEED "node_seed" +#define SECTION_NODE_SIZE "node_size" #define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water" #define SECTION_PEER_IP "peer_ip" #define SECTION_PEER_PORT "peer_port" @@ -81,8 +82,9 @@ void Config::setup(const std::string& strConf, bool bTestNet, bool bQuiet) // that with "db" as the data directory. // - TESTNET = bTestNet; - QUIET = bQuiet; + TESTNET = bTestNet; + QUIET = bQuiet; + NODE_SIZE = 0; // TESTNET forces a "testnet-" prefix on the conf file and db directory. strDbPath = TESTNET ? "testnet-db" : "db"; @@ -329,6 +331,28 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_RPC_ALLOW_REMOTE, strTemp)) RPC_ALLOW_REMOTE = boost::lexical_cast(strTemp); + if (sectionSingleB(secConfig, SECTION_NODE_SIZE, strTemp)) + { + if (strTemp == "tiny") + NODE_SIZE = 0; + else if (strTemp == "small") + NODE_SIZE = 1; + else if (strTemp == "medium") + NODE_SIZE = 2; + else if (strTemp == "large") + NODE_SIZE = 3; + else if (strTemp == "huge") + NODE_SIZE = 4; + else + { + NODE_SIZE = boost::lexical_cast(strTemp); + if (NODE_SIZE < 0) + NODE_SIZE = 0; + else if (NODE_SIZE > 4) + NODE_SIZE = 4; + } + } + (void) sectionSingleB(secConfig, SECTION_WEBSOCKET_IP, WEBSOCKET_IP); if (sectionSingleB(secConfig, SECTION_WEBSOCKET_PORT, strTemp)) @@ -427,4 +451,27 @@ void Config::load() } } +int Config::getSize(SizedItemName item) +{ + SizedItem sizeTable[] = { + { siSweepInterval, { 10, 30, 60, 90, 90 } }, + { siLedgerFetch, { 2, 4, 5, 6, 6 } }, + { siValidationsSize, { 256, 256, 512, 1024, 1024 } }, + { siValidationsAge, { 500, 500, 500, 500, 500 } }, + { siNodeCacheSize, { 8192, 32768, 131072, 1048576, 0 } }, + { siNodeCacheAge, { 30, 60, 90, 300, 600 } }, + { siLedgerSize, { 32, 64, 128, 1024, 0 } }, + { siLedgerAge, { 30, 60, 120, 300, 600 } }, + }; + + for (int i = 0; i < (sizeof(sizeTable) / sizeof(SizedItem)); ++i) + { + if (sizeTable[i].item == item) + return sizeTable[i].sizes[NODE_SIZE]; + } + assert(false); + return -1; +} + + // vim:ts=4 diff --git a/src/cpp/ripple/Config.h b/src/cpp/ripple/Config.h index 6fe20fcc46..904f12ca90 100644 --- a/src/cpp/ripple/Config.h +++ b/src/cpp/ripple/Config.h @@ -48,6 +48,24 @@ const int SYSTEM_WEBSOCKET_PUBLIC_PORT = 6563; // XXX Going away. // Might connect with fewer for testing. #define DEFAULT_PEER_CONNECT_LOW_WATER 4 +enum SizedItemName +{ + siSweepInterval, + siValidationsSize, + siValidationsAge, + siNodeCacheSize, + siNodeCacheAge, + siLedgerSize, + siLedgerAge, + siLedgerFetch, +}; + +struct SizedItem +{ + SizedItemName item; + int sizes[5]; +}; + class Config { public: @@ -139,6 +157,7 @@ public: // Node storage configuration uint32 LEDGER_HISTORY; + int NODE_SIZE; // Client behavior int ACCOUNT_PROBE_MAX; // How far to scan for accounts. @@ -150,6 +169,7 @@ public: Config(); + int getSize(SizedItemName); void setup(const std::string& strConf, bool bTestNet, bool bQuiet); void load(); }; diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index d5e248f2e1..44cfffbd44 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -20,6 +20,12 @@ HashedObjectStore::HashedObjectStore(int cacheSize, int cacheAge) : mWriteSet.reserve(128); } +void HashedObjectStore::tune(int size, int age) +{ + mCache.setTargetSize(size); + mCache.setTargetAge(age); +} + bool HashedObjectStore::store(HashedObjectType type, uint32 index, const std::vector& data, const uint256& hash) @@ -45,7 +51,7 @@ bool HashedObjectStore::store(HashedObjectType type, uint32 index, if (!mWritePending) { mWritePending = true; - boost::thread(boost::bind(&HashedObjectStore::bulkWrite, this)).detach(); + theApp->getJobQueue().addJob(jtWRITE, boost::bind(&HashedObjectStore::bulkWrite, this)); } } // else @@ -64,7 +70,6 @@ void HashedObjectStore::waitWrite() void HashedObjectStore::bulkWrite() { - LoadEvent::autoptr event(theApp->getJobQueue().getLoadEventAP(jtDISK)); while (1) { std::vector< boost::shared_ptr > set; @@ -84,7 +89,48 @@ void HashedObjectStore::bulkWrite() } // cLog(lsTRACE) << "HOS: writing " << set.size(); - static boost::format fExists("SELECT ObjType FROM CommittedObjects WHERE Hash = '%s';"); +#ifndef NO_SQLITE3_PREPARE + + { + Database* db = theApp->getHashNodeDB()->getDB(); + ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); + static SqliteStatement pSt(db->getSqliteDB(), + "INSERT OR IGNORE INTO CommittedObjects " + "(Hash,ObjType,LedgerIndex,Object) VALUES (?, ?, ?, ?);"); + + db->executeSQL("BEGIN TRANSACTION;"); + + BOOST_FOREACH(const boost::shared_ptr& it, set) + { + const char* type; + + switch (it->getType()) + { + case hotLEDGER: type = "L"; break; + case hotTRANSACTION: type = "T"; break; + case hotACCOUNT_NODE: type = "A"; break; + case hotTRANSACTION_NODE: type = "N"; break; + default: type = "U"; + } + + pSt.reset(); + pSt.bind(1, it->getHash().GetHex()); + pSt.bind(2, type); + pSt.bind(3, it->getIndex()); + pSt.bindStatic(4, it->getData()); + int ret = pSt.step(); + if (!pSt.isDone(ret)) + { + cLog(lsFATAL) << "Error saving hashed object " << ret; + assert(false); + } + } + + db->executeSQL("END TRANSACTION;"); + } + +#else + static boost::format fAdd("INSERT OR IGNORE INTO CommittedObjects " "(Hash,ObjType,LedgerIndex,Object) VALUES ('%s','%c','%u',%s);"); @@ -112,6 +158,8 @@ void HashedObjectStore::bulkWrite() db->executeSQL("END TRANSACTION;"); } +#endif + } } @@ -129,28 +177,53 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) } if (mNegativeCache.isPresent(hash)) - return HashedObject::pointer(); + return obj; if (!theApp || !theApp->getHashNodeDB()) - return HashedObject::pointer(); - std::string sql = "SELECT * FROM CommittedObjects WHERE Hash='"; - sql.append(hash.GetHex()); - sql.append("';"); + return obj; std::vector data; std::string type; uint32 index; +#ifndef NO_SQLITE3_PREPARE + { + ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); + static SqliteStatement pSt(theApp->getHashNodeDB()->getDB()->getSqliteDB(), + "SELECT ObjType,LedgerIndex,Object FROM CommittedObjects WHERE Hash = ?;"); + + pSt.reset(); + pSt.bind(1, hash.GetHex()); + + int ret = pSt.step(); + if (pSt.isDone(ret)) + { + mNegativeCache.add(hash); + cLog(lsTRACE) << "HOS: " << hash <<" fetch: not in db"; + return obj; + } + + type = pSt.peekString(0); + index = pSt.getUInt32(1); + pSt.getBlob(2).swap(data); + } + +#else + + std::string sql = "SELECT * FROM CommittedObjects WHERE Hash='"; + sql.append(hash.GetHex()); + sql.append("';"); + + { ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); Database* db = theApp->getHashNodeDB()->getDB(); if (!db->executeSQL(sql) || !db->startIterRows()) { -// cLog(lsTRACE) << "HOS: " << hash << " fetch: not in db"; sl.unlock(); mNegativeCache.add(hash); - return HashedObject::pointer(); + return obj; } db->getStr("ObjType", type); @@ -161,6 +234,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) db->getBinary("Object", &(data.front()), size); db->endIterRows(); } +#endif assert(Serializer::getSHA512Half(data) == hash); @@ -175,7 +249,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) assert(false); cLog(lsERROR) << "Invalid hashed object"; mNegativeCache.add(hash); - return HashedObject::pointer(); + return obj; } obj = boost::make_shared(htype, index, data, hash); @@ -198,7 +272,7 @@ int HashedObjectStore::import(const std::string& file) uint256 hash; std::string hashStr; importDB->getStr("Hash", hashStr); - hash.SetHex(hashStr); + hash.SetHex(hashStr, true); if (hash.isZero()) { cLog(lsWARNING) << "zero hash found in import table"; diff --git a/src/cpp/ripple/HashedObject.h b/src/cpp/ripple/HashedObject.h index d96101370b..74fc7a1424 100644 --- a/src/cpp/ripple/HashedObject.h +++ b/src/cpp/ripple/HashedObject.h @@ -67,6 +67,7 @@ public: void bulkWrite(); void waitWrite(); + void tune(int size, int age); void sweep() { mCache.sweep(); mNegativeCache.sweep(); } int import(const std::string&); diff --git a/src/cpp/ripple/JobQueue.cpp b/src/cpp/ripple/JobQueue.cpp index 40b7bf44ca..fe1dcd829a 100644 --- a/src/cpp/ripple/JobQueue.cpp +++ b/src/cpp/ripple/JobQueue.cpp @@ -17,6 +17,7 @@ JobQueue::JobQueue() : mLastJob(0), mThreadCount(0), mShuttingDown(false) mJobLoads[jtPROPOSAL_ut].setTargetLatency(500, 1250); mJobLoads[jtPUBLEDGER].setTargetLatency(1000, 2500); mJobLoads[jtVALIDATION_t].setTargetLatency(500, 1500); + mJobLoads[jtWRITE].setTargetLatency(750, 1500); mJobLoads[jtTRANSACTION_l].setTargetLatency(100, 500); mJobLoads[jtPROPOSAL_t].setTargetLatency(100, 500); @@ -40,6 +41,7 @@ const char* Job::toString(JobType t) case jtTRANSACTION: return "transaction"; case jtPUBLEDGER: return "publishLedger"; case jtVALIDATION_t: return "trustedValidation"; + case jtWRITE: return "writeObjects"; case jtTRANSACTION_l: return "localTransaction"; case jtPROPOSAL_t: return "trustedProposal"; case jtADMIN: return "administration"; @@ -196,7 +198,7 @@ void JobQueue::setThreadCount(int c) { c = boost::thread::hardware_concurrency(); if (c < 0) - c = 0; + c = 2; c += 2; cLog(lsINFO) << "Auto-tuning to " << c << " validation/transaction/proposal threads"; } @@ -244,7 +246,7 @@ void JobQueue::threadEntry() break; sl.unlock(); - cLog(lsDEBUG) << "Doing " << Job::toString(job.getType()) << " job"; + cLog(lsTRACE) << "Doing " << Job::toString(job.getType()) << " job"; job.doJob(); sl.lock(); } diff --git a/src/cpp/ripple/JobQueue.h b/src/cpp/ripple/JobQueue.h index f45d2bb33c..dfdcd1039e 100644 --- a/src/cpp/ripple/JobQueue.h +++ b/src/cpp/ripple/JobQueue.h @@ -28,10 +28,11 @@ enum JobType jtTRANSACTION = 5, // A transaction received from the network jtPUBLEDGER = 6, // Publish a fully-accepted ledger jtVALIDATION_t = 7, // A validation from a trusted source - jtTRANSACTION_l = 8, // A local transaction - jtPROPOSAL_t = 9, // A proposal from a trusted source - jtADMIN = 10, // An administrative operation - jtDEATH = 11, // job of death, used internally + jtWRITE = 8, // Write out hashed objects + jtTRANSACTION_l = 9, // A local transaction + jtPROPOSAL_t = 10, // A proposal from a trusted source + jtADMIN = 11, // An administrative operation + jtDEATH = 12, // job of death, used internally // special types not dispatched by the job pool jtPEER = 17, diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index c4c483132c..31be1ed682 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -7,6 +7,8 @@ #include "../json/writer.h" +#include "../database/SqliteDatabase.h" + #include "Application.h" #include "Ledger.h" #include "utils.h" @@ -507,13 +509,13 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) } db->getStr("LedgerHash", hash); - ledgerHash.SetHex(hash); + ledgerHash.SetHex(hash, true); db->getStr("PrevHash", hash); - prevHash.SetHex(hash); + prevHash.SetHex(hash, true); db->getStr("AccountSetHash", hash); - accountHash.SetHex(hash); + accountHash.SetHex(hash, true); db->getStr("TransSetHash", hash); - transHash.SetHex(hash); + transHash.SetHex(hash, true); totCoins = db->getBigInt("TotalCoins"); closingTime = db->getBigInt("ClosingTime"); prevClosingTime = db->getBigInt("PrevClosingTime"); @@ -560,12 +562,43 @@ uint256 Ledger::getHashByIndex(uint32 ledgerIndex) db->endIterRows(); } - ret.SetHex(hash); + ret.SetHex(hash, true); return ret; } bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& parentHash) { +#ifndef NO_SQLITE3_PREPARE + + DatabaseCon *con = theApp->getLedgerDB(); + ScopedLock sl(con->getDBLock()); + + static SqliteStatement pSt(con->getDB()->getSqliteDB(), + "SELECT LedgerHash,PrevHash FROM Ledgers Where LedgerSeq = ?;"); + + pSt.reset(); + pSt.bind(1, ledgerIndex); + + int ret = pSt.step(); + if (pSt.isDone(ret)) + { + cLog(lsTRACE) << "Don't have ledger " << ledgerIndex; + return false; + } + if (!pSt.isRow(ret)) + { + assert(false); + cLog(lsFATAL) << "Unexpected statement result " << ret; + return false; + } + + ledgerHash.SetHex(pSt.peekString(0), true); + parentHash.SetHex(pSt.peekString(1), true); + + return true; + +#else + std::string sql="SELECT LedgerHash,PrevHash FROM Ledgers WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); @@ -581,12 +614,14 @@ bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& db->endIterRows(); } - ledgerHash.SetHex(hash); - parentHash.SetHex(prevHash); + ledgerHash.SetHex(hash, true); + parentHash.SetHex(prevHash, true); assert(ledgerHash.isNonZero() && ((ledgerIndex == 0) || parentHash.isNonZero())); return true; + +#endif } Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index f63cb0a70b..7b11adc26d 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -153,6 +153,13 @@ public: // low level functions SHAMap::ref peekTransactionMap() { return mTransactionMap; } SHAMap::ref peekAccountStateMap() { return mAccountStateMap; } + void dropCache() + { + if (mTransactionMap) + mTransactionMap->dropCache(); + if (mAccountStateMap) + mAccountStateMap->dropCache(); + } // ledger sync functions void setAcquiring(void); diff --git a/src/cpp/ripple/LedgerAcquire.cpp b/src/cpp/ripple/LedgerAcquire.cpp index d97a60f9d4..a17cc6ef68 100644 --- a/src/cpp/ripple/LedgerAcquire.cpp +++ b/src/cpp/ripple/LedgerAcquire.cpp @@ -14,7 +14,9 @@ SETUP_LOG(); DECLARE_INSTANCE(LedgerAcquire); #define LA_DEBUG -#define LEDGER_ACQUIRE_TIMEOUT 750 +#define LEDGER_ACQUIRE_TIMEOUT 750 // millisecond for each ledger timeout +#define LEDGER_TIMEOUT_COUNT 10 // how many timeouts before we giveup +#define LEDGER_TIMEOUT_AGGRESSIVE 4 // how many timeouts before we get aggressive #define TRUST_NETWORK PeerSet::PeerSet(const uint256& hash, int interval) : mHash(hash), mTimerInterval(interval), mTimeouts(0), @@ -165,7 +167,7 @@ bool LedgerAcquire::tryLocal() void LedgerAcquire::onTimer(bool progress) { - if (getTimeouts() > 6) + if (getTimeouts() > LEDGER_TIMEOUT_COUNT) { cLog(lsWARNING) << "Six timeouts for ledger " << mHash; setFailed(); @@ -301,7 +303,7 @@ void LedgerAcquire::trigger(Peer::ref peer) { tmGL.set_querytype(ripple::qtINDIRECT); - if (!isProgress() && !mFailed && mByHash && (getTimeouts() > 2)) + if (!isProgress() && !mFailed && mByHash && (getTimeouts() > LEDGER_TIMEOUT_AGGRESSIVE)) { std::vector need = getNeededHashes(); if (!need.empty()) diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 99c00018f0..7f22632134 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -1261,14 +1261,17 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei if (!bSenderHigh) saBalance.negate(); // Put balance in low terms. - cLog(lsDEBUG) << boost::str(boost::format("rippleCredit> %s (%s) -> %s : %s") + STAmount saBefore = saBalance; + + saBalance += saAmount; + + cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: %s -- (%s > %s) -> %s : %s") % RippleAddress::createHumanAccountID(uSenderID) + % saBefore.getFullText() % saBalance.getFullText() % RippleAddress::createHumanAccountID(uReceiverID) % saAmount.getFullText()); - saBalance += saAmount; - if (!bSenderHigh) saBalance.negate(); @@ -1311,6 +1314,13 @@ TER LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceive saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. + cLog(lsINFO) << boost::str(boost::format("rippleSend> %s -- %s--> %s (%s) : %s") + % RippleAddress::createHumanAccountID(uSenderID) + % saTransitFee.getFullText() + % RippleAddress::createHumanAccountID(uReceiverID) + % saActual.getFullText() + % saAmount.getFullText()); + terResult = rippleCredit(uIssuerID, uReceiverID, saAmount); if (tesSUCCESS == terResult) @@ -1322,7 +1332,6 @@ TER LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceive TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) { - assert(!saAmount.isNegative()); TER terResult = tesSUCCESS; if (!saAmount) @@ -1345,6 +1354,8 @@ TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiv % (sleReceiver ? (sleReceiver->getFieldAmount(sfBalance)).getFullText() : "-") % saAmount.getFullText()); + assert(!saAmount.isNegative()); + if (sleSender) { sleSender->setFieldAmount(sfBalance, sleSender->getFieldAmount(sfBalance) - saAmount); @@ -1368,6 +1379,13 @@ TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiv { STAmount saActual; + cLog(lsINFO) << boost::str(boost::format("accountSend: %s -> %s : %s") + % RippleAddress::createHumanAccountID(uSenderID) + % RippleAddress::createHumanAccountID(uReceiverID) + % saAmount.getFullText()); + + assert(!saAmount.isNegative()); + terResult = rippleSend(uSenderID, uReceiverID, saAmount, saActual); } diff --git a/src/cpp/ripple/LedgerHistory.cpp b/src/cpp/ripple/LedgerHistory.cpp index dc30e87e3e..39aab0a04c 100644 --- a/src/cpp/ripple/LedgerHistory.cpp +++ b/src/cpp/ripple/LedgerHistory.cpp @@ -115,4 +115,10 @@ Ledger::pointer LedgerHistory::canonicalizeLedger(Ledger::pointer ledger, bool s return ledger; } +void LedgerHistory::tune(int size, int age) +{ + mLedgersByHash.setTargetSize(size); + mLedgersByHash.setTargetAge(age); +} + // vim:ts=4 diff --git a/src/cpp/ripple/LedgerHistory.h b/src/cpp/ripple/LedgerHistory.h index 965ac492a1..9ee0ed9e87 100644 --- a/src/cpp/ripple/LedgerHistory.h +++ b/src/cpp/ripple/LedgerHistory.h @@ -19,6 +19,7 @@ public: Ledger::pointer getLedgerBySeq(uint32 index); Ledger::pointer getLedgerByHash(const uint256& hash); Ledger::pointer canonicalizeLedger(Ledger::pointer, bool cache); + void tune(int size, int age); void sweep() { mLedgersByHash.sweep(); } }; diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index e27c350075..553a4e1473 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -195,7 +195,8 @@ bool LedgerMaster::acquireMissingLedger(Ledger::ref origLedger, const uint256& l theApp->getIOService().post(boost::bind(&LedgerMaster::missingAcquireComplete, this, mMissingLedger)); } - if (theApp->getMasterLedgerAcquire().getFetchCount() < 4) + int fetch = theConfig.getSize(siLedgerFetch); + if (theApp->getMasterLedgerAcquire().getFetchCount() < fetch) { int count = 0; typedef std::pair u_pair; @@ -203,7 +204,7 @@ bool LedgerMaster::acquireMissingLedger(Ledger::ref origLedger, const uint256& l std::vector vec = origLedger->getLedgerHashes(); BOOST_REVERSE_FOREACH(const u_pair& it, vec) { - if ((count < 3) && (it.first < ledgerSeq) && + if ((count < fetch) && (it.first < ledgerSeq) && !mCompleteLedgers.hasValue(it.first) && !theApp->getMasterLedgerAcquire().find(it.second)) { ++count; @@ -499,11 +500,11 @@ void LedgerMaster::tryPublish() } } + mTooFast = false; if (!mPubLedgers.empty() && !mPubThread) { theApp->getOPs().clearNeedNetworkLedger(); mPubThread = true; - mTooFast = false; theApp->getJobQueue().addJob(jtPUBLEDGER, boost::bind(&LedgerMaster::pubThread, this)); } } diff --git a/src/cpp/ripple/LedgerMaster.h b/src/cpp/ripple/LedgerMaster.h index dac64c862e..dfcf8ef2e8 100644 --- a/src/cpp/ripple/LedgerMaster.h +++ b/src/cpp/ripple/LedgerMaster.h @@ -129,6 +129,7 @@ public: void resumeAcquiring(); + void tune(int size, int age) { mLedgerHistory.tune(size, age); } void sweep(void) { mLedgerHistory.sweep(); } void addValidateCallback(callback& c) { mOnValidate.push_back(c); } diff --git a/src/cpp/ripple/OfferCreateTransactor.cpp b/src/cpp/ripple/OfferCreateTransactor.cpp index 5b46c53e1a..7e313f93b0 100644 --- a/src/cpp/ripple/OfferCreateTransactor.cpp +++ b/src/cpp/ripple/OfferCreateTransactor.cpp @@ -8,12 +8,13 @@ SETUP_LOG(); // Take as much as possible. Adjusts account balances. Charges fees on top to taker. -// --> uBookBase: The order book to take against. -// --> saTakerPays: What the taker offers (w/ issuer) -// --> saTakerGets: What the taker wanted (w/ issuer) -// <-- saTakerPaid: What taker paid not including fees. To reduce an offer. -// <-- saTakerGot: What taker got not including fees. To reduce an offer. -// <-- terResult: tesSUCCESS or terNO_ACCOUNT +// --> uBookBase: The order book to take against. +// --> saTakerPays: What the taker offers (w/ issuer) +// --> saTakerGets: What the taker wanted (w/ issuer) +// <-- saTakerPaid: What taker paid including saved not including fees. To reduce an offer. +// <-- saTakerGot: What taker got not including fees. To reduce an offer. +// <-- terResult: tesSUCCESS or terNO_ACCOUNT +// <-- bUnfunded: if tesSUCCESS, consider offer unfunded after taking. TER OfferCreateTransactor::takeOffers( bool bPassive, const uint256& uBookBase, @@ -22,7 +23,8 @@ TER OfferCreateTransactor::takeOffers( const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, - STAmount& saTakerGot) + STAmount& saTakerGot, + bool& bUnfunded) { assert(saTakerPays && saTakerGets); @@ -31,6 +33,7 @@ TER OfferCreateTransactor::takeOffers( uint256 uTipIndex = uBookBase; const uint256 uBookEnd = Ledger::getQualityNext(uBookBase); const uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays); + STAmount saTakerRate = STAmount::setRate(uTakeQuality); const uint160 uTakerPaysAccountID = saTakerPays.getIssuer(); const uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); TER terResult = temUNCERTAIN; @@ -39,8 +42,9 @@ TER OfferCreateTransactor::takeOffers( boost::unordered_set usOfferUnfundedBecame; // Offers that became unfunded. boost::unordered_set usAccountTouched; // Accounts touched. - saTakerPaid = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer()); - saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer()); + saTakerPaid = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer()); + saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer()); + bUnfunded = false; while (temUNCERTAIN == terResult) { @@ -48,10 +52,9 @@ TER OfferCreateTransactor::takeOffers( uint64 uTipQuality; // Figure out next offer to take, if needed. - if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) + if (saTakerGot < saTakerGets // Have less than wanted. + && saTakerPaid < saTakerPays) // Didn't spend all funds. { - // Taker, still, needs to get and pay. - sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->getNextLedgerIndex(uTipIndex, uBookEnd)); if (sleOfferDir) { @@ -168,6 +171,7 @@ TER OfferCreateTransactor::takeOffers( saPay, saOfferPays, saOfferGets, + saTakerPays, saTakerGets, saSubTakerPaid, saSubTakerGot, @@ -190,42 +194,49 @@ TER OfferCreateTransactor::takeOffers( if (bOfferDelete) { // Offer now fully claimed or now unfunded. - cLog(lsINFO) << "takeOffers: offer claimed: delete"; + cLog(lsINFO) << "takeOffers: Offer claimed: Delete."; usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. // Offer owner's account is no longer pristine. usAccountTouched.insert(uOfferOwnerID); } + else if (saSubTakerGot) + { + cLog(lsINFO) << "takeOffers: Offer partial claim."; + } else { - cLog(lsINFO) << "takeOffers: offer partial claim."; + // Taker got nothing, probably due to rounding. Consider taker unfunded. + cLog(lsINFO) << "takeOffers: No claim."; + + bUnfunded = true; + terResult = tesSUCCESS; // Done. } assert(uTakerGetsAccountID == saSubTakerGot.getIssuer()); assert(uTakerPaysAccountID == saSubTakerPaid.getIssuer()); - // Offer owner pays taker. - // saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? + if (!bUnfunded) + { + terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker. - terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); // Offer owner pays issuer transfer fee. - if (tesSUCCESS == terResult) - terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); - // Taker pays offer owner. - // saSubTakerPaid.setIssuer(uTakerPaysAccountID); + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner. - if (tesSUCCESS == terResult) - terResult = mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); // Taker pays issuer transfer fee. - if (tesSUCCESS == terResult) - terResult = mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); + // Reduce amount considered paid by taker's rate (not actual cost). + saTakerPaid += std::min(saPay, STAmount::multiply(saSubTakerGot, saTakerRate, saPay)); + saTakerGot += saSubTakerGot; - saTakerGot += saSubTakerGot; - saTakerPaid += saSubTakerPaid; - - if (tesSUCCESS == terResult) - terResult = temUNCERTAIN; + if (tesSUCCESS == terResult) + terResult = temUNCERTAIN; + } } } } @@ -359,6 +370,7 @@ TER OfferCreateTransactor::doApply() STAmount saOfferPaid; STAmount saOfferGot; + bool bUnfunded = false; if (tesSUCCESS == terResult) { @@ -378,9 +390,9 @@ TER OfferCreateTransactor::doApply() mTxnAccount, saTakerGets, saTakerPays, - saOfferPaid, // How much was spent. - saOfferGot // How much was got. - ); + saOfferPaid, // How much would have spent at full price. + saOfferGot, // How much was got. + bUnfunded); cLog(lsWARNING) << "OfferCreate: takeOffers=" << terResult; cLog(lsWARNING) << "OfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); @@ -388,7 +400,7 @@ TER OfferCreateTransactor::doApply() cLog(lsWARNING) << "OfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); cLog(lsWARNING) << "OfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText(); - if (tesSUCCESS == terResult) + if (tesSUCCESS == terResult && !bUnfunded) { saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got. saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid. @@ -406,7 +418,8 @@ TER OfferCreateTransactor::doApply() if (tesSUCCESS != terResult || !saTakerPays // Wants nothing more. || !saTakerGets // Offering nothing more. - || !mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Not funded. + || !mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive() // Not funded. + || bUnfunded) // Consider unfunded. { // Complete as is. nothing(); diff --git a/src/cpp/ripple/OfferCreateTransactor.h b/src/cpp/ripple/OfferCreateTransactor.h index 3310168c61..dbe57f89d1 100644 --- a/src/cpp/ripple/OfferCreateTransactor.h +++ b/src/cpp/ripple/OfferCreateTransactor.h @@ -1,5 +1,7 @@ -#include "Transactor.h" +#ifndef __OFFERCREATETRANSACTOR__ +#define __OFFERCREATETRANSACTOR__ +#include "Transactor.h" class OfferCreateTransactor : public Transactor { @@ -11,12 +13,13 @@ class OfferCreateTransactor : public Transactor const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, - STAmount& saTakerGot); + STAmount& saTakerGot, + bool& bUnfunded); public: OfferCreateTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} - TER doApply(); }; +#endif // vim:ts=4 diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index 674c811d00..a7f03cc1cf 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -244,8 +244,8 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax } else { - // Remove implied first and last nodes. + spPath.mPath.erase(spPath.mPath.begin()); spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1); @@ -301,7 +301,9 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax // Last element is for non-XRP, continue by adding ripple lines and order books. // Create new paths for each outbound account not already in the path. - AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState())); + AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState())); + SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mAccountID)); + bool bRequireAuth = isSetBit(sleSrc->getFieldU32(sfFlags), lsfRequireAuth); BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) { @@ -317,9 +319,10 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) % STAmount::createHumanCurrency(speEnd.mCurrencyID)); } - else if (!rspEntry->getBalance().isPositive() // No IOUs to send. - && (!rspEntry->getLimitPeer() // Peer does not extend credit. - || *rspEntry->getBalance().negate() >= rspEntry->getLimitPeer())) // No credit left. + else if (!rspEntry->getBalance().isPositive() // No IOUs to send. + && (!rspEntry->getLimitPeer() // Peer does not extend credit. + || *rspEntry->getBalance().negate() >= rspEntry->getLimitPeer() // No credit left. + || (bRequireAuth && !rspEntry->getAuth()))) // Not authorized to hold credit. { // Path has no credit left. Ignore it. cLog(lsDEBUG) << diff --git a/src/cpp/ripple/Peer.cpp b/src/cpp/ripple/Peer.cpp index 7c0590dbfc..7b686f3afc 100644 --- a/src/cpp/ripple/Peer.cpp +++ b/src/cpp/ripple/Peer.cpp @@ -1176,7 +1176,10 @@ void Peer::recvGetObjectByHash(ripple::TMGetObjectByHash& packet) punishPeer(LT_BadData); } else + { + cLog(lsDEBUG) << "Got wanted hash " << hash; theApp->getHashedObjectStore().store(type, seq, data, hash); + } } else cLog(lsWARNING) << "Received unwanted hash " << getIP() << " " << hash; diff --git a/src/cpp/ripple/RippleState.cpp b/src/cpp/ripple/RippleState.cpp index 72b20b6c62..1ec6ebdaf8 100644 --- a/src/cpp/ripple/RippleState.cpp +++ b/src/cpp/ripple/RippleState.cpp @@ -14,6 +14,8 @@ RippleState::RippleState(SerializedLedgerEntry::ref ledgerEntry) : AccountItem(l mValid(false), mViewLowest(true) { + mFlags = mLedgerEntry->getFieldU32(sfFlags); + mLowLimit = mLedgerEntry->getFieldAmount(sfLowLimit); mHighLimit = mLedgerEntry->getFieldAmount(sfHighLimit); diff --git a/src/cpp/ripple/RippleState.h b/src/cpp/ripple/RippleState.h index a89c554761..ac79673368 100644 --- a/src/cpp/ripple/RippleState.h +++ b/src/cpp/ripple/RippleState.h @@ -17,6 +17,8 @@ public: typedef boost::shared_ptr pointer; private: + uint32 mFlags; + RippleAddress mLowID; RippleAddress mHighID; @@ -42,16 +44,19 @@ public: void setViewAccount(const uint160& accountID); - const RippleAddress getAccountID() const { return mViewLowest ? mLowID : mHighID; } - const RippleAddress getAccountIDPeer() const { return mViewLowest ? mHighID : mLowID; } + const RippleAddress getAccountID() const { return mViewLowest ? mLowID : mHighID; } + const RippleAddress getAccountIDPeer() const { return !mViewLowest ? mLowID : mHighID; } - STAmount getBalance() const { return mBalance; } + bool getAuth() const { return isSetBit(mFlags, mViewLowest ? lsfLowAuth : lsfHighAuth); } + bool getAuthPeer() const { return isSetBit(mFlags, !mViewLowest ? lsfLowAuth : lsfHighAuth); } - STAmount getLimit() const { return mViewLowest ? mLowLimit : mHighLimit; } - STAmount getLimitPeer() const { return mViewLowest ? mHighLimit : mLowLimit; } + STAmount getBalance() const { return mBalance; } - uint32 getQualityIn() const { return((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn)); } - uint32 getQualityOut() const { return((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut)); } + STAmount getLimit() const { return mViewLowest ? mLowLimit : mHighLimit; } + STAmount getLimitPeer() const { return !mViewLowest ? mLowLimit : mHighLimit; } + + uint32 getQualityIn() const { return((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn)); } + uint32 getQualityOut() const { return((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut)); } SerializedLedgerEntry::pointer getSLE() { return mLedgerEntry; } const SerializedLedgerEntry& peekSLE() const { return *mLedgerEntry; } diff --git a/src/cpp/ripple/SHAMap.cpp b/src/cpp/ripple/SHAMap.cpp index 15355ed1d5..524232a9a5 100644 --- a/src/cpp/ripple/SHAMap.cpp +++ b/src/cpp/ripple/SHAMap.cpp @@ -851,6 +851,15 @@ bool SHAMap::getPath(const uint256& index, std::vector< std::vector int TaggedCache::getTar return mTargetSize; } +template void TaggedCache::setTargetSize(int s) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + mTargetSize = s; + Log(lsDEBUG, TaggedCachePartition) << mName << " target size set to " << s; +} + template int TaggedCache::getTargetAge() const { boost::recursive_mutex::scoped_lock sl(mLock); return mTargetAge; } +template void TaggedCache::setTargetAge(int s) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + mTargetAge = s; + Log(lsDEBUG, TaggedCachePartition) << mName << " target age set to " << s; +} + template int TaggedCache::getCacheSize() { boost::recursive_mutex::scoped_lock sl(mLock); diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index 8101d5f514..5d34e59ec6 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -12,6 +12,12 @@ SETUP_LOG(); typedef std::map::value_type u160_val_pair; typedef boost::shared_ptr VSpointer; +void ValidationCollection::tune(int size, int age) +{ + mValidations.setTargetSize(size); + mValidations.setTargetAge(age); +} + VSpointer ValidationCollection::findCreateSet(const uint256& ledgerHash) { VSpointer j = mValidations.fetch(ledgerHash); diff --git a/src/cpp/ripple/ValidationCollection.h b/src/cpp/ripple/ValidationCollection.h index b65559f795..7bba8bad45 100644 --- a/src/cpp/ripple/ValidationCollection.h +++ b/src/cpp/ripple/ValidationCollection.h @@ -48,6 +48,7 @@ public: boost::unordered_map getCurrentValidations(uint256 currentLedger); std::list getCurrentTrustedValidations(); + void tune(int size, int age); void flush(); void sweep() { mValidations.sweep(); } }; diff --git a/test/offer-test.js b/test/offer-test.js index 725ebd230b..56083b2e7f 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -16,7 +16,8 @@ buster.testRunner.timeout = 5000; buster.testCase("Offer tests", { 'setUp' : testutils.build_setup(), - // 'setUp' : testutils.build_setup({ verbose: true, standalone: false }), + // 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, standalone: true }), 'tearDown' : testutils.build_teardown(), "offer create then cancel in one ledger" : @@ -73,7 +74,7 @@ buster.testCase("Offer tests", { }); }, - "offer create then crossing offer, no trust lines with self" : + "Offer create then self crossing offer, no trust lines with self" : function (done) { var self = this; @@ -109,6 +110,138 @@ buster.testCase("Offer tests", { }); }, + "Offer create then crossing offer with XRP. Negative balance." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create mtgox account."; + + testutils.payment(self.remote, "root", "mtgox", "1149999730", callback); + }, + function (callback) { + self.what = "Create alice account."; + + testutils.payment(self.remote, "root", "alice", "499946999680", callback); + }, + function (callback) { + self.what = "Create bob account."; + + testutils.payment(self.remote, "root", "bob", "10199999920", callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("mtgox") + .transfer_rate(1005000000) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "500/USD/mtgox", + "bob" : "50/USD/mtgox", + "mtgox" : "100/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : [ "50/USD/alice", "2710505431213761e-33/USD/bob" ] + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("alice", "50/USD/mtgox", "150000.0") // get 50/USD pay 150000/XRP + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Unfund offer."; + + testutils.payments(self.remote, + { + "alice" : "100/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Set limits 2."; + + testutils.credit_limits(self.remote, + { + "mtgox" : "0/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "-50/USD/mtgox" ], + "bob" : [ "2710505431213761e-33/USD/mtgox" ], + }, + callback); + }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("bob", "2000.0", "1/USD/mtgox") // get 2,000/XRP pay 1/USD (has insufficient USD) + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "-50/USD/mtgox", String(499946999680-3*(Transaction.fees['default'].to_number())) ], + "bob" : [ "2710505431213761e-33/USD/mtgox", String(10199999920-2*(Transaction.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + "Offer create then crossing offer with XRP. Reverse order." : function (done) { var self = this;